1use cssparser::{ParseError, Parser, ParserInput, ToCss, Token};
4use proc_macro::TokenStream;
5use proc_macro2::{Ident, Span};
6use quote::{format_ident, quote};
7use std::error::Error;
8use std::fmt::Write;
9use std::fs;
10use std::path::{Path, PathBuf};
11use syn::LitStr;
12
13fn get_base_name(filename: &Path, full_path: bool) -> Result<String, Box<dyn Error>> {
14 let out = if full_path {
15 filename
16 .parent()
17 .ok_or("Can't get parent name")?
18 .join(filename.file_stem().ok_or("Can't get file stem")?)
19 .to_str()
20 .ok_or("Can.t convert path to string")?
21 .to_owned()
22 .replace('/', "_")
23 } else {
24 filename
25 .file_stem()
26 .ok_or("Can't get file stem")?
27 .to_str()
28 .ok_or("Can't get file stem")?
29 .to_owned()
30 };
31
32 Ok(out)
33}
34
35struct ParsedCss {
36 generated_css: String,
37 idents: Vec<(String, String)>,
38}
39
40fn parse_css(filename: &Path, css_source: &[u8]) -> Result<ParsedCss, Box<dyn Error>> {
41 let css_source = String::from_utf8(css_source.into())?;
42 let mut input = ParserInput::new(&css_source);
43 let mut parser = Parser::new(&mut input);
44
45 let mut css_out = String::new();
46 let mut idents = Vec::new();
47
48 let mut is_class = true;
49 while !parser.is_exhausted() {
50 match parser.next().unwrap() {
51 Token::Ident(i) => {
52 if is_class {
53 css_out += i;
54 } else {
55 let tag = get_base_name(filename, true)?;
56 let tagged_ident = format!("{}-{}", tag, i);
57
58 write!(&mut css_out, ".{}", tagged_ident)?;
59 idents.push((i.to_string(), tagged_ident));
60 }
61 }
62 Token::CurlyBracketBlock => {
63 parser
64 .parse_nested_block(|parser| -> Result<(), ParseError<'_, &String>> {
65 css_out += "{";
66 while !parser.is_exhausted() {
67 css_out += &parser.next().unwrap().to_css_string();
68 }
69 css_out += "}";
70 Ok(())
71 })
72 .map_err(|_| "Can't parse CSS")?;
73 is_class = true;
74 }
75 Token::Delim('.') => {
76 is_class = false;
77 }
78 token => {
79 println!("{:?}", token);
80 css_out += &token.to_css_string();
81 }
82 }
83 }
84
85 Ok(ParsedCss {
86 idents,
87 generated_css: css_out,
88 })
89}
90
91fn compile_sass(source: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
92 rsass::compile_scss(
93 source,
94 rsass::output::Format {
95 style: rsass::output::Style::Compressed,
96 ..Default::default()
97 },
98 )
99 .map_err(|e| format!("Can't parse SASS {}", e).into())
100}
101
102fn load_css(filename: &Path) -> Result<Vec<u8>, Box<dyn Error>> {
103 let is_sass = filename.extension().ok_or("Can't get extensions")? == "scss";
104 let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
105 let full_path = manifest_dir.join(filename);
106
107 let source = fs::read(full_path)?;
108
109 if is_sass {
110 compile_sass(&source)
111 } else {
112 Ok(source)
113 }
114}
115
116#[proc_macro]
119pub fn import_style(item: TokenStream) -> TokenStream {
120 let filename: PathBuf = syn::parse::<LitStr>(item).unwrap().value().into();
121
122 let css = load_css(&filename).unwrap();
123 let parsed = parse_css(&filename, &css).unwrap();
124
125 let base_name = get_base_name(&filename, false).unwrap();
126 let inner_struct_name = format_ident!("__{}", base_name);
127
128 let inner_struct_fields = parsed.idents.iter().map(|(ident, _)| {
129 let ident = Ident::new(ident, Span::call_site());
130 quote! { #ident: &'static str, }
131 });
132
133 let outer_struct_name = format_ident!("_{}", base_name);
134
135 let inner_struct_values = parsed.idents.iter().map(|(ident, new_ident)| {
136 let ident = format_ident!("{}", ident);
137 quote! { #ident: #new_ident, }
138 });
139
140 let base_name_ident = format_ident!("{}", base_name);
141 let css = parsed.generated_css;
142 quote! {
143 use std::ops::Deref;
144
145 struct #inner_struct_name {
146 #(#inner_struct_fields)*
147 }
148
149 struct #outer_struct_name {
150 css: &'static str,
151 names: #inner_struct_name,
152 initialized: bool,
153 }
154
155 static #base_name_ident: #outer_struct_name = #outer_struct_name {
156 css: #css,
157 names: #inner_struct_name {
158 #(#inner_struct_values)*
159 },
160 initialized: false,
161 };
162
163 impl Deref for #outer_struct_name {
164 type Target = #inner_struct_name;
165
166 fn deref(&self) -> &Self::Target {
167 if !self.initialized {
168 let init = &self.initialized as *const bool;
169 let init_mut = init as *mut bool;
170 unsafe {
171 *init_mut = true;
172 }
173
174 let window = css_loader::web_sys::window().unwrap();
175 let document = window.document().unwrap();
176 let css = document.create_element("style").unwrap();
177 css.set_text_content(Some(self.css));
178 document.head().unwrap().append_child(&css).unwrap();
179 }
180
181 &self.names
182 }
183 }
184 }
185 .into()
186}