Skip to main content

nargo_style_processor/
lib.rs

1#![warn(missing_docs)]
2
3use nargo_types::{Error, Result, Span};
4use notify::{self, Watcher};
5use std::path::Path;
6
7/// CSS 预处理器类型
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub enum PreprocessorType {
10    /// 标准 CSS
11    Css,
12    /// SCSS/SASS
13    Scss,
14    /// Less
15    Less,
16    /// Stylus
17    Stylus,
18}
19
20use std::collections::HashMap;
21
22/// CSS 处理器,用于处理和优化 CSS 代码
23#[derive(Default)]
24pub struct StyleProcessor {
25    /// 是否启用压缩
26    minify: bool,
27    /// 是否移除未使用的样式
28    remove_unused: bool,
29    /// CSS 预处理器类型
30    preprocessor: Option<PreprocessorType>,
31    /// 缓存已处理的代码,提高性能
32    cache: HashMap<(String, PreprocessorType), String>,
33}
34
35impl StyleProcessor {
36    /// 创建新的样式处理器
37    pub fn new() -> Self {
38        Self { minify: false, remove_unused: false, preprocessor: None, cache: HashMap::new() }
39    }
40
41    /// 设置是否启用压缩
42    pub fn with_minify(mut self, minify: bool) -> Self {
43        self.minify = minify;
44        self
45    }
46
47    /// 设置是否移除未使用的样式
48    pub fn with_remove_unused(mut self, remove_unused: bool) -> Self {
49        self.remove_unused = remove_unused;
50        self
51    }
52
53    /// 设置 CSS 预处理器类型
54    pub fn with_preprocessor(mut self, preprocessor: PreprocessorType) -> Self {
55        self.preprocessor = Some(preprocessor);
56        self
57    }
58
59    /// 处理 CSS 代码
60    ///
61    /// # 参数
62    /// * `css` - 要处理的 CSS 代码
63    /// * `used_selectors` - 已使用的选择器列表,用于移除未使用的样式
64    ///
65    /// # 返回值
66    /// 处理后的 CSS 代码
67    pub fn process(&mut self, css: &str, used_selectors: Option<&Vec<String>>) -> Result<String> {
68        let mut processed = css.to_string();
69
70        // 处理预处理器代码
71        if let Some(preprocessor) = self.preprocessor.clone() {
72            processed = self.process_preprocessor(&processed, &preprocessor)?;
73        }
74
75        // 移除未使用的样式
76        if self.remove_unused && used_selectors.is_some() {
77            processed = self.remove_unused_styles(&processed, used_selectors.unwrap())?;
78        }
79
80        // 压缩 CSS
81        if self.minify {
82            processed = self.minify_css(&processed);
83        }
84        else {
85            // 添加处理注释
86            processed = format!("/* Processed by Nargo Style Processor */\n{}", processed);
87        }
88
89        Ok(processed)
90    }
91
92    /// 处理预处理器代码
93    fn process_preprocessor(&mut self, code: &str, preprocessor: &PreprocessorType) -> Result<String> {
94        // 检查缓存
95        let cache_key = (code.to_string(), preprocessor.clone());
96        if let Some(cached) = self.cache.get(&cache_key) {
97            return Ok(cached.clone());
98        }
99
100        let processed = match preprocessor {
101            PreprocessorType::Css => code.to_string(),
102            PreprocessorType::Scss => code.to_string(),
103            PreprocessorType::Less => code.to_string(),
104            PreprocessorType::Stylus => code.to_string(),
105        };
106
107        // 存入缓存
108        self.cache.insert(cache_key, processed.clone());
109        Ok(processed)
110    }
111
112    /// 移除未使用的样式
113    fn remove_unused_styles(&self, css: &str, used_selectors: &Vec<String>) -> Result<String> {
114        // 简单的未使用样式移除实现
115        let mut result = String::new();
116        let mut remaining = css.to_string();
117
118        while !remaining.is_empty() {
119            // 查找规则开始
120            if let Some(start_idx) = remaining.find('{') {
121                // 提取选择器
122                let selector = remaining[..start_idx].trim().to_string();
123
124                // 查找规则结束
125                let mut brace_count = 1;
126                let mut end_idx = start_idx + 1;
127
128                while end_idx < remaining.len() && brace_count > 0 {
129                    if remaining.chars().nth(end_idx) == Some('{') {
130                        brace_count += 1;
131                    }
132                    else if remaining.chars().nth(end_idx) == Some('}') {
133                        brace_count -= 1;
134                    }
135                    end_idx += 1;
136                }
137
138                // 提取规则
139                let rule = remaining[..end_idx].to_string();
140
141                // 检查选择器是否被使用
142                if self.is_selector_used(&selector, used_selectors) {
143                    result.push_str(&rule);
144                    result.push_str("\n");
145                }
146
147                // 更新剩余内容
148                remaining = remaining[end_idx..].trim_start().to_string();
149            }
150            else {
151                // 没有更多规则,添加剩余内容
152                result.push_str(&remaining);
153                break;
154            }
155        }
156
157        Ok(result)
158    }
159
160    /// 检查选择器是否被使用
161    fn is_selector_used(&self, selector: &str, used_selectors: &Vec<String>) -> bool {
162        // 简单实现:检查选择器是否在使用列表中
163        let selectors = selector.split(',').map(|s| s.trim());
164
165        for s in selectors {
166            if used_selectors.contains(&s.to_string()) {
167                return true;
168            }
169        }
170
171        false
172    }
173
174    /// 压缩 CSS
175    fn minify_css(&self, css: &str) -> String {
176        css.trim().split('\n').map(|s| s.trim()).filter(|s| !s.is_empty() && !s.starts_with("/*") && !s.ends_with("*/")).collect::<Vec<_>>().join("").replace(" {", "{").replace("{ ", "{").replace(" }", "}").replace("} ", "}").replace(": ", ":").replace("; ", ";").replace("/*", "").replace("*/", "")
177    }
178}
179
180/// 处理 CSS 代码的便捷函数
181///
182/// # 参数
183/// * `css` - 要处理的 CSS 代码
184///
185/// # 返回值
186/// 处理后的 CSS 代码
187pub fn process(css: &str) -> Result<String> {
188    let mut processor = StyleProcessor::new();
189    processor.process(css, None)
190}
191
192/// 处理 CSS 代码并移除未使用的样式
193///
194/// # 参数
195/// * `css` - 要处理的 CSS 代码
196/// * `used_selectors` - 已使用的选择器列表
197///
198/// # 返回值
199/// 处理后的 CSS 代码
200pub fn process_with_used_selectors(css: &str, used_selectors: &Vec<String>) -> Result<String> {
201    let mut processor = StyleProcessor::new().with_remove_unused(true);
202    processor.process(css, Some(used_selectors))
203}
204
205/// 压缩 CSS 代码
206///
207/// # 参数
208/// * `css` - 要压缩的 CSS 代码
209///
210/// # 返回值
211/// 压缩后的 CSS 代码
212pub fn minify(css: &str) -> Result<String> {
213    let mut processor = StyleProcessor::new().with_minify(true);
214    processor.process(css, None)
215}
216
217/// 优化 CSS 代码(移除未使用的样式并压缩)
218///
219/// # 参数
220/// * `css` - 要优化的 CSS 代码
221/// * `used_selectors` - 已使用的选择器列表
222///
223/// # 返回值
224/// 优化后的 CSS 代码
225pub fn optimize(css: &str, used_selectors: &Vec<String>) -> Result<String> {
226    let mut processor = StyleProcessor::new().with_remove_unused(true).with_minify(true);
227    processor.process(css, Some(used_selectors))
228}
229
230/// 使用预处理器处理 CSS 代码
231///
232/// # 参数
233/// * `css` - 要处理的 CSS 代码
234/// * `preprocessor` - CSS 预处理器类型
235///
236/// # 返回值
237/// 处理后的 CSS 代码
238pub fn process_with_preprocessor(css: &str, preprocessor: PreprocessorType) -> Result<String> {
239    let mut processor = StyleProcessor::new().with_preprocessor(preprocessor);
240    processor.process(css, None)
241}
242
243/// 监听文件变化并自动重新处理样式
244///
245/// # 参数
246/// * `processor` - 样式处理器实例
247/// * `file_path` - 要监听的文件路径
248/// * `output_path` - 输出文件路径
249/// * `callback` - 文件变化时的回调函数
250///
251/// # 返回值
252/// 监听结果
253pub fn watch(processor: &StyleProcessor, file_path: &str, output_path: &str, callback: Option<fn()>) -> Result<()> {
254    // 复制处理器配置
255    let minify = processor.minify;
256    let remove_unused = processor.remove_unused;
257    let preprocessor = processor.preprocessor.clone();
258    let file_path_str = file_path.to_string();
259    let file_path_closure = file_path_str.clone();
260    let output_path = output_path.to_string();
261
262    // 创建文件监视器
263    match notify::recommended_watcher(move |res: std::result::Result<notify::Event, notify::Error>| {
264        match res {
265            Ok(event) => {
266                if let notify::EventKind::Modify(_) = event.kind {
267                    // 文件被修改,重新处理
268                    if let Ok(css) = std::fs::read_to_string(&file_path_closure) {
269                        let mut processor = StyleProcessor::new().with_minify(minify).with_remove_unused(remove_unused).with_preprocessor(preprocessor.clone().unwrap_or(PreprocessorType::Css));
270
271                        if let Ok(processed) = processor.process(&css, None) {
272                            if std::fs::write(&output_path, processed).is_ok() {
273                                println!("Style updated: {}", output_path);
274                                if let Some(cb) = callback {
275                                    cb();
276                                }
277                            }
278                        }
279                    }
280                }
281            }
282            Err(e) => println!("watch error: {:?}", e),
283        }
284    }) {
285        Ok(mut watcher) => {
286            // 开始监听文件
287            if let Err(err) = watcher.watch(Path::new(&file_path_str), notify::RecursiveMode::NonRecursive) {
288                return Err(Error::external_error("notify".to_string(), err.to_string(), Span::default()));
289            }
290            Ok(())
291        }
292        Err(err) => Err(Error::external_error("notify".to_string(), err.to_string(), Span::default())),
293    }
294}