// SPDX-License-Identifier: AGPL-3.0-or-later //! Nom parsers used within the parsing steps. use nom::{ bytes::complete::{tag, take_while}, character::complete::multispace0, combinator::map_res, sequence::{pair, preceded}, IResult, }; /// Determines if the provided character is an ascii digit. const fn is_decimal_digit(c: char) -> bool { c.is_ascii_digit() } /// Parses a string slice into a [`u16`]. /// /// # Errors /// /// This function will return an error if the string cannot be parsed. fn from_decimal(input: &str) -> Result { input.parse::() } /// Retrieves all the digits from a CRN and maps them to a [`u16`]. /// /// # Errors /// /// This function will return an error if nom cannot parse the input. fn crn_digits(input: &str) -> IResult<&str, u16> { map_res(take_while(is_decimal_digit), from_decimal)(input) } /// Parses a course reference number. /// /// # Errors /// /// This function will return an error if nom cannot parse the input. pub fn course_reference_number(input: &str) -> IResult<&str, u16> { preceded(pair(tag("CRN"), multispace0), crn_digits)(input) } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use super::*; #[test] fn crn_parser_basic() { assert_eq!(course_reference_number("CRN 5912").unwrap().1, 5912); assert_eq!(course_reference_number("CRN 17146").unwrap().1, 17146); } #[test] fn crn_parser_postfix() { assert_eq!( course_reference_number("CRN 331 [Distance]").unwrap().1, 331 ); } #[test] fn crn_parser_extra_whitespace() { assert_eq!(course_reference_number("CRN 8913 ").unwrap().1, 8913); assert_eq!(course_reference_number("CRN 61151").unwrap().1, 61151); } #[test] fn crn_parser_no_whitespace() { assert_eq!(course_reference_number("CRN615").unwrap().1, 615); } }