diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/query.rs | 244 |
1 files changed, 126 insertions, 118 deletions
diff --git a/src/query.rs b/src/query.rs index 796e372..26b02aa 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,17 +1,67 @@ +use core::fmt; +use std::str::FromStr; + #[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), + Element(QueryElement), + DirectElement(QueryElement), +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct QueryElement { + pub tag: Option<String>, + pub class: Option<String>, + pub id: Option<String>, +} + +#[rustfmt::skip] // leave me alone +impl FromStr for QueryElement { + type Err = QueryParseError; + + fn from_str(raw: &str) -> Result<Self, Self::Err> { + let mut element = QueryElement::default(); + + if let Some(idx) = raw.find(' ') { + return Err(QueryParseError::MalformedComponent { + raw: raw.into(), + idx, + error: "spaces not allowed in element queries".into() + }); + } + + let mut wrk = raw; + loop { + // Guards the match from a panic + if wrk.is_empty() { break; } + + let part = match wrk[1..].find(['#', '.']) { + None => { + // Last part + let part = wrk; + wrk = &wrk[wrk.len()..]; + part + }, + Some(idx) => { + let part = &wrk[..idx+1]; + wrk = &wrk[idx+1..]; + part + } + }; + + if let Some(id) = part.strip_prefix('#') { + println!("id {id}"); + element.id = Some(id.into()); + } else if let Some(class) = part.strip_prefix('.') { + println!("class {class}"); + element.class = Some(class.into()); + } else { + element.tag = Some(part.into()); + } + } + + Ok(element) + } } pub fn parse_query(mut raw: &str) -> Result<Vec<QueryComponent>, QueryParseError> { @@ -33,6 +83,10 @@ pub fn parse_query(mut raw: &str) -> Result<Vec<QueryComponent>, QueryParseError let part = &raw[..idx]; if &raw[idx..idx + 1] == ">" { + if components.is_empty() { + return Err(QueryParseError::StartsDirect); + } + if next_direct { return Err(QueryParseError::DoubleDirect); } else { @@ -49,137 +103,91 @@ pub fn parse_query(mut raw: &str) -> Result<Vec<QueryComponent>, QueryParseError 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())); - } + let element: QueryElement = part.parse()?; + if next_direct { + components.push(QueryComponent::DirectElement(element)); + next_direct = false; } 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())); - } + components.push(QueryComponent::Element(element)); } } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub enum QueryParseError { - #[error("Query ends with '>' which does not make sense. Are you missing a selector?")] + StartsDirect, 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 }, + MalformedComponent { + raw: String, + idx: usize, + error: String, + }, +} + +impl fmt::Display for QueryParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::StartsDirect => { + write!(f, "cannot start query with a direct relation") + } + Self::EndsInDirect => { + write!( + f, + "Query ends with '>' which does not make sense. Are you missing a selector?" + ) + } + Self::DoubleDirect => { + write!(f, "Two direct descendent selectors (>) appeard together") + } + Self::MalformedComponent { raw, idx, error } => { + write!(f, "{error}\n{raw}\n{:>width$}^", width = idx - 1) + } + } + } } #[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)) - }; + use crate::query::{QueryComponent, QueryElement}; - (ID $tag:expr) => { - $crate::query::QueryComponent::Id(String::from($tag)) - }; - - (>ID $tag:expr) => { - $crate::query::QueryComponent::DirectId(String::from($tag)) - }; + use super::parse_query; - (. $tag:expr) => { - $crate::query::QueryComponent::Class(String::from($tag)) + macro_rules! txt { + () => { + None }; - (>. $tag:expr) => { - $crate::query::QueryComponent::DirectClass(String::from($tag)) + ($txt:expr) => { + Some(String::from($txt)) }; } #[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"; + fn parses_query_element() { + let raw = "main#wide.post"; let parse = parse_query(raw).unwrap(); - assert_eq!(parse, vec![qc!("main"), qc!(>."post")]) + assert_eq!( + parse, + vec![QueryComponent::Element(QueryElement { + tag: txt!("main"), + id: txt!("wide"), + class: txt!("post") + })] + ) } #[test] - fn parses_complex() { - let raw = "main > article"; + fn parses_direct_query_element() { + let raw = "> main#wide.post"; let parse = parse_query(raw).unwrap(); - assert_eq!(parse, vec![qc!("main"), qc!(>."post")]) + assert_eq!( + parse, + vec![QueryComponent::DirectElement(QueryElement { + tag: txt!("main"), + id: txt!("wide"), + class: txt!("post") + })] + ) } } |