modcli/
loader.rs

1use std::collections::HashMap;
2use crate::command::Command;
3
4#[cfg(feature = "internal-commands")]
5use crate::commands::{PingCommand, EchoCommand, HelloCommand, HelpCommand};
6
7pub mod sources;
8
9use crate::loader::sources::CommandSource;
10
11/// Registry for available CLI commands
12pub struct CommandRegistry {
13    commands: HashMap<String, Box<dyn Command>>,
14}
15
16impl CommandRegistry {
17    /// Create a new command registry and register internal commands (if enabled)
18    pub fn new() -> Self {
19        let mut reg = Self {
20            commands: HashMap::new(),
21        };
22
23        #[cfg(feature = "internal-commands")]
24        reg.load_internal_commands();
25
26        reg
27    }
28
29    /// Register a new command
30    pub fn register(&mut self, cmd: Box<dyn Command>) {
31        self.commands.insert(cmd.name().to_string(), cmd);
32    }
33
34    /// Execute a command if it exists, passing args
35    pub fn execute(&self, cmd: &str, args: &[String]) {
36        if let Some(command) = self.commands.get(cmd) {
37            if command.name() == "help" {
38                // Special case for help: render help output with registry context
39                if args.len() > 1 {
40                    println!("Invalid usage: Too many arguments. Usage: help [command]");
41                    return;
42                }
43    
44                if args.len() == 1 {
45                    let query = &args[0];
46                    if let Some(target) = self.commands.get(query) {
47                        if target.hidden() {
48                            println!("No help available for '{}'", query);
49                        } else {
50                            println!(
51                                "{} - {}",
52                                target.name(),
53                                target.help().unwrap_or("No description.")
54                            );
55                        }
56                    } else {
57                        println!("Unknown command: {}", query);
58                    }
59                    return;
60                }
61    
62                println!("Help:");
63                for command in self.commands.values() {
64                    if !command.hidden() {
65                        println!(
66                            "  {:<12} {}",
67                            command.name(),
68                            command.help().unwrap_or("No description")
69                        );
70                    }
71                }
72            } else {
73                // Normal command execution
74                if let Err(err) = command.validate(args) {
75                    eprintln!("Invalid usage: {}", err);
76                    return;
77                }
78    
79                command.execute(args);
80            }
81        } else {
82            eprintln!("Unknown command: {}", cmd);
83        }
84    }  
85
86    /// Load built-in internal commands (enabled via feature flag)
87    #[cfg(feature = "internal-commands")]
88    pub fn load_internal_commands(&mut self) {
89        self.register(Box::new(PingCommand));
90        self.register(Box::new(EchoCommand));
91        self.register(Box::new(HelloCommand));
92        self.register(Box::new(HelpCommand::new()));
93    }
94
95    /// Load commands dynamically from a source (e.g. JSON, plugin)
96    pub fn load_from(&mut self, source: Box<dyn CommandSource>) {
97        for cmd in source.load_commands() {
98            self.register(cmd);
99        }
100    }
101
102    /// Number of loaded commands
103    pub fn len(&self) -> usize {
104        self.commands.len()
105    }
106}