1pub mod ast;
56pub mod parser;
57
58use crate::ast::Module;
59use crate::parser::ParserResult;
60use glob::glob;
61use quote::quote;
62use std::collections::HashMap;
63use std::env;
64use std::fmt;
65use std::fs::File;
66use std::io::Write;
67use std::ops::Index;
68use std::path::{Path, PathBuf};
69use std::str::FromStr;
70pub use css_modules_macros::*;
71
72#[derive(Debug)]
74pub struct CssModules {
75 ast: ast::Stylesheet,
76}
77
78impl Default for CssModules {
79 fn default() -> Self {
80 Self {
81 ast: ast::Stylesheet::default(),
82 }
83 }
84}
85
86impl fmt::Display for CssModules {
87 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
88 for (_, module) in &self.ast.modules {
89 for child in &module.children {
90 write!(formatter, "{}", child)?
91 }
92 }
93
94 Ok(())
95 }
96}
97
98impl CssModules {
99 pub fn modules(&self) -> Vec<&Module> {
101 self.ast
102 .modules
103 .iter()
104 .map(|(_, module)| module)
105 .collect::<Vec<&Module>>()
106 }
107
108 pub fn add_module<'m>(&mut self, path: &str) -> ParserResult<'m, &Module> {
110 let path = PathBuf::from_str(path).unwrap().canonicalize().unwrap();
111
112 self.ast.add_module(path)
113 }
114
115 pub fn add_modules<'m>(&mut self, pattern: &str) -> ParserResult<'m, ()> {
117 for entry in glob(pattern).expect("Failed to read glob pattern") {
118 self.ast.add_module(entry.unwrap())?;
119 }
120
121 Ok(())
122 }
123
124 pub fn has_module(self, path: &str) -> bool {
126 let path = PathBuf::from_str(path).unwrap().canonicalize().unwrap();
127
128 self.ast.has_module(path)
129 }
130
131 pub fn remove_module(self, path: &str) {
133 let path = PathBuf::from_str(path).unwrap().canonicalize().unwrap();
134
135 self.ast.remove_module(path)
136 }
137
138 pub fn compile(&self, stylesheet_name: &str) {
140 fn write(filename: &PathBuf, content: String) {
141 use std::fs::create_dir_all;
142
143 let dirname = filename.parent().unwrap();
144
145 create_dir_all(&dirname).expect("could not create directory.");
146
147 let mut file = File::create(&filename).expect("could not create file.");
148
149 file.write_all(content.as_bytes())
150 .expect("could not write to file.");
151 }
152
153 fn is_fresh(input: &PathBuf, output: &PathBuf) -> bool {
154 if !output.is_file() {
155 false
156 } else {
157 let input = input.metadata().unwrap();
158 let output = output.metadata().unwrap();
159
160 output.modified().unwrap() >= input.modified().unwrap()
161 }
162 }
163
164 let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
165 let manifest_dir = Path::new(&manifest_dir);
166 let out_dir = env::var("OUT_DIR").unwrap();
167 let out_dir = Path::new(&out_dir);
168 let mut stylesheet = String::new();
169 let mut re_render = false;
170
171 for module in &self.modules() {
172 if !is_fresh(&module.input_path, &module.output_path) {
173 let module_name = module.input_path.file_name().unwrap().to_str().unwrap();
174 let mut identifiers = Vec::new();
175
176 for (_old, _new) in &module.identifiers {
177 identifiers.push(quote! {(#_old, #_new)});
178 }
179
180 let output = quote! {
181 CssModuleBuilder::new()
182 .with_identifiers(vec![#(#identifiers),*])
183 .with_module_name(#module_name)
184 .with_stylesheet_name(#stylesheet_name)
185 .finish()
186 };
187
188 write(&out_dir.join(&module.output_path), output.to_string());
189
190 re_render = true;
191 }
192
193 for child in &module.children {
194 stylesheet.push_str(&format!("{}", child));
195 }
196 }
197
198 if re_render {
199 let stylesheet_path = manifest_dir.join(&stylesheet_name);
200
201 write(&stylesheet_path, stylesheet);
202 }
203 }
204}
205
206#[derive(Debug, PartialEq)]
208pub struct CssModuleBuilder<'b> {
209 identifiers: Option<HashMap<&'b str, &'b str>>,
210 module_name: Option<&'b str>,
211 stylesheet_name: Option<&'b str>,
212}
213
214impl<'b> CssModuleBuilder<'b> {
215 pub fn new() -> Self {
216 Self {
217 identifiers: None,
218 module_name: None,
219 stylesheet_name: None,
220 }
221 }
222
223 pub fn with_identifiers(self, identifiers: Vec<(&'b str, &'b str)>) -> Self {
224 Self {
225 identifiers: Some(identifiers.into_iter().collect()),
226 ..self
227 }
228 }
229
230 pub fn with_module_name(self, module_name: &'b str) -> Self {
231 Self {
232 module_name: Some(module_name),
233 ..self
234 }
235 }
236
237 pub fn with_stylesheet_name(self, stylesheet_name: &'b str) -> Self {
238 Self {
239 stylesheet_name: Some(stylesheet_name),
240 ..self
241 }
242 }
243
244 pub fn finish(self) -> CssModule<'b> {
245 if let (Some(identifiers), Some(module_name), Some(stylesheet_name)) =
246 (self.identifiers, self.module_name, self.stylesheet_name)
247 {
248 CssModule {
249 identifiers,
250 module_name,
251 stylesheet_name,
252 }
253 } else {
254 panic!("Unable to initialize CssModule, not all parameters are set.");
255 }
256 }
257}
258
259#[derive(Debug, PartialEq)]
271pub struct CssModule<'m> {
272 pub identifiers: HashMap<&'m str, &'m str>,
273 pub module_name: &'m str,
274 pub stylesheet_name: &'m str,
275}
276
277impl<'m> Index<&'m str> for CssModule<'m> {
278 type Output = &'m str;
279
280 fn index<'b>(&self, identifier: &'b str) -> &&'m str {
281 match self.identifiers.get(identifier) {
282 Some(new_identifier) => &new_identifier,
283 None => panic!(
284 "Class `{}` was not found in {}",
285 identifier, self.module_name
286 ),
287 }
288 }
289}