1#[cfg(feature = "custom-commands")]
2#[cfg(feature = "custom-commands")]
4#[cfg(feature = "plugins")]
6pub mod plugins;
7
8#[cfg(feature = "plugins")]
9use crate::loader::plugins::PluginLoader;
10
11#[cfg(feature = "internal-commands")]
12use crate::commands::{FrameworkCommand, HelloCommand, HelpCommand, PingCommand, ShellCommand};
13use crate::output::hook;
14
15use crate::command::Command;
16use std::collections::HashMap;
17
18#[cfg(feature = "json-loader")]
19use crate::loader::sources::CommandSource;
20
21#[cfg(feature = "json-loader")]
22pub mod sources;
23
24pub struct CommandRegistry {
42 prefix: String,
43 commands: HashMap<String, Box<dyn Command>>,
44 aliases: HashMap<String, String>,
45}
46
47impl Default for CommandRegistry {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl CommandRegistry {
54 pub fn new() -> Self {
56 let mut reg = Self {
57 prefix: String::new(),
58 commands: HashMap::new(),
59 aliases: HashMap::new(),
60 };
61
62 #[cfg(feature = "custom-commands")]
63 reg.load_custom_commands();
64
65 #[cfg(feature = "internal-commands")]
66 reg.load_internal_commands();
67
68 reg
69 }
70
71 pub fn set_prefix(&mut self, prefix: &str) {
74 self.prefix = prefix.to_string();
75 }
76
77 pub fn get_prefix(&self) -> &str {
80 &self.prefix
81 }
82
83 pub fn get(&self, name: &str) -> Option<&dyn Command> {
86 self.commands.get(name).map(|b| b.as_ref())
87 }
88
89 pub fn register(&mut self, cmd: Box<dyn Command>) {
92 let name = cmd.name().to_string();
94 let alias_list: Vec<String> = cmd.aliases().iter().map(|a| a.to_string()).collect();
95
96 self.commands.insert(name.clone(), cmd);
97
98 for alias in alias_list {
100 if !self.commands.contains_key(&alias) {
102 self.aliases.insert(alias, name.clone());
103 }
104 }
105 }
106
107 pub fn all(&self) -> impl Iterator<Item = &Box<dyn Command>> {
110 self.commands.values()
111 }
112
113 #[cfg(feature = "plugins")]
115 pub fn load_plugins(&mut self, path: &str) {
116 let loader = PluginLoader::new(path);
117 for plugin in loader.load_plugins() {
118 self.register(plugin);
119 }
120 }
121
122 pub fn execute(&self, cmd: &str, args: &[String]) {
124 let mut token = cmd.to_string();
126 if !self.prefix.is_empty() {
127 let expect = format!("{}:", self.prefix);
128 if token.starts_with(&expect) {
129 token = token[expect.len()..].to_string();
130 }
131 }
132
133 let resolved_name = if self.commands.contains_key(&token) {
135 Some(token.clone())
136 } else {
137 self.aliases.get(&token).cloned()
138 };
139
140 if let Some(name) = resolved_name {
141 let command = &self.commands[&name];
142 if let Err(err) = command.validate(args) {
144 let err_msg = format!("Invalid usage: {err}");
145 hook::error(&err_msg);
146 return;
147 }
148 command.execute_with(args, self);
150 } else {
151 let unknown =
152 format!("[{cmd}]. Type `help` or `--help` for a list of available commands.");
153 hook::unknown(&unknown);
154 }
155 }
156
157 #[cfg(feature = "internal-commands")]
158 pub fn load_internal_commands(&mut self) {
159 self.register(Box::new(PingCommand));
160 self.register(Box::new(HelloCommand));
161 self.register(Box::new(ShellCommand));
162 self.register(Box::new(FrameworkCommand));
163 self.register(Box::new(HelpCommand::new()));
164 }
165
166 #[cfg(feature = "json-loader")]
167 pub fn load_from(&mut self, source: Box<dyn CommandSource>) {
168 for cmd in source.load_commands() {
169 self.register(cmd);
170 }
171 }
172
173 pub fn len(&self) -> usize {
174 self.commands.len()
175 }
176
177 pub fn is_empty(&self) -> bool {
178 self.commands.is_empty()
179 }
180
181 #[cfg(feature = "custom-commands")]
182 pub fn load_custom_commands(&mut self) {
183 }
185}