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