Skip to main content

procss/ast/ruleset/
rule.rs

1// ┌───────────────────────────────────────────────────────────────────────────┐
2// │                                                                           │
3// │  ██████╗ ██████╗  ██████╗   Copyright (C) 2022, The Prospective Company   │
4// │  ██╔══██╗██╔══██╗██╔═══██╗                                                │
5// │  ██████╔╝██████╔╝██║   ██║  This file is part of the Procss library,      │
6// │  ██╔═══╝ ██╔══██╗██║   ██║  distributed under the terms of the            │
7// │  ██║     ██║  ██║╚██████╔╝  Apache License 2.0.  The full license can     │
8// │  ╚═╝     ╚═╝  ╚═╝ ╚═════╝   be found in the LICENSE file.                 │
9// │                                                                           │
10// └───────────────────────────────────────────────────────────────────────────┘
11
12use std::borrow::Cow;
13
14use nom::branch::alt;
15use nom::bytes::complete::{is_not, tag};
16use nom::combinator::recognize;
17use nom::error::ParseError;
18use nom::multi::many0;
19use nom::sequence::tuple;
20use nom::IResult;
21
22use crate::ast::token::{comment0, parse_string_literal, parse_symbol, trim_whitespace};
23use crate::render::RenderCss;
24use crate::transform::TransformCss;
25
26/// A CSS rule, of the form `xxx: yyy` (delimited by `;` in a ruleset).
27#[derive(Clone, Debug, Eq, PartialEq, Hash)]
28pub struct Rule<'a> {
29    pub property: Cow<'a, str>,
30    pub value: Cow<'a, str>,
31}
32
33impl<'a> TransformCss<Rule<'a>> for Rule<'a> {
34    fn transform_each<F: FnMut(&mut Rule<'a>)>(&mut self, f: &mut F) {
35        f(self)
36    }
37}
38
39impl<'a> RenderCss for Rule<'a> {
40    fn render(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        let Rule { property, value } = self;
42        write!(f, "{}:", property)?;
43        trim_whitespace(value, f);
44        write!(f, ";")
45    }
46}
47
48// TODO property is not the same parser as tag.
49// TODO this Cow is not borrowed ...
50impl<'a> crate::parser::ParseCss<'a> for Rule<'a> {
51    fn parse<E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Self, E> {
52        let (input, property) = parse_symbol(input)?;
53        let (input, _) = tuple((comment0, tag(":"), comment0))(input)?;
54        let (input, value) =
55            recognize(many0(alt((is_not("\";}"), parse_string_literal()))))(input)?;
56        Ok((input, Rule {
57            property: property.into(),
58            value: value.into(),
59        }))
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use std::assert_matches::assert_matches;
66
67    use super::*;
68    use crate::parser::ParseCss;
69
70    #[test]
71    fn test_rule_value_string() {
72        assert_matches!(
73            Rule::parse::<()>("--column-selector--background: url(\"test\")"),
74            Ok(("", Rule {
75                property,
76                value,
77            })) if value == "url(\"test\")" && property == "--column-selector--background"
78        )
79    }
80
81    #[test]
82    fn test_rule_escaped_string() {
83        assert_matches!(
84            Rule::parse::<()>("test: \"\\1234\""),
85            Ok(("", Rule {
86                property,
87                value,
88            })) if value == "\"\\1234\"" && property == "test"
89        )
90    }
91
92    #[test]
93    fn test_rule_escaped_string_2() {
94        assert_matches!(
95            Rule::parse::<()>("test: \": test ; alpha\""),
96            Ok(("", Rule {
97                property,
98                value,
99            })) if value == "\": test ; alpha\"" && property ==  "test"
100        )
101    }
102
103    #[ignore]
104    #[test]
105    fn test_rule_escaped_string_3() {
106        assert_matches!(
107            Rule::parse::<()>("test: ': test ; alpha'"),
108            Ok(("", Rule {
109                property,
110                value,
111            })) if value == "\"\\1234\"" && property == "test"
112        )
113    }
114}