ricecoder_cli/commands/
custom.rs1use super::custom_storage::CustomCommandsStorage;
5use super::Command;
6use crate::error::{CliError, CliResult};
7use ricecoder_commands::{CommandManager, CommandRegistry, ConfigManager};
8use std::collections::HashMap;
9use std::path::PathBuf;
10
11#[derive(Debug, Clone)]
13pub enum CustomAction {
14 List,
16 Info(String),
18 Run(String, Vec<String>),
20 Load(String),
22 Search(String),
24}
25
26pub struct CustomCommandHandler {
28 action: CustomAction,
29 manager: CommandManager,
30 storage: CustomCommandsStorage,
31}
32
33impl CustomCommandHandler {
34 pub fn new(action: CustomAction) -> Self {
36 let storage = CustomCommandsStorage::default();
38 let registry = storage
39 .load_all()
40 .unwrap_or_else(|_| CommandRegistry::new());
41 let manager = CommandManager::new(registry);
42
43 Self {
44 action,
45 manager,
46 storage,
47 }
48 }
49
50 pub fn with_manager(action: CustomAction, manager: CommandManager) -> Self {
52 let storage = CustomCommandsStorage::default();
53 Self {
54 action,
55 manager,
56 storage,
57 }
58 }
59}
60
61impl Command for CustomCommandHandler {
62 fn execute(&self) -> CliResult<()> {
63 match &self.action {
64 CustomAction::List => self.handle_list(),
65 CustomAction::Info(name) => self.handle_info(name),
66 CustomAction::Run(name, args) => self.handle_run(name, args),
67 CustomAction::Load(file) => self.handle_load(file),
68 CustomAction::Search(query) => self.handle_search(query),
69 }
70 }
71}
72
73impl CustomCommandHandler {
74 fn handle_list(&self) -> CliResult<()> {
76 let commands = self.manager.list_commands();
77
78 if commands.is_empty() {
79 println!("No custom commands available.");
80 println!("Use 'rice custom load <file>' to load commands from a file.");
81 return Ok(());
82 }
83
84 println!("Available Commands:");
85 println!("==================\n");
86
87 for cmd in commands {
88 println!("{:<20} {}", cmd.name, cmd.description);
89 }
90
91 Ok(())
92 }
93
94 fn handle_info(&self, name: &str) -> CliResult<()> {
96 let cmd = self
97 .manager
98 .get_command(name)
99 .map_err(|e| CliError::Internal(e.to_string()))?;
100
101 println!("Command: {}", cmd.name);
102 println!("Description: {}", cmd.description);
103 println!("Command: {}", cmd.command);
104
105 if !cmd.arguments.is_empty() {
106 println!("\nArguments:");
107 for arg in &cmd.arguments {
108 let required = if arg.required { "required" } else { "optional" };
109 println!(" {} ({}): {}", arg.name, required, arg.description);
110 if let Some(default) = &arg.default {
111 println!(" Default: {}", default);
112 }
113 }
114 }
115
116 Ok(())
117 }
118
119 fn handle_run(&self, name: &str, args: &[String]) -> CliResult<()> {
121 let cmd = self
123 .manager
124 .get_command(name)
125 .map_err(|e| CliError::Internal(e.to_string()))?;
126
127 let mut arguments = HashMap::new();
129 for (i, arg) in args.iter().enumerate() {
130 if i < cmd.arguments.len() {
131 arguments.insert(cmd.arguments[i].name.clone(), arg.clone());
132 }
133 }
134
135 let cwd = std::env::current_dir()
137 .unwrap_or_else(|_| PathBuf::from("."))
138 .to_string_lossy()
139 .to_string();
140
141 let result = self
142 .manager
143 .execute(name, arguments, cwd)
144 .map_err(|e| CliError::Internal(e.to_string()))?;
145
146 if result.success {
148 println!("{}", result.stdout);
149 } else {
150 eprintln!("{}", result.stderr);
151 return Err(CliError::Internal(format!(
152 "Command failed with exit code {}",
153 result.exit_code
154 )));
155 }
156
157 Ok(())
158 }
159
160 fn handle_load(&self, file: &str) -> CliResult<()> {
162 let registry =
163 ConfigManager::load_from_file(file).map_err(|e| CliError::Internal(e.to_string()))?;
164
165 let commands = registry.list_all();
166
167 if commands.is_empty() {
168 println!("No commands found in file: {}", file);
169 return Ok(());
170 }
171
172 println!("Loaded {} command(s) from {}", commands.len(), file);
173
174 for cmd in commands {
176 println!(" - {}: {}", cmd.name, cmd.description);
177
178 match self.storage.save_command(&cmd) {
179 Ok(path) => {
180 println!(" Saved to: {}", path.display());
181 }
182 Err(e) => {
183 eprintln!(" Warning: Failed to save command: {}", e);
184 }
185 }
186 }
187
188 Ok(())
189 }
190
191 fn handle_search(&self, query: &str) -> CliResult<()> {
193 let results = self.manager.search_commands(query);
194
195 if results.is_empty() {
196 println!("No commands found matching '{}'", query);
197 return Ok(());
198 }
199
200 println!("Search results for '{}':", query);
201 println!("========================\n");
202
203 for cmd in results {
204 println!("{:<20} {}", cmd.name, cmd.description);
205 }
206
207 Ok(())
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_custom_handler_list_empty() {
217 let handler = CustomCommandHandler::new(CustomAction::List);
218 let result = handler.execute();
220 assert!(result.is_ok());
221 }
222
223 #[test]
224 fn test_custom_handler_info_not_found() {
225 let handler = CustomCommandHandler::new(CustomAction::Info("nonexistent".to_string()));
226 let result = handler.execute();
227 assert!(result.is_err());
228 }
229
230 #[test]
231 fn test_custom_handler_run_not_found() {
232 let handler =
233 CustomCommandHandler::new(CustomAction::Run("nonexistent".to_string(), vec![]));
234 let result = handler.execute();
235 assert!(result.is_err());
236 }
237
238 #[test]
239 fn test_custom_handler_search_empty() {
240 let handler = CustomCommandHandler::new(CustomAction::Search("test".to_string()));
241 let result = handler.execute();
243 assert!(result.is_ok());
244 }
245}