clap_builder/output/
help_template.rs

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