1use std::{
2 collections::{BTreeMap, HashMap},
3 fs::{self, read_to_string},
4 path::Path,
5};
6
7use crate::{
8 config::{read_config, Config},
9 visit_class::check_html,
10 visit_macros::check_macro,
11 visit_map::check_js,
12 visit_selectors::ClassVisitor,
13};
14use lightningcss::{
15 stylesheet::{ParserOptions, PrinterOptions, StyleSheet},
16 visitor::Visit,
17};
18
19pub fn start_all() {
20 let config = read_config();
21 match config {
22 Ok(config) => {
23 let validate = config.validate();
24 if config.validate().is_ok() {
25 let mut css_walker = CSSWalker::new(&config.ignored_files);
26
27 for dir in &config.css_dir {
28 css_walker.walk_tree(dir, &config);
29 }
30
31 let mut macro_class_walker =
32 MacroClassWalker::new(&css_walker.class_visitor);
33
34 if let Some(macros) = &config.macro_classes {
35 for dir in macros {
36 macro_class_walker.walk_tree(dir, &config);
37 }
38 }
39
40 let mut html_walker = HTMLWalker::new(
41 &css_walker.class_visitor,
42 ¯o_class_walker,
43 );
44
45 for dir in &config.html_dir {
46 html_walker.walk_tree(dir, &config);
47 }
48
49 let mut js_walker = JSWalker::new(&css_walker.class_visitor);
50 if let Some(js) = &config.js_map {
51 for dir in js {
52 js_walker.walk_tree(dir, &config);
53 }
54 }
55
56 if let Some(assets_dir) = &config.assets_dir {
57 for dir in assets_dir {
58 html_walker.walk_tree(dir, &config);
59 }
60 }
61 } else {
62 println!("{}", validate.err().unwrap());
63 }
64 }
65 Err(e) => {
66 println!("{}", e);
67 }
68 }
69}
70
71fn handle_path(path: &Path) -> &Path {
72 if path.starts_with("./") {
73 if let Some(path2) = path.to_str() {
74 let mut path2 = path2.chars();
75 path2.next();
76 path2.next();
77 return Path::new(path2.as_str());
78 }
79 }
80 path
81}
82
83trait TreeWalker {
84 fn walk(&mut self, old_content: String, path: &Path) -> Option<String>;
85
86 fn write(&self, path: &Path, new_content: Option<&str>, config: &Config) {
87 let output_path = Path::new(&config.output_dir);
88 let new_path = output_path.join(handle_path(path));
89 if let Some(parent) = new_path.parent() {
90 let _ = std::fs::create_dir_all(parent);
91 if let Some(new_content) = new_content {
92 let _ = std::fs::write(new_path, new_content);
93 } else {
94 let _ = fs::copy(path, new_path);
95 }
96 }
97 }
98
99 fn walk_tree(&mut self, dir: &String, config: &Config) {
100 if let Ok(paths) = std::fs::read_dir(dir) {
101 for path in paths.flatten() {
102 if let Ok(meta) = path.metadata() {
103 if meta.is_file() {
104 if let Ok(old_content) = read_to_string(path.path()) {
105 if let Some(new_content) =
106 self.walk(old_content, &path.path())
107 {
108 self.write(
109 &path.path(),
110 Some(&new_content),
111 config,
112 );
113 }
114 } else {
115 self.write(&path.path(), None, config)
116 }
117 } else if meta.is_dir() {
118 if let Some(path) = path.path().to_str() {
119 self.walk_tree(&path.to_string(), config);
120 }
121 }
122 }
123 }
124 }
125 }
126}
127
128struct CSSWalker<'a> {
129 pub class_visitor: ClassVisitor,
130 pub ignored: &'a Option<Vec<String>>,
131}
132
133impl<'a> CSSWalker<'a> {
134 pub fn new(ignored: &'a Option<Vec<String>>) -> Self {
135 Self {
136 class_visitor: ClassVisitor::default(),
137 ignored,
138 }
139 }
140
141 pub fn is_ignored(&self, path: &Path) -> bool {
142 if let Some(ignored) = self.ignored {
143 if let Some(path) = path.to_str() {
144 return ignored.contains(&path.to_string());
145 }
146 }
147 false
148 }
149}
150
151impl<'a> TreeWalker for CSSWalker<'a> {
152 fn walk(&mut self, old_content: String, path: &Path) -> Option<String> {
153 if self.is_ignored(path) {
154 return None;
155 }
156 let a = StyleSheet::parse(&old_content, ParserOptions::default());
157 if let Ok(mut stylesheet) = a {
158 let _ = stylesheet.visit(&mut self.class_visitor);
159 let opt = PrinterOptions {
160 minify: true,
161 ..Default::default()
162 };
163 if let Ok(f) = stylesheet.to_css(opt) {
164 return Some(f.code);
165 }
166 }
167 None
168 }
169}
170
171struct HTMLWalker<'a> {
172 pub class_visitor: &'a ClassVisitor,
173 pub macros_walker: &'a MacroClassWalker<'a>,
174}
175
176impl<'a> HTMLWalker<'a> {
177 pub fn new(
178 class_visitor: &'a ClassVisitor,
179 macros_walker: &'a MacroClassWalker,
180 ) -> Self {
181 Self {
182 class_visitor,
183 macros_walker,
184 }
185 }
186}
187
188impl<'a> TreeWalker for HTMLWalker<'a> {
189 fn walk(&mut self, mut old_content: String, path: &Path) -> Option<String> {
190 if let Some(updated_macros) =
191 self.macros_walker.macros.get(path.to_str().unwrap())
192 {
193 for m in updated_macros {
194 old_content = old_content.replace(m.0, m.1);
195 }
196 }
197 if let Ok(html) = check_html(&old_content, self.class_visitor) {
198 return Some(html);
199 }
200 None
201 }
202}
203
204struct JSWalker<'a> {
205 pub class_visitor: &'a ClassVisitor,
206}
207
208impl<'a> JSWalker<'a> {
209 pub fn new(class_visitor: &'a ClassVisitor) -> Self {
210 Self { class_visitor }
211 }
212}
213
214impl<'a> TreeWalker for JSWalker<'a> {
215 fn walk(&mut self, old_content: String, _: &Path) -> Option<String> {
216 if let Some(html) = check_js(&old_content, self.class_visitor) {
217 if let Ok(html) = String::from_utf8(html) {
218 return Some(html);
219 }
220 }
221 None
222 }
223}
224
225pub struct MacroClassWalker<'a> {
226 pub class_visitor: &'a ClassVisitor,
227 pub macros: HashMap<String, BTreeMap<String, String>>,
228}
229
230impl<'a> MacroClassWalker<'a> {
231 pub fn new(class_visitor: &'a ClassVisitor) -> Self {
232 Self {
233 class_visitor,
234 macros: HashMap::new(),
235 }
236 }
237
238 pub fn insert(&mut self, path: String, map: BTreeMap<String, String>) {
239 self.macros.insert(path, map);
240 }
241}
242
243impl<'a> TreeWalker for MacroClassWalker<'a> {
244 fn walk(&mut self, old_content: String, path: &Path) -> Option<String> {
245 check_macro(old_content, self.class_visitor, self, path)
246 }
247}