Skip to main content

hexz_cli/ui/
help.rs

1use clap::Command;
2
3const BOLD: &str = "\x1b[1m";
4const RESET: &str = "\x1b[0m";
5const YELLOW: &str = "\x1b[33m";
6const GREEN: &str = "\x1b[32m";
7const CYAN: &str = "\x1b[36m";
8
9pub struct Printer {
10    cmd: Command,
11}
12
13impl Printer {
14    pub fn new(cmd: Command) -> Self {
15        Self { cmd }
16    }
17
18    /// Prints the top-level help menu (categories and list of commands)
19    pub fn print_help(&mut self) {
20        let bin_name = self.cmd.get_bin_name().unwrap_or("hexz").to_string();
21
22        println!(
23            "{}Usage:{} {}{}{} {}[OPTIONS]{} {}COMMAND{}",
24            BOLD, RESET, GREEN, bin_name, RESET, CYAN, RESET, YELLOW, RESET
25        );
26        println!();
27        if let Some(about) = self.cmd.get_about() {
28            println!("{}", about);
29        }
30        println!();
31
32        let mut archive_cmds = Vec::new();
33        let mut vm_cmds = Vec::new();
34        let mut sys_cmds = Vec::new();
35        let mut other_cmds = Vec::new();
36
37        let subcommands: Vec<Command> = self.cmd.get_subcommands().cloned().collect();
38
39        for sub in subcommands {
40            let name = sub.get_name().to_string();
41            if name == "help" {
42                continue;
43            }
44
45            let about = sub.get_about().map(|a| a.to_string()).unwrap_or_default();
46            let item = (name.clone(), about);
47
48            match name.as_str() {
49                "pack" | "inspect" | "diff" | "ls" | "build" | "analyze" | "convert" => {
50                    archive_cmds.push(item)
51                }
52                "boot" | "install" | "snap" | "commit" | "mount" | "unmount" => vm_cmds.push(item),
53                "doctor" | "bench" | "serve" | "keygen" | "sign" | "verify" | "overlay" => {
54                    sys_cmds.push(item)
55                }
56                _ => other_cmds.push(item),
57            }
58        }
59
60        self.print_section("Archive Operations", archive_cmds);
61        self.print_section("Virtual Machine Operations", vm_cmds);
62        self.print_section("System & Diagnostics", sys_cmds);
63
64        if !other_cmds.is_empty() {
65            self.print_section("Other Commands", other_cmds);
66        }
67
68        println!("{}Options:{}", BOLD, RESET);
69        println!("  {}{:<15}{} Print help", GREEN, "-h, --help", RESET);
70        println!("  {}{:<15}{} Print version", GREEN, "-V, --version", RESET);
71        println!();
72        println!(
73            "Run '{}{}{} COMMAND --help{}' for more information on a command.",
74            BOLD, YELLOW, bin_name, RESET
75        );
76    }
77
78    fn print_section(&self, header: &str, cmds: Vec<(String, String)>) {
79        if cmds.is_empty() {
80            return;
81        }
82
83        println!("{}{}{}:{}", BOLD, YELLOW, header, RESET);
84
85        for (name, about) in cmds {
86            println!("  {}{:<12}{} {}", GREEN, name, RESET, about);
87        }
88        println!();
89    }
90
91    /// Prints detailed help for a specific subcommand
92    pub fn print_subcommand_help(&mut self, sub_name: &str) {
93        let sub = match self.cmd.find_subcommand(sub_name) {
94            Some(s) => s,
95            None => return,
96        };
97
98        let bin_name = self.cmd.get_bin_name().unwrap_or("hexz");
99
100        // 1. Usage
101        println!(
102            "{}Usage:{} {} {} {} {} {}[OPTIONS] [ARGS]{}",
103            BOLD, RESET, GREEN, bin_name, sub_name, RESET, CYAN, RESET
104        );
105        println!();
106
107        // 2. Detailed Description (long_about)
108        if let Some(about) = sub.get_long_about().or_else(|| sub.get_about()) {
109            println!("{}", about);
110        }
111        println!();
112
113        // Collect all arguments
114        let args: Vec<_> = sub.get_arguments().collect();
115
116        // Partition into Positionals (Arguments) and Options (Flags)
117        // Robust check: Positionals are arguments that have NO short flag AND NO long flag.
118        let (mut positionals, mut flags): (Vec<_>, Vec<_>) = args
119            .into_iter()
120            .filter(|a| a.get_id() != "help" && a.get_id() != "version")
121            .partition(|a| a.get_short().is_none() && a.get_long().is_none());
122
123        // Sort positionals by index (so SOURCE comes before OUTPUT)
124        // If index is missing, we push it to the end.
125        positionals.sort_by_key(|a| a.get_index().unwrap_or(usize::MAX));
126
127        // Sort flags alphabetically
128        flags.sort_by(|a, b| a.get_id().cmp(b.get_id()));
129
130        // 3. Arguments Section (Positional)
131        if !positionals.is_empty() {
132            println!("{}Arguments:{}", BOLD, RESET);
133            for arg in positionals {
134                let name = arg.get_id().as_str().to_uppercase();
135                let help = arg.get_help().map(|h| h.to_string()).unwrap_or_default();
136
137                // Check if required
138                let required_note = if arg.is_required_set() {
139                    format!("{} (required){}", YELLOW, RESET)
140                } else {
141                    String::new()
142                };
143
144                println!("  {}{:<28}{} {}{}", GREEN, name, RESET, help, required_note);
145            }
146            println!();
147        }
148
149        // 4. Options Section (Flags)
150        println!("{}Options:{}", BOLD, RESET);
151
152        for arg in flags {
153            let short = arg
154                .get_short()
155                .map(|s| format!("-{},", s))
156                .unwrap_or_default();
157            let long = arg
158                .get_long()
159                .map(|l| format!("--{}", l))
160                .unwrap_or_default();
161
162            // Handle values like <OUTPUT>
163            let value = if arg.get_action().takes_values() {
164                let val_name = arg
165                    .get_value_names()
166                    .and_then(|names| names.first())
167                    .map(|s| s.to_string())
168                    .unwrap_or_else(|| "VAL".to_string());
169                format!(" <{}>", val_name.to_uppercase())
170            } else {
171                String::new()
172            };
173
174            let flag_str = format!("{} {}{}", short, long, value);
175            let help_text = arg.get_help().map(|h| h.to_string()).unwrap_or_default();
176
177            let required_note = if arg.is_required_set() {
178                format!("{} (required){}", YELLOW, RESET)
179            } else {
180                String::new()
181            };
182
183            println!(
184                "  {}{:<28}{} {}{}",
185                GREEN,
186                flag_str.trim(),
187                RESET,
188                help_text,
189                required_note
190            );
191        }
192
193        // Always show help flag
194        println!("  {}{:<28}{} Print help", GREEN, "-h, --help", RESET);
195        println!();
196
197        // 5. Example Usage
198        println!("{}Example:{}", BOLD, RESET);
199        if let Some(example) = sub.get_after_help() {
200            println!("  {}", example);
201        } else {
202            println!("  {} {} [flags] [args]", bin_name, sub_name);
203        }
204        println!();
205    }
206}