use nom::{
IResult,
bytes::complete::{take, take_until, tag, take_while},
sequence::{delimited},
character::complete::{char},
multi::{many0},
combinator::{opt},
error::{context, ParseError, VerboseError},
};
pub mod errors;
use super::errors::NEW_LINE_ERROR;
use errors::*;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Identifier {
Id(String),
Class(String),
Host(String),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CssRule {
identifier: Identifier,
selector: String,
value: String,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CssRoot {
rules: Vec<CssRule>,
}
pub fn identifier() -> impl Fn(&str) -> IResult<&str, Identifier, nom::error::VerboseError<&str>> {
move |input| {
let (input, ident): (&str, &str) = context(
IDENTIFIER_ERROR,
take(1usize)
)(input)?;
let ident = match ident {
"#" => Ok(Identifier::Id(String::from(ident))),
"." => Ok(Identifier::Class(String::from(ident))),
":" => Ok(Identifier::Host(String::from(ident))),
c => {
Err(nom::Err::Error(VerboseError::from_char(input, c.chars().take(1).collect::<Vec<char>>().first().unwrap().to_owned())))
}
}?;
Ok((input, ident))
}
}
pub fn css_rule(input: &str) -> IResult<&str, CssRule, VerboseError<&str>> {
let input = input.trim_start_matches('\n');
let (input, space_before) = take_while(move |s| s == ' ')(input)?;
let (input, identifier) = identifier()(input)?;
let (input, selector) = take_until("{")(input)?;
let selector = selector.trim_end();
let opening_tag = format!("{}", '{');
let closing_tag = format!("{}{}", space_before, '}');
let (input, value) = delimited(
tag(opening_tag.as_str()),
take_until(closing_tag.as_str()),
tag(closing_tag.as_str())
)(input)?;
Ok((input, CssRule {
identifier,
selector: String::from(selector),
value: String::from(value),
}))
}
pub fn css(input: &str) -> IResult<&str, CssRoot, VerboseError<&str>> {
let input =input.trim_end();
let input =input.trim_end_matches('\n');
let input =input.trim_end_matches(' ');
let (input, rules) = many0(css_rule)(input)?;
let (input, _rest) = nom::character::complete::multispace0(input)?;
Ok((input, CssRoot {
rules,
}))
}
#[test]
fn parse_css_rule_test() -> Result<(), String> {
let css_string = r#"
.test-class {
display: block;
background-color: rgb(0,0,0,0);
}
"#;
let result = css(css_string).unwrap();
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}
#[test]
fn parse_css_rules_test() -> Result<(), String> {
let css_string = r#"
.test-class {
display: block;
background-color: rgb(0,0,0,0);
}
#test-id {
display: block;
background-color: rgb(0,0,0,0);
background-image: url({{#test}}{{/test}});
}
"#;
let result = css(css_string).map_err(|e| e.to_string())?;
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
},
CssRule {
identifier: Identifier::Id("#".to_string()),
selector: "test-id".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);
background-image: url({{#test}}{{/test}});\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}
#[test]
fn parse_css_rule_with_hover_pseudo_selector_test() -> Result<(), String> {
let css_string = r#"
.test-class:hover {
display: block;
background-color: rgb(0,0,0,0);
}
"#;
let result = css(css_string).unwrap();
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class:hover".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}
#[test]
fn parse_css_rule_with_first_child_selector_test() -> Result<(), String> {
let css_string = r#"
.test-class:first-child {
display: block;
background-color: rgb(0,0,0,0);
}
"#;
let result = css(css_string).unwrap();
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class:first-child".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}
#[test]
fn parse_css_rule_with_nth_child_selector_test() -> Result<(), String> {
let css_string = r#"
.test-class:nth-child(2){
display: block;
background-color: rgb(0,0,0,0);
}
"#;
let result = css(css_string).unwrap();
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class:nth-child(2)".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}
#[test]
fn parse_css_rule_with_after_selector_test() -> Result<(), String> {
let css_string = r#"
.test-class::after{
display: block;
background-color: rgb(0,0,0,0);
}
"#;
let result = css(css_string).unwrap();
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class::after".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}
#[test]
fn parse_css_rule_with_before_selector_test() -> Result<(), String> {
let css_string = r#"
.test-class::before {
display: block;
background-color: rgb(0,0,0,0);
}
"#;
let result = css(css_string).unwrap();
assert_eq!(result.1, CssRoot {
rules: vec![
CssRule {
identifier: Identifier::Class(".".to_string()),
selector: "test-class::before".to_string(),
value: "\n display: block;
background-color: rgb(0,0,0,0);\n".to_string(),
}
],
});
assert_eq!(result.0, "");
Ok(())
}