1use proc_macro2::{Delimiter, TokenStream, TokenTree};
2
3pub struct CssData {
4 pub css: String,
5 #[cfg(feature = "spans")]
6 pub spans: Vec<(usize, proc_macro2::Span)>,
7}
8
9pub fn parse(input: TokenStream) -> CssData {
15 let mut data = CssData {
16 css: String::new(),
17 #[cfg(feature = "spans")]
18 spans: Vec::new(),
19 };
20 parse_recursive(input, &mut data);
21 data
22}
23
24fn parse_recursive(input: TokenStream, out: &mut CssData) {
25 let mut input = input.into_iter().peekable();
26
27 while let Some(tree) = input.next() {
28 match tree {
29 TokenTree::Punct(punct) => {
30 #[cfg(feature = "spans")]
31 let pos = out.css.len();
32
33 let ch = punct.as_char();
34 out.css.push(ch);
35 if !matches!(ch, '.' | '#' | '-' | '@' | ':' | '&') {
36 #[cfg(feature = "spans")]
37 out.spans.push((pos, punct.span()));
38
39 out.css.push(' ');
40 }
41 }
42 TokenTree::Ident(ident) => {
43 #[cfg(feature = "spans")]
44 out.spans.push((out.css.len(), ident.span()));
45
46 out.css.push_str(ident.to_string().trim_start_matches("r#"));
47
48 let next = input.peek();
50 if !matches!(next, Some(TokenTree::Punct(p)) if matches!(p.as_char(), '-' | ':'))
51 && !matches!(next, Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
52 {
53 out.css.push(' ');
54 }
55 }
56 TokenTree::Group(group) => {
57 let (open, close) = match group.delimiter() {
58 Delimiter::Brace => ("{ ", "} "),
59 Delimiter::Parenthesis => ("( ", ") "),
60 Delimiter::Bracket => ("[ ", "] "),
61 Delimiter::None => ("", ""),
62 };
63
64 #[cfg(feature = "spans")]
65 out.spans.push((out.css.len(), group.span_open()));
66
67 out.css.push_str(open);
68 parse_recursive(group.stream(), out);
69
70 #[cfg(feature = "spans")]
71 out.spans.push((out.css.len(), group.span_close()));
72
73 out.css.push_str(close);
74 }
75 TokenTree::Literal(lit) => {
76 #[cfg(feature = "spans")]
77 out.spans.push((out.css.len(), lit.span()));
78
79 let str = lit.to_string();
80 if str.starts_with('r') {
81 if let Ok(str) = syn::parse_str::<syn::LitStr>(&str) {
83 let str = str.value().replace(|ch| ch == '\n' || ch == '\r', " ");
85 out.css.push_str(&str);
86 out.css.push(' ');
87 continue;
88 }
89 }
90 out.css.push_str(&str);
91 out.css.push(' ');
92 }
93 }
94 }
95}