summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Forrest <git@sophieforrest.com>2024-09-12 02:32:13 +1200
committerSophie Forrest <git@sophieforrest.com>2024-09-12 02:32:13 +1200
commit042fb23701c8f30f4e806dde183e54027e212817 (patch)
treeceb6e09eaec25becb4f68885d4637142f5ec2d1a
parent14a8735623c7383c1ba5eb4fad13a5588beffabb (diff)
feat(lib): use nom parsers
Parsing should now be more reliable in general.
-rw-r--r--src/lib.rs76
1 files changed, 28 insertions, 48 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 279000a..10a1c39 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,13 +9,17 @@ mod parser;
use std::collections::{HashMap, HashSet};
+use parser::{offering, subtitle, title};
use scraper::{CaseSensitivity, ElementRef, Html, Selector};
use serde::{Deserialize, Serialize};
-use tracing::{debug, info};
+use tracing::{debug, info, warn};
/// Slice used for splitting requirements for parsing.
const SPLIT_SLICE: &[char] = &[';', ','];
+/// Alias to the nom error type.
+type NomError<'a> = nom::Err<nom::error::Error<&'a str>>;
+
/// A VUW course, along with all relevant data.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
@@ -49,30 +53,24 @@ pub struct Course<'a> {
}
impl<'a> Course<'a> {
- /// Parses the courde id.
+ /// Parses the course id.
///
- /// # Panics
+ /// # Errors
///
- /// Panics if string is sliced in the middle of a character.
- pub fn parse_courseid(&mut self, elem: ElementRef<'a>) {
- elem.children().for_each(|child| {
- child.children().for_each(|c| {
- if let Some(text) = c.value().as_text() {
- // The actual text we're looking for
- let text: &str = text.trim();
+ /// This function will return an error if nom fails to parse the course title or subtitle.
+ pub fn parse_courseid(&mut self, elem: ElementRef<'a>) -> Result<(), NomError> {
+ for child in elem.children().flat_map(|child| child.children()) {
+ if let Some(text) = child.value().as_text() {
+ self.title = title(text)?.1;
+ } else if let Some(text) = child
+ .first_child()
+ .and_then(|node| node.value().as_text().map(|text| &**text))
+ {
+ self.subtitle = subtitle(text)?.1;
+ }
+ }
- self.title = text;
- } else if let Some(text) = c
- .first_child()
- .and_then(|node| node.value().as_text().map(|text| &**text))
- {
- if let Some((indice, _char)) = text.char_indices().nth(1) {
- // Skip over "-"
- self.subtitle = text.get(indice..).expect("indice should be valid").trim();
- }
- }
- });
- });
+ Ok(())
}
/// Parses the course points, prerequisites, and restrictions from the given element.
@@ -158,30 +156,10 @@ impl<'a> Course<'a> {
.and_then(|el| el.first_child()?.value().as_text());
if let Some(details) = details {
- let details_split: Vec<&str> = details.split(" \u{2022} ").take(2).collect();
-
- info!("{:#?}", &details_split);
-
- let offering = CourseOffering::new(
- details_split
- .last()
- .expect("course reference number should exist")
- .get(4..)
- .expect("course reference number digits should start at this indice")
- .split_whitespace()
- .next()
- .expect("course reference number should exist")
- .parse::<u16>()
- .expect("course reference number should be parseable"),
- Trimester::try_from(
- *details_split
- .first()
- .expect("trimester element should exist"),
- )
- .expect("should be parseable into a trimester"),
- );
+ info!("{:#?}", &details);
- self.timetable.push(offering);
+ self.timetable
+ .push(offering(details).expect("cannot parse course offering").1);
}
}
}
@@ -256,7 +234,7 @@ pub enum Trimester {
}
impl TryFrom<&str> for Trimester {
- type Error = String;
+ type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
@@ -269,7 +247,7 @@ impl TryFrom<&str> for Trimester {
"2+3/3" => Ok(Self::TwoThree),
"3+1/3" => Ok(Self::ThreeOne),
"1+2+3/3" | "2+3+1/3" | "full year" => Ok(Self::FullYear),
- _ => Err(String::from("Invalid trimester.")),
+ _ => Err("Invalid trimester."),
}
}
}
@@ -300,7 +278,9 @@ pub fn parse_document(document: &Html) -> HashMap<&str, Course<'_>> {
working_course = Course::default();
working_course.subject_areas.insert(subject_area);
- working_course.parse_courseid(elem);
+ working_course
+ .parse_courseid(elem)
+ .expect("could not parse courseid");
} else if elem_value.has_class("notoffered", CaseSensitivity::CaseSensitive) {
working_course.offered = false;
} else if elem_value.has_class("subjectarea", CaseSensitivity::CaseSensitive) {