1 use clap::ArgAction;
2 use roff::{bold, italic, roman, Inline, Roff};
3
subcommand_heading(cmd: &clap::Command) -> &str4 pub(crate) fn subcommand_heading(cmd: &clap::Command) -> &str {
5 match cmd.get_subcommand_help_heading() {
6 Some(title) => title,
7 None => "SUBCOMMANDS",
8 }
9 }
10
about(roff: &mut Roff, cmd: &clap::Command)11 pub(crate) fn about(roff: &mut Roff, cmd: &clap::Command) {
12 let s = match cmd.get_about().or_else(|| cmd.get_long_about()) {
13 Some(about) => format!("{} - {}", cmd.get_name(), about),
14 None => cmd.get_name().to_string(),
15 };
16 roff.text([roman(s)]);
17 }
18
description(roff: &mut Roff, cmd: &clap::Command)19 pub(crate) fn description(roff: &mut Roff, cmd: &clap::Command) {
20 if let Some(about) = cmd.get_long_about().or_else(|| cmd.get_about()) {
21 for line in about.to_string().lines() {
22 if line.trim().is_empty() {
23 roff.control("PP", []);
24 } else {
25 roff.text([roman(line)]);
26 }
27 }
28 }
29 }
30
synopsis(roff: &mut Roff, cmd: &clap::Command)31 pub(crate) fn synopsis(roff: &mut Roff, cmd: &clap::Command) {
32 let mut line = vec![bold(cmd.get_name()), roman(" ")];
33
34 for opt in cmd.get_arguments().filter(|i| !i.is_hide_set()) {
35 let (lhs, rhs) = option_markers(opt);
36 match (opt.get_short(), opt.get_long()) {
37 (Some(short), Some(long)) => {
38 line.push(roman(lhs));
39 line.push(bold(format!("-{short}")));
40 line.push(roman("|"));
41 line.push(bold(format!("--{long}",)));
42 line.push(roman(rhs));
43 }
44 (Some(short), None) => {
45 line.push(roman(lhs));
46 line.push(bold(format!("-{short} ")));
47 line.push(roman(rhs));
48 }
49 (None, Some(long)) => {
50 line.push(roman(lhs));
51 line.push(bold(format!("--{long}")));
52 line.push(roman(rhs));
53 }
54 (None, None) => continue,
55 };
56
57 if matches!(opt.get_action(), ArgAction::Count) {
58 line.push(roman("..."))
59 }
60 line.push(roman(" "));
61 }
62
63 for arg in cmd.get_positionals() {
64 let (lhs, rhs) = option_markers(arg);
65 line.push(roman(lhs));
66 if let Some(value) = arg.get_value_names() {
67 line.push(italic(value.join(" ")));
68 } else {
69 line.push(italic(arg.get_id().as_str()));
70 }
71 line.push(roman(rhs));
72 line.push(roman(" "));
73 }
74
75 if cmd.has_subcommands() {
76 let (lhs, rhs) = subcommand_markers(cmd);
77 line.push(roman(lhs));
78 line.push(italic(
79 cmd.get_subcommand_value_name()
80 .unwrap_or_else(|| subcommand_heading(cmd))
81 .to_lowercase(),
82 ));
83 line.push(roman(rhs));
84 }
85
86 roff.text(line);
87 }
88
options(roff: &mut Roff, cmd: &clap::Command)89 pub(crate) fn options(roff: &mut Roff, cmd: &clap::Command) {
90 let items: Vec<_> = cmd.get_arguments().filter(|i| !i.is_hide_set()).collect();
91
92 for opt in items.iter().filter(|a| !a.is_positional()) {
93 let mut header = match (opt.get_short(), opt.get_long()) {
94 (Some(short), Some(long)) => {
95 vec![short_option(short), roman(", "), long_option(long)]
96 }
97 (Some(short), None) => vec![short_option(short)],
98 (None, Some(long)) => vec![long_option(long)],
99 (None, None) => vec![],
100 };
101
102 if opt.get_action().takes_values() {
103 if let Some(value) = &opt.get_value_names() {
104 header.push(roman("="));
105 header.push(italic(value.join(" ")));
106 }
107 }
108
109 if let Some(defs) = option_default_values(opt) {
110 header.push(roman(" "));
111 header.push(roman(defs));
112 }
113
114 let mut body = vec![];
115 let mut arg_help_written = false;
116 if let Some(help) = option_help(opt) {
117 arg_help_written = true;
118 body.push(roman(help.to_string()));
119 }
120
121 roff.control("TP", []);
122 roff.text(header);
123 roff.text(body);
124
125 if let Some((possible_values_text, with_help)) = get_possible_values(opt) {
126 if arg_help_written {
127 // It looks nice to have a separation between the help and the values
128 roff.text([Inline::LineBreak]);
129 }
130 if with_help {
131 roff.text([Inline::LineBreak, italic("Possible values:")]);
132
133 // Need to indent twice to get it to look right, because .TP heading indents, but
134 // that indent doesn't Carry over to the .IP for the bullets. The standard shift
135 // size is 7 for terminal devices
136 roff.control("RS", ["14"]);
137 for line in possible_values_text {
138 roff.control("IP", ["\\(bu", "2"]);
139 roff.text([roman(line)]);
140 }
141 roff.control("RE", []);
142 } else {
143 let possible_value_text: Vec<Inline> = vec![
144 Inline::LineBreak,
145 roman("["),
146 italic("possible values: "),
147 roman(possible_values_text.join(", ")),
148 roman("]"),
149 ];
150 roff.text(possible_value_text);
151 }
152 }
153
154 if let Some(env) = option_environment(opt) {
155 roff.control("RS", []);
156 roff.text(env);
157 roff.control("RE", []);
158 }
159 }
160
161 for pos in items.iter().filter(|a| a.is_positional()) {
162 let mut header = vec![];
163 let (lhs, rhs) = option_markers(pos);
164 header.push(roman(lhs));
165 if let Some(value) = pos.get_value_names() {
166 header.push(italic(value.join(" ")));
167 } else {
168 header.push(italic(pos.get_id().as_str()));
169 };
170 header.push(roman(rhs));
171
172 if let Some(defs) = option_default_values(pos) {
173 header.push(roman(format!(" {defs}")));
174 }
175
176 let mut body = vec![];
177 let mut arg_help_written = false;
178 if let Some(help) = option_help(pos) {
179 body.push(roman(help.to_string()));
180 arg_help_written = true;
181 }
182
183 roff.control("TP", []);
184 roff.text(header);
185 roff.text(body);
186
187 if let Some(env) = option_environment(pos) {
188 roff.control("RS", []);
189 roff.text(env);
190 roff.control("RE", []);
191 }
192 // If possible options are available
193 if let Some((possible_values_text, with_help)) = get_possible_values(pos) {
194 if arg_help_written {
195 // It looks nice to have a separation between the help and the values
196 roff.text([Inline::LineBreak]);
197 }
198 if with_help {
199 roff.text([Inline::LineBreak, italic("Possible values:")]);
200
201 // Need to indent twice to get it to look right, because .TP heading indents, but
202 // that indent doesn't Carry over to the .IP for the bullets. The standard shift
203 // size is 7 for terminal devices
204 roff.control("RS", ["14"]);
205 for line in possible_values_text {
206 roff.control("IP", ["\\(bu", "2"]);
207 roff.text([roman(line)]);
208 }
209 roff.control("RE", []);
210 } else {
211 let possible_value_text: Vec<Inline> = vec![
212 Inline::LineBreak,
213 roman("["),
214 italic("possible values: "),
215 roman(possible_values_text.join(", ")),
216 roman("]"),
217 ];
218 roff.text(possible_value_text);
219 }
220 }
221 }
222 }
223
subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str)224 pub(crate) fn subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str) {
225 for sub in cmd.get_subcommands().filter(|s| !s.is_hide_set()) {
226 roff.control("TP", []);
227
228 let name = format!(
229 "{}-{}({})",
230 cmd.get_display_name().unwrap_or_else(|| cmd.get_name()),
231 sub.get_name(),
232 section
233 );
234 roff.text([roman(name)]);
235
236 if let Some(about) = sub.get_about().or_else(|| sub.get_long_about()) {
237 for line in about.to_string().lines() {
238 roff.text([roman(line)]);
239 }
240 }
241 }
242 }
243
version(cmd: &clap::Command) -> String244 pub(crate) fn version(cmd: &clap::Command) -> String {
245 format!(
246 "v{}",
247 cmd.get_long_version()
248 .or_else(|| cmd.get_version())
249 .unwrap()
250 )
251 }
252
after_help(roff: &mut Roff, cmd: &clap::Command)253 pub(crate) fn after_help(roff: &mut Roff, cmd: &clap::Command) {
254 if let Some(about) = cmd.get_after_long_help().or_else(|| cmd.get_after_help()) {
255 for line in about.to_string().lines() {
256 roff.text([roman(line)]);
257 }
258 }
259 }
260
subcommand_markers(cmd: &clap::Command) -> (&'static str, &'static str)261 fn subcommand_markers(cmd: &clap::Command) -> (&'static str, &'static str) {
262 markers(cmd.is_subcommand_required_set())
263 }
264
option_markers(opt: &clap::Arg) -> (&'static str, &'static str)265 fn option_markers(opt: &clap::Arg) -> (&'static str, &'static str) {
266 markers(opt.is_required_set())
267 }
268
markers(required: bool) -> (&'static str, &'static str)269 fn markers(required: bool) -> (&'static str, &'static str) {
270 if required {
271 ("<", ">")
272 } else {
273 ("[", "]")
274 }
275 }
276
short_option(opt: char) -> Inline277 fn short_option(opt: char) -> Inline {
278 bold(format!("-{opt}"))
279 }
280
long_option(opt: &str) -> Inline281 fn long_option(opt: &str) -> Inline {
282 bold(format!("--{opt}"))
283 }
284
option_help(opt: &clap::Arg) -> Option<&clap::builder::StyledStr>285 fn option_help(opt: &clap::Arg) -> Option<&clap::builder::StyledStr> {
286 if !opt.is_hide_long_help_set() {
287 let long_help = opt.get_long_help();
288 if long_help.is_some() {
289 return long_help;
290 }
291 }
292 if !opt.is_hide_short_help_set() {
293 return opt.get_help();
294 }
295
296 None
297 }
298
option_environment(opt: &clap::Arg) -> Option<Vec<Inline>>299 fn option_environment(opt: &clap::Arg) -> Option<Vec<Inline>> {
300 if opt.is_hide_env_set() {
301 return None;
302 } else if let Some(env) = opt.get_env() {
303 return Some(vec![
304 roman("May also be specified with the "),
305 bold(env.to_string_lossy().into_owned()),
306 roman(" environment variable. "),
307 ]);
308 }
309
310 None
311 }
312
option_default_values(opt: &clap::Arg) -> Option<String>313 fn option_default_values(opt: &clap::Arg) -> Option<String> {
314 if opt.is_hide_default_value_set() || !opt.get_action().takes_values() {
315 return None;
316 } else if !opt.get_default_values().is_empty() {
317 let values = opt
318 .get_default_values()
319 .iter()
320 .map(|s| s.to_string_lossy())
321 .collect::<Vec<_>>()
322 .join(",");
323
324 return Some(format!("[default: {values}]"));
325 }
326
327 None
328 }
329
get_possible_values(arg: &clap::Arg) -> Option<(Vec<String>, bool)>330 fn get_possible_values(arg: &clap::Arg) -> Option<(Vec<String>, bool)> {
331 let possibles = &arg.get_possible_values();
332 let possibles: Vec<&clap::builder::PossibleValue> =
333 possibles.iter().filter(|pos| !pos.is_hide_set()).collect();
334
335 if !(possibles.is_empty() || arg.is_hide_possible_values_set()) {
336 return Some(format_possible_values(&possibles));
337 }
338 None
339 }
340
format_possible_values(possibles: &Vec<&clap::builder::PossibleValue>) -> (Vec<String>, bool)341 fn format_possible_values(possibles: &Vec<&clap::builder::PossibleValue>) -> (Vec<String>, bool) {
342 let mut lines = vec![];
343 let with_help = possibles.iter().any(|p| p.get_help().is_some());
344 if with_help {
345 for value in possibles {
346 let val_name = value.get_name();
347 match value.get_help() {
348 Some(help) => lines.push(format!("{val_name}: {help}")),
349 None => lines.push(val_name.to_string()),
350 }
351 }
352 } else {
353 lines.append(&mut possibles.iter().map(|p| p.get_name().to_string()).collect());
354 }
355 (lines, with_help)
356 }
357