diff options
author | gennyble <gen@nyble.dev> | 2023-12-28 01:07:53 -0600 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2023-12-28 01:07:53 -0600 |
commit | a14a861d824230458333defd5cf43bafd2ca596d (patch) | |
tree | b5e2108e38268b02417e83c009545ac714032218 /src/query.rs | |
parent | a567b9eb9930e3ec0d6da00f58d217843a732e2f (diff) | |
download | cutie-a14a861d824230458333defd5cf43bafd2ca596d.tar.gz cutie-a14a861d824230458333defd5cf43bafd2ca596d.zip |
query parsing, but this is not what i wanted actually
Diffstat (limited to 'src/query.rs')
-rw-r--r-- | src/query.rs | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 0000000..796e372 --- /dev/null +++ b/src/query.rs @@ -0,0 +1,185 @@ +#[derive(Clone, Debug, PartialEq)] +pub enum QueryComponent { + /// Every child element of the tag + TagName(String), + /// Only direct children with the tag + DirectTagName(String), + /// The child element with the ID + Id(String), + /// Only direct children with the tag + DirectId(String), + /// Every child that has the class + Class(String), + /// Only direct children with the class + DirectClass(String), +} + +pub fn parse_query(mut raw: &str) -> Result<Vec<QueryComponent>, QueryParseError> { + let mut components = vec![]; + + let mut next_direct = false; + loop { + if raw.is_empty() { + break Ok(components); + } + + let part = match raw.find(['>', ' ']) { + None => { + let part = raw; + raw = &raw[raw.len()..raw.len()]; + part + } + Some(idx) => { + let part = &raw[..idx]; + + if &raw[idx..idx + 1] == ">" { + if next_direct { + return Err(QueryParseError::DoubleDirect); + } else { + next_direct = true; + } + } + + raw = &raw[idx + 1..]; + part + } + }; + + if part.is_empty() { + continue; + } + + if let Some(id) = part.strip_prefix('#') { + if id.contains(['#', '.']) { + return Err(QueryParseError::UnknownComponent { + malformed: id.into(), + }); + } + + if next_direct { + components.push(QueryComponent::DirectId(id.into())); + next_direct = false; + } else { + components.push(QueryComponent::Id(id.into())); + } + } else if let Some(class) = part.strip_prefix('.') { + if class.contains(['#', '.']) { + return Err(QueryParseError::UnknownComponent { + malformed: class.into(), + }); + } + + if next_direct { + components.push(QueryComponent::DirectClass(class.into())); + next_direct = false; + } else { + components.push(QueryComponent::Class(class.into())); + } + } else { + if part.contains(['#', '.']) { + return Err(QueryParseError::UnknownComponent { + malformed: part.into(), + }); + } + + if next_direct { + components.push(QueryComponent::DirectTagName(part.into())); + next_direct = false; + } else { + components.push(QueryComponent::TagName(part.into())); + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum QueryParseError { + #[error("Query ends with '>' which does not make sense. Are you missing a selector?")] + EndsInDirect, + #[error("Two direct descendent selectors (>) appeard together")] + DoubleDirect, + #[error( + "The component {malformed} does not make sense. Valid selectors are #id, .class, and tag" + )] + UnknownComponent { malformed: String }, +} + +#[cfg(test)] +mod test { + use super::parse_query; + + macro_rules! qc { + ($tag:expr) => { + $crate::query::QueryComponent::TagName(String::from($tag)) + }; + + (>$tag:expr) => { + $crate::query::QueryComponent::DirectTagName(String::from($tag)) + }; + + (ID $tag:expr) => { + $crate::query::QueryComponent::Id(String::from($tag)) + }; + + (>ID $tag:expr) => { + $crate::query::QueryComponent::DirectId(String::from($tag)) + }; + + (. $tag:expr) => { + $crate::query::QueryComponent::Class(String::from($tag)) + }; + + (>. $tag:expr) => { + $crate::query::QueryComponent::DirectClass(String::from($tag)) + }; + } + + #[test] + fn parses_tags() { + let raw = "main section p"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!("section"), qc!("p")]) + } + + #[test] + fn parses_direct_tags() { + let raw = "main > section > p"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!(> "section"), qc!(> "p")]) + } + + #[test] + fn parses_id() { + let raw = "main #job"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!(ID "job")]) + } + + #[test] + fn parses_direct_id() { + let raw = "main > #job"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!(>ID "job")]) + } + + #[test] + fn parses_class() { + let raw = "main .post"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!(."post")]) + } + + #[test] + fn parses_direct_class() { + let raw = "main > .post"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!(>."post")]) + } + + #[test] + fn parses_complex() { + let raw = "main > article"; + let parse = parse_query(raw).unwrap(); + assert_eq!(parse, vec![qc!("main"), qc!(>."post")]) + } +} |