Skip to main content

just_engine/runner/plugin/
config.rs

1//! Plugin configuration file parsing.
2
3use std::path::Path;
4use std::fs;
5
6use super::registry::PluginError;
7
8/// Configuration for a native plugin.
9#[derive(Debug, Clone)]
10pub struct NativePluginConfig {
11    /// Path to the dynamic library.
12    pub path: String,
13    /// Whether the plugin is enabled.
14    pub enabled: bool,
15}
16
17/// Configuration for a JavaScript plugin.
18#[derive(Debug, Clone)]
19pub struct JsPluginConfig {
20    /// Path to the JavaScript file.
21    pub path: String,
22    /// Whether the plugin is enabled.
23    pub enabled: bool,
24}
25
26/// Method override configuration.
27#[derive(Debug, Clone)]
28pub struct OverrideConfig {
29    /// Plugin name providing the override.
30    pub plugin: String,
31    /// Method name in the plugin.
32    pub method: String,
33}
34
35/// Complete plugin configuration.
36#[derive(Debug, Clone)]
37pub struct PluginConfig {
38    /// Native plugins to load.
39    pub native_plugins: Vec<NativePluginConfig>,
40    /// JavaScript plugins to load.
41    pub js_plugins: Vec<JsPluginConfig>,
42    /// Method overrides (key: "Object.method").
43    pub overrides: std::collections::HashMap<String, OverrideConfig>,
44}
45
46impl PluginConfig {
47    /// Create an empty configuration.
48    pub fn new() -> Self {
49        PluginConfig {
50            native_plugins: Vec::new(),
51            js_plugins: Vec::new(),
52            overrides: std::collections::HashMap::new(),
53        }
54    }
55
56    /// Load configuration from a TOML file.
57    ///
58    /// Expected format:
59    /// ```toml
60    /// [plugins]
61    /// native = [
62    ///     { path = "./plugins/libcustom.so", enabled = true }
63    /// ]
64    /// javascript = [
65    ///     { path = "./plugins/utils.js", enabled = true }
66    /// ]
67    ///
68    /// [overrides]
69    /// "Array.map" = { plugin = "fast-array", method = "fastMap" }
70    /// ```
71    pub fn load(path: &Path) -> Result<Self, PluginError> {
72        let content = fs::read_to_string(path)
73            .map_err(|e| PluginError::ConfigError(format!("Failed to read config file: {}", e)))?;
74
75        Self::parse(&content)
76    }
77
78    /// Parse configuration from a TOML string.
79    /// Note: This is a simple parser. For production use, consider using the `toml` crate.
80    pub fn parse(content: &str) -> Result<Self, PluginError> {
81        let mut config = PluginConfig::new();
82
83        // Simple line-by-line parsing
84        // This is a basic implementation - full TOML parsing would use the `toml` crate
85        let mut current_section = String::new();
86        let mut in_native_array = false;
87        let mut in_js_array = false;
88
89        for line in content.lines() {
90            let line = line.trim();
91
92            // Skip empty lines and comments
93            if line.is_empty() || line.starts_with('#') {
94                continue;
95            }
96
97            // Section headers
98            if line.starts_with('[') && line.ends_with(']') {
99                current_section = line[1..line.len() - 1].to_string();
100                in_native_array = false;
101                in_js_array = false;
102                continue;
103            }
104
105            // Handle plugins section
106            if current_section == "plugins" {
107                if line.starts_with("native") {
108                    in_native_array = line.contains('[');
109                    in_js_array = false;
110                } else if line.starts_with("javascript") {
111                    in_js_array = line.contains('[');
112                    in_native_array = false;
113                } else if line == "]" {
114                    in_native_array = false;
115                    in_js_array = false;
116                } else if in_native_array || in_js_array {
117                    // Parse plugin entry: { path = "...", enabled = true/false }
118                    if let Some(entry) = Self::parse_plugin_entry(line) {
119                        if in_native_array {
120                            config.native_plugins.push(NativePluginConfig {
121                                path: entry.0,
122                                enabled: entry.1,
123                            });
124                        } else {
125                            config.js_plugins.push(JsPluginConfig {
126                                path: entry.0,
127                                enabled: entry.1,
128                            });
129                        }
130                    }
131                }
132            }
133
134            // Handle overrides section
135            if current_section == "overrides" {
136                if let Some((key, override_config)) = Self::parse_override_entry(line) {
137                    config.overrides.insert(key, override_config);
138                }
139            }
140        }
141
142        Ok(config)
143    }
144
145    /// Parse a plugin entry like: { path = "./plugins/lib.so", enabled = true }
146    fn parse_plugin_entry(line: &str) -> Option<(String, bool)> {
147        let line = line.trim().trim_start_matches('{').trim_end_matches('}').trim_end_matches(',');
148
149        let mut path = None;
150        let mut enabled = true;
151
152        for part in line.split(',') {
153            let part = part.trim();
154            if part.starts_with("path") {
155                if let Some(value) = part.split('=').nth(1) {
156                    path = Some(
157                        value
158                            .trim()
159                            .trim_matches('"')
160                            .to_string(),
161                    );
162                }
163            } else if part.starts_with("enabled") {
164                if let Some(value) = part.split('=').nth(1) {
165                    enabled = value.trim() == "true";
166                }
167            }
168        }
169
170        path.map(|p| (p, enabled))
171    }
172
173    /// Parse an override entry like: "Array.map" = { plugin = "fast-array", method = "fastMap" }
174    fn parse_override_entry(line: &str) -> Option<(String, OverrideConfig)> {
175        let parts: Vec<&str> = line.splitn(2, '=').collect();
176        if parts.len() != 2 {
177            return None;
178        }
179
180        let key = parts[0].trim().trim_matches('"').to_string();
181        let value = parts[1].trim().trim_start_matches('{').trim_end_matches('}');
182
183        let mut plugin = None;
184        let mut method = None;
185
186        for part in value.split(',') {
187            let part = part.trim();
188            if part.starts_with("plugin") {
189                if let Some(v) = part.split('=').nth(1) {
190                    plugin = Some(v.trim().trim_matches('"').to_string());
191                }
192            } else if part.starts_with("method") {
193                if let Some(v) = part.split('=').nth(1) {
194                    method = Some(v.trim().trim_matches('"').to_string());
195                }
196            }
197        }
198
199        if let (Some(plugin), Some(method)) = (plugin, method) {
200            Some((key, OverrideConfig { plugin, method }))
201        } else {
202            None
203        }
204    }
205}
206
207impl Default for PluginConfig {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn test_parse_empty_config() {
219        let config = PluginConfig::parse("").unwrap();
220        assert!(config.native_plugins.is_empty());
221        assert!(config.js_plugins.is_empty());
222        assert!(config.overrides.is_empty());
223    }
224
225    #[test]
226    fn test_parse_plugin_entry() {
227        let entry = PluginConfig::parse_plugin_entry(
228            r#"{ path = "./plugins/lib.so", enabled = true }"#
229        );
230        assert!(entry.is_some());
231        let (path, enabled) = entry.unwrap();
232        assert_eq!(path, "./plugins/lib.so");
233        assert!(enabled);
234    }
235}