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