ricecoder_commands/
config.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CommandsConfig {
11 pub commands: Vec<CommandDefinition>,
13}
14
15pub struct ConfigManager;
17
18impl ConfigManager {
19 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 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 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 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 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 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 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(®istry, &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(®istry, &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(®istry, &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(®istry, &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(®istry, &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}