Skip to main content

omni_dev/cli/
help.rs

1//! Help command implementation for comprehensive CLI documentation
2
3use anyhow::Result;
4use clap::{builder::StyledStr, Command, CommandFactory, Parser};
5
6/// Help command for displaying comprehensive usage information
7#[derive(Parser)]
8pub struct HelpCommand {
9    // No subcommands needed - this command shows all help
10}
11
12/// Help generator for creating comprehensive CLI documentation
13pub struct HelpGenerator {
14    app: Command,
15}
16
17impl HelpGenerator {
18    /// Create a new help generator with the current CLI app
19    pub fn new() -> Self {
20        use crate::cli::Cli;
21
22        // Build the clap app to get the command structure
23        let app = Cli::command();
24
25        Self { app }
26    }
27}
28
29impl Default for HelpGenerator {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl HelpGenerator {
36    /// Generate comprehensive help for all commands
37    pub fn generate_all_help(&self) -> Result<String> {
38        let mut help_sections = Vec::new();
39
40        // Add main app help
41        let main_help = self.render_command_help(&self.app, "");
42        help_sections.push(main_help);
43
44        // Collect help for all subcommands recursively
45        self.collect_help_recursive(&self.app, "", &mut help_sections);
46
47        // Join all sections with separators
48        let separator = format!("\n\n{}\n\n", "=".repeat(80));
49        Ok(help_sections.join(&separator))
50    }
51
52    /// Recursively collect help for all subcommands
53    ///
54    /// IMPORTANT: Commands are sorted lexicographically to ensure consistent,
55    /// predictable output order. This is critical for:
56    /// - User experience (predictable command discovery)
57    /// - Golden/snapshot tests (deterministic output)
58    /// - Documentation generation (stable ordering)
59    ///
60    /// When adding new commands, ensure this sorting is preserved.
61    fn collect_help_recursive(&self, cmd: &Command, prefix: &str, help_sections: &mut Vec<String>) {
62        // Collect all subcommands and sort them lexicographically by name
63        let mut subcommands: Vec<_> = cmd.get_subcommands().collect();
64        subcommands.sort_by(|a, b| a.get_name().cmp(b.get_name()));
65
66        for subcmd in subcommands {
67            // Skip the help command itself to avoid infinite recursion
68            if subcmd.get_name() == "help" {
69                continue;
70            }
71
72            let current_path = if prefix.is_empty() {
73                subcmd.get_name().to_string()
74            } else {
75                format!("{} {}", prefix, subcmd.get_name())
76            };
77
78            // Render help for this subcommand
79            let subcmd_help = self.render_command_help(subcmd, &current_path);
80            help_sections.push(subcmd_help);
81
82            // Recursively collect help for nested subcommands (also sorted)
83            self.collect_help_recursive(subcmd, &current_path, help_sections);
84        }
85    }
86
87    /// Render help for a specific command
88    fn render_command_help(&self, cmd: &Command, path: &str) -> String {
89        let mut output = String::new();
90
91        // Command header
92        let cmd_name = if path.is_empty() {
93            cmd.get_name().to_string()
94        } else {
95            format!("omni-dev {}", path)
96        };
97
98        let about = cmd
99            .get_about()
100            .map(|s| self.styled_str_to_string(s))
101            .unwrap_or_else(|| "No description available".to_string());
102
103        output.push_str(&format!("{} - {}\n\n", cmd_name, about));
104
105        // Render the actual help content
106        let help_str = cmd.clone().render_help();
107        output.push_str(&help_str.to_string());
108
109        output
110    }
111
112    /// Convert StyledStr to regular String (removes ANSI codes for plain text)
113    fn styled_str_to_string(&self, styled: &StyledStr) -> String {
114        styled.to_string()
115    }
116}
117
118impl HelpCommand {
119    /// Execute the help command - shows comprehensive help for all commands
120    pub fn execute(self) -> Result<()> {
121        let generator = HelpGenerator::new();
122        let help_output = generator.generate_all_help()?;
123        println!("{}", help_output);
124        Ok(())
125    }
126}