tailwind_rs_postcss/
plugin_loader.rs

1//! Plugin loader for PostCSS integration
2//!
3//! This module provides plugin loading and management capabilities.
4
5use crate::ast::CSSNode;
6use crate::error::Result;
7use serde::{Deserialize, Serialize};
8
9/// Plugin configuration
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct PluginConfig {
12    pub name: String,
13    pub version: Option<String>,
14    pub options: serde_json::Value,
15}
16
17impl PluginConfig {
18    /// Check if plugin requires JavaScript execution
19    pub fn requires_js(&self) -> bool {
20        // Check if plugin is a JavaScript plugin
21        match self.name.as_str() {
22            "autoprefixer" | "cssnano" | "postcss-preset-env" | "postcss-import" => true,
23            _ => {
24                // Check if it's an NPM package
25                self.name.contains('/') || self.name.starts_with('@')
26            }
27        }
28    }
29}
30
31/// Plugin loader for managing plugins
32#[derive(Debug)]
33pub struct PluginLoader;
34
35/// Plugin execution result
36#[derive(Debug)]
37pub enum PluginResult {
38    Native(NativePlugin),
39    JavaScript(JSPlugin),
40}
41
42/// Native Rust plugin
43#[derive(Debug)]
44pub struct NativePlugin;
45
46impl NativePlugin {
47    pub fn transform(&self, ast: CSSNode) -> Result<CSSNode> {
48        Ok(ast)
49    }
50}
51
52/// JavaScript plugin
53#[derive(Debug)]
54pub struct JSPlugin {
55    pub name: String,
56}
57
58impl PluginLoader {
59    /// Create a new plugin loader
60    pub fn new() -> Self {
61        Self
62    }
63
64    /// Load a plugin
65    pub async fn load_plugin(&self, config: &PluginConfig) -> Result<PluginResult> {
66        if config.requires_js() {
67            // Load JavaScript plugin
68            Ok(PluginResult::JavaScript(JSPlugin {
69                name: config.name.clone(),
70            }))
71        } else {
72            // Load native Rust plugin
73            Ok(PluginResult::Native(NativePlugin))
74        }
75    }
76
77    /// Load multiple plugins
78    pub async fn load_plugins(&self, configs: &[PluginConfig]) -> Result<Vec<PluginResult>> {
79        let mut results = Vec::new();
80
81        for config in configs {
82            let result = self.load_plugin(config).await?;
83            results.push(result);
84        }
85
86        Ok(results)
87    }
88
89    /// Validate plugin configuration
90    pub fn validate_config(&self, config: &PluginConfig) -> Result<()> {
91        if config.name.is_empty() {
92            return Err(crate::error::PostCSSError::config(
93                "Plugin name cannot be empty",
94            ));
95        }
96
97        // Check for valid plugin names
98        if !self.is_valid_plugin_name(&config.name) {
99            return Err(crate::error::PostCSSError::config(&format!(
100                "Invalid plugin name: {}",
101                config.name
102            )));
103        }
104
105        Ok(())
106    }
107
108    /// Check if plugin name is valid
109    fn is_valid_plugin_name(&self, name: &str) -> bool {
110        // Basic validation for plugin names
111        !name.is_empty()
112            && name.len() <= 100
113            && name
114                .chars()
115                .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '/' || c == '@')
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_plugin_loader_creation() {
125        let _loader = PluginLoader::new();
126        assert!(true); // Basic test
127    }
128}