css_parser_project/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use pest::Parser;
use pest_derive::Parser;
use regex::Regex;
use thiserror::Error;

#[derive(Parser)]
#[grammar = "css-grammar.pest"]
pub struct CSSParser;

#[derive(Error, Debug)]
pub enum CSSParseError {
    #[error("Could not parse this selector: {0}")]
    SelectorParse(String),
    
    #[error("Could not parse this property: {0}")]
    PropertyParse(String),

    #[error("Wrong hex-digit for color used: '{0}'")]
    HexDigitParse(String),
    
    #[error("Wrong hex-code for color used: '{0}'")]
    InvalidHexColor(String),
    
    #[error("Wrong dimension for property used: '{0}'")]
    DimensionParse(String),
    
    #[error("Could not parse this CSS-block: {0}")]
    CSSBlockParse(String),
}

type Result<T> = std::result::Result<T, CSSParseError>;

pub fn parse_selector(input: &str) -> Result<String> {
    CSSParser::parse(Rule::selector, input)
        .map(|pairs| pairs.as_str().to_string())
        .map_err(|e| CSSParseError::SelectorParse(format!("'{}': {}", input, e)))
}

pub fn parse_property(input: &str) -> Result<String> {
    CSSParser::parse(Rule::property, input)
        .map_err(|e| CSSParseError::PropertyParse(format!("'{}': {}", input, e))) 
        .map(|pairs| {
            pairs
                .into_iter()
                .next()
                .map(|pair| pair.as_str().to_string())
                .ok_or_else(|| CSSParseError::PropertyParse(format!("No valid property found in '{}'", input)))
        })
        .and_then(|res| res) 
}


pub fn parse_hex_color(input: &str) -> Result<String> {
    let hex_color_regex = Regex::new(r"^#([0-9A-Fa-f]{6})$").unwrap();
    if hex_color_regex.is_match(input) {
        Ok(format!("color: {}; ", input))
    } else {
        Err(CSSParseError::InvalidHexColor(input.to_string()))
    }
}

pub fn parse_dimension(input: &str) -> Result<String> {
    CSSParser::parse(Rule::dimension, input)
        .map(|pairs| pairs.as_str().to_string())
        .map_err(|e| CSSParseError::DimensionParse(format!("'{}': {}", input, e)))
}

pub fn parse_hex_digit(input: &str) -> Result<String> {
    CSSParser::parse(Rule::hex_digit, input)
        .map(|pairs| pairs.as_str().to_string())
        .map_err(|e| CSSParseError::HexDigitParse(format!("'{}': {}", input, e)))
}

pub fn parse_css_file(input: &str) -> Result<String> {
    CSSParser::parse(Rule::css_block, input)
        .map(|pairs| {
            let mut css_output = String::new();
            for pair in pairs {
                match pair.as_rule() {
                    Rule::css_block => {
                        let mut block_str = String::new();
                        let mut inner_pairs = pair.into_inner();

                        if let Some(selector_pair) = inner_pairs.next() {
                            let selector = selector_pair.as_str();
                            block_str.push_str(&format!("Selector:\n  {}\n", selector));
                        }

                        if let Some(properties_pair) = inner_pairs.next() {
                            let properties = parse_properties(properties_pair);
                            block_str.push_str(&format!("Properties:\n{}\n", properties));
                        }

                        css_output.push_str(&block_str);
                    }
                    _ => {
                        eprintln!("Unexpected rule: {:?}", pair.as_rule());
                    }
                }
            }
            css_output
        })
        .map_err(|e| CSSParseError::CSSBlockParse(format!("'{}': {}", input, e)))
}

fn parse_properties(pair: pest::iterators::Pair<Rule>) -> String {
    pair.into_inner()
        .filter_map(|p| {
            if p.as_rule() == Rule::property {
                Some(format!("  {}", p.as_str()))
            } else {
                None
            }
        })
        .collect::<Vec<String>>()
        .join("\n")
}