1use std::collections::HashMap;
2
3use slash_lang::parser::ast::Command;
4
5use crate::command::SlashCommand;
6use crate::env::SlenvLoader;
7use crate::executor::{CommandOutput, CommandRunner, ExecutionError, PipeValue};
8
9pub struct CommandRegistry {
14 commands: HashMap<String, Box<dyn SlashCommand>>,
15 env: SlenvLoader,
16}
17
18impl CommandRegistry {
19 pub fn new(env: SlenvLoader) -> Self {
21 Self {
22 commands: HashMap::new(),
23 env,
24 }
25 }
26
27 pub fn register(&mut self, cmd: Box<dyn SlashCommand>) {
29 self.commands.insert(cmd.name().to_string(), cmd);
30 }
31
32 pub fn env(&self) -> &SlenvLoader {
34 &self.env
35 }
36
37 pub fn env_mut(&mut self) -> &mut SlenvLoader {
39 &mut self.env
40 }
41}
42
43impl CommandRunner for CommandRegistry {
44 fn run(
45 &self,
46 cmd: &Command,
47 input: Option<&PipeValue>,
48 ) -> Result<CommandOutput, ExecutionError> {
49 let handler = self
50 .commands
51 .get(&cmd.name)
52 .ok_or_else(|| ExecutionError::Runner(format!("unknown command: /{}", cmd.name)))?;
53
54 let primary = cmd.primary.as_ref().map(|p| self.env.resolve(p));
56
57 let resolved_args: Vec<slash_lang::parser::ast::Arg> = cmd
59 .args
60 .iter()
61 .map(|a| slash_lang::parser::ast::Arg {
62 name: a.name.clone(),
63 value: a.value.as_ref().map(|v| self.env.resolve(v)),
64 })
65 .collect();
66
67 let valid_methods = handler.methods();
69 for arg in &resolved_args {
70 if !valid_methods.iter().any(|m| m.name == arg.name) {
71 let known: Vec<&str> = valid_methods.iter().map(|m| m.name).collect();
72 return Err(ExecutionError::Runner(format!(
73 "/{}: unknown method '.{}' — valid methods: {}",
74 cmd.name,
75 arg.name,
76 if known.is_empty() {
77 "(none)".to_string()
78 } else {
79 known.join(", ")
80 },
81 )));
82 }
83 }
84
85 handler.execute(primary.as_deref(), &resolved_args, input)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::executor::{Execute, Executor};
93
94 fn registry() -> CommandRegistry {
95 CommandRegistry::new(SlenvLoader::empty())
96 }
97
98 #[test]
99 fn unknown_command_errors() {
100 let reg = registry();
101 let ex = Executor::new(reg);
102 let prog = slash_lang::parser::parse("/nonexistent").unwrap();
103 assert!(ex.execute(&prog).is_err());
104 }
105
106 #[test]
107 fn env_resolution_in_args() {
108 let mut env = SlenvLoader::empty();
109 env.insert_mut("MSG", "resolved");
110 let reg = CommandRegistry::new(env);
111 assert_eq!(reg.env().resolve("$MSG"), "resolved");
112 }
113}