• 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 let Some(value) = &opt.get_value_names() {
103             header.push(roman("="));
104             header.push(italic(value.join(" ")));
105         }
106 
107         if let Some(defs) = option_default_values(opt) {
108             header.push(roman(" "));
109             header.push(roman(defs));
110         }
111 
112         let mut body = vec![];
113         let mut arg_help_written = false;
114         if let Some(help) = option_help(opt) {
115             arg_help_written = true;
116             body.push(roman(help.to_string()));
117         }
118 
119         roff.control("TP", []);
120         roff.text(header);
121         roff.text(body);
122 
123         if let Some((possible_values_text, with_help)) = get_possible_values(opt) {
124             if arg_help_written {
125                 // It looks nice to have a separation between the help and the values
126                 roff.text([Inline::LineBreak]);
127             }
128             if with_help {
129                 roff.text([Inline::LineBreak, italic("Possible values:")]);
130 
131                 // Need to indent twice to get it to look right, because .TP heading indents, but
132                 // that indent doesn't Carry over to the .IP for the bullets. The standard shift
133                 // size is 7 for terminal devices
134                 roff.control("RS", ["14"]);
135                 for line in possible_values_text {
136                     roff.control("IP", ["\\(bu", "2"]);
137                     roff.text([roman(line)]);
138                 }
139                 roff.control("RE", []);
140             } else {
141                 let possible_value_text: Vec<Inline> = vec![
142                     Inline::LineBreak,
143                     roman("["),
144                     italic("possible values: "),
145                     roman(possible_values_text.join(", ")),
146                     roman("]"),
147                 ];
148                 roff.text(possible_value_text);
149             }
150         }
151 
152         if let Some(env) = option_environment(opt) {
153             roff.control("RS", []);
154             roff.text(env);
155             roff.control("RE", []);
156         }
157     }
158 
159     for pos in items.iter().filter(|a| a.is_positional()) {
160         let mut header = vec![];
161         let (lhs, rhs) = option_markers(pos);
162         header.push(roman(lhs));
163         if let Some(value) = pos.get_value_names() {
164             header.push(italic(value.join(" ")));
165         } else {
166             header.push(italic(pos.get_id().as_str()));
167         };
168         header.push(roman(rhs));
169 
170         if let Some(defs) = option_default_values(pos) {
171             header.push(roman(format!(" {}", defs)));
172         }
173 
174         let mut body = vec![];
175         let mut arg_help_written = false;
176         if let Some(help) = option_help(pos) {
177             body.push(roman(help.to_string()));
178             arg_help_written = true;
179         }
180 
181         roff.control("TP", []);
182         roff.text(header);
183         roff.text(body);
184 
185         if let Some(env) = option_environment(pos) {
186             roff.control("RS", []);
187             roff.text(env);
188             roff.control("RE", []);
189         }
190         // If possible options are available
191         if let Some((possible_values_text, with_help)) = get_possible_values(pos) {
192             if arg_help_written {
193                 // It looks nice to have a separation between the help and the values
194                 roff.text([Inline::LineBreak]);
195             }
196             if with_help {
197                 roff.text([Inline::LineBreak, italic("Possible values:")]);
198 
199                 // Need to indent twice to get it to look right, because .TP heading indents, but
200                 // that indent doesn't Carry over to the .IP for the bullets. The standard shift
201                 // size is 7 for terminal devices
202                 roff.control("RS", ["14"]);
203                 for line in possible_values_text {
204                     roff.control("IP", ["\\(bu", "2"]);
205                     roff.text([roman(line)]);
206                 }
207                 roff.control("RE", []);
208             } else {
209                 let possible_value_text: Vec<Inline> = vec![
210                     Inline::LineBreak,
211                     roman("["),
212                     italic("possible values: "),
213                     roman(possible_values_text.join(", ")),
214                     roman("]"),
215                 ];
216                 roff.text(possible_value_text);
217             }
218         }
219     }
220 }
221 
subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str)222 pub(crate) fn subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str) {
223     for sub in cmd.get_subcommands().filter(|s| !s.is_hide_set()) {
224         roff.control("TP", []);
225 
226         let name = format!(
227             "{}-{}({})",
228             cmd.get_display_name().unwrap_or_else(|| cmd.get_name()),
229             sub.get_name(),
230             section
231         );
232         roff.text([roman(name)]);
233 
234         if let Some(about) = sub.get_about().or_else(|| sub.get_long_about()) {
235             for line in about.to_string().lines() {
236                 roff.text([roman(line)]);
237             }
238         }
239     }
240 }
241 
version(cmd: &clap::Command) -> String242 pub(crate) fn version(cmd: &clap::Command) -> String {
243     format!(
244         "v{}",
245         cmd.get_long_version()
246             .or_else(|| cmd.get_version())
247             .unwrap()
248     )
249 }
250 
after_help(roff: &mut Roff, cmd: &clap::Command)251 pub(crate) fn after_help(roff: &mut Roff, cmd: &clap::Command) {
252     if let Some(about) = cmd.get_after_long_help().or_else(|| cmd.get_after_help()) {
253         for line in about.to_string().lines() {
254             roff.text([roman(line)]);
255         }
256     }
257 }
258 
subcommand_markers(cmd: &clap::Command) -> (&'static str, &'static str)259 fn subcommand_markers(cmd: &clap::Command) -> (&'static str, &'static str) {
260     markers(cmd.is_subcommand_required_set())
261 }
262 
option_markers(opt: &clap::Arg) -> (&'static str, &'static str)263 fn option_markers(opt: &clap::Arg) -> (&'static str, &'static str) {
264     markers(opt.is_required_set())
265 }
266 
markers(required: bool) -> (&'static str, &'static str)267 fn markers(required: bool) -> (&'static str, &'static str) {
268     if required {
269         ("<", ">")
270     } else {
271         ("[", "]")
272     }
273 }
274 
short_option(opt: char) -> Inline275 fn short_option(opt: char) -> Inline {
276     bold(format!("-{}", opt))
277 }
278 
long_option(opt: &str) -> Inline279 fn long_option(opt: &str) -> Inline {
280     bold(format!("--{}", opt))
281 }
282 
option_help(opt: &clap::Arg) -> Option<&clap::builder::StyledStr>283 fn option_help(opt: &clap::Arg) -> Option<&clap::builder::StyledStr> {
284     if !opt.is_hide_long_help_set() {
285         let long_help = opt.get_long_help();
286         if long_help.is_some() {
287             return long_help;
288         }
289     }
290     if !opt.is_hide_short_help_set() {
291         return opt.get_help();
292     }
293 
294     None
295 }
296 
option_environment(opt: &clap::Arg) -> Option<Vec<Inline>>297 fn option_environment(opt: &clap::Arg) -> Option<Vec<Inline>> {
298     if opt.is_hide_env_set() {
299         return None;
300     } else if let Some(env) = opt.get_env() {
301         return Some(vec![
302             roman("May also be specified with the "),
303             bold(env.to_string_lossy().into_owned()),
304             roman(" environment variable. "),
305         ]);
306     }
307 
308     None
309 }
310 
option_default_values(opt: &clap::Arg) -> Option<String>311 fn option_default_values(opt: &clap::Arg) -> Option<String> {
312     if opt.is_hide_default_value_set() || !opt.get_action().takes_values() {
313         return None;
314     } else if !opt.get_default_values().is_empty() {
315         let values = opt
316             .get_default_values()
317             .iter()
318             .map(|s| s.to_string_lossy())
319             .collect::<Vec<_>>()
320             .join(",");
321 
322         return Some(format!("[default: {}]", values));
323     }
324 
325     None
326 }
327 
get_possible_values(arg: &clap::Arg) -> Option<(Vec<String>, bool)>328 fn get_possible_values(arg: &clap::Arg) -> Option<(Vec<String>, bool)> {
329     let possibles = &arg.get_possible_values();
330     let possibles: Vec<&clap::builder::PossibleValue> =
331         possibles.iter().filter(|pos| !pos.is_hide_set()).collect();
332 
333     if !(possibles.is_empty() || arg.is_hide_possible_values_set()) {
334         return Some(format_possible_values(&possibles));
335     }
336     None
337 }
338 
format_possible_values(possibles: &Vec<&clap::builder::PossibleValue>) -> (Vec<String>, bool)339 fn format_possible_values(possibles: &Vec<&clap::builder::PossibleValue>) -> (Vec<String>, bool) {
340     let mut lines = vec![];
341     let with_help = possibles.iter().any(|p| p.get_help().is_some());
342     if with_help {
343         for value in possibles {
344             let val_name = value.get_name();
345             match value.get_help() {
346                 Some(help) => lines.push(format!("{}: {}", val_name, help)),
347                 None => lines.push(val_name.to_string()),
348             }
349         }
350     } else {
351         lines.append(&mut possibles.iter().map(|p| p.get_name().to_string()).collect());
352     }
353     (lines, with_help)
354 }
355