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 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 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 println!(
99 "{}Usage:{} {} {} {} {} {}[OPTIONS] [ARGS]{}",
100 BOLD, RESET, GREEN, bin_name, sub_name, RESET, CYAN, RESET
101 );
102 println!();
103
104 if let Some(about) = sub.get_long_about().or_else(|| sub.get_about()) {
106 println!("{}", about);
107 }
108 println!();
109
110 let args: Vec<_> = sub.get_arguments().collect();
112
113 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 positionals.sort_by_key(|a| a.get_index().unwrap_or(usize::MAX));
123
124 flags.sort_by(|a, b| a.get_id().cmp(b.get_id()));
126
127 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 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 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 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 println!(" {}{:<28}{} Print help", GREEN, "-h, --help", RESET);
192 println!();
193
194 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}