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
9#[derive(Debug)]
11pub struct Printer {
12 cmd: Command,
13}
14
15impl Printer {
16 pub const fn new(cmd: Command) -> Self {
18 Self { cmd }
19 }
20
21 pub fn print_help(&mut self) {
23 let bin_name = self.cmd.get_bin_name().unwrap_or("hexz").to_string();
24
25 println!(
26 "{BOLD}Usage:{RESET} {GREEN}{bin_name}{RESET} {CYAN}[OPTIONS]{RESET} {YELLOW}COMMAND{RESET}"
27 );
28 println!();
29 if let Some(about) = self.cmd.get_about() {
30 println!("{about}");
31 }
32 println!();
33
34 let mut create_cmds = Vec::new();
35 let mut inspect_cmds = Vec::new();
36 let mut network_cmds = Vec::new();
37 let mut infra_cmds = Vec::new();
38
39 let subcommands: Vec<Command> = self.cmd.get_subcommands().cloned().collect();
40
41 for sub in subcommands {
42 let name = sub.get_name().to_string();
43 if name == "help" {
44 continue;
45 }
46
47 let about = sub.get_about().map(ToString::to_string).unwrap_or_default();
48 let item = (name.clone(), about);
49
50 match name.as_str() {
51 "pack" | "extract" | "init" | "checkout" | "commit" | "status" => create_cmds.push(item),
52 "inspect" | "show" | "diff" | "log" | "ls" | "predict" | "convert" => inspect_cmds.push(item),
53 "mount" | "unmount" | "shell" | "serve" | "remote" | "push" | "pull" => network_cmds.push(item),
54 "keygen" | "sign" | "verify" | "doctor" => infra_cmds.push(item),
55 _ => {}
56 }
57 }
58
59 self.print_section("core archive & workspace workflows", create_cmds);
60 self.print_section("data inspection & conversion", inspect_cmds);
61 self.print_section("networking & cloud collaboration", network_cmds);
62 self.print_section("security & system health", infra_cmds);
63
64 println!("{BOLD}Options:{RESET}");
65 println!(" {GREEN}{:<15}{RESET} Print help", "-h, --help");
66 println!(" {GREEN}{:<15}{RESET} Print version", "-V, --version");
67 println!();
68 println!(
69 "Run '{BOLD}{YELLOW}{bin_name} COMMAND --help{RESET}' for more information on a command."
70 );
71 }
72
73 #[allow(clippy::unused_self)]
74 fn print_section(&self, header: &str, cmds: Vec<(String, String)>) {
75 if cmds.is_empty() {
76 return;
77 }
78
79 println!("{BOLD}{YELLOW}{header}:{RESET}");
80
81 for (name, about) in cmds {
82 println!(" {GREEN}{name:<12}{RESET} {about}");
83 }
84 println!();
85 }
86
87 pub fn print_subcommand_help(&mut self, sub_name: &str) {
89 let Some(sub) = self.cmd.find_subcommand(sub_name) else {
90 return;
91 };
92
93 let bin_name = self.cmd.get_bin_name().unwrap_or("hexz");
94
95 println!(
97 "{BOLD}Usage:{RESET} {GREEN} {bin_name} {sub_name} {RESET} {CYAN}[OPTIONS] [ARGS]{RESET}"
98 );
99 println!();
100
101 if let Some(about) = sub.get_long_about().or_else(|| sub.get_about()) {
103 println!("{about}");
104 }
105 println!();
106
107 let (mut positionals, mut flags): (Vec<_>, Vec<_>) = sub
111 .get_arguments()
112 .filter(|a| a.get_id() != "help" && a.get_id() != "version")
113 .partition(|a| a.get_short().is_none() && a.get_long().is_none());
114
115 positionals.sort_by_key(|a| a.get_index().unwrap_or(usize::MAX));
118
119 flags.sort_by(|a, b| a.get_id().cmp(b.get_id()));
121
122 if !positionals.is_empty() {
124 println!("{BOLD}Arguments:{RESET}");
125 for arg in positionals {
126 let name = arg.get_id().as_str().to_uppercase();
127 let help = arg.get_help().map(ToString::to_string).unwrap_or_default();
128
129 let required_note = if arg.is_required_set() {
131 format!("{YELLOW} (required){RESET}")
132 } else {
133 String::new()
134 };
135
136 println!(" {GREEN}{name:<28}{RESET} {help}{required_note}");
137 }
138 println!();
139 }
140
141 println!("{BOLD}Options:{RESET}");
143
144 for arg in flags {
145 let short = arg
146 .get_short()
147 .map(|s| format!("-{s},"))
148 .unwrap_or_default();
149 let long = arg
150 .get_long()
151 .map(|l| format!("--{l}"))
152 .unwrap_or_default();
153
154 let value = if arg.get_action().takes_values() {
156 let val_name = arg
157 .get_value_names()
158 .and_then(|names| names.first())
159 .map_or_else(|| "VAL".to_string(), ToString::to_string);
160 format!(" <{}>", val_name.to_uppercase())
161 } else {
162 String::new()
163 };
164
165 let flag_str = format!("{short} {long}{value}");
166 let help_text = arg.get_help().map(ToString::to_string).unwrap_or_default();
167
168 let required_note = if arg.is_required_set() {
169 format!("{YELLOW} (required){RESET}")
170 } else {
171 String::new()
172 };
173
174 let trimmed = flag_str.trim();
175 println!(
176 " {GREEN}{trimmed:<28}{RESET} {help_text}{required_note}"
177 );
178 }
179
180 println!(" {GREEN}{:<28}{RESET} Print help", "-h, --help");
182 println!();
183
184 println!("{BOLD}Example:{RESET}");
186 if let Some(example) = sub.get_after_help() {
187 println!(" {example}");
188 } else {
189 println!(" {bin_name} {sub_name} [flags] [args]");
190 }
191 println!();
192 }
193}