ricecoder_commands/
config.rs

1use crate::error::{CommandError, Result};
2use crate::registry::CommandRegistry;
3use crate::types::CommandDefinition;
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::Path;
7
8/// Configuration file format for commands
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CommandsConfig {
11    /// List of command definitions
12    pub commands: Vec<CommandDefinition>,
13}
14
15/// Command configuration manager
16pub struct ConfigManager;
17
18impl ConfigManager {
19    /// Load commands from a YAML file
20    pub fn load_from_yaml<P: AsRef<Path>>(path: P) -> Result<CommandRegistry> {
21        let content = fs::read_to_string(path)
22            .map_err(|e| CommandError::ConfigError(format!("Failed to read config file: {}", e)))?;
23
24        let config: CommandsConfig = serde_yaml::from_str(&content)?;
25
26        let mut registry = CommandRegistry::new();
27        for command in config.commands {
28            registry.register(command)?;
29        }
30
31        Ok(registry)
32    }
33
34    /// Load commands from a JSON file
35    pub fn load_from_json<P: AsRef<Path>>(path: P) -> Result<CommandRegistry> {
36        let content = fs::read_to_string(path)
37            .map_err(|e| CommandError::ConfigError(format!("Failed to read config file: {}", e)))?;
38
39        let config: CommandsConfig = serde_json::from_str(&content)?;
40
41        let mut registry = CommandRegistry::new();
42        for command in config.commands {
43            registry.register(command)?;
44        }
45
46        Ok(registry)
47    }
48
49    /// Load commands from a file (auto-detect format)
50    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<CommandRegistry> {
51        let path = path.as_ref();
52        let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
53
54        match extension {
55            "yaml" | "yml" => Self::load_from_yaml(path),
56            "json" => Self::load_from_json(path),
57            _ => Err(CommandError::ConfigError(
58                "Unsupported file format. Use .yaml, .yml, or .json".to_string(),
59            )),
60        }
61    }
62
63    /// Save commands to a YAML file
64    pub fn save_to_yaml<P: AsRef<Path>>(registry: &CommandRegistry, path: P) -> Result<()> {
65        let config = CommandsConfig {
66            commands: registry.list_all(),
67        };
68
69        let content = serde_yaml::to_string(&config)?;
70        fs::write(path, content).map_err(|e| {
71            CommandError::ConfigError(format!("Failed to write config file: {}", e))
72        })?;
73
74        Ok(())
75    }
76
77    /// Save commands to a JSON file
78    pub fn save_to_json<P: AsRef<Path>>(registry: &CommandRegistry, path: P) -> Result<()> {
79        let config = CommandsConfig {
80            commands: registry.list_all(),
81        };
82
83        let content = serde_json::to_string_pretty(&config)?;
84        fs::write(path, content).map_err(|e| {
85            CommandError::ConfigError(format!("Failed to write config file: {}", e))
86        })?;
87
88        Ok(())
89    }
90
91    /// Save commands to a file (auto-detect format)
92    pub fn save_to_file<P: AsRef<Path>>(registry: &CommandRegistry, path: P) -> Result<()> {
93        let path = path.as_ref();
94        let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
95
96        match extension {
97            "yaml" | "yml" => Self::save_to_yaml(registry, path),
98            "json" => Self::save_to_json(registry, path),
99            _ => Err(CommandError::ConfigError(
100                "Unsupported file format. Use .yaml, .yml, or .json".to_string(),
101            )),
102        }
103    }
104
105    /// Merge multiple registries
106    pub fn merge_registries(registries: Vec<CommandRegistry>) -> Result<CommandRegistry> {
107        let mut merged = CommandRegistry::new();
108
109        for registry in registries {
110            for command in registry.list_all() {
111                merged.register(command)?;
112            }
113        }
114
115        Ok(merged)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::types::{ArgumentType, CommandArgument};
123    use tempfile::NamedTempFile;
124
125    fn create_test_registry() -> CommandRegistry {
126        let mut registry = CommandRegistry::new();
127        let cmd = CommandDefinition::new("test", "Test Command", "echo test")
128            .with_description("A test command")
129            .with_argument(
130                CommandArgument::new("name", ArgumentType::String)
131                    .with_description("User name")
132                    .with_required(true),
133            );
134        registry.register(cmd).ok();
135        registry
136    }
137
138    #[test]
139    fn test_save_and_load_yaml() {
140        let registry = create_test_registry();
141        let temp_file = NamedTempFile::new().unwrap();
142        let path = temp_file.path().with_extension("yaml");
143
144        ConfigManager::save_to_yaml(&registry, &path).unwrap();
145        let loaded = ConfigManager::load_from_yaml(&path).unwrap();
146
147        assert_eq!(loaded.count(), 1);
148        assert!(loaded.exists("test"));
149    }
150
151    #[test]
152    fn test_save_and_load_json() {
153        let registry = create_test_registry();
154        let temp_file = NamedTempFile::new().unwrap();
155        let path = temp_file.path().with_extension("json");
156
157        ConfigManager::save_to_json(&registry, &path).unwrap();
158        let loaded = ConfigManager::load_from_json(&path).unwrap();
159
160        assert_eq!(loaded.count(), 1);
161        assert!(loaded.exists("test"));
162    }
163
164    #[test]
165    fn test_auto_detect_yaml() {
166        let registry = create_test_registry();
167        let temp_file = NamedTempFile::new().unwrap();
168        let path = temp_file.path().with_extension("yaml");
169
170        ConfigManager::save_to_file(&registry, &path).unwrap();
171        let loaded = ConfigManager::load_from_file(&path).unwrap();
172
173        assert_eq!(loaded.count(), 1);
174    }
175
176    #[test]
177    fn test_auto_detect_json() {
178        let registry = create_test_registry();
179        let temp_file = NamedTempFile::new().unwrap();
180        let path = temp_file.path().with_extension("json");
181
182        ConfigManager::save_to_file(&registry, &path).unwrap();
183        let loaded = ConfigManager::load_from_file(&path).unwrap();
184
185        assert_eq!(loaded.count(), 1);
186    }
187
188    #[test]
189    fn test_unsupported_format() {
190        let registry = create_test_registry();
191        let temp_file = NamedTempFile::new().unwrap();
192        let path = temp_file.path().with_extension("txt");
193
194        assert!(ConfigManager::save_to_file(&registry, &path).is_err());
195    }
196
197    #[test]
198    fn test_merge_registries() {
199        let mut registry1 = CommandRegistry::new();
200        registry1
201            .register(CommandDefinition::new("cmd1", "Cmd1", "echo 1"))
202            .ok();
203
204        let mut registry2 = CommandRegistry::new();
205        registry2
206            .register(CommandDefinition::new("cmd2", "Cmd2", "echo 2"))
207            .ok();
208
209        let merged = ConfigManager::merge_registries(vec![registry1, registry2]).unwrap();
210        assert_eq!(merged.count(), 2);
211        assert!(merged.exists("cmd1"));
212        assert!(merged.exists("cmd2"));
213    }
214}