modcli/commands/
help.rs

1use crate::command::Command;
2use crate::error::ModCliError;
3use crate::loader::CommandRegistry;
4use crate::output::hook;
5use crate::output::markdown;
6use crate::output::messages;
7
8/// Built-in help command (execution handled by registry internally)
9pub struct HelpCommand;
10
11impl Default for HelpCommand {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl HelpCommand {
18    pub fn new() -> Self {
19        Self
20    }
21}
22
23impl Command for HelpCommand {
24    fn name(&self) -> &str {
25        "help"
26    }
27
28    fn aliases(&self) -> &[&str] {
29        &["--help", "-h"]
30    }
31
32    fn help(&self) -> Option<&str> {
33        Some("Displays help information")
34    }
35
36    fn validate(&self, args: &[String]) -> Result<(), ModCliError> {
37        if args.len() > 1 {
38            Err(ModCliError::InvalidUsage(
39                "Too many arguments. Usage: help [command]".into(),
40            ))
41        } else {
42            Ok(())
43        }
44    }
45
46    fn execute(&self, _args: &[String]) {}
47
48    fn execute_with(&self, args: &[String], registry: &CommandRegistry) {
49        // validate() already ensures args.len() <= 1
50        if args.len() == 1 {
51            let query = &args[0];
52            // If a direct command matches and is visible, show its help
53            if let Some(target) = registry.get(query) {
54                if registry.is_visible(target) {
55                    let name_line = target.name().to_string();
56                    println!("{name_line}");
57                    let body = target.help().unwrap_or("No description.");
58                    let rendered = markdown::render_markdown(body);
59                    print!("{rendered}");
60                } else {
61                    println!("No help available for '{query}'");
62                }
63                return;
64            }
65
66            // Namespace help: list children of `query:` that are visible
67            let ns = format!("{query}:");
68            let mut any = false;
69            let ns_header_fallback = format!("Help ({query}):");
70            let header = messages::message_or_default("help.ns_header", &ns_header_fallback);
71            println!("{header}");
72            for command in registry.all() {
73                let name = command.name();
74                if name.starts_with(&ns) && registry.is_visible(command.as_ref()) {
75                    println!(
76                        "  {:<20} {}",
77                        name,
78                        markdown::render_markdown(command.help().unwrap_or("No description"))
79                    );
80                    any = true;
81                }
82            }
83            if !any {
84                let unknown =
85                    format!("[{query}]. Type `help` or `--help` for a list of available commands.");
86                hook::unknown(&unknown);
87            }
88            return;
89        }
90
91        let header = messages::message_or_default("help.header", "Help:");
92        println!("{header}");
93        for command in registry.all() {
94            let name = command.name();
95            let top_level = !name.contains(':');
96            if top_level && registry.is_visible(command.as_ref()) {
97                println!(
98                    "  {:<12} {}",
99                    name,
100                    markdown::render_markdown(command.help().unwrap_or("No description"))
101                );
102            }
103        }
104        if let Some(footer) = messages::get_message("help.footer") {
105            println!("{footer}");
106        }
107    }
108}