• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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