tailwind_rs_postcss/
tailwind_processor.rs

1//! @tailwind Directive Processing System
2//!
3//! This module provides comprehensive processing of Tailwind CSS directives
4//! including @tailwind base, @tailwind components, @tailwind utilities,
5//! @apply directives, and @layer directives.
6
7use regex::Regex;
8use std::collections::HashMap;
9use thiserror::Error;
10
11/// Main processor for Tailwind CSS directives
12pub 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/// Configuration for Tailwind directive processing
23#[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/// Custom directive configuration
34#[derive(Debug, Clone)]
35pub struct CustomDirective {
36    pub name: String,
37    pub pattern: String,
38    pub handler: String,
39}
40
41/// Cache for processed directives
42#[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
49/// Directive parser for @tailwind, @apply, and @layer directives
50pub struct DirectiveParser {
51    patterns: Vec<DirectivePattern>,
52    config: ParserConfig,
53}
54
55/// Directive pattern matching
56#[derive(Debug, Clone)]
57pub struct DirectivePattern {
58    pub name: String,
59    pub regex: Regex,
60    pub capture_groups: usize,
61}
62
63/// Parser configuration
64#[derive(Debug, Clone)]
65pub struct ParserConfig {
66    pub case_sensitive: bool,
67    pub multiline: bool,
68    pub dotall: bool,
69}
70
71/// CSS injector for base, components, and utilities
72pub struct CSSInjector {
73    base_css: String,
74    components_css: String,
75    utilities_css: String,
76    config: InjectionConfig,
77}
78
79/// Injection configuration
80#[derive(Debug, Clone)]
81pub struct InjectionConfig {
82    pub preserve_order: bool,
83    pub minify: bool,
84    pub source_map: bool,
85}
86
87/// Tailwind directive types
88#[derive(Debug, Clone, PartialEq)]
89pub enum TailwindDirective {
90    Base,
91    Components,
92    Utilities,
93}
94
95/// @apply directive structure
96#[derive(Debug, Clone)]
97pub struct ApplyDirective {
98    pub classes: Vec<String>,
99    pub selector: String,
100    pub line_number: usize,
101}
102
103/// @layer directive structure
104#[derive(Debug, Clone, PartialEq)]
105pub enum LayerDirective {
106    Base,
107    Components,
108    Utilities,
109    Custom(String),
110}
111
112/// Directive match result
113#[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/// Processing result with statistics
123#[derive(Debug, Clone)]
124pub struct ProcessingResult {
125    pub processed_css: String,
126    pub directives_processed: Vec<DirectiveMatch>,
127    pub statistics: ProcessingStatistics,
128}
129
130/// Processing statistics
131#[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/// Error types for Tailwind processing
143#[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    /// Create a new TailwindProcessor with default configuration
166    pub fn new() -> Self {
167        Self::with_config(TailwindConfig::default())
168    }
169
170    /// Create a new TailwindProcessor with custom configuration
171    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    /// Create a new TailwindProcessor with cache
188    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    /// Process @tailwind directives in CSS
204    pub fn process_directives(
205        &mut self,
206        css: &str,
207    ) -> Result<ProcessingResult, TailwindProcessorError> {
208        let start_time = std::time::Instant::now();
209
210        // Parse all directives
211        let directives = self.directive_parser.parse_tailwind_directives(css)?;
212        let apply_directives = self.directive_parser.parse_apply_directives(css)?;
213        let layer_directives = self.directive_parser.parse_layer_directives(css)?;
214
215        // Process each directive type
216        let mut processed_css = css.to_string();
217        let mut processed_directives = Vec::new();
218
219        // Process @tailwind directives
220        for directive in &directives {
221            let css_content = self.generate_css_for_directive(directive)?;
222            processed_css = self.replace_directive(&processed_css, directive, &css_content)?;
223            processed_directives.push(DirectiveMatch {
224                directive_type: "tailwind".to_string(),
225                content: format!("@tailwind {}", directive.to_string()),
226                line_number: 0, // Will be filled by parser
227                start_pos: 0,   // Will be filled by parser
228                end_pos: 0,     // Will be filled by parser
229            });
230        }
231
232        // Process @apply directives
233        for apply in &apply_directives {
234            let css_content = self.process_apply_directive(apply)?;
235            processed_css = self.replace_apply_directive(&processed_css, apply, &css_content)?;
236            processed_directives.push(DirectiveMatch {
237                directive_type: "apply".to_string(),
238                content: format!("@apply {}", apply.classes.join(" ")),
239                line_number: apply.line_number,
240                start_pos: 0, // Will be filled by parser
241                end_pos: 0,   // Will be filled by parser
242            });
243        }
244
245        // Process @layer directives
246        for layer in &layer_directives {
247            let css_content = self.process_layer_directive(layer)?;
248            processed_css = self.replace_layer_directive(&processed_css, layer, &css_content)?;
249            processed_directives.push(DirectiveMatch {
250                directive_type: "layer".to_string(),
251                content: format!("@layer {}", layer.to_string()),
252                line_number: 0, // Will be filled by parser
253                start_pos: 0,   // Will be filled by parser
254                end_pos: 0,     // Will be filled by parser
255            });
256        }
257
258        let processing_time = start_time.elapsed().as_millis() as u64;
259
260        // Generate statistics
261        let statistics = ProcessingStatistics {
262            total_directives: processed_directives.len(),
263            base_directives: directives
264                .iter()
265                .filter(|d| matches!(d, TailwindDirective::Base))
266                .count(),
267            component_directives: directives
268                .iter()
269                .filter(|d| matches!(d, TailwindDirective::Components))
270                .count(),
271            utility_directives: directives
272                .iter()
273                .filter(|d| matches!(d, TailwindDirective::Utilities))
274                .count(),
275            apply_directives: apply_directives.len(),
276            layer_directives: layer_directives.len(),
277            processing_time_ms: processing_time,
278        };
279
280        Ok(ProcessingResult {
281            processed_css,
282            directives_processed: processed_directives,
283            statistics,
284        })
285    }
286
287    /// Process @apply directive
288    pub fn process_apply(&mut self, css: &str) -> Result<String, TailwindProcessorError> {
289        let apply_directives = self.directive_parser.parse_apply_directives(css)?;
290        let mut processed_css = css.to_string();
291
292        for apply in apply_directives {
293            let css_content = self.process_apply_directive(&apply)?;
294            processed_css = self.replace_apply_directive(&processed_css, &apply, &css_content)?;
295        }
296
297        Ok(processed_css)
298    }
299
300    /// Process @layer directive
301    pub fn process_layer(&mut self, css: &str) -> Result<String, TailwindProcessorError> {
302        let layer_directives = self.directive_parser.parse_layer_directives(css)?;
303        let mut processed_css = css.to_string();
304
305        for layer in layer_directives {
306            let css_content = self.process_layer_directive(&layer)?;
307            processed_css = self.replace_layer_directive(&processed_css, &layer, &css_content)?;
308        }
309
310        Ok(processed_css)
311    }
312
313    /// Generate CSS for a specific directive
314    fn generate_css_for_directive(
315        &self,
316        directive: &TailwindDirective,
317    ) -> Result<String, TailwindProcessorError> {
318        match directive {
319            TailwindDirective::Base => {
320                if self.config.base_styles {
321                    Ok(self.css_injector.inject_base(&self.base_styles)?)
322                } else {
323                    Ok(String::new())
324                }
325            }
326            TailwindDirective::Components => {
327                if self.config.component_styles {
328                    Ok(self
329                        .css_injector
330                        .inject_components(&self.component_styles)?)
331                } else {
332                    Ok(String::new())
333                }
334            }
335            TailwindDirective::Utilities => {
336                if self.config.utility_styles {
337                    Ok(self.css_injector.inject_utilities(&self.utility_styles)?)
338                } else {
339                    Ok(String::new())
340                }
341            }
342        }
343    }
344
345    /// Process @apply directive
346    fn process_apply_directive(
347        &mut self,
348        apply: &ApplyDirective,
349    ) -> Result<String, TailwindProcessorError> {
350        let mut css = String::new();
351
352        for class in &apply.classes {
353            let class_css = self.resolve_class_to_css(class)?;
354            css.push_str(&format!("{} {{ {} }}\n", apply.selector, class_css));
355        }
356
357        Ok(css)
358    }
359
360    /// Process @layer directive
361    fn process_layer_directive(
362        &self,
363        layer: &LayerDirective,
364    ) -> Result<String, TailwindProcessorError> {
365        match layer {
366            LayerDirective::Base => Ok(self.css_injector.inject_base(&self.base_styles)?),
367            LayerDirective::Components => Ok(self
368                .css_injector
369                .inject_components(&self.component_styles)?),
370            LayerDirective::Utilities => {
371                Ok(self.css_injector.inject_utilities(&self.utility_styles)?)
372            }
373            LayerDirective::Custom(name) => {
374                // Handle custom layers
375                Ok(format!("/* Custom layer: {} */\n", name))
376            }
377        }
378    }
379
380    /// Resolve class name to CSS
381    fn resolve_class_to_css(&mut self, class: &str) -> Result<String, TailwindProcessorError> {
382        // Check cache first
383        if let Some(cached) = self.cache.get_cached_apply(class) {
384            return Ok(cached.clone());
385        }
386
387        // TODO: Implement actual class resolution
388        // This would integrate with the existing CSS generator
389        let css = format!("/* TODO: Resolve class {} */", class);
390
391        // Cache the result
392        self.cache.cache_apply(class.to_string(), css.clone());
393
394        Ok(css)
395    }
396
397    /// Replace directive in CSS
398    fn replace_directive(
399        &self,
400        css: &str,
401        directive: &TailwindDirective,
402        content: &str,
403    ) -> Result<String, TailwindProcessorError> {
404        let pattern = match directive {
405            TailwindDirective::Base => r"@tailwind\s+base;",
406            TailwindDirective::Components => r"@tailwind\s+components;",
407            TailwindDirective::Utilities => r"@tailwind\s+utilities;",
408        };
409
410        let regex = Regex::new(pattern).map_err(|e| TailwindProcessorError::CSSParsingError {
411            error: format!("Invalid regex pattern: {}", e),
412        })?;
413
414        Ok(regex.replace_all(css, content).to_string())
415    }
416
417    /// Replace @apply directive in CSS
418    fn replace_apply_directive(
419        &self,
420        css: &str,
421        apply: &ApplyDirective,
422        content: &str,
423    ) -> Result<String, TailwindProcessorError> {
424        let pattern = format!(r"@apply\s+{};", apply.classes.join(r"\s+"));
425        let regex = Regex::new(&pattern).map_err(|e| TailwindProcessorError::CSSParsingError {
426            error: format!("Invalid regex pattern: {}", e),
427        })?;
428
429        Ok(regex.replace_all(css, content).to_string())
430    }
431
432    /// Replace @layer directive in CSS
433    fn replace_layer_directive(
434        &self,
435        css: &str,
436        layer: &LayerDirective,
437        content: &str,
438    ) -> Result<String, TailwindProcessorError> {
439        let pattern = match layer {
440            LayerDirective::Base => r"@layer\s+base;",
441            LayerDirective::Components => r"@layer\s+components;",
442            LayerDirective::Utilities => r"@layer\s+utilities;",
443            LayerDirective::Custom(name) => &format!(r"@layer\s+{};", regex::escape(name)),
444        };
445
446        let regex = Regex::new(pattern).map_err(|e| TailwindProcessorError::CSSParsingError {
447            error: format!("Invalid regex pattern: {}", e),
448        })?;
449
450        Ok(regex.replace_all(css, content).to_string())
451    }
452}
453
454impl Default for TailwindConfig {
455    fn default() -> Self {
456        Self {
457            base_styles: true,
458            component_styles: true,
459            utility_styles: true,
460            apply_processing: true,
461            layer_processing: true,
462            custom_directives: Vec::new(),
463        }
464    }
465}
466
467impl ProcessorCache {
468    /// Create a new processor cache
469    pub fn new() -> Self {
470        Self {
471            directive_cache: HashMap::new(),
472            apply_cache: HashMap::new(),
473            layer_cache: HashMap::new(),
474        }
475    }
476
477    /// Get cached directive
478    pub fn get_cached_directive(&self, directive: &str) -> Option<&String> {
479        self.directive_cache.get(directive)
480    }
481
482    /// Cache directive
483    pub fn cache_directive(&mut self, directive: String, css: String) {
484        self.directive_cache.insert(directive, css);
485    }
486
487    /// Get cached apply
488    pub fn get_cached_apply(&self, class: &str) -> Option<&String> {
489        self.apply_cache.get(class)
490    }
491
492    /// Cache apply
493    pub fn cache_apply(&mut self, class: String, css: String) {
494        self.apply_cache.insert(class, css);
495    }
496}
497
498impl DirectiveParser {
499    /// Create a new directive parser
500    pub fn new() -> Self {
501        let patterns = vec![
502            DirectivePattern {
503                name: "tailwind_base".to_string(),
504                regex: Regex::new(r"@tailwind\s+base;").unwrap(),
505                capture_groups: 0,
506            },
507            DirectivePattern {
508                name: "tailwind_components".to_string(),
509                regex: Regex::new(r"@tailwind\s+components;").unwrap(),
510                capture_groups: 0,
511            },
512            DirectivePattern {
513                name: "tailwind_utilities".to_string(),
514                regex: Regex::new(r"@tailwind\s+utilities;").unwrap(),
515                capture_groups: 0,
516            },
517            DirectivePattern {
518                name: "apply".to_string(),
519                regex: Regex::new(r"@apply\s+([^;]+);").unwrap(),
520                capture_groups: 1,
521            },
522            DirectivePattern {
523                name: "layer".to_string(),
524                regex: Regex::new(r"@layer\s+([^;{]+);").unwrap(),
525                capture_groups: 1,
526            },
527        ];
528
529        Self {
530            patterns,
531            config: ParserConfig {
532                case_sensitive: false,
533                multiline: true,
534                dotall: false,
535            },
536        }
537    }
538
539    /// Parse @tailwind directives
540    pub fn parse_tailwind_directives(
541        &self,
542        css: &str,
543    ) -> Result<Vec<TailwindDirective>, TailwindProcessorError> {
544        let mut directives = Vec::new();
545
546        // Parse base directives
547        if self.patterns[0].regex.is_match(css) {
548            directives.push(TailwindDirective::Base);
549        }
550
551        // Parse components directives
552        if self.patterns[1].regex.is_match(css) {
553            directives.push(TailwindDirective::Components);
554        }
555
556        // Parse utilities directives
557        if self.patterns[2].regex.is_match(css) {
558            directives.push(TailwindDirective::Utilities);
559        }
560
561        Ok(directives)
562    }
563
564    /// Parse @apply directives
565    pub fn parse_apply_directives(
566        &self,
567        css: &str,
568    ) -> Result<Vec<ApplyDirective>, TailwindProcessorError> {
569        let mut directives = Vec::new();
570        let apply_pattern = &self.patterns[3];
571
572        for (line_num, line) in css.lines().enumerate() {
573            for cap in apply_pattern.regex.captures_iter(line) {
574                let classes_str = &cap[1];
575                let classes: Vec<String> = classes_str
576                    .split_whitespace()
577                    .map(|s| s.to_string())
578                    .collect();
579
580                directives.push(ApplyDirective {
581                    classes,
582                    selector: "".to_string(), // Will be determined by context
583                    line_number: line_num + 1,
584                });
585            }
586        }
587
588        Ok(directives)
589    }
590
591    /// Parse @layer directives
592    pub fn parse_layer_directives(
593        &self,
594        css: &str,
595    ) -> Result<Vec<LayerDirective>, TailwindProcessorError> {
596        let mut directives = Vec::new();
597        let layer_pattern = &self.patterns[4];
598
599        for cap in layer_pattern.regex.captures_iter(css) {
600            let layer_name = &cap[1];
601            let directive = match layer_name {
602                "base" => LayerDirective::Base,
603                "components" => LayerDirective::Components,
604                "utilities" => LayerDirective::Utilities,
605                name => LayerDirective::Custom(name.to_string()),
606            };
607            directives.push(directive);
608        }
609
610        Ok(directives)
611    }
612}
613
614impl CSSInjector {
615    /// Create a new CSS injector
616    pub fn new() -> Self {
617        Self {
618            base_css: String::new(),
619            components_css: String::new(),
620            utilities_css: String::new(),
621            config: InjectionConfig {
622                preserve_order: true,
623                minify: false,
624                source_map: false,
625            },
626        }
627    }
628
629    /// Inject base styles
630    pub fn inject_base(&self, css: &str) -> Result<String, TailwindProcessorError> {
631        // TODO: Implement actual base CSS injection
632        Ok(format!("/* Base styles */\n{}", css))
633    }
634
635    /// Inject component styles
636    pub fn inject_components(&self, css: &str) -> Result<String, TailwindProcessorError> {
637        // TODO: Implement actual component CSS injection
638        Ok(format!("/* Component styles */\n{}", css))
639    }
640
641    /// Inject utility styles
642    pub fn inject_utilities(&self, css: &str) -> Result<String, TailwindProcessorError> {
643        // TODO: Implement actual utility CSS injection
644        Ok(format!("/* Utility styles */\n{}", css))
645    }
646}
647
648impl std::fmt::Display for TailwindDirective {
649    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
650        match self {
651            TailwindDirective::Base => write!(f, "base"),
652            TailwindDirective::Components => write!(f, "components"),
653            TailwindDirective::Utilities => write!(f, "utilities"),
654        }
655    }
656}
657
658impl std::fmt::Display for LayerDirective {
659    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
660        match self {
661            LayerDirective::Base => write!(f, "base"),
662            LayerDirective::Components => write!(f, "components"),
663            LayerDirective::Utilities => write!(f, "utilities"),
664            LayerDirective::Custom(name) => write!(f, "{}", name),
665        }
666    }
667}
668
669#[cfg(test)]
670mod tests {
671    use super::*;
672
673    #[test]
674    fn test_tailwind_directive_processing() {
675        let mut processor = TailwindProcessor::new();
676        let css = "@tailwind base; @tailwind components; @tailwind utilities;";
677        let result = processor.process_directives(css);
678        assert!(result.is_ok());
679    }
680
681    #[test]
682    fn test_apply_directive_processing() {
683        let mut processor = TailwindProcessor::new();
684        let css = ".btn { @apply bg-blue-500 text-white px-4 py-2; }";
685        let result = processor.process_apply(css);
686        assert!(result.is_ok());
687    }
688
689    #[test]
690    fn test_layer_directive_processing() {
691        let mut processor = TailwindProcessor::new();
692        let css = "@layer base; @layer components; @layer utilities;";
693        let result = processor.process_layer(css);
694        assert!(result.is_ok());
695    }
696
697    #[test]
698    fn test_directive_parser() {
699        let parser = DirectiveParser::new();
700        let css = "@tailwind base; @tailwind components; @tailwind utilities;";
701        let directives = parser.parse_tailwind_directives(css).unwrap();
702        assert_eq!(directives.len(), 3);
703        assert!(directives.contains(&TailwindDirective::Base));
704        assert!(directives.contains(&TailwindDirective::Components));
705        assert!(directives.contains(&TailwindDirective::Utilities));
706    }
707
708    #[test]
709    fn test_apply_parser() {
710        let parser = DirectiveParser::new();
711        let css = ".btn { @apply bg-blue-500 text-white px-4 py-2; }";
712        let directives = parser.parse_apply_directives(css).unwrap();
713        assert_eq!(directives.len(), 1);
714        assert_eq!(directives[0].classes.len(), 4);
715        assert!(directives[0].classes.contains(&"bg-blue-500".to_string()));
716    }
717
718    #[test]
719    fn test_layer_parser() {
720        let parser = DirectiveParser::new();
721        let css = "@layer base; @layer components; @layer utilities;";
722        let directives = parser.parse_layer_directives(css).unwrap();
723        assert_eq!(directives.len(), 3);
724        assert!(directives.contains(&LayerDirective::Base));
725        assert!(directives.contains(&LayerDirective::Components));
726        assert!(directives.contains(&LayerDirective::Utilities));
727    }
728}