1use crate::parser::{self, Error, ParserResult, Rule};
2use pest::iterators::{Pair, Pairs};
3use std::collections::HashMap;
4use std::fmt;
5use std::fs::File;
6use std::io::prelude::*;
7use std::path::PathBuf;
8
9pub type Identifiers = HashMap<String, String>;
10pub type Children = Vec<Child>;
11
12#[derive(Debug, PartialEq)]
13pub enum Child {
14 AtRule {
15 name: Option<String>,
16 rule: Option<String>,
17 children: Children,
18 },
19 Comment {
20 value: Option<String>,
21 },
22 Property {
23 name: Option<String>,
24 value: Option<String>,
25 },
26 SelectRule {
27 rule: Option<String>,
28 children: Children,
29 },
30}
31
32impl fmt::Display for Child {
33 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
34 match self {
35 Child::AtRule {
36 name,
37 rule,
38 children,
39 } => {
40 if let (Some(name), Some(rule)) = (name, rule) {
41 if children.is_empty() {
42 write!(formatter, "@{} {}; ", name, rule.trim())?;
43 } else {
44 write!(formatter, "@{} {} {{ ", name, rule.trim())?;
45
46 for child in children {
47 write!(formatter, "{}", child)?;
48 }
49
50 write!(formatter, "}}\n")?;
51 }
52 } else if let Some(name) = name {
53 if children.is_empty() {
54 write!(formatter, "@{};", name)?;
55 } else {
56 write!(formatter, "@{} {{ ", name)?;
57
58 for child in children {
59 write!(formatter, "{}", child)?;
60 }
61
62 write!(formatter, "}}\n")?;
63 }
64 }
65 }
66 Child::SelectRule { rule, children } => {
67 if children.is_empty() {
68 write!(formatter, "")?;
69 } else if let Some(rule) = rule {
70 write!(formatter, "{} {{ ", rule)?;
71
72 for child in children {
73 write!(formatter, "{}", child)?;
74 }
75
76 write!(formatter, "}}\n")?;
77 }
78 }
79 Child::Property { name, value } => {
80 if let (Some(name), Some(value)) = (name, value) {
81 write!(formatter, "{}: {}; ", name, value)?;
82 } else if let Some(name) = name {
83 write!(formatter, "{}:; ", name)?;
84 }
85 }
86 Child::Comment { value } => {
87 if let Some(value) = value {
88 write!(formatter, "{}", value)?;
89 }
90 }
91 };
92
93 Ok(())
94 }
95}
96
97#[derive(Debug, PartialEq)]
98pub struct Context<'c> {
99 pub module: &'c mut Module,
100 pub name: &'c str,
101 pub path: &'c PathBuf,
102 pub stylesheet: &'c mut Stylesheet,
103}
104
105impl<'c> Context<'c> {
106 fn add_identifier(&mut self, identifier: String) -> String {
107 self.module
108 .identifiers
109 .entry(identifier.clone())
110 .or_insert(format!(
111 "{}__{}__{}",
112 &self.name, &identifier, &self.stylesheet.identifiers
113 ));
114
115 self.stylesheet.identifiers += 1;
116
117 self.module.identifiers.get(&identifier).unwrap().to_owned()
118 }
119}
120
121#[derive(Debug, PartialEq)]
122pub struct Module {
123 pub children: Children,
124 pub identifiers: Identifiers,
125 pub input_path: PathBuf,
126 pub output_path: PathBuf,
127}
128
129#[cfg(test)]
130impl Default for Module {
131 fn default() -> Self {
132 use std::str::FromStr;
133
134 let path = PathBuf::from_str(file!()).unwrap();
135
136 Self {
137 children: Vec::new(),
138 identifiers: HashMap::new(),
139 input_path: path.clone(),
140 output_path: path.clone().with_extension("css.rs"),
141 }
142 }
143}
144
145impl fmt::Display for Module {
146 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
147 for child in &self.children {
149 write!(formatter, "{}", child)?;
150 }
151
152 Ok(())
153 }
154}
155
156impl<'m> Module {
157 pub fn new(
158 mut stylesheet: &mut Stylesheet,
159 path: PathBuf,
160 input: &'m str,
161 ) -> ParserResult<'m, Self> {
162 let pairs = parser::stylesheet(&input)?;
163 let mut module = Module {
164 children: Children::new(),
165 identifiers: Identifiers::new(),
166 input_path: path.clone(),
167 output_path: path.clone().with_extension("css.rs"),
168 };
169 let mut context = Context {
170 module: &mut module,
171 name: &path.file_stem().unwrap().to_str().unwrap(),
172 path: &path.parent().unwrap().to_path_buf(),
173 stylesheet: &mut stylesheet,
174 };
175
176 for pair in pairs {
177 let child = match pair.as_rule() {
178 Rule::comment => comment(pair),
179 Rule::atrule => atrule(&mut context, pair)?,
180 Rule::selectrule => selectrule(&mut context, pair)?,
181 Rule::EOI => None,
182 _ => return Err(Error::from(pair)),
183 };
184
185 if let Some(child) = child {
186 context.module.children.push(child);
187 }
188 }
189
190 Ok(module)
191 }
192}
193
194pub type Modules = HashMap<PathBuf, Module>;
195
196#[derive(Debug, PartialEq)]
197pub struct Stylesheet {
198 pub identifiers: u64,
199 pub modules: Modules,
200}
201
202impl Default for Stylesheet {
203 fn default() -> Self {
204 Self {
205 identifiers: 0,
206 modules: HashMap::new(),
207 }
208 }
209}
210
211impl Stylesheet {
212 pub fn add_module<'m>(&mut self, path: PathBuf) -> ParserResult<'m, &Module> {
213 let mut file = File::open(path.clone()).expect("file not found");
214 let mut input = String::new();
215
216 file.read_to_string(&mut input).unwrap();
217
218 let module = Module::new(self, path.clone(), &input)?;
219
220 Ok(self.modules.entry(path).or_insert(module))
221 }
222
223 #[cfg(test)]
224 fn add_test_module<'m>(&mut self, input: &str) -> ParserResult<'m, &Module> {
225 use std::str::FromStr;
226
227 let path = PathBuf::from_str(file!()).unwrap();
228
229 let module = Module::new(self, path.clone(), &input)?;
230
231 Ok(self.modules.entry(path).or_insert(module))
232 }
233
234 pub fn has_module(self, path: PathBuf) -> bool {
235 self.modules.contains_key(&path)
236 }
237
238 pub fn remove_module(mut self, path: PathBuf) {
239 self.modules.remove_entry(&path);
240 }
241}
242
243pub fn atrule<'t>(
244 mut context: &mut Context,
245 pair: Pair<'t, Rule>,
246) -> ParserResult<'t, Option<Child>> {
247 let mut name: Option<String> = None;
248 let mut rule: Option<String> = None;
249 let mut children = Vec::new();
250
251 for pair in pair.into_inner() {
252 let child = match pair.as_rule() {
253 Rule::identifier => {
254 name = Some(pair.as_str().into());
255
256 None
257 }
258 Rule::atrule_rule => {
259 if Some("keyframes".into()) == name {
260 rule = Some(format!(
261 "{}",
262 &context.add_identifier(pair.as_str().trim().into())
263 ));
264 } else if Some("import".into()) == name {
265 let quotes: &[_] = &['"', '\''];
266 let path = context
267 .path
268 .clone()
269 .join(pair.as_str().trim_matches(quotes));
270 let import = context.stylesheet.add_module(path)?;
271
272 for (old, new) in import.identifiers.iter() {
273 context
274 .module
275 .identifiers
276 .entry(old.clone())
277 .or_insert(new.clone());
278 }
279
280 return Ok(None);
281 } else {
282 rule = Some(pair.as_str().into());
283 }
284
285 None
286 }
287 Rule::comment | Rule::line_comment => comment(pair),
288 Rule::property => property(&mut context, pair)?,
289 Rule::atrule => atrule(&mut context, pair)?,
290 Rule::selectrule => selectrule(&mut context, pair)?,
291 _ => return Err(Error::from(pair)),
292 };
293
294 if let Some(child) = child {
295 children.push(child);
296 }
297 }
298
299 Ok(Some(Child::AtRule {
300 name,
301 rule,
302 children,
303 }))
304}
305
306pub fn comment<'t>(pair: Pair<'t, Rule>) -> Option<Child> {
307 Some(Child::Comment {
308 value: Some(pair.as_str().into()),
309 })
310}
311
312pub fn property<'t>(
313 mut context: &mut Context,
314 pair: Pair<'t, Rule>,
315) -> ParserResult<'t, Option<Child>> {
316 let mut name: Option<String> = None;
317 let mut value: Option<String> = None;
318
319 for pair in pair.into_inner() {
320 match pair.as_rule() {
321 Rule::identifier => {
322 name = Some(pair.as_str().into());
323 }
324 Rule::property_value => {
325 if Some("animation".into()) == name || Some("animation-name".into()) == name {
326 value = replace_identifiers(&mut context, parser::animation(pair.as_str())?)?;
327 } else {
328 value = Some(pair.as_str().trim().into());
329 }
330 }
331 _ => return Err(Error::from(pair)),
332 }
333 }
334
335 Ok(Some(Child::Property { name, value }))
336}
337
338pub fn selectrule<'t>(
339 mut context: &mut Context,
340 pair: Pair<'t, Rule>,
341) -> ParserResult<'t, Option<Child>> {
342 let mut rule: Option<String> = None;
343 let mut children = Vec::new();
344
345 for pair in pair.into_inner() {
346 let child = match pair.as_rule() {
347 Rule::selectrule_rule => {
348 rule = replace_identifiers(&mut context, parser::selector(pair.as_str())?)?;
349
350 None
351 }
352 Rule::comment | Rule::line_comment => comment(pair),
353 Rule::property => property(&mut context, pair)?,
354 Rule::atrule => atrule(&mut context, pair)?,
355 Rule::selectrule => selectrule(&mut context, pair)?,
356 _ => return Err(Error::from(pair)),
357 };
358
359 if let Some(child) = child {
360 children.push(child);
361 }
362 }
363
364 Ok(Some(Child::SelectRule { rule, children }))
365}
366
367pub fn replace_identifiers<'t>(
368 context: &mut Context,
369 pairs: Pairs<Rule>,
370) -> ParserResult<'t, Option<String>> {
371 let mut result = String::new();
372
373 for pair in pairs {
374 match pair.as_rule() {
375 Rule::identifier => {
376 result.push_str(&format!(
377 "{}",
378 &context.add_identifier(pair.as_str().trim().into())
379 ));
380 }
381 Rule::selector_class => {
382 result.push_str(&format!(
383 ".{}",
384 &context.add_identifier(pair.as_str()[1..].trim().into())
385 ));
386 }
387 _ => {
388 result.push_str(pair.as_str());
389 }
390 }
391 }
392
393 result = result.trim().into();
394
395 if result.is_empty() {
396 Ok(None)
397 } else {
398 Ok(Some(result))
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn empty_stylesheet_parses() {
408 assert_eq!(
409 Stylesheet::default(),
410 Stylesheet {
411 identifiers: 0,
412 modules: HashMap::new(),
413 }
414 )
415 }
416
417 #[test]
418 fn empty_select_rule_parses() {
419 assert_eq!(
420 Stylesheet::default().add_test_module(".foobar {}").unwrap(),
421 &Module {
422 children: vec![Child::SelectRule {
423 children: Vec::new(),
424 rule: Some(".ast__foobar__0".into()),
425 }],
426 identifiers: vec![("foobar".into(), "ast__foobar__0".into())]
427 .into_iter()
428 .collect(),
429 ..Module::default()
430 }
431 )
432 }
433
434 #[test]
435 fn select_rule_with_property_parses() {
436 assert_eq!(
437 Stylesheet::default()
438 .add_test_module(".foobar { color: red; }")
439 .unwrap(),
440 &Module {
441 children: vec![Child::SelectRule {
442 rule: Some(".ast__foobar__0".into()),
443 children: vec![Child::Property {
444 name: Some("color".into()),
445 value: Some("red".into())
446 }],
447 }],
448 identifiers: vec![("foobar".into(), "ast__foobar__0".into())]
449 .into_iter()
450 .collect(),
451 ..Module::default()
452 }
453 )
454 }
455
456 #[test]
457 fn empty_at_rule_parses() {
458 assert_eq!(
459 Stylesheet::default()
460 .add_test_module("@keyframes foobar;")
461 .unwrap(),
462 &Module {
463 children: vec![Child::AtRule {
464 name: Some("keyframes".into()),
465 children: Vec::new(),
466 rule: Some("ast__foobar__0".into()),
467 }],
468 identifiers: vec![("foobar".into(), "ast__foobar__0".into())]
469 .into_iter()
470 .collect(),
471 ..Module::default()
472 }
473 )
474 }
475
476 #[test]
477 fn unclosed_block_is_an_error() {
478 assert!(Stylesheet::default().add_test_module("p {").is_err());
479 }
480
481 #[test]
482 fn format_empty_module() {
483 let mut stylesheet = Stylesheet::default();
484 let module = stylesheet.add_test_module("").unwrap();
485
486 assert_eq!(format!("{}", module), String::new());
487 }
488
489 #[test]
490 fn format_selectrule_with_property() {
491 let mut stylesheet = Stylesheet::default();
492 let module = stylesheet
493 .add_test_module("p.foobar { color : #fff ; }")
494 .unwrap();
495
496 assert_eq!(
497 format!("{}", module),
498 String::from("p.ast__foobar__0 { color: #fff; }\n")
499 );
500 }
501}