1use crate::ast::{CSSNode, CSSRule};
7use crate::parser::{CSSParser, ParseOptions};
8use crate::transformer::{CSSTransformer, TransformOptions};
9use crate::js_bridge::JSBridge;
10use crate::plugin_loader::{PluginLoader, PluginConfig, PluginResult};
11use crate::source_map::{SourceMapGenerator, SourceMap};
12use crate::error::{PostCSSError, Result};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18#[derive(Debug)]
20pub struct PostCSSEngine {
21 config: PostCSSConfig,
22 parser: CSSParser,
23 transformer: CSSTransformer,
24 js_bridge: Option<JSBridge>,
25 plugin_loader: PluginLoader,
26 source_map_generator: SourceMapGenerator,
27 cache: Arc<RwLock<HashMap<String, ProcessedCSS>>>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct PostCSSConfig {
33 pub plugins: Vec<PluginConfig>,
35 pub source_map: bool,
37 pub source_map_options: SourceMapOptions,
39 pub parser_options: ParseOptions,
41 pub transform_options: TransformOptions,
43 pub performance: PerformanceOptions,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SourceMapOptions {
50 pub inline: bool,
52 pub file: Option<String>,
54 pub source_root: Option<String>,
56 pub sources_content: bool,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct PerformanceOptions {
63 pub enable_cache: bool,
65 pub cache_size_limit: usize,
67 pub parallel_processing: bool,
69 pub memory_optimization: bool,
71}
72
73#[derive(Debug, Clone)]
75pub struct ProcessedCSS {
76 pub css: String,
78 pub source_map: Option<SourceMap>,
80 pub warnings: Vec<ProcessingWarning>,
82 pub metrics: ProcessingMetrics,
84}
85
86#[derive(Debug, Clone)]
88pub struct ProcessingWarning {
89 pub message: String,
90 pub line: Option<usize>,
91 pub column: Option<usize>,
92 pub severity: WarningSeverity,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum WarningSeverity {
98 Info,
99 Warning,
100 Error,
101}
102
103#[derive(Debug, Clone)]
105pub struct ProcessingMetrics {
106 pub parse_time: std::time::Duration,
107 pub transform_time: std::time::Duration,
108 pub generate_time: std::time::Duration,
109 pub total_time: std::time::Duration,
110 pub memory_usage: usize,
111 pub rules_processed: usize,
112 pub plugins_executed: usize,
113}
114
115impl Default for PostCSSConfig {
116 fn default() -> Self {
117 Self {
118 plugins: Vec::new(),
119 source_map: true,
120 source_map_options: SourceMapOptions::default(),
121 parser_options: ParseOptions::default(),
122 transform_options: TransformOptions::default(),
123 performance: PerformanceOptions::default(),
124 }
125 }
126}
127
128impl Default for SourceMapOptions {
129 fn default() -> Self {
130 Self {
131 inline: false,
132 file: None,
133 source_root: None,
134 sources_content: true,
135 }
136 }
137}
138
139impl Default for PerformanceOptions {
140 fn default() -> Self {
141 Self {
142 enable_cache: true,
143 cache_size_limit: 1000,
144 parallel_processing: true,
145 memory_optimization: true,
146 }
147 }
148}
149
150impl PostCSSEngine {
151 pub fn new(config: PostCSSConfig) -> Result<Self> {
153 let parser = CSSParser::new(config.parser_options.clone());
154 let transformer = CSSTransformer::new(config.transform_options.clone());
155 let plugin_loader = PluginLoader::new();
156 let source_map_generator = SourceMapGenerator::new();
157
158 let js_bridge = if config.plugins.iter().any(|p| p.requires_js()) {
160 Some(JSBridge::new()?)
161 } else {
162 None
163 };
164
165 Ok(Self {
166 config,
167 parser,
168 transformer,
169 js_bridge,
170 plugin_loader,
171 source_map_generator,
172 cache: Arc::new(RwLock::new(HashMap::new())),
173 })
174 }
175
176 pub async fn process_css(&self, input: &str) -> Result<ProcessedCSS> {
178 let start_time = std::time::Instant::now();
179
180 if self.config.performance.enable_cache {
182 if let Some(cached) = self.get_cached_result(input).await {
183 return Ok(cached);
184 }
185 }
186
187 let parse_start = std::time::Instant::now();
189 let ast = self.parser.parse(input)?;
190 let parse_time = parse_start.elapsed();
191
192 let transform_start = std::time::Instant::now();
194 let transformed_ast = self.apply_transformations(ast).await?;
195 let transform_time = transform_start.elapsed();
196
197 let generate_start = std::time::Instant::now();
199 let css = self.generate_css(&transformed_ast)?;
200 let generate_time = generate_start.elapsed();
201
202 let source_map = if self.config.source_map {
204 let source_map_options = crate::source_map::SourceMapOptions {
205 inline: self.config.source_map_options.inline,
206 file: self.config.source_map_options.file.clone(),
207 source_root: self.config.source_map_options.source_root.clone(),
208 sources_content: self.config.source_map_options.sources_content,
209 };
210 Some(self.source_map_generator.generate(
211 input,
212 &css,
213 &source_map_options,
214 )?)
215 } else {
216 None
217 };
218
219 let total_time = start_time.elapsed();
220 let metrics = ProcessingMetrics {
221 parse_time,
222 transform_time,
223 generate_time,
224 total_time,
225 memory_usage: self.get_memory_usage(),
226 rules_processed: self.count_rules(&transformed_ast),
227 plugins_executed: self.config.plugins.len(),
228 };
229
230 let result = ProcessedCSS {
231 css,
232 source_map,
233 warnings: Vec::new(), metrics,
235 };
236
237 if self.config.performance.enable_cache {
239 self.cache_result(input, &result).await;
240 }
241
242 Ok(result)
243 }
244
245 async fn apply_transformations(&self, mut ast: CSSNode) -> Result<CSSNode> {
247 for plugin_config in &self.config.plugins {
248 let plugin_result = self.plugin_loader.load_plugin(plugin_config).await?;
249
250 match plugin_result {
251 PluginResult::Native(plugin) => {
252 ast = plugin.transform(ast)?;
253 }
254 PluginResult::JavaScript(js_plugin) => {
255 if let Some(js_bridge) = &self.js_bridge {
256 ast = js_bridge.execute_plugin(&js_plugin.name, ast).await?;
257 } else {
258 return Err(PostCSSError::JavaScriptBridgeNotAvailable);
259 }
260 }
261 }
262 }
263
264 ast = self.transformer.transform(ast)?;
266
267 Ok(ast)
268 }
269
270 fn generate_css(&self, ast: &CSSNode) -> Result<String> {
272 match ast {
273 CSSNode::Stylesheet(rules) => {
274 let mut css = String::new();
275 for rule in rules {
276 css.push_str(&self.rule_to_css(rule)?);
277 css.push('\n');
278 }
279 Ok(css)
280 }
281 _ => Err(PostCSSError::InvalidAST("Expected stylesheet".to_string())),
282 }
283 }
284
285 fn rule_to_css(&self, rule: &CSSRule) -> Result<String> {
287 let mut css = String::new();
288
289 css.push_str(&rule.selector);
291 css.push_str(" {\n");
292
293 for declaration in &rule.declarations {
295 css.push_str(" ");
296 css.push_str(&declaration.property);
297 css.push_str(": ");
298 css.push_str(&declaration.value);
299 if declaration.important {
300 css.push_str(" !important");
301 }
302 css.push_str(";\n");
303 }
304
305 css.push('}');
306 Ok(css)
307 }
308
309 async fn get_cached_result(&self, input: &str) -> Option<ProcessedCSS> {
311 let cache = self.cache.read().await;
312 cache.get(input).cloned()
313 }
314
315 async fn cache_result(&self, input: &str, result: &ProcessedCSS) {
317 let mut cache = self.cache.write().await;
318
319 if cache.len() >= self.config.performance.cache_size_limit {
321 let keys_to_remove: Vec<String> = cache.keys().take(cache.len() / 2).cloned().collect();
323 for key in keys_to_remove {
324 cache.remove(&key);
325 }
326 }
327
328 cache.insert(input.to_string(), result.clone());
329 }
330
331 fn get_memory_usage(&self) -> usize {
333 std::mem::size_of::<Self>() + self.cache.try_read().map(|c| c.len() * 1024).unwrap_or(0)
336 }
337
338 fn count_rules(&self, ast: &CSSNode) -> usize {
340 match ast {
341 CSSNode::Stylesheet(rules) => rules.len(),
342 _ => 0,
343 }
344 }
345
346 pub async fn get_metrics(&self) -> EngineMetrics {
348 let cache = self.cache.read().await;
349 EngineMetrics {
350 cache_size: cache.len(),
351 memory_usage: self.get_memory_usage(),
352 plugins_loaded: self.config.plugins.len(),
353 js_bridge_available: self.js_bridge.is_some(),
354 }
355 }
356
357 pub async fn clear_cache(&self) {
359 let mut cache = self.cache.write().await;
360 cache.clear();
361 }
362}
363
364#[derive(Debug, Clone)]
366pub struct EngineMetrics {
367 pub cache_size: usize,
368 pub memory_usage: usize,
369 pub plugins_loaded: usize,
370 pub js_bridge_available: bool,
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[tokio::test]
378 async fn test_engine_creation() {
379 let config = PostCSSConfig::default();
380 let engine = PostCSSEngine::new(config);
381 assert!(engine.is_ok());
382 }
383
384 #[tokio::test]
385 async fn test_css_processing() {
386 let engine = PostCSSEngine::new(PostCSSConfig::default()).unwrap();
387 let input = ".test { color: red; }";
388 let result = engine.process_css(input).await;
389 assert!(result.is_ok());
390
391 let css = result.unwrap();
392 assert!(css.css.contains(".test"));
393 assert!(css.css.contains("color: red"));
394 }
395
396 #[tokio::test]
397 async fn test_caching() {
398 let mut config = PostCSSConfig::default();
399 config.performance.enable_cache = true;
400
401 let engine = PostCSSEngine::new(config).unwrap();
402 let input = ".test { color: red; }";
403
404 let result1 = engine.process_css(input).await.unwrap();
406
407 let result2 = engine.process_css(input).await.unwrap();
409
410 assert_eq!(result1.css, result2.css);
411 }
412}