basalt_api/command/
registry.rs1use std::collections::HashMap;
4
5use crate::context::Context;
6
7use super::args::CommandArgs;
8use super::dispatch::Command;
9
10pub struct CommandRegistry {
15 commands: HashMap<String, Box<dyn Command>>,
16}
17
18impl CommandRegistry {
19 pub fn new() -> Self {
21 Self {
22 commands: HashMap::new(),
23 }
24 }
25
26 pub fn register(&mut self, command: impl Command + 'static) {
29 let name = command.name().to_string();
30 self.commands.insert(name, Box::new(command));
31 }
32
33 pub fn execute(&self, name: &str, args: &CommandArgs, ctx: &dyn Context) -> bool {
37 if let Some(cmd) = self.commands.get(name) {
38 cmd.execute(args, ctx);
39 true
40 } else {
41 false
42 }
43 }
44
45 pub fn commands(&self) -> impl Iterator<Item = &dyn Command> {
47 self.commands.values().map(|c| c.as_ref())
48 }
49
50 pub fn get(&self, name: &str) -> Option<&dyn Command> {
52 self.commands.get(name).map(|c| c.as_ref())
53 }
54
55 pub fn len(&self) -> usize {
57 self.commands.len()
58 }
59
60 pub fn is_empty(&self) -> bool {
62 self.commands.is_empty()
63 }
64}
65
66impl Default for CommandRegistry {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::testing::NoopContext;
75
76 use super::super::args::Arg;
77 use super::*;
78
79 struct PingCommand;
80 impl Command for PingCommand {
81 fn name(&self) -> &str {
82 "ping"
83 }
84 fn description(&self) -> &str {
85 "Responds with pong"
86 }
87 fn execute(&self, _args: &CommandArgs, ctx: &dyn Context) {
88 ctx.chat().send("Pong!");
89 }
90 }
91
92 struct EchoCommand;
93 impl Command for EchoCommand {
94 fn name(&self) -> &str {
95 "echo"
96 }
97 fn description(&self) -> &str {
98 "Echoes the arguments"
99 }
100 fn execute(&self, args: &CommandArgs, ctx: &dyn Context) {
101 ctx.chat().send(args.raw());
102 }
103 }
104
105 #[test]
106 fn register_and_execute() {
107 let mut registry = CommandRegistry::new();
108 registry.register(PingCommand);
109
110 let args = CommandArgs::new(String::new());
111 let ctx = NoopContext;
112 assert!(registry.execute("ping", &args, &ctx));
113 }
114
115 #[test]
116 fn unknown_command_returns_false() {
117 let registry = CommandRegistry::new();
118 let args = CommandArgs::new(String::new());
119 assert!(!registry.execute("nonexistent", &args, &NoopContext));
120 }
121
122 #[test]
123 fn multiple_commands() {
124 let mut registry = CommandRegistry::new();
125 registry.register(PingCommand);
126 registry.register(EchoCommand);
127 assert_eq!(registry.len(), 2);
128 }
129
130 #[test]
131 fn get_command() {
132 let mut registry = CommandRegistry::new();
133 registry.register(PingCommand);
134 assert!(registry.get("ping").is_some());
135 assert!(registry.get("nonexistent").is_none());
136 }
137
138 #[test]
139 fn iterate_commands() {
140 let mut registry = CommandRegistry::new();
141 registry.register(PingCommand);
142 registry.register(EchoCommand);
143 let names: Vec<&str> = registry.commands().map(|c| c.name()).collect();
144 assert_eq!(names.len(), 2);
145 }
146
147 #[test]
148 fn empty_registry() {
149 let registry = CommandRegistry::new();
150 assert!(registry.is_empty());
151 }
152
153 #[test]
154 fn default_is_empty() {
155 let registry = CommandRegistry::default();
156 assert!(registry.is_empty());
157 }
158
159 #[test]
160 fn parse_and_execute() {
161 let mut registry = CommandRegistry::new();
162 registry.register(EchoCommand);
163
164 let schema = vec![super::super::args::CommandArg {
165 name: "msg".into(),
166 arg_type: Arg::Message,
167 validation: super::super::args::Validation::Auto,
168 required: true,
169 }];
170 let args = super::super::args::parse_args("hello world", &schema).unwrap();
171 registry.execute("echo", &args, &NoopContext);
172 }
173}