1use std::collections::HashMap;
8use regex::Regex;
9use thiserror::Error;
10
11pub struct TailwindProcessor {
13 base_styles: String,
14 component_styles: String,
15 utility_styles: String,
16 config: TailwindConfig,
17 cache: ProcessorCache,
18 directive_parser: DirectiveParser,
19 css_injector: CSSInjector,
20}
21
22#[derive(Debug, Clone)]
24pub struct TailwindConfig {
25 pub base_styles: bool,
26 pub component_styles: bool,
27 pub utility_styles: bool,
28 pub apply_processing: bool,
29 pub layer_processing: bool,
30 pub custom_directives: Vec<CustomDirective>,
31}
32
33#[derive(Debug, Clone)]
35pub struct CustomDirective {
36 pub name: String,
37 pub pattern: String,
38 pub handler: String,
39}
40
41#[derive(Debug, Clone)]
43pub struct ProcessorCache {
44 directive_cache: HashMap<String, String>,
45 apply_cache: HashMap<String, String>,
46 layer_cache: HashMap<String, String>,
47}
48
49pub struct DirectiveParser {
51 patterns: Vec<DirectivePattern>,
52 config: ParserConfig,
53}
54
55#[derive(Debug, Clone)]
57pub struct DirectivePattern {
58 pub name: String,
59 pub regex: Regex,
60 pub capture_groups: usize,
61}
62
63#[derive(Debug, Clone)]
65pub struct ParserConfig {
66 pub case_sensitive: bool,
67 pub multiline: bool,
68 pub dotall: bool,
69}
70
71pub struct CSSInjector {
73 base_css: String,
74 components_css: String,
75 utilities_css: String,
76 config: InjectionConfig,
77}
78
79#[derive(Debug, Clone)]
81pub struct InjectionConfig {
82 pub preserve_order: bool,
83 pub minify: bool,
84 pub source_map: bool,
85}
86
87#[derive(Debug, Clone, PartialEq)]
89pub enum TailwindDirective {
90 Base,
91 Components,
92 Utilities,
93}
94
95#[derive(Debug, Clone)]
97pub struct ApplyDirective {
98 pub classes: Vec<String>,
99 pub selector: String,
100 pub line_number: usize,
101}
102
103#[derive(Debug, Clone, PartialEq)]
105pub enum LayerDirective {
106 Base,
107 Components,
108 Utilities,
109 Custom(String),
110}
111
112#[derive(Debug, Clone)]
114pub struct DirectiveMatch {
115 pub directive_type: String,
116 pub content: String,
117 pub line_number: usize,
118 pub start_pos: usize,
119 pub end_pos: usize,
120}
121
122#[derive(Debug, Clone)]
124pub struct ProcessingResult {
125 pub processed_css: String,
126 pub directives_processed: Vec<DirectiveMatch>,
127 pub statistics: ProcessingStatistics,
128}
129
130#[derive(Debug, Clone)]
132pub struct ProcessingStatistics {
133 pub total_directives: usize,
134 pub base_directives: usize,
135 pub component_directives: usize,
136 pub utility_directives: usize,
137 pub apply_directives: usize,
138 pub layer_directives: usize,
139 pub processing_time_ms: u64,
140}
141
142#[derive(Debug, Error)]
144pub enum TailwindProcessorError {
145 #[error("Invalid @tailwind directive: {directive}")]
146 InvalidDirective { directive: String },
147
148 #[error("Unknown class in @apply: {class}")]
149 UnknownClass { class: String },
150
151 #[error("Circular @apply dependency: {class}")]
152 CircularDependency { class: String },
153
154 #[error("Invalid @layer directive: {layer}")]
155 InvalidLayer { layer: String },
156
157 #[error("CSS parsing error: {error}")]
158 CSSParsingError { error: String },
159
160 #[error("Configuration error: {error}")]
161 ConfigurationError { error: String },
162}
163
164impl TailwindProcessor {
165 pub fn new() -> Self {
167 Self::with_config(TailwindConfig::default())
168 }
169
170 pub fn with_config(config: TailwindConfig) -> Self {
172 let directive_parser = DirectiveParser::new();
173 let css_injector = CSSInjector::new();
174 let cache = ProcessorCache::new();
175
176 Self {
177 base_styles: String::new(),
178 component_styles: String::new(),
179 utility_styles: String::new(),
180 config,
181 cache,
182 directive_parser,
183 css_injector,
184 }
185 }
186
187 pub fn with_cache(config: TailwindConfig, cache: ProcessorCache) -> Self {
189 let directive_parser = DirectiveParser::new();
190 let css_injector = CSSInjector::new();
191
192 Self {
193 base_styles: String::new(),
194 component_styles: String::new(),
195 utility_styles: String::new(),
196 config,
197 cache,
198 directive_parser,
199 css_injector,
200 }
201 }
202
203 pub fn process_directives(&mut self, css: &str) -> Result<ProcessingResult, TailwindProcessorError> {
205 let start_time = std::time::Instant::now();
206
207 let directives = self.directive_parser.parse_tailwind_directives(css)?;
209 let apply_directives = self.directive_parser.parse_apply_directives(css)?;
210 let layer_directives = self.directive_parser.parse_layer_directives(css)?;
211
212 let mut processed_css = css.to_string();
214 let mut processed_directives = Vec::new();
215
216 for directive in &directives {
218 let css_content = self.generate_css_for_directive(directive)?;
219 processed_css = self.replace_directive(&processed_css, directive, &css_content)?;
220 processed_directives.push(DirectiveMatch {
221 directive_type: "tailwind".to_string(),
222 content: format!("@tailwind {}", directive.to_string()),
223 line_number: 0, start_pos: 0, end_pos: 0, });
227 }
228
229 for apply in &apply_directives {
231 let css_content = self.process_apply_directive(apply)?;
232 processed_css = self.replace_apply_directive(&processed_css, apply, &css_content)?;
233 processed_directives.push(DirectiveMatch {
234 directive_type: "apply".to_string(),
235 content: format!("@apply {}", apply.classes.join(" ")),
236 line_number: apply.line_number,
237 start_pos: 0, end_pos: 0, });
240 }
241
242 for layer in &layer_directives {
244 let css_content = self.process_layer_directive(layer)?;
245 processed_css = self.replace_layer_directive(&processed_css, layer, &css_content)?;
246 processed_directives.push(DirectiveMatch {
247 directive_type: "layer".to_string(),
248 content: format!("@layer {}", layer.to_string()),
249 line_number: 0, start_pos: 0, end_pos: 0, });
253 }
254
255 let processing_time = start_time.elapsed().as_millis() as u64;
256
257 let statistics = ProcessingStatistics {
259 total_directives: processed_directives.len(),
260 base_directives: directives.iter().filter(|d| matches!(d, TailwindDirective::Base)).count(),
261 component_directives: directives.iter().filter(|d| matches!(d, TailwindDirective::Components)).count(),
262 utility_directives: directives.iter().filter(|d| matches!(d, TailwindDirective::Utilities)).count(),
263 apply_directives: apply_directives.len(),
264 layer_directives: layer_directives.len(),
265 processing_time_ms: processing_time,
266 };
267
268 Ok(ProcessingResult {
269 processed_css,
270 directives_processed: processed_directives,
271 statistics,
272 })
273 }
274
275 pub fn process_apply(&mut self, css: &str) -> Result<String, TailwindProcessorError> {
277 let apply_directives = self.directive_parser.parse_apply_directives(css)?;
278 let mut processed_css = css.to_string();
279
280 for apply in apply_directives {
281 let css_content = self.process_apply_directive(&apply)?;
282 processed_css = self.replace_apply_directive(&processed_css, &apply, &css_content)?;
283 }
284
285 Ok(processed_css)
286 }
287
288 pub fn process_layer(&mut self, css: &str) -> Result<String, TailwindProcessorError> {
290 let layer_directives = self.directive_parser.parse_layer_directives(css)?;
291 let mut processed_css = css.to_string();
292
293 for layer in layer_directives {
294 let css_content = self.process_layer_directive(&layer)?;
295 processed_css = self.replace_layer_directive(&processed_css, &layer, &css_content)?;
296 }
297
298 Ok(processed_css)
299 }
300
301 fn generate_css_for_directive(&self, directive: &TailwindDirective) -> Result<String, TailwindProcessorError> {
303 match directive {
304 TailwindDirective::Base => {
305 if self.config.base_styles {
306 Ok(self.css_injector.inject_base(&self.base_styles)?)
307 } else {
308 Ok(String::new())
309 }
310 },
311 TailwindDirective::Components => {
312 if self.config.component_styles {
313 Ok(self.css_injector.inject_components(&self.component_styles)?)
314 } else {
315 Ok(String::new())
316 }
317 },
318 TailwindDirective::Utilities => {
319 if self.config.utility_styles {
320 Ok(self.css_injector.inject_utilities(&self.utility_styles)?)
321 } else {
322 Ok(String::new())
323 }
324 },
325 }
326 }
327
328 fn process_apply_directive(&mut self, apply: &ApplyDirective) -> Result<String, TailwindProcessorError> {
330 let mut css = String::new();
331
332 for class in &apply.classes {
333 let class_css = self.resolve_class_to_css(class)?;
334 css.push_str(&format!("{} {{ {} }}\n", apply.selector, class_css));
335 }
336
337 Ok(css)
338 }
339
340 fn process_layer_directive(&self, layer: &LayerDirective) -> Result<String, TailwindProcessorError> {
342 match layer {
343 LayerDirective::Base => Ok(self.css_injector.inject_base(&self.base_styles)?),
344 LayerDirective::Components => Ok(self.css_injector.inject_components(&self.component_styles)?),
345 LayerDirective::Utilities => Ok(self.css_injector.inject_utilities(&self.utility_styles)?),
346 LayerDirective::Custom(name) => {
347 Ok(format!("/* Custom layer: {} */\n", name))
349 },
350 }
351 }
352
353 fn resolve_class_to_css(&mut self, class: &str) -> Result<String, TailwindProcessorError> {
355 if let Some(cached) = self.cache.get_cached_apply(class) {
357 return Ok(cached.clone());
358 }
359
360 let css = format!("/* TODO: Resolve class {} */", class);
363
364 self.cache.cache_apply(class.to_string(), css.clone());
366
367 Ok(css)
368 }
369
370 fn replace_directive(&self, css: &str, directive: &TailwindDirective, content: &str) -> Result<String, TailwindProcessorError> {
372 let pattern = match directive {
373 TailwindDirective::Base => r"@tailwind\s+base;",
374 TailwindDirective::Components => r"@tailwind\s+components;",
375 TailwindDirective::Utilities => r"@tailwind\s+utilities;",
376 };
377
378 let regex = Regex::new(pattern).map_err(|e| TailwindProcessorError::CSSParsingError {
379 error: format!("Invalid regex pattern: {}", e)
380 })?;
381
382 Ok(regex.replace_all(css, content).to_string())
383 }
384
385 fn replace_apply_directive(&self, css: &str, apply: &ApplyDirective, content: &str) -> Result<String, TailwindProcessorError> {
387 let pattern = format!(r"@apply\s+{};", apply.classes.join(r"\s+"));
388 let regex = Regex::new(&pattern).map_err(|e| TailwindProcessorError::CSSParsingError {
389 error: format!("Invalid regex pattern: {}", e)
390 })?;
391
392 Ok(regex.replace_all(css, content).to_string())
393 }
394
395 fn replace_layer_directive(&self, css: &str, layer: &LayerDirective, content: &str) -> Result<String, TailwindProcessorError> {
397 let pattern = match layer {
398 LayerDirective::Base => r"@layer\s+base;",
399 LayerDirective::Components => r"@layer\s+components;",
400 LayerDirective::Utilities => r"@layer\s+utilities;",
401 LayerDirective::Custom(name) => &format!(r"@layer\s+{};", regex::escape(name)),
402 };
403
404 let regex = Regex::new(pattern).map_err(|e| TailwindProcessorError::CSSParsingError {
405 error: format!("Invalid regex pattern: {}", e)
406 })?;
407
408 Ok(regex.replace_all(css, content).to_string())
409 }
410}
411
412impl Default for TailwindConfig {
413 fn default() -> Self {
414 Self {
415 base_styles: true,
416 component_styles: true,
417 utility_styles: true,
418 apply_processing: true,
419 layer_processing: true,
420 custom_directives: Vec::new(),
421 }
422 }
423}
424
425impl ProcessorCache {
426 pub fn new() -> Self {
428 Self {
429 directive_cache: HashMap::new(),
430 apply_cache: HashMap::new(),
431 layer_cache: HashMap::new(),
432 }
433 }
434
435 pub fn get_cached_directive(&self, directive: &str) -> Option<&String> {
437 self.directive_cache.get(directive)
438 }
439
440 pub fn cache_directive(&mut self, directive: String, css: String) {
442 self.directive_cache.insert(directive, css);
443 }
444
445 pub fn get_cached_apply(&self, class: &str) -> Option<&String> {
447 self.apply_cache.get(class)
448 }
449
450 pub fn cache_apply(&mut self, class: String, css: String) {
452 self.apply_cache.insert(class, css);
453 }
454}
455
456impl DirectiveParser {
457 pub fn new() -> Self {
459 let patterns = vec![
460 DirectivePattern {
461 name: "tailwind_base".to_string(),
462 regex: Regex::new(r"@tailwind\s+base;").unwrap(),
463 capture_groups: 0,
464 },
465 DirectivePattern {
466 name: "tailwind_components".to_string(),
467 regex: Regex::new(r"@tailwind\s+components;").unwrap(),
468 capture_groups: 0,
469 },
470 DirectivePattern {
471 name: "tailwind_utilities".to_string(),
472 regex: Regex::new(r"@tailwind\s+utilities;").unwrap(),
473 capture_groups: 0,
474 },
475 DirectivePattern {
476 name: "apply".to_string(),
477 regex: Regex::new(r"@apply\s+([^;]+);").unwrap(),
478 capture_groups: 1,
479 },
480 DirectivePattern {
481 name: "layer".to_string(),
482 regex: Regex::new(r"@layer\s+([^;{]+);").unwrap(),
483 capture_groups: 1,
484 },
485 ];
486
487 Self {
488 patterns,
489 config: ParserConfig {
490 case_sensitive: false,
491 multiline: true,
492 dotall: false,
493 },
494 }
495 }
496
497 pub fn parse_tailwind_directives(&self, css: &str) -> Result<Vec<TailwindDirective>, TailwindProcessorError> {
499 let mut directives = Vec::new();
500
501 if self.patterns[0].regex.is_match(css) {
503 directives.push(TailwindDirective::Base);
504 }
505
506 if self.patterns[1].regex.is_match(css) {
508 directives.push(TailwindDirective::Components);
509 }
510
511 if self.patterns[2].regex.is_match(css) {
513 directives.push(TailwindDirective::Utilities);
514 }
515
516 Ok(directives)
517 }
518
519 pub fn parse_apply_directives(&self, css: &str) -> Result<Vec<ApplyDirective>, TailwindProcessorError> {
521 let mut directives = Vec::new();
522 let apply_pattern = &self.patterns[3];
523
524 for (line_num, line) in css.lines().enumerate() {
525 for cap in apply_pattern.regex.captures_iter(line) {
526 let classes_str = &cap[1];
527 let classes: Vec<String> = classes_str.split_whitespace().map(|s| s.to_string()).collect();
528
529 directives.push(ApplyDirective {
530 classes,
531 selector: "".to_string(), line_number: line_num + 1,
533 });
534 }
535 }
536
537 Ok(directives)
538 }
539
540 pub fn parse_layer_directives(&self, css: &str) -> Result<Vec<LayerDirective>, TailwindProcessorError> {
542 let mut directives = Vec::new();
543 let layer_pattern = &self.patterns[4];
544
545 for cap in layer_pattern.regex.captures_iter(css) {
546 let layer_name = &cap[1];
547 let directive = match layer_name {
548 "base" => LayerDirective::Base,
549 "components" => LayerDirective::Components,
550 "utilities" => LayerDirective::Utilities,
551 name => LayerDirective::Custom(name.to_string()),
552 };
553 directives.push(directive);
554 }
555
556 Ok(directives)
557 }
558}
559
560impl CSSInjector {
561 pub fn new() -> Self {
563 Self {
564 base_css: String::new(),
565 components_css: String::new(),
566 utilities_css: String::new(),
567 config: InjectionConfig {
568 preserve_order: true,
569 minify: false,
570 source_map: false,
571 },
572 }
573 }
574
575 pub fn inject_base(&self, css: &str) -> Result<String, TailwindProcessorError> {
577 Ok(format!("/* Base styles */\n{}", css))
579 }
580
581 pub fn inject_components(&self, css: &str) -> Result<String, TailwindProcessorError> {
583 Ok(format!("/* Component styles */\n{}", css))
585 }
586
587 pub fn inject_utilities(&self, css: &str) -> Result<String, TailwindProcessorError> {
589 Ok(format!("/* Utility styles */\n{}", css))
591 }
592}
593
594impl std::fmt::Display for TailwindDirective {
595 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596 match self {
597 TailwindDirective::Base => write!(f, "base"),
598 TailwindDirective::Components => write!(f, "components"),
599 TailwindDirective::Utilities => write!(f, "utilities"),
600 }
601 }
602}
603
604impl std::fmt::Display for LayerDirective {
605 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
606 match self {
607 LayerDirective::Base => write!(f, "base"),
608 LayerDirective::Components => write!(f, "components"),
609 LayerDirective::Utilities => write!(f, "utilities"),
610 LayerDirective::Custom(name) => write!(f, "{}", name),
611 }
612 }
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618
619 #[test]
620 fn test_tailwind_directive_processing() {
621 let mut processor = TailwindProcessor::new();
622 let css = "@tailwind base; @tailwind components; @tailwind utilities;";
623 let result = processor.process_directives(css);
624 assert!(result.is_ok());
625 }
626
627 #[test]
628 fn test_apply_directive_processing() {
629 let mut processor = TailwindProcessor::new();
630 let css = ".btn { @apply bg-blue-500 text-white px-4 py-2; }";
631 let result = processor.process_apply(css);
632 assert!(result.is_ok());
633 }
634
635 #[test]
636 fn test_layer_directive_processing() {
637 let mut processor = TailwindProcessor::new();
638 let css = "@layer base; @layer components; @layer utilities;";
639 let result = processor.process_layer(css);
640 assert!(result.is_ok());
641 }
642
643 #[test]
644 fn test_directive_parser() {
645 let parser = DirectiveParser::new();
646 let css = "@tailwind base; @tailwind components; @tailwind utilities;";
647 let directives = parser.parse_tailwind_directives(css).unwrap();
648 assert_eq!(directives.len(), 3);
649 assert!(directives.contains(&TailwindDirective::Base));
650 assert!(directives.contains(&TailwindDirective::Components));
651 assert!(directives.contains(&TailwindDirective::Utilities));
652 }
653
654 #[test]
655 fn test_apply_parser() {
656 let parser = DirectiveParser::new();
657 let css = ".btn { @apply bg-blue-500 text-white px-4 py-2; }";
658 let directives = parser.parse_apply_directives(css).unwrap();
659 assert_eq!(directives.len(), 1);
660 assert_eq!(directives[0].classes.len(), 4);
661 assert!(directives[0].classes.contains(&"bg-blue-500".to_string()));
662 }
663
664 #[test]
665 fn test_layer_parser() {
666 let parser = DirectiveParser::new();
667 let css = "@layer base; @layer components; @layer utilities;";
668 let directives = parser.parse_layer_directives(css).unwrap();
669 assert_eq!(directives.len(), 3);
670 assert!(directives.contains(&LayerDirective::Base));
671 assert!(directives.contains(&LayerDirective::Components));
672 assert!(directives.contains(&LayerDirective::Utilities));
673 }
674}