1use regex::Regex;
8use std::collections::HashMap;
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(
205 &mut self,
206 css: &str,
207 ) -> Result<ProcessingResult, TailwindProcessorError> {
208 let start_time = std::time::Instant::now();
209
210 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 let mut processed_css = css.to_string();
217 let mut processed_directives = Vec::new();
218
219 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, start_pos: 0, end_pos: 0, });
230 }
231
232 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, end_pos: 0, });
243 }
244
245 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, start_pos: 0, end_pos: 0, });
256 }
257
258 let processing_time = start_time.elapsed().as_millis() as u64;
259
260 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 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 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 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 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 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 Ok(format!("/* Custom layer: {} */\n", name))
376 }
377 }
378 }
379
380 fn resolve_class_to_css(&mut self, class: &str) -> Result<String, TailwindProcessorError> {
382 if let Some(cached) = self.cache.get_cached_apply(class) {
384 return Ok(cached.clone());
385 }
386
387 let css = format!("/* TODO: Resolve class {} */", class);
390
391 self.cache.cache_apply(class.to_string(), css.clone());
393
394 Ok(css)
395 }
396
397 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 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 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 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 pub fn get_cached_directive(&self, directive: &str) -> Option<&String> {
479 self.directive_cache.get(directive)
480 }
481
482 pub fn cache_directive(&mut self, directive: String, css: String) {
484 self.directive_cache.insert(directive, css);
485 }
486
487 pub fn get_cached_apply(&self, class: &str) -> Option<&String> {
489 self.apply_cache.get(class)
490 }
491
492 pub fn cache_apply(&mut self, class: String, css: String) {
494 self.apply_cache.insert(class, css);
495 }
496}
497
498impl DirectiveParser {
499 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 pub fn parse_tailwind_directives(
541 &self,
542 css: &str,
543 ) -> Result<Vec<TailwindDirective>, TailwindProcessorError> {
544 let mut directives = Vec::new();
545
546 if self.patterns[0].regex.is_match(css) {
548 directives.push(TailwindDirective::Base);
549 }
550
551 if self.patterns[1].regex.is_match(css) {
553 directives.push(TailwindDirective::Components);
554 }
555
556 if self.patterns[2].regex.is_match(css) {
558 directives.push(TailwindDirective::Utilities);
559 }
560
561 Ok(directives)
562 }
563
564 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(), line_number: line_num + 1,
584 });
585 }
586 }
587
588 Ok(directives)
589 }
590
591 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 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 pub fn inject_base(&self, css: &str) -> Result<String, TailwindProcessorError> {
631 Ok(format!("/* Base styles */\n{}", css))
633 }
634
635 pub fn inject_components(&self, css: &str) -> Result<String, TailwindProcessorError> {
637 Ok(format!("/* Component styles */\n{}", css))
639 }
640
641 pub fn inject_utilities(&self, css: &str) -> Result<String, TailwindProcessorError> {
643 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}