use nom::branch::alt;
use nom::error::ParseError;
use nom::multi::many0;
use nom::sequence::tuple;
use nom::IResult;
use super::combinator::*;
use super::selector_term::*;
use crate::parser::*;
use crate::render::*;
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum SelectorPath<'a> {
Cons(
SelectorTerm<'a, Option<&'a str>>,
Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)>,
),
PartialCons(
SelectorTerm<'a, ()>,
Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)>,
),
}
use SelectorPath::*;
impl<'a> SelectorPath<'a> {
fn cons(
&self,
selector: &SelectorTerm<'a, ()>,
tail: Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)>,
) -> Self {
match self {
Cons(x, _) => Cons(x.join(selector), tail),
PartialCons(x, _) => PartialCons(x.join(selector), tail),
}
}
fn tail(&self) -> Vec<(Combinator, SelectorTerm<'a, Option<&'a str>>)> {
match self {
Cons(_, tail) => tail.clone(),
PartialCons(_, tail) => tail.clone(),
}
}
pub fn join(&self, other: &Self) -> Self {
match (&self, other) {
(head, Cons(selector, tail)) => {
let mut new_tail = head.tail();
new_tail.push((Combinator::Null, selector.clone()));
new_tail.append(&mut tail.clone());
head.cons(&SelectorTerm::default(), new_tail)
}
(head, PartialCons(selector2, tail2)) => {
let mut new_tail = head.tail();
match new_tail.pop() {
Some((c, last)) => {
new_tail.push((c, last.join(selector2)));
new_tail.extend(tail2.iter().cloned());
head.cons(&SelectorTerm::default(), new_tail)
}
None => {
new_tail.append(&mut tail2.clone());
head.cons(selector2, new_tail)
}
}
}
}
}
}
impl<'a> RenderCss for SelectorPath<'a> {
fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
Cons(selector, rest) => {
selector.render(f)?;
rest.render(f)?;
}
PartialCons(selector, rest) => {
write!(f, "&")?;
selector.render(f)?;
rest.render(f)?;
}
};
Ok(())
}
}
impl<'a> ParseCss<'a> for SelectorPath<'a> {
fn parse<E>(input: &'a str) -> IResult<&'a str, Self, E>
where
E: ParseError<&'a str>,
{
alt((parse_selector_self_list, parse_selector_list))(input)
}
}
fn parse_selector_list<'a, E>(input: &'a str) -> IResult<&'a str, SelectorPath<'a>, E>
where
E: ParseError<&'a str>,
{
let (rest, x) = SelectorTerm::parse(input)?;
let (rest, combinators) = many0(tuple((Combinator::parse, SelectorTerm::parse)))(rest)?;
Ok((rest, Cons(x, combinators)))
}
fn parse_selector_self_list<'a, E>(input: &'a str) -> IResult<&'a str, SelectorPath<'a>, E>
where
E: ParseError<&'a str>,
{
let (rest, x) = SelectorTerm::parse(input)?;
let (rest, combinators) = many0(tuple((Combinator::parse, SelectorTerm::parse)))(rest)?;
Ok((rest, PartialCons(x, combinators)))
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
use super::*;
#[test]
fn test_null() {
assert_matches!(
SelectorPath::parse::<()>("div img"),
Ok((
"",
SelectorPath::Cons(
SelectorTerm {
tag: Some("div"),
..
},
xs
)
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
)
}
#[test]
fn test_desc() {
assert_matches!(
SelectorPath::parse::<()>("div > img"),
Ok((
"",
SelectorPath::Cons(
SelectorTerm {
tag: Some("div"),
..
},
xs
)
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
)
}
#[test]
fn test_self() {
assert_matches!(
SelectorPath::parse::<()>("& > img"),
Ok((
"",
SelectorPath::PartialCons(
_,
xs
)
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
)
}
#[ignore]
#[test]
fn test_inner_self() {
assert_matches!(
SelectorPath::parse::<()>("@nest div & img"),
Ok((
"",
SelectorPath::PartialCons(
_,
xs
)
)) if xs.len() == 1 && xs[0].1.tag == Some("img")
)
}
}