css_parser_project/
lib.rs1#![doc = include_str!("../docs.md")]
2
3use pest::Parser;
4use pest_derive::Parser;
5use regex::Regex;
6use thiserror::Error;
7
8#[derive(Parser)]
10#[grammar = "css-grammar.pest"]
11pub struct CSSParser;
12
13#[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}