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