1use crate::ast::{CSSNode, CSSRule};
7use crate::error::{PostCSSError, Result};
8use crate::js_bridge::JSBridge;
9use crate::parser::{CSSParser, ParseOptions};
10use crate::plugin_loader::{PluginConfig, PluginLoader, PluginResult};
11use crate::source_map::{SourceMap, SourceMapGenerator};
12use crate::transformer::{CSSTransformer, TransformOptions};
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(
211 self.source_map_generator
212 .generate(input, &css, &source_map_options)?,
213 )
214 } else {
215 None
216 };
217
218 let total_time = start_time.elapsed();
219 let metrics = ProcessingMetrics {
220 parse_time,
221 transform_time,
222 generate_time,
223 total_time,
224 memory_usage: self.get_memory_usage(),
225 rules_processed: self.count_rules(&transformed_ast),
226 plugins_executed: self.config.plugins.len(),
227 };
228
229 let result = ProcessedCSS {
230 css,
231 source_map,
232 warnings: Vec::new(), metrics,
234 };
235
236 if self.config.performance.enable_cache {
238 self.cache_result(input, &result).await;
239 }
240
241 Ok(result)
242 }
243
244 async fn apply_transformations(&self, mut ast: CSSNode) -> Result<CSSNode> {
246 for plugin_config in &self.config.plugins {
247 let plugin_result = self.plugin_loader.load_plugin(plugin_config).await?;
248
249 match plugin_result {
250 PluginResult::Native(plugin) => {
251 ast = plugin.transform(ast)?;
252 }
253 PluginResult::JavaScript(js_plugin) => {
254 if let Some(js_bridge) = &self.js_bridge {
255 ast = js_bridge.execute_plugin(&js_plugin.name, ast).await?;
256 } else {
257 return Err(PostCSSError::JavaScriptBridgeNotAvailable);
258 }
259 }
260 }
261 }
262
263 ast = self.transformer.transform(ast)?;
265
266 Ok(ast)
267 }
268
269 fn generate_css(&self, ast: &CSSNode) -> Result<String> {
271 match ast {
272 CSSNode::Stylesheet(rules) => {
273 let mut css = String::new();
274 for rule in rules {
275 css.push_str(&self.rule_to_css(rule)?);
276 css.push('\n');
277 }
278 Ok(css)
279 }
280 _ => Err(PostCSSError::InvalidAST("Expected stylesheet".to_string())),
281 }
282 }
283
284 fn rule_to_css(&self, rule: &CSSRule) -> Result<String> {
286 let mut css = String::new();
287
288 css.push_str(&rule.selector);
290 css.push_str(" {\n");
291
292 for declaration in &rule.declarations {
294 css.push_str(" ");
295 css.push_str(&declaration.property);
296 css.push_str(": ");
297 css.push_str(&declaration.value);
298 if declaration.important {
299 css.push_str(" !important");
300 }
301 css.push_str(";\n");
302 }
303
304 css.push('}');
305 Ok(css)
306 }
307
308 async fn get_cached_result(&self, input: &str) -> Option<ProcessedCSS> {
310 let cache = self.cache.read().await;
311 cache.get(input).cloned()
312 }
313
314 async fn cache_result(&self, input: &str, result: &ProcessedCSS) {
316 let mut cache = self.cache.write().await;
317
318 if cache.len() >= self.config.performance.cache_size_limit {
320 let keys_to_remove: Vec<String> = cache.keys().take(cache.len() / 2).cloned().collect();
322 for key in keys_to_remove {
323 cache.remove(&key);
324 }
325 }
326
327 cache.insert(input.to_string(), result.clone());
328 }
329
330 fn get_memory_usage(&self) -> usize {
332 std::mem::size_of::<Self>() + self.cache.try_read().map(|c| c.len() * 1024).unwrap_or(0)
335 }
336
337 fn count_rules(&self, ast: &CSSNode) -> usize {
339 match ast {
340 CSSNode::Stylesheet(rules) => rules.len(),
341 _ => 0,
342 }
343 }
344
345 pub async fn get_metrics(&self) -> EngineMetrics {
347 let cache = self.cache.read().await;
348 EngineMetrics {
349 cache_size: cache.len(),
350 memory_usage: self.get_memory_usage(),
351 plugins_loaded: self.config.plugins.len(),
352 js_bridge_available: self.js_bridge.is_some(),
353 }
354 }
355
356 pub async fn clear_cache(&self) {
358 let mut cache = self.cache.write().await;
359 cache.clear();
360 }
361}
362
363#[derive(Debug, Clone)]
365pub struct EngineMetrics {
366 pub cache_size: usize,
367 pub memory_usage: usize,
368 pub plugins_loaded: usize,
369 pub js_bridge_available: bool,
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[tokio::test]
377 async fn test_engine_creation() {
378 let config = PostCSSConfig::default();
379 let engine = PostCSSEngine::new(config);
380 assert!(engine.is_ok());
381 }
382
383 #[tokio::test]
384 async fn test_css_processing() {
385 let engine = PostCSSEngine::new(PostCSSConfig::default()).unwrap();
386 let input = ".test { color: red; }";
387 let result = engine.process_css(input).await;
388 assert!(result.is_ok());
389
390 let css = result.unwrap();
391 assert!(css.css.contains(".test"));
392 assert!(css.css.contains("color: red"));
393 }
394
395 #[tokio::test]
396 async fn test_caching() {
397 let mut config = PostCSSConfig::default();
398 config.performance.enable_cache = true;
399
400 let engine = PostCSSEngine::new(config).unwrap();
401 let input = ".test { color: red; }";
402
403 let result1 = engine.process_css(input).await.unwrap();
405
406 let result2 = engine.process_css(input).await.unwrap();
408
409 assert_eq!(result1.css, result2.css);
410 }
411}