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