css_parser_project/
lib.rs

1#![doc = include_str!("../docs.md")]
2
3use pest::Parser;
4use pest_derive::Parser;
5use regex::Regex;
6use thiserror::Error;
7
8/// The CSSParser struct is a parser that uses the Pest library to parse CSS code according to a predefined grammar.
9#[derive(Parser)]
10#[grammar = "css-grammar.pest"]
11pub struct CSSParser;
12
13/// The CSSParseError enum defines different error types that occur during the CSS parsing process, each carrying a String describing the specific error encountered.
14#[derive(Error, Debug)]
15pub enum CSSParseError {
16    #[error("Could not parse this selector: {0}")]
17    SelectorParse(String),
18    
19    #[error("Could not parse this property: {0}")]
20    PropertyParse(String),
21
22    #[error("Wrong hex-digit for color used: '{0}'")]
23    HexDigitParse(String),
24    
25    #[error("Wrong hex-code for color used: '{0}'")]
26    InvalidHexColor(String),
27    
28    #[error("Wrong dimension for property used: '{0}'")]
29    DimensionParse(String),
30    
31    #[error("Could not parse this CSS-block: {0}")]
32    CSSBlockParse(String),
33}
34
35type Result<T> = std::result::Result<T, CSSParseError>;
36
37pub fn parse_selector(input: &str) -> Result<String> {
38    CSSParser::parse(Rule::selector, input)
39        .map(|pairs| pairs.as_str().to_string())
40        .map_err(|e| CSSParseError::SelectorParse(format!("'{}': {}", input, e)))
41}
42
43pub fn parse_property(input: &str) -> Result<String> {
44    CSSParser::parse(Rule::property, input)
45        .map_err(|e| CSSParseError::PropertyParse(format!("'{}': {}", input, e))) 
46        .map(|pairs| {
47            pairs
48                .into_iter()
49                .next()
50                .map(|pair| pair.as_str().to_string())
51                .ok_or_else(|| CSSParseError::PropertyParse(format!("No valid property found in '{}'", input)))
52        })
53        .and_then(|res| res) 
54}
55
56
57pub fn parse_hex_color(input: &str) -> Result<String> {
58    let hex_color_regex = Regex::new(r"^#([0-9A-Fa-f]{6})$").unwrap();
59    if hex_color_regex.is_match(input) {
60        Ok(format!("color: {}; ", input))
61    } else {
62        Err(CSSParseError::InvalidHexColor(input.to_string()))
63    }
64}
65
66pub fn parse_dimension(input: &str) -> Result<String> {
67    CSSParser::parse(Rule::dimension, input)
68        .map(|pairs| pairs.as_str().to_string())
69        .map_err(|e| CSSParseError::DimensionParse(format!("'{}': {}", input, e)))
70}
71
72pub fn parse_hex_digit(input: &str) -> Result<String> {
73    CSSParser::parse(Rule::hex_digit, input)
74        .map(|pairs| pairs.as_str().to_string())
75        .map_err(|e| CSSParseError::HexDigitParse(format!("'{}': {}", input, e)))
76}
77
78pub fn parse_css_file(input: &str) -> Result<String> {
79    CSSParser::parse(Rule::css_block, input)
80        .map(|pairs| {
81            let mut css_output = String::new();
82            for pair in pairs {
83                match pair.as_rule() {
84                    Rule::css_block => {
85                        let mut block_str = String::new();
86                        let mut inner_pairs = pair.into_inner();
87
88                        if let Some(selector_pair) = inner_pairs.next() {
89                            let selector = selector_pair.as_str();
90                            block_str.push_str(&format!("Selector:\n  {}\n", selector));
91                        }
92
93                        if let Some(properties_pair) = inner_pairs.next() {
94                            let properties = parse_properties(properties_pair);
95                            block_str.push_str(&format!("Properties:\n{}\n", properties));
96                        }
97
98                        css_output.push_str(&block_str);
99                    }
100                    _ => {
101                        eprintln!("Unexpected rule: {:?}", pair.as_rule());
102                    }
103                }
104            }
105            css_output
106        })
107        .map_err(|e| CSSParseError::CSSBlockParse(format!("'{}': {}", input, e)))
108}
109
110fn parse_properties(pair: pest::iterators::Pair<Rule>) -> String {
111    pair.into_inner()
112        .filter_map(|p| {
113            if p.as_rule() == Rule::property {
114                Some(format!("  {}", p.as_str()))
115            } else {
116                None
117            }
118        })
119        .collect::<Vec<String>>()
120        .join("\n")
121}