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 std::collections::HashMap;
8use regex::Regex;
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(&mut self, css: &str) -> Result<ProcessingResult, TailwindProcessorError> {
205        let start_time = std::time::Instant::now();
206        
207        // Parse all directives
208        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        // Process each directive type
213        let mut processed_css = css.to_string();
214        let mut processed_directives = Vec::new();
215        
216        // Process @tailwind directives
217        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, // Will be filled by parser
224                start_pos: 0,    // Will be filled by parser
225                end_pos: 0,     // Will be filled by parser
226            });
227        }
228        
229        // Process @apply directives
230        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,    // Will be filled by parser
238                end_pos: 0,     // Will be filled by parser
239            });
240        }
241        
242        // Process @layer directives
243        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, // Will be filled by parser
250                start_pos: 0,    // Will be filled by parser
251                end_pos: 0,     // Will be filled by parser
252            });
253        }
254        
255        let processing_time = start_time.elapsed().as_millis() as u64;
256        
257        // Generate statistics
258        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    /// Process @apply directive
276    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    /// Process @layer directive
289    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    /// Generate CSS for a specific directive
302    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    /// Process @apply directive
329    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    /// Process @layer directive
341    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                // Handle custom layers
348                Ok(format!("/* Custom layer: {} */\n", name))
349            },
350        }
351    }
352    
353    /// Resolve class name to CSS
354    fn resolve_class_to_css(&mut self, class: &str) -> Result<String, TailwindProcessorError> {
355        // Check cache first
356        if let Some(cached) = self.cache.get_cached_apply(class) {
357            return Ok(cached.clone());
358        }
359        
360        // TODO: Implement actual class resolution
361        // This would integrate with the existing CSS generator
362        let css = format!("/* TODO: Resolve class {} */", class);
363        
364        // Cache the result
365        self.cache.cache_apply(class.to_string(), css.clone());
366        
367        Ok(css)
368    }
369    
370    /// Replace directive in CSS
371    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    /// Replace @apply directive in CSS
386    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    /// Replace @layer directive in CSS
396    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    /// Create a new processor cache
427    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    /// Get cached directive
436    pub fn get_cached_directive(&self, directive: &str) -> Option<&String> {
437        self.directive_cache.get(directive)
438    }
439    
440    /// Cache directive
441    pub fn cache_directive(&mut self, directive: String, css: String) {
442        self.directive_cache.insert(directive, css);
443    }
444    
445    /// Get cached apply
446    pub fn get_cached_apply(&self, class: &str) -> Option<&String> {
447        self.apply_cache.get(class)
448    }
449    
450    /// Cache apply
451    pub fn cache_apply(&mut self, class: String, css: String) {
452        self.apply_cache.insert(class, css);
453    }
454}
455
456impl DirectiveParser {
457    /// Create a new directive parser
458    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    /// Parse @tailwind directives
498    pub fn parse_tailwind_directives(&self, css: &str) -> Result<Vec<TailwindDirective>, TailwindProcessorError> {
499        let mut directives = Vec::new();
500        
501        // Parse base directives
502        if self.patterns[0].regex.is_match(css) {
503            directives.push(TailwindDirective::Base);
504        }
505        
506        // Parse components directives
507        if self.patterns[1].regex.is_match(css) {
508            directives.push(TailwindDirective::Components);
509        }
510        
511        // Parse utilities directives
512        if self.patterns[2].regex.is_match(css) {
513            directives.push(TailwindDirective::Utilities);
514        }
515        
516        Ok(directives)
517    }
518    
519    /// Parse @apply directives
520    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(), // Will be determined by context
532                    line_number: line_num + 1,
533                });
534            }
535        }
536        
537        Ok(directives)
538    }
539    
540    /// Parse @layer directives
541    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    /// Create a new CSS injector
562    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    /// Inject base styles
576    pub fn inject_base(&self, css: &str) -> Result<String, TailwindProcessorError> {
577        // TODO: Implement actual base CSS injection
578        Ok(format!("/* Base styles */\n{}", css))
579    }
580    
581    /// Inject component styles
582    pub fn inject_components(&self, css: &str) -> Result<String, TailwindProcessorError> {
583        // TODO: Implement actual component CSS injection
584        Ok(format!("/* Component styles */\n{}", css))
585    }
586    
587    /// Inject utility styles
588    pub fn inject_utilities(&self, css: &str) -> Result<String, TailwindProcessorError> {
589        // TODO: Implement actual utility CSS injection
590        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}