modcli/commands/
help.rs

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