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" => {
52 create_cmds.push(item);
53 }
54 "inspect" | "show" | "diff" | "log" | "ls" | "predict" | "convert" => {
55 inspect_cmds.push(item);
56 }
57 "mount" | "unmount" | "shell" | "serve" | "remote" | "push" | "pull" => {
58 network_cmds.push(item);
59 }
60 "keygen" | "sign" | "verify" | "doctor" => infra_cmds.push(item),
61 _ => {}
62 }
63 }
64
65 self.print_section("core archive & workspace workflows", create_cmds);
66 self.print_section("data inspection & conversion", inspect_cmds);
67 self.print_section("networking & cloud collaboration", network_cmds);
68 self.print_section("security & system health", infra_cmds);
69
70 println!("{BOLD}Options:{RESET}");
71 println!(" {GREEN}{:<15}{RESET} Print help", "-h, --help");
72 println!(" {GREEN}{:<15}{RESET} Print version", "-V, --version");
73 println!();
74 println!(
75 "Run '{BOLD}{YELLOW}{bin_name} COMMAND --help{RESET}' for more information on a command."
76 );
77 }
78
79 #[allow(clippy::unused_self)]
80 fn print_section(&self, header: &str, cmds: Vec<(String, String)>) {
81 if cmds.is_empty() {
82 return;
83 }
84
85 println!("{BOLD}{YELLOW}{header}:{RESET}");
86
87 for (name, about) in cmds {
88 println!(" {GREEN}{name:<12}{RESET} {about}");
89 }
90 println!();
91 }
92
93 pub fn print_subcommand_help(&mut self, sub_name: &str) {
95 let Some(sub) = self.cmd.find_subcommand(sub_name) else {
96 return;
97 };
98
99 let bin_name = self.cmd.get_bin_name().unwrap_or("hexz");
100
101 println!(
103 "{BOLD}Usage:{RESET} {GREEN} {bin_name} {sub_name} {RESET} {CYAN}[OPTIONS] [ARGS]{RESET}"
104 );
105 println!();
106
107 if let Some(about) = sub.get_long_about().or_else(|| sub.get_about()) {
109 println!("{about}");
110 }
111 println!();
112
113 let (mut positionals, mut flags): (Vec<_>, Vec<_>) = sub
117 .get_arguments()
118 .filter(|a| a.get_id() != "help" && a.get_id() != "version")
119 .partition(|a| a.get_short().is_none() && a.get_long().is_none());
120
121 positionals.sort_by_key(|a| a.get_index().unwrap_or(usize::MAX));
124
125 flags.sort_by(|a, b| a.get_id().cmp(b.get_id()));
127
128 if !positionals.is_empty() {
130 println!("{BOLD}Arguments:{RESET}");
131 for arg in positionals {
132 let name = arg.get_id().as_str().to_uppercase();
133 let help = arg.get_help().map(ToString::to_string).unwrap_or_default();
134
135 let required_note = if arg.is_required_set() {
137 format!("{YELLOW} (required){RESET}")
138 } else {
139 String::new()
140 };
141
142 println!(" {GREEN}{name:<28}{RESET} {help}{required_note}");
143 }
144 println!();
145 }
146
147 println!("{BOLD}Options:{RESET}");
149
150 for arg in flags {
151 let short = arg
152 .get_short()
153 .map(|s| format!("-{s},"))
154 .unwrap_or_default();
155 let long = arg.get_long().map(|l| format!("--{l}")).unwrap_or_default();
156
157 let value = if arg.get_action().takes_values() {
159 let val_name = arg
160 .get_value_names()
161 .and_then(|names| names.first())
162 .map_or_else(|| "VAL".to_string(), ToString::to_string);
163 format!(" <{}>", val_name.to_uppercase())
164 } else {
165 String::new()
166 };
167
168 let flag_str = format!("{short} {long}{value}");
169 let help_text = arg.get_help().map(ToString::to_string).unwrap_or_default();
170
171 let required_note = if arg.is_required_set() {
172 format!("{YELLOW} (required){RESET}")
173 } else {
174 String::new()
175 };
176
177 let trimmed = flag_str.trim();
178 println!(" {GREEN}{trimmed:<28}{RESET} {help_text}{required_note}");
179 }
180
181 println!(" {GREEN}{:<28}{RESET} Print help", "-h, --help");
183 println!();
184
185 println!("{BOLD}Example:{RESET}");
187 if let Some(example) = sub.get_after_help() {
188 println!(" {example}");
189 } else {
190 println!(" {bin_name} {sub_name} [flags] [args]");
191 }
192 println!();
193 }
194}