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