ricecoder_commands/
manager.rs

1use crate::config::ConfigManager;
2use crate::error::Result;
3use crate::executor::CommandExecutor;
4use crate::output_injection::{OutputInjectionConfig, OutputInjector};
5use crate::registry::CommandRegistry;
6use crate::types::{CommandDefinition, CommandExecutionResult};
7use std::collections::HashMap;
8use std::path::Path;
9
10/// High-level command manager for executing and managing commands
11pub struct CommandManager {
12    registry: CommandRegistry,
13    output_config: OutputInjectionConfig,
14}
15
16impl CommandManager {
17    /// Create a new command manager
18    pub fn new(registry: CommandRegistry) -> Self {
19        Self {
20            registry,
21            output_config: OutputInjectionConfig::default(),
22        }
23    }
24
25    /// Create a command manager from a config file
26    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
27        let registry = ConfigManager::load_from_file(path)?;
28        Ok(Self::new(registry))
29    }
30
31    /// Set the output injection configuration
32    pub fn set_output_config(&mut self, config: OutputInjectionConfig) {
33        self.output_config = config;
34    }
35
36    /// Get the output injection configuration
37    pub fn output_config(&self) -> &OutputInjectionConfig {
38        &self.output_config
39    }
40
41    /// Get the registry
42    pub fn registry(&self) -> &CommandRegistry {
43        &self.registry
44    }
45
46    /// Get a mutable reference to the registry
47    pub fn registry_mut(&mut self) -> &mut CommandRegistry {
48        &mut self.registry
49    }
50
51    /// Execute a command by ID
52    pub fn execute(
53        &self,
54        command_id: &str,
55        arguments: HashMap<String, String>,
56        cwd: String,
57    ) -> Result<CommandExecutionResult> {
58        let command = self.registry.get(command_id)?;
59
60        // Validate arguments
61        CommandExecutor::validate_arguments(&command, &arguments)?;
62
63        // Build context with defaults
64        let context = CommandExecutor::build_context_with_defaults(&command, arguments, cwd)?;
65
66        // Execute the command
67        CommandExecutor::execute(&command, &context)
68    }
69
70    /// Execute a command and get injected output
71    pub fn execute_and_inject(
72        &self,
73        command_id: &str,
74        arguments: HashMap<String, String>,
75        cwd: String,
76    ) -> Result<String> {
77        let result = self.execute(command_id, arguments, cwd)?;
78        OutputInjector::inject(&result, &self.output_config)
79    }
80
81    /// List all commands
82    pub fn list_commands(&self) -> Vec<CommandDefinition> {
83        self.registry.list_all()
84    }
85
86    /// List enabled commands
87    pub fn list_enabled_commands(&self) -> Vec<CommandDefinition> {
88        self.registry.list_enabled()
89    }
90
91    /// Get command details
92    pub fn get_command(&self, command_id: &str) -> Result<CommandDefinition> {
93        self.registry.get(command_id)
94    }
95
96    /// Search for commands
97    pub fn search_commands(&self, query: &str) -> Vec<CommandDefinition> {
98        self.registry.search(query)
99    }
100
101    /// Find commands by tag
102    pub fn find_commands_by_tag(&self, tag: &str) -> Vec<CommandDefinition> {
103        self.registry.find_by_tag(tag)
104    }
105
106    /// Register a new command
107    pub fn register_command(&mut self, command: CommandDefinition) -> Result<()> {
108        self.registry.register(command)
109    }
110
111    /// Unregister a command
112    pub fn unregister_command(&mut self, command_id: &str) -> Result<()> {
113        self.registry.unregister(command_id)
114    }
115
116    /// Enable a command
117    pub fn enable_command(&mut self, command_id: &str) -> Result<()> {
118        self.registry.enable(command_id)
119    }
120
121    /// Disable a command
122    pub fn disable_command(&mut self, command_id: &str) -> Result<()> {
123        self.registry.disable(command_id)
124    }
125
126    /// Save commands to a file
127    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
128        ConfigManager::save_to_file(&self.registry, path)
129    }
130
131    /// Reload commands from a file
132    pub fn reload_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
133        self.registry = ConfigManager::load_from_file(path)?;
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::types::{ArgumentType, CommandArgument};
142
143    fn create_test_manager() -> CommandManager {
144        let mut registry = CommandRegistry::new();
145        let cmd = CommandDefinition::new("test", "Test Command", "echo {{message}}")
146            .with_description("A test command")
147            .with_argument(
148                CommandArgument::new("message", ArgumentType::String)
149                    .with_description("Message to echo")
150                    .with_required(true),
151            );
152        registry.register(cmd).ok();
153        CommandManager::new(registry)
154    }
155
156    #[test]
157    fn test_list_commands() {
158        let manager = create_test_manager();
159        let commands = manager.list_commands();
160        assert_eq!(commands.len(), 1);
161        assert_eq!(commands[0].id, "test");
162    }
163
164    #[test]
165    fn test_get_command() {
166        let manager = create_test_manager();
167        let cmd = manager.get_command("test").unwrap();
168        assert_eq!(cmd.id, "test");
169        assert_eq!(cmd.name, "Test Command");
170    }
171
172    #[test]
173    fn test_search_commands() {
174        let manager = create_test_manager();
175        let results = manager.search_commands("test");
176        assert_eq!(results.len(), 1);
177    }
178
179    #[test]
180    fn test_register_command() {
181        let mut manager = create_test_manager();
182        let cmd = CommandDefinition::new("new-cmd", "New Command", "echo new");
183        assert!(manager.register_command(cmd).is_ok());
184        assert_eq!(manager.list_commands().len(), 2);
185    }
186
187    #[test]
188    fn test_unregister_command() {
189        let mut manager = create_test_manager();
190        assert!(manager.unregister_command("test").is_ok());
191        assert_eq!(manager.list_commands().len(), 0);
192    }
193
194    #[test]
195    fn test_enable_disable() {
196        let mut manager = create_test_manager();
197        assert!(manager.disable_command("test").is_ok());
198        assert_eq!(manager.list_enabled_commands().len(), 0);
199        assert!(manager.enable_command("test").is_ok());
200        assert_eq!(manager.list_enabled_commands().len(), 1);
201    }
202
203    #[test]
204    fn test_execute_command() {
205        let manager = create_test_manager();
206        let mut args = HashMap::new();
207        args.insert("message".to_string(), "Hello".to_string());
208
209        let result = manager.execute("test", args, ".".to_string()).unwrap();
210        assert!(result.success);
211        assert!(result.stdout.contains("Hello"));
212    }
213
214    #[test]
215    fn test_execute_missing_required_argument() {
216        let manager = create_test_manager();
217        let args = HashMap::new();
218        let result = manager.execute("test", args, ".".to_string());
219        assert!(result.is_err());
220    }
221
222    #[test]
223    fn test_execute_nonexistent_command() {
224        let manager = create_test_manager();
225        let args = HashMap::new();
226        let result = manager.execute("nonexistent", args, ".".to_string());
227        assert!(result.is_err());
228    }
229
230    #[test]
231    fn test_output_config() {
232        let mut manager = create_test_manager();
233        let config = OutputInjectionConfig {
234            inject_stdout: false,
235            ..Default::default()
236        };
237        manager.set_output_config(config);
238        assert!(!manager.output_config().inject_stdout);
239    }
240}