Skip to main content

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