• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Std
2 use std::borrow::Cow;
3 use std::cmp;
4 use std::usize;
5 
6 // Internal
7 use crate::builder::PossibleValue;
8 use crate::builder::Str;
9 use crate::builder::StyledStr;
10 use crate::builder::{Arg, Command};
11 use crate::output::display_width;
12 use crate::output::wrap;
13 use crate::output::Usage;
14 use crate::output::TAB;
15 use crate::output::TAB_WIDTH;
16 use crate::util::FlatSet;
17 
18 /// `clap` auto-generated help writer
19 pub(crate) struct AutoHelp<'cmd, 'writer> {
20     template: HelpTemplate<'cmd, 'writer>,
21 }
22 
23 // Public Functions
24 impl<'cmd, 'writer> AutoHelp<'cmd, 'writer> {
25     /// Create a new `HelpTemplate` instance.
new( writer: &'writer mut StyledStr, cmd: &'cmd Command, usage: &'cmd Usage<'cmd>, use_long: bool, ) -> Self26     pub(crate) fn new(
27         writer: &'writer mut StyledStr,
28         cmd: &'cmd Command,
29         usage: &'cmd Usage<'cmd>,
30         use_long: bool,
31     ) -> Self {
32         Self {
33             template: HelpTemplate::new(writer, cmd, usage, use_long),
34         }
35     }
36 
write_help(&mut self)37     pub(crate) fn write_help(&mut self) {
38         let pos = self
39             .template
40             .cmd
41             .get_positionals()
42             .any(|arg| should_show_arg(self.template.use_long, arg));
43         let non_pos = self
44             .template
45             .cmd
46             .get_non_positionals()
47             .any(|arg| should_show_arg(self.template.use_long, arg));
48         let subcmds = self.template.cmd.has_visible_subcommands();
49 
50         let template = if non_pos || pos || subcmds {
51             DEFAULT_TEMPLATE
52         } else {
53             DEFAULT_NO_ARGS_TEMPLATE
54         };
55         self.template.write_templated_help(template);
56     }
57 }
58 
59 const DEFAULT_TEMPLATE: &str = "\
60 {before-help}{about-with-newline}
61 {usage-heading} {usage}
62 
63 {all-args}{after-help}\
64     ";
65 
66 const DEFAULT_NO_ARGS_TEMPLATE: &str = "\
67 {before-help}{about-with-newline}
68 {usage-heading} {usage}{after-help}\
69     ";
70 
71 /// `clap` HelpTemplate Writer.
72 ///
73 /// Wraps a writer stream providing different methods to generate help for `clap` objects.
74 pub(crate) struct HelpTemplate<'cmd, 'writer> {
75     writer: &'writer mut StyledStr,
76     cmd: &'cmd Command,
77     usage: &'cmd Usage<'cmd>,
78     next_line_help: bool,
79     term_w: usize,
80     use_long: bool,
81 }
82 
83 // Public Functions
84 impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
85     /// Create a new `HelpTemplate` instance.
new( writer: &'writer mut StyledStr, cmd: &'cmd Command, usage: &'cmd Usage<'cmd>, use_long: bool, ) -> Self86     pub(crate) fn new(
87         writer: &'writer mut StyledStr,
88         cmd: &'cmd Command,
89         usage: &'cmd Usage<'cmd>,
90         use_long: bool,
91     ) -> Self {
92         debug!(
93             "HelpTemplate::new cmd={}, use_long={}",
94             cmd.get_name(),
95             use_long
96         );
97         let term_w = match cmd.get_term_width() {
98             Some(0) => usize::MAX,
99             Some(w) => w,
100             None => {
101                 let (current_width, _h) = dimensions();
102                 let current_width = current_width.unwrap_or(100);
103                 let max_width = match cmd.get_max_term_width() {
104                     None | Some(0) => usize::MAX,
105                     Some(mw) => mw,
106                 };
107                 cmp::min(current_width, max_width)
108             }
109         };
110         let next_line_help = cmd.is_next_line_help_set();
111 
112         HelpTemplate {
113             writer,
114             cmd,
115             usage,
116             next_line_help,
117             term_w,
118             use_long,
119         }
120     }
121 
122     /// Write help to stream for the parser in the format defined by the template.
123     ///
124     /// For details about the template language see [`Command::help_template`].
125     ///
126     /// [`Command::help_template`]: Command::help_template()
write_templated_help(&mut self, template: &str)127     pub(crate) fn write_templated_help(&mut self, template: &str) {
128         debug!("HelpTemplate::write_templated_help");
129 
130         let mut parts = template.split('{');
131         if let Some(first) = parts.next() {
132             self.none(first);
133         }
134         for part in parts {
135             if let Some((tag, rest)) = part.split_once('}') {
136                 match tag {
137                     "name" => {
138                         self.write_display_name();
139                     }
140                     #[cfg(not(feature = "unstable-v5"))]
141                     "bin" => {
142                         self.write_bin_name();
143                     }
144                     "version" => {
145                         self.write_version();
146                     }
147                     "author" => {
148                         self.write_author(false, false);
149                     }
150                     "author-with-newline" => {
151                         self.write_author(false, true);
152                     }
153                     "author-section" => {
154                         self.write_author(true, true);
155                     }
156                     "about" => {
157                         self.write_about(false, false);
158                     }
159                     "about-with-newline" => {
160                         self.write_about(false, true);
161                     }
162                     "about-section" => {
163                         self.write_about(true, true);
164                     }
165                     "usage-heading" => {
166                         self.header("Usage:");
167                     }
168                     "usage" => {
169                         self.writer.extend(
170                             self.usage
171                                 .create_usage_no_title(&[])
172                                 .unwrap_or_default()
173                                 .into_iter(),
174                         );
175                     }
176                     "all-args" => {
177                         self.write_all_args();
178                     }
179                     "options" => {
180                         // Include even those with a heading as we don't have a good way of
181                         // handling help_heading in the template.
182                         self.write_args(
183                             &self.cmd.get_non_positionals().collect::<Vec<_>>(),
184                             "options",
185                             option_sort_key,
186                         );
187                     }
188                     "positionals" => {
189                         self.write_args(
190                             &self.cmd.get_positionals().collect::<Vec<_>>(),
191                             "positionals",
192                             positional_sort_key,
193                         );
194                     }
195                     "subcommands" => {
196                         self.write_subcommands(self.cmd);
197                     }
198                     "tab" => {
199                         self.none(TAB);
200                     }
201                     "after-help" => {
202                         self.write_after_help();
203                     }
204                     "before-help" => {
205                         self.write_before_help();
206                     }
207                     _ => {
208                         self.none("{");
209                         self.none(tag);
210                         self.none("}");
211                     }
212                 }
213                 self.none(rest);
214             }
215         }
216     }
217 }
218 
219 /// Basic template methods
220 impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
221     /// Writes binary name of a Parser Object to the wrapped stream.
write_display_name(&mut self)222     fn write_display_name(&mut self) {
223         debug!("HelpTemplate::write_display_name");
224 
225         let display_name = wrap(
226             &self
227                 .cmd
228                 .get_display_name()
229                 .unwrap_or_else(|| self.cmd.get_name())
230                 .replace("{n}", "\n"),
231             self.term_w,
232         );
233         self.none(&display_name);
234     }
235 
236     /// Writes binary name of a Parser Object to the wrapped stream.
write_bin_name(&mut self)237     fn write_bin_name(&mut self) {
238         debug!("HelpTemplate::write_bin_name");
239 
240         let bin_name = if let Some(bn) = self.cmd.get_bin_name() {
241             if bn.contains(' ') {
242                 // In case we're dealing with subcommands i.e. git mv is translated to git-mv
243                 bn.replace(' ', "-")
244             } else {
245                 wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
246             }
247         } else {
248             wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
249         };
250         self.none(&bin_name);
251     }
252 
write_version(&mut self)253     fn write_version(&mut self) {
254         let version = self
255             .cmd
256             .get_version()
257             .or_else(|| self.cmd.get_long_version());
258         if let Some(output) = version {
259             self.none(wrap(output, self.term_w));
260         }
261     }
262 
write_author(&mut self, before_new_line: bool, after_new_line: bool)263     fn write_author(&mut self, before_new_line: bool, after_new_line: bool) {
264         if let Some(author) = self.cmd.get_author() {
265             if before_new_line {
266                 self.none("\n");
267             }
268             self.none(wrap(author, self.term_w));
269             if after_new_line {
270                 self.none("\n");
271             }
272         }
273     }
274 
write_about(&mut self, before_new_line: bool, after_new_line: bool)275     fn write_about(&mut self, before_new_line: bool, after_new_line: bool) {
276         let about = if self.use_long {
277             self.cmd.get_long_about().or_else(|| self.cmd.get_about())
278         } else {
279             self.cmd.get_about()
280         };
281         if let Some(output) = about {
282             if before_new_line {
283                 self.none("\n");
284             }
285             let mut output = output.clone();
286             replace_newline_var(&mut output);
287             output.wrap(self.term_w);
288             self.writer.extend(output.into_iter());
289             if after_new_line {
290                 self.none("\n");
291             }
292         }
293     }
294 
write_before_help(&mut self)295     fn write_before_help(&mut self) {
296         debug!("HelpTemplate::write_before_help");
297         let before_help = if self.use_long {
298             self.cmd
299                 .get_before_long_help()
300                 .or_else(|| self.cmd.get_before_help())
301         } else {
302             self.cmd.get_before_help()
303         };
304         if let Some(output) = before_help {
305             let mut output = output.clone();
306             replace_newline_var(&mut output);
307             output.wrap(self.term_w);
308             self.writer.extend(output.into_iter());
309             self.none("\n\n");
310         }
311     }
312 
write_after_help(&mut self)313     fn write_after_help(&mut self) {
314         debug!("HelpTemplate::write_after_help");
315         let after_help = if self.use_long {
316             self.cmd
317                 .get_after_long_help()
318                 .or_else(|| self.cmd.get_after_help())
319         } else {
320             self.cmd.get_after_help()
321         };
322         if let Some(output) = after_help {
323             self.none("\n\n");
324             let mut output = output.clone();
325             replace_newline_var(&mut output);
326             output.wrap(self.term_w);
327             self.writer.extend(output.into_iter());
328         }
329     }
330 }
331 
332 /// Arg handling
333 impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
334     /// Writes help for all arguments (options, flags, args, subcommands)
335     /// including titles of a Parser Object to the wrapped stream.
write_all_args(&mut self)336     pub(crate) fn write_all_args(&mut self) {
337         debug!("HelpTemplate::write_all_args");
338         let pos = self
339             .cmd
340             .get_positionals()
341             .filter(|a| a.get_help_heading().is_none())
342             .filter(|arg| should_show_arg(self.use_long, arg))
343             .collect::<Vec<_>>();
344         let non_pos = self
345             .cmd
346             .get_non_positionals()
347             .filter(|a| a.get_help_heading().is_none())
348             .filter(|arg| should_show_arg(self.use_long, arg))
349             .collect::<Vec<_>>();
350         let subcmds = self.cmd.has_visible_subcommands();
351 
352         let custom_headings = self
353             .cmd
354             .get_arguments()
355             .filter_map(|arg| arg.get_help_heading())
356             .collect::<FlatSet<_>>();
357 
358         let mut first = true;
359 
360         if subcmds {
361             if !first {
362                 self.none("\n\n");
363             }
364             first = false;
365             let default_help_heading = Str::from("Commands");
366             self.header(
367                 self.cmd
368                     .get_subcommand_help_heading()
369                     .unwrap_or(&default_help_heading),
370             );
371             self.header(":");
372             self.none("\n");
373 
374             self.write_subcommands(self.cmd);
375         }
376 
377         if !pos.is_empty() {
378             if !first {
379                 self.none("\n\n");
380             }
381             first = false;
382             // Write positional args if any
383             self.header("Arguments:");
384             self.none("\n");
385             self.write_args(&pos, "Arguments", positional_sort_key);
386         }
387 
388         if !non_pos.is_empty() {
389             if !first {
390                 self.none("\n\n");
391             }
392             first = false;
393             self.header("Options:");
394             self.none("\n");
395             self.write_args(&non_pos, "Options", option_sort_key);
396         }
397         if !custom_headings.is_empty() {
398             for heading in custom_headings {
399                 let args = self
400                     .cmd
401                     .get_arguments()
402                     .filter(|a| {
403                         if let Some(help_heading) = a.get_help_heading() {
404                             return help_heading == heading;
405                         }
406                         false
407                     })
408                     .filter(|arg| should_show_arg(self.use_long, arg))
409                     .collect::<Vec<_>>();
410 
411                 if !args.is_empty() {
412                     if !first {
413                         self.none("\n\n");
414                     }
415                     first = false;
416                     self.header(heading);
417                     self.header(":");
418                     self.none("\n");
419                     self.write_args(&args, heading, option_sort_key);
420                 }
421             }
422         }
423     }
424     /// Sorts arguments by length and display order and write their help to the wrapped stream.
write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey)425     fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) {
426         debug!("HelpTemplate::write_args {}", _category);
427         // The shortest an arg can legally be is 2 (i.e. '-x')
428         let mut longest = 2;
429         let mut ord_v = Vec::new();
430 
431         // Determine the longest
432         for &arg in args.iter().filter(|arg| {
433             // If it's NextLineHelp we don't care to compute how long it is because it may be
434             // NextLineHelp on purpose simply *because* it's so long and would throw off all other
435             // args alignment
436             should_show_arg(self.use_long, arg)
437         }) {
438             if longest_filter(arg) {
439                 longest = longest.max(display_width(&arg.to_string()));
440                 debug!(
441                     "HelpTemplate::write_args: arg={:?} longest={}",
442                     arg.get_id(),
443                     longest
444                 );
445             }
446 
447             let key = (sort_key)(arg);
448             ord_v.push((key, arg));
449         }
450         ord_v.sort_by(|a, b| a.0.cmp(&b.0));
451 
452         let next_line_help = self.will_args_wrap(args, longest);
453 
454         for (i, (_, arg)) in ord_v.iter().enumerate() {
455             if i != 0 {
456                 self.none("\n");
457                 if next_line_help && self.use_long {
458                     self.none("\n");
459                 }
460             }
461             self.write_arg(arg, next_line_help, longest);
462         }
463     }
464 
465     /// Writes help for an argument to the wrapped stream.
write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize)466     fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
467         let spec_vals = &self.spec_vals(arg);
468 
469         self.none(TAB);
470         self.short(arg);
471         self.long(arg);
472         self.writer.extend(arg.stylize_arg_suffix(None).into_iter());
473         self.align_to_about(arg, next_line_help, longest);
474 
475         let about = if self.use_long {
476             arg.get_long_help()
477                 .or_else(|| arg.get_help())
478                 .unwrap_or_default()
479         } else {
480             arg.get_help()
481                 .or_else(|| arg.get_long_help())
482                 .unwrap_or_default()
483         };
484 
485         self.help(Some(arg), about, spec_vals, next_line_help, longest);
486     }
487 
488     /// Writes argument's short command to the wrapped stream.
short(&mut self, arg: &Arg)489     fn short(&mut self, arg: &Arg) {
490         debug!("HelpTemplate::short");
491 
492         if let Some(s) = arg.get_short() {
493             self.literal(format!("-{s}"));
494         } else if arg.get_long().is_some() {
495             self.none("    ");
496         }
497     }
498 
499     /// Writes argument's long command to the wrapped stream.
long(&mut self, arg: &Arg)500     fn long(&mut self, arg: &Arg) {
501         debug!("HelpTemplate::long");
502         if let Some(long) = arg.get_long() {
503             if arg.get_short().is_some() {
504                 self.none(", ");
505             }
506             self.literal(format!("--{long}"));
507         }
508     }
509 
510     /// Write alignment padding between arg's switches/values and its about message.
align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize)511     fn align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
512         debug!(
513             "HelpTemplate::align_to_about: arg={}, next_line_help={}, longest={}",
514             arg.get_id(),
515             next_line_help,
516             longest
517         );
518         if self.use_long || next_line_help {
519             // long help prints messages on the next line so it doesn't need to align text
520             debug!("HelpTemplate::align_to_about: printing long help so skip alignment");
521         } else if !arg.is_positional() {
522             let self_len = display_width(&arg.to_string());
523             // Since we're writing spaces from the tab point we first need to know if we
524             // had a long and short, or just short
525             let padding = if arg.get_long().is_some() {
526                 // Only account 4 after the val
527                 TAB_WIDTH
528             } else {
529                 // Only account for ', --' + 4 after the val
530                 TAB_WIDTH + 4
531             };
532             let spcs = longest + padding - self_len;
533             debug!(
534                 "HelpTemplate::align_to_about: positional=false arg_len={}, spaces={}",
535                 self_len, spcs
536             );
537 
538             self.spaces(spcs);
539         } else {
540             let self_len = display_width(&arg.to_string());
541             let padding = TAB_WIDTH;
542             let spcs = longest + padding - self_len;
543             debug!(
544                 "HelpTemplate::align_to_about: positional=true arg_len={}, spaces={}",
545                 self_len, spcs
546             );
547 
548             self.spaces(spcs);
549         }
550     }
551 
552     /// Writes argument's help to the wrapped stream.
help( &mut self, arg: Option<&Arg>, about: &StyledStr, spec_vals: &str, next_line_help: bool, longest: usize, )553     fn help(
554         &mut self,
555         arg: Option<&Arg>,
556         about: &StyledStr,
557         spec_vals: &str,
558         next_line_help: bool,
559         longest: usize,
560     ) {
561         debug!("HelpTemplate::help");
562 
563         // Is help on next line, if so then indent
564         if next_line_help {
565             debug!("HelpTemplate::help: Next Line...{:?}", next_line_help);
566             self.none("\n");
567             self.none(TAB);
568             self.none(NEXT_LINE_INDENT);
569         }
570 
571         let spaces = if next_line_help {
572             TAB.len() + NEXT_LINE_INDENT.len()
573         } else if let Some(true) = arg.map(|a| a.is_positional()) {
574             longest + TAB_WIDTH * 2
575         } else {
576             longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4
577         };
578         let trailing_indent = spaces; // Don't indent any further than the first line is indented
579         let trailing_indent = self.get_spaces(trailing_indent);
580 
581         let mut help = about.clone();
582         replace_newline_var(&mut help);
583         if !spec_vals.is_empty() {
584             if !help.is_empty() {
585                 let sep = if self.use_long && arg.is_some() {
586                     "\n\n"
587                 } else {
588                     " "
589                 };
590                 help.none(sep);
591             }
592             help.none(spec_vals);
593         }
594         let avail_chars = self.term_w.saturating_sub(spaces);
595         debug!(
596             "HelpTemplate::help: help_width={}, spaces={}, avail={}",
597             spaces,
598             help.display_width(),
599             avail_chars
600         );
601         help.wrap(avail_chars);
602         help.indent("", &trailing_indent);
603         let help_is_empty = help.is_empty();
604         self.writer.extend(help.into_iter());
605         if let Some(arg) = arg {
606             const DASH_SPACE: usize = "- ".len();
607             const COLON_SPACE: usize = ": ".len();
608             let possible_vals = arg.get_possible_values();
609             if self.use_long
610                 && !arg.is_hide_possible_values_set()
611                 && possible_vals.iter().any(PossibleValue::should_show_help)
612             {
613                 debug!(
614                     "HelpTemplate::help: Found possible vals...{:?}",
615                     possible_vals
616                 );
617                 if !help_is_empty {
618                     self.none("\n\n");
619                     self.spaces(spaces);
620                 }
621                 self.none("Possible values:");
622                 let longest = possible_vals
623                     .iter()
624                     .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name)))
625                     .max()
626                     .expect("Only called with possible value");
627                 let help_longest = possible_vals
628                     .iter()
629                     .filter_map(|f| f.get_visible_help().map(|h| h.display_width()))
630                     .max()
631                     .expect("Only called with possible value with help");
632                 // should new line
633                 let taken = longest + spaces + DASH_SPACE;
634 
635                 let possible_value_new_line =
636                     self.term_w >= taken && self.term_w < taken + COLON_SPACE + help_longest;
637 
638                 let spaces = spaces + TAB_WIDTH - DASH_SPACE;
639                 let trailing_indent = if possible_value_new_line {
640                     spaces + DASH_SPACE
641                 } else {
642                     spaces + longest + DASH_SPACE + COLON_SPACE
643                 };
644                 let trailing_indent = self.get_spaces(trailing_indent);
645 
646                 for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) {
647                     self.none("\n");
648                     self.spaces(spaces);
649                     self.none("- ");
650                     self.literal(pv.get_name());
651                     if let Some(help) = pv.get_help() {
652                         debug!("HelpTemplate::help: Possible Value help");
653 
654                         if possible_value_new_line {
655                             self.none(":\n");
656                             self.spaces(trailing_indent.len());
657                         } else {
658                             self.none(": ");
659                             // To align help messages
660                             self.spaces(longest - display_width(pv.get_name()));
661                         }
662 
663                         let avail_chars = if self.term_w > trailing_indent.len() {
664                             self.term_w - trailing_indent.len()
665                         } else {
666                             usize::MAX
667                         };
668 
669                         let mut help = help.clone();
670                         replace_newline_var(&mut help);
671                         help.wrap(avail_chars);
672                         help.indent("", &trailing_indent);
673                         self.writer.extend(help.into_iter());
674                     }
675                 }
676             }
677         }
678     }
679 
680     /// Will use next line help on writing args.
will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool681     fn will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool {
682         args.iter()
683             .filter(|arg| should_show_arg(self.use_long, arg))
684             .any(|arg| {
685                 let spec_vals = &self.spec_vals(arg);
686                 self.arg_next_line_help(arg, spec_vals, longest)
687             })
688     }
689 
arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool690     fn arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool {
691         if self.next_line_help || arg.is_next_line_help_set() || self.use_long {
692             // setting_next_line
693             true
694         } else {
695             // force_next_line
696             let h = arg.get_help().unwrap_or_default();
697             let h_w = h.display_width() + display_width(spec_vals);
698             let taken = if arg.is_positional() {
699                 longest + TAB_WIDTH * 2
700             } else {
701                 longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4
702             };
703             self.term_w >= taken
704                 && (taken as f32 / self.term_w as f32) > 0.40
705                 && h_w > (self.term_w - taken)
706         }
707     }
708 
spec_vals(&self, a: &Arg) -> String709     fn spec_vals(&self, a: &Arg) -> String {
710         debug!("HelpTemplate::spec_vals: a={}", a);
711         let mut spec_vals = Vec::new();
712         #[cfg(feature = "env")]
713         if let Some(ref env) = a.env {
714             if !a.is_hide_env_set() {
715                 debug!(
716                     "HelpTemplate::spec_vals: Found environment variable...[{:?}:{:?}]",
717                     env.0, env.1
718                 );
719                 let env_val = if !a.is_hide_env_values_set() {
720                     format!(
721                         "={}",
722                         env.1
723                             .as_ref()
724                             .map(|s| s.to_string_lossy())
725                             .unwrap_or_default()
726                     )
727                 } else {
728                     Default::default()
729                 };
730                 let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
731                 spec_vals.push(env_info);
732             }
733         }
734         if a.is_takes_value_set() && !a.is_hide_default_value_set() && !a.default_vals.is_empty() {
735             debug!(
736                 "HelpTemplate::spec_vals: Found default value...[{:?}]",
737                 a.default_vals
738             );
739 
740             let pvs = a
741                 .default_vals
742                 .iter()
743                 .map(|pvs| pvs.to_string_lossy())
744                 .map(|pvs| {
745                     if pvs.contains(char::is_whitespace) {
746                         Cow::from(format!("{pvs:?}"))
747                     } else {
748                         pvs
749                     }
750                 })
751                 .collect::<Vec<_>>()
752                 .join(" ");
753 
754             spec_vals.push(format!("[default: {pvs}]"));
755         }
756 
757         let als = a
758             .aliases
759             .iter()
760             .filter(|&als| als.1) // visible
761             .map(|als| als.0.as_str()) // name
762             .collect::<Vec<_>>()
763             .join(", ");
764         if !als.is_empty() {
765             debug!("HelpTemplate::spec_vals: Found aliases...{:?}", a.aliases);
766             spec_vals.push(format!("[aliases: {als}]"));
767         }
768 
769         let als = a
770             .short_aliases
771             .iter()
772             .filter(|&als| als.1) // visible
773             .map(|&als| als.0.to_string()) // name
774             .collect::<Vec<_>>()
775             .join(", ");
776         if !als.is_empty() {
777             debug!(
778                 "HelpTemplate::spec_vals: Found short aliases...{:?}",
779                 a.short_aliases
780             );
781             spec_vals.push(format!("[short aliases: {als}]"));
782         }
783 
784         let possible_vals = a.get_possible_values();
785         if !(a.is_hide_possible_values_set()
786             || possible_vals.is_empty()
787             || self.use_long && possible_vals.iter().any(PossibleValue::should_show_help))
788         {
789             debug!(
790                 "HelpTemplate::spec_vals: Found possible vals...{:?}",
791                 possible_vals
792             );
793 
794             let pvs = possible_vals
795                 .iter()
796                 .filter_map(PossibleValue::get_visible_quoted_name)
797                 .collect::<Vec<_>>()
798                 .join(", ");
799 
800             spec_vals.push(format!("[possible values: {pvs}]"));
801         }
802         let connector = if self.use_long { "\n" } else { " " };
803         spec_vals.join(connector)
804     }
805 
header<T: Into<String>>(&mut self, msg: T)806     fn header<T: Into<String>>(&mut self, msg: T) {
807         self.writer.header(msg);
808     }
809 
literal<T: Into<String>>(&mut self, msg: T)810     fn literal<T: Into<String>>(&mut self, msg: T) {
811         self.writer.literal(msg);
812     }
813 
none<T: Into<String>>(&mut self, msg: T)814     fn none<T: Into<String>>(&mut self, msg: T) {
815         self.writer.none(msg);
816     }
817 
get_spaces(&self, n: usize) -> String818     fn get_spaces(&self, n: usize) -> String {
819         " ".repeat(n)
820     }
821 
spaces(&mut self, n: usize)822     fn spaces(&mut self, n: usize) {
823         self.none(self.get_spaces(n));
824     }
825 }
826 
827 /// Subcommand handling
828 impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
829     /// Writes help for subcommands of a Parser Object to the wrapped stream.
write_subcommands(&mut self, cmd: &Command)830     fn write_subcommands(&mut self, cmd: &Command) {
831         debug!("HelpTemplate::write_subcommands");
832         // The shortest an arg can legally be is 2 (i.e. '-x')
833         let mut longest = 2;
834         let mut ord_v = Vec::new();
835         for subcommand in cmd
836             .get_subcommands()
837             .filter(|subcommand| should_show_subcommand(subcommand))
838         {
839             let mut styled = StyledStr::new();
840             styled.literal(subcommand.get_name());
841             if let Some(short) = subcommand.get_short_flag() {
842                 styled.none(", ");
843                 styled.literal(format!("-{short}"));
844             }
845             if let Some(long) = subcommand.get_long_flag() {
846                 styled.none(", ");
847                 styled.literal(format!("--{long}"));
848             }
849             longest = longest.max(styled.display_width());
850             ord_v.push((subcommand.get_display_order(), styled, subcommand));
851         }
852         ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
853 
854         debug!("HelpTemplate::write_subcommands longest = {}", longest);
855 
856         let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
857 
858         let mut first = true;
859         for (_, sc_str, sc) in ord_v {
860             if first {
861                 first = false;
862             } else {
863                 self.none("\n");
864             }
865             self.write_subcommand(sc_str, sc, next_line_help, longest);
866         }
867     }
868 
869     /// Will use next line help on writing subcommands.
will_subcommands_wrap<'a>( &self, subcommands: impl IntoIterator<Item = &'a Command>, longest: usize, ) -> bool870     fn will_subcommands_wrap<'a>(
871         &self,
872         subcommands: impl IntoIterator<Item = &'a Command>,
873         longest: usize,
874     ) -> bool {
875         subcommands
876             .into_iter()
877             .filter(|&subcommand| should_show_subcommand(subcommand))
878             .any(|subcommand| {
879                 let spec_vals = &self.sc_spec_vals(subcommand);
880                 self.subcommand_next_line_help(subcommand, spec_vals, longest)
881             })
882     }
883 
write_subcommand( &mut self, sc_str: StyledStr, cmd: &Command, next_line_help: bool, longest: usize, )884     fn write_subcommand(
885         &mut self,
886         sc_str: StyledStr,
887         cmd: &Command,
888         next_line_help: bool,
889         longest: usize,
890     ) {
891         debug!("HelpTemplate::write_subcommand");
892 
893         let spec_vals = &self.sc_spec_vals(cmd);
894 
895         let about = cmd
896             .get_about()
897             .or_else(|| cmd.get_long_about())
898             .unwrap_or_default();
899 
900         self.subcmd(sc_str, next_line_help, longest);
901         self.help(None, about, spec_vals, next_line_help, longest)
902     }
903 
sc_spec_vals(&self, a: &Command) -> String904     fn sc_spec_vals(&self, a: &Command) -> String {
905         debug!("HelpTemplate::sc_spec_vals: a={}", a.get_name());
906         let mut spec_vals = vec![];
907 
908         let mut short_als = a
909             .get_visible_short_flag_aliases()
910             .map(|a| format!("-{a}"))
911             .collect::<Vec<_>>();
912         let als = a.get_visible_aliases().map(|s| s.to_string());
913         short_als.extend(als);
914         let all_als = short_als.join(", ");
915         if !all_als.is_empty() {
916             debug!(
917                 "HelpTemplate::spec_vals: Found aliases...{:?}",
918                 a.get_all_aliases().collect::<Vec<_>>()
919             );
920             debug!(
921                 "HelpTemplate::spec_vals: Found short flag aliases...{:?}",
922                 a.get_all_short_flag_aliases().collect::<Vec<_>>()
923             );
924             spec_vals.push(format!("[aliases: {all_als}]"));
925         }
926 
927         spec_vals.join(" ")
928     }
929 
subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool930     fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool {
931         if self.next_line_help | self.use_long {
932             // setting_next_line
933             true
934         } else {
935             // force_next_line
936             let h = cmd.get_about().unwrap_or_default();
937             let h_w = h.display_width() + display_width(spec_vals);
938             let taken = longest + TAB_WIDTH * 2;
939             self.term_w >= taken
940                 && (taken as f32 / self.term_w as f32) > 0.40
941                 && h_w > (self.term_w - taken)
942         }
943     }
944 
945     /// Writes subcommand to the wrapped stream.
subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize)946     fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
947         let width = sc_str.display_width();
948 
949         self.none(TAB);
950         self.writer.extend(sc_str.into_iter());
951         if !next_line_help {
952             self.spaces(longest + TAB_WIDTH - width);
953         }
954     }
955 }
956 
957 const NEXT_LINE_INDENT: &str = "        ";
958 
959 type ArgSortKey = fn(arg: &Arg) -> (usize, String);
960 
positional_sort_key(arg: &Arg) -> (usize, String)961 fn positional_sort_key(arg: &Arg) -> (usize, String) {
962     (arg.get_index().unwrap_or(0), String::new())
963 }
964 
option_sort_key(arg: &Arg) -> (usize, String)965 fn option_sort_key(arg: &Arg) -> (usize, String) {
966     // Formatting key like this to ensure that:
967     // 1. Argument has long flags are printed just after short flags.
968     // 2. For two args both have short flags like `-c` and `-C`, the
969     //    `-C` arg is printed just after the `-c` arg
970     // 3. For args without short or long flag, print them at last(sorted
971     //    by arg name).
972     // Example order: -a, -b, -B, -s, --select-file, --select-folder, -x
973 
974     let key = if let Some(x) = arg.get_short() {
975         let mut s = x.to_ascii_lowercase().to_string();
976         s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
977         s
978     } else if let Some(x) = arg.get_long() {
979         x.to_string()
980     } else {
981         let mut s = '{'.to_string();
982         s.push_str(arg.get_id().as_str());
983         s
984     };
985     (arg.get_display_order(), key)
986 }
987 
dimensions() -> (Option<usize>, Option<usize>)988 pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) {
989     #[cfg(not(feature = "wrap_help"))]
990     return (None, None);
991 
992     #[cfg(feature = "wrap_help")]
993     terminal_size::terminal_size()
994         .map(|(w, h)| (Some(w.0.into()), Some(h.0.into())))
995         .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES")))
996 }
997 
998 #[cfg(feature = "wrap_help")]
parse_env(var: &str) -> Option<usize>999 fn parse_env(var: &str) -> Option<usize> {
1000     some!(some!(std::env::var_os(var)).to_str())
1001         .parse::<usize>()
1002         .ok()
1003 }
1004 
should_show_arg(use_long: bool, arg: &Arg) -> bool1005 fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
1006     debug!(
1007         "should_show_arg: use_long={:?}, arg={}",
1008         use_long,
1009         arg.get_id()
1010     );
1011     if arg.is_hide_set() {
1012         return false;
1013     }
1014     (!arg.is_hide_long_help_set() && use_long)
1015         || (!arg.is_hide_short_help_set() && !use_long)
1016         || arg.is_next_line_help_set()
1017 }
1018 
should_show_subcommand(subcommand: &Command) -> bool1019 fn should_show_subcommand(subcommand: &Command) -> bool {
1020     !subcommand.is_hide_set()
1021 }
1022 
replace_newline_var(styled: &mut StyledStr)1023 fn replace_newline_var(styled: &mut StyledStr) {
1024     for (_, content) in styled.iter_mut() {
1025         *content = content.replace("{n}", "\n");
1026     }
1027 }
1028 
longest_filter(arg: &Arg) -> bool1029 fn longest_filter(arg: &Arg) -> bool {
1030     arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none()
1031 }
1032 
1033 #[cfg(test)]
1034 mod test {
1035     #[test]
1036     #[cfg(feature = "wrap_help")]
wrap_help_last_word()1037     fn wrap_help_last_word() {
1038         use super::*;
1039 
1040         let help = String::from("foo bar baz");
1041         assert_eq!(wrap(&help, 5), "foo\nbar\nbaz");
1042     }
1043 
1044     #[test]
1045     #[cfg(feature = "unicode")]
display_width_handles_non_ascii()1046     fn display_width_handles_non_ascii() {
1047         use super::*;
1048 
1049         // Popular Danish tongue-twister, the name of a fruit dessert.
1050         let text = "rødgrød med fløde";
1051         assert_eq!(display_width(text), 17);
1052         // Note that the string width is smaller than the string
1053         // length. This is due to the precomposed non-ASCII letters:
1054         assert_eq!(text.len(), 20);
1055     }
1056 
1057     #[test]
1058     #[cfg(feature = "unicode")]
display_width_handles_emojis()1059     fn display_width_handles_emojis() {
1060         use super::*;
1061 
1062         let text = "��";
1063         // There is a single `char`...
1064         assert_eq!(text.chars().count(), 1);
1065         // but it is double-width:
1066         assert_eq!(display_width(text), 2);
1067         // This is much less than the byte length:
1068         assert_eq!(text.len(), 4);
1069     }
1070 }
1071