1#![allow(clippy::write_with_newline)]
5
6use std::borrow::Cow;
8use std::cmp;
9use std::collections::BTreeMap;
10
11use 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
24pub(crate) struct AutoHelp<'cmd, 'writer> {
26 template: HelpTemplate<'cmd, 'writer>,
27}
28
29impl<'cmd, 'writer> AutoHelp<'cmd, 'writer> {
31 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
77pub(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
90impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
92 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 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 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
253impl HelpTemplate<'_, '_> {
255 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 #[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 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
367impl HelpTemplate<'_, '_> {
369 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!(self.writer, "{header}{help_heading}:{header:#}\n",);
411
412 self.write_subcommands(self.cmd);
413 }
414
415 if !pos.is_empty() {
416 if !first {
417 self.writer.push_str("\n\n");
418 }
419 first = false;
420 let help_heading = "Arguments";
422 let _ = write!(self.writer, "{header}{help_heading}:{header:#}\n",);
423 self.write_args(&pos, "Arguments", positional_sort_key);
424 }
425
426 if !non_pos.is_empty() {
427 if !first {
428 self.writer.push_str("\n\n");
429 }
430 first = false;
431 let help_heading = "Options";
432 let _ = write!(self.writer, "{header}{help_heading}:{header:#}\n",);
433 self.write_args(&non_pos, "Options", option_sort_key);
434 }
435 if !custom_headings.is_empty() {
436 for heading in custom_headings {
437 let args = self
438 .cmd
439 .get_arguments()
440 .filter(|a| {
441 if let Some(help_heading) = a.get_help_heading() {
442 return help_heading == heading;
443 }
444 false
445 })
446 .filter(|arg| should_show_arg(self.use_long, arg))
447 .collect::<Vec<_>>();
448
449 if !args.is_empty() {
450 if !first {
451 self.writer.push_str("\n\n");
452 }
453 first = false;
454 let _ = write!(self.writer, "{header}{heading}:{header:#}\n",);
455 self.write_args(&args, heading, option_sort_key);
456 }
457 }
458 }
459 if subcmds && flatten {
460 let mut cmd = self.cmd.clone();
461 cmd.build();
462 self.write_flat_subcommands(&cmd, &mut first);
463 }
464 }
465
466 fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) {
468 debug!("HelpTemplate::write_args {_category}");
469 let mut longest = 2;
471 let mut ord_v = BTreeMap::new();
472
473 for &arg in args.iter().filter(|arg| {
475 should_show_arg(self.use_long, arg)
479 }) {
480 if longest_filter(arg) {
481 longest = longest.max(display_width(&arg.to_string()));
482 debug!(
483 "HelpTemplate::write_args: arg={:?} longest={}",
484 arg.get_id(),
485 longest
486 );
487 }
488
489 let key = (sort_key)(arg);
490 ord_v.insert(key, arg);
491 }
492
493 let next_line_help = self.will_args_wrap(args, longest);
494
495 for (i, (_, arg)) in ord_v.iter().enumerate() {
496 if i != 0 {
497 self.writer.push_str("\n");
498 if next_line_help && self.use_long {
499 self.writer.push_str("\n");
500 }
501 }
502 self.write_arg(arg, next_line_help, longest);
503 }
504 }
505
506 fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
508 let spec_vals = &self.spec_vals(arg);
509
510 self.writer.push_str(TAB);
511 self.short(arg);
512 self.long(arg);
513 self.writer
514 .push_styled(&arg.stylize_arg_suffix(self.styles, None));
515 self.align_to_about(arg, next_line_help, longest);
516
517 let about = if self.use_long {
518 arg.get_long_help()
519 .or_else(|| arg.get_help())
520 .unwrap_or_default()
521 } else {
522 arg.get_help()
523 .or_else(|| arg.get_long_help())
524 .unwrap_or_default()
525 };
526
527 self.help(Some(arg), about, spec_vals, next_line_help, longest);
528 }
529
530 fn short(&mut self, arg: &Arg) {
532 debug!("HelpTemplate::short");
533 use std::fmt::Write as _;
534 let literal = &self.styles.get_literal();
535
536 if let Some(s) = arg.get_short() {
537 let _ = write!(self.writer, "{literal}-{s}{literal:#}",);
538 } else if arg.get_long().is_some() {
539 self.writer.push_str(" ");
540 }
541 }
542
543 fn long(&mut self, arg: &Arg) {
545 debug!("HelpTemplate::long");
546 use std::fmt::Write as _;
547 let literal = &self.styles.get_literal();
548
549 if let Some(long) = arg.get_long() {
550 if arg.get_short().is_some() {
551 self.writer.push_str(", ");
552 }
553 let _ = write!(self.writer, "{literal}--{long}{literal:#}",);
554 }
555 }
556
557 fn align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
559 debug!(
560 "HelpTemplate::align_to_about: arg={}, next_line_help={}, longest={}",
561 arg.get_id(),
562 next_line_help,
563 longest
564 );
565 let padding = if self.use_long || next_line_help {
566 debug!("HelpTemplate::align_to_about: printing long help so skip alignment");
568 0
569 } else if !arg.is_positional() {
570 let self_len = display_width(&arg.to_string());
571 let padding = if arg.get_long().is_some() {
574 TAB_WIDTH
576 } else {
577 TAB_WIDTH + 4
579 };
580 let spcs = longest + padding - self_len;
581 debug!(
582 "HelpTemplate::align_to_about: positional=false arg_len={self_len}, spaces={spcs}"
583 );
584
585 spcs
586 } else {
587 let self_len = display_width(&arg.to_string());
588 let padding = TAB_WIDTH;
589 let spcs = longest + padding - self_len;
590 debug!(
591 "HelpTemplate::align_to_about: positional=true arg_len={self_len}, spaces={spcs}",
592 );
593
594 spcs
595 };
596
597 self.write_padding(padding);
598 }
599
600 fn help(
602 &mut self,
603 arg: Option<&Arg>,
604 about: &StyledStr,
605 spec_vals: &str,
606 next_line_help: bool,
607 longest: usize,
608 ) {
609 debug!("HelpTemplate::help");
610 use std::fmt::Write as _;
611 let literal = &self.styles.get_literal();
612
613 if next_line_help {
615 debug!("HelpTemplate::help: Next Line...{next_line_help:?}");
616 self.writer.push_str("\n");
617 self.writer.push_str(TAB);
618 self.writer.push_str(NEXT_LINE_INDENT);
619 }
620
621 let spaces = if next_line_help {
622 TAB.len() + NEXT_LINE_INDENT.len()
623 } else if arg.map(|a| a.is_positional()).unwrap_or(true) {
624 longest + TAB_WIDTH * 2
625 } else {
626 longest + TAB_WIDTH * 2 + 4 };
628 let trailing_indent = spaces; let trailing_indent = self.get_spaces(trailing_indent);
630
631 let mut help = about.clone();
632 help.replace_newline_var();
633 if !spec_vals.is_empty() {
634 if !help.is_empty() {
635 let sep = if self.use_long && arg.is_some() {
636 "\n\n"
637 } else {
638 " "
639 };
640 help.push_str(sep);
641 }
642 help.push_str(spec_vals);
643 }
644 let avail_chars = self.term_w.saturating_sub(spaces);
645 debug!(
646 "HelpTemplate::help: help_width={}, spaces={}, avail={}",
647 spaces,
648 help.display_width(),
649 avail_chars
650 );
651 help.wrap(avail_chars);
652 help.indent("", &trailing_indent);
653 let help_is_empty = help.is_empty();
654 self.writer.push_styled(&help);
655 if let Some(arg) = arg {
656 if !arg.is_hide_possible_values_set() && self.use_long_pv(arg) {
657 const DASH_SPACE: usize = "- ".len();
658 let possible_vals = arg.get_possible_values();
659 if !possible_vals.is_empty() {
660 debug!("HelpTemplate::help: Found possible vals...{possible_vals:?}");
661 let longest = possible_vals
662 .iter()
663 .filter(|f| !f.is_hide_set())
664 .map(|f| display_width(f.get_name()))
665 .max()
666 .expect("Only called with possible value");
667
668 let spaces = spaces + TAB_WIDTH - DASH_SPACE;
669 let trailing_indent = spaces + DASH_SPACE;
670 let trailing_indent = self.get_spaces(trailing_indent);
671
672 if !help_is_empty {
673 let _ = write!(self.writer, "\n\n{:spaces$}", "");
674 }
675 self.writer.push_str("Possible values:");
676 for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) {
677 let name = pv.get_name();
678
679 let mut descr = StyledStr::new();
680 let _ = write!(&mut descr, "{literal}{name}{literal:#}",);
681 if let Some(help) = pv.get_help() {
682 debug!("HelpTemplate::help: Possible Value help");
683 let padding = longest - display_width(name);
685 let _ = write!(&mut descr, ": {:padding$}", "");
686 descr.push_styled(help);
687 }
688
689 let avail_chars = if self.term_w > trailing_indent.len() {
690 self.term_w - trailing_indent.len()
691 } else {
692 usize::MAX
693 };
694 descr.replace_newline_var();
695 descr.wrap(avail_chars);
696 descr.indent("", &trailing_indent);
697
698 let _ = write!(self.writer, "\n{:spaces$}- ", "",);
699 self.writer.push_styled(&descr);
700 }
701 }
702 }
703 }
704 }
705
706 fn will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool {
708 args.iter()
709 .filter(|arg| should_show_arg(self.use_long, arg))
710 .any(|arg| {
711 let spec_vals = &self.spec_vals(arg);
712 self.arg_next_line_help(arg, spec_vals, longest)
713 })
714 }
715
716 fn arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool {
717 if self.next_line_help || arg.is_next_line_help_set() || self.use_long {
718 true
720 } else {
721 let h = arg
723 .get_help()
724 .or_else(|| arg.get_long_help())
725 .unwrap_or_default();
726 let h_w = h.display_width() + display_width(spec_vals);
727 let taken = if arg.is_positional() {
728 longest + TAB_WIDTH * 2
729 } else {
730 longest + TAB_WIDTH * 2 + 4 };
732 self.term_w >= taken
733 && (taken as f32 / self.term_w as f32) > 0.40
734 && h_w > (self.term_w - taken)
735 }
736 }
737
738 fn spec_vals(&self, a: &Arg) -> String {
739 debug!("HelpTemplate::spec_vals: a={a}");
740 let mut spec_vals = Vec::new();
741 #[cfg(feature = "env")]
742 if let Some(ref env) = a.env {
743 if !a.is_hide_env_set() {
744 debug!(
745 "HelpTemplate::spec_vals: Found environment variable...[{:?}:{:?}]",
746 env.0, env.1
747 );
748 let env_val = if !a.is_hide_env_values_set() {
749 format!(
750 "={}",
751 env.1
752 .as_ref()
753 .map(|s| s.to_string_lossy())
754 .unwrap_or_default()
755 )
756 } else {
757 Default::default()
758 };
759 let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
760 spec_vals.push(env_info);
761 }
762 }
763 if a.is_takes_value_set() && !a.is_hide_default_value_set() && !a.default_vals.is_empty() {
764 debug!(
765 "HelpTemplate::spec_vals: Found default value...[{:?}]",
766 a.default_vals
767 );
768
769 let pvs = a
770 .default_vals
771 .iter()
772 .map(|pvs| pvs.to_string_lossy())
773 .map(|pvs| {
774 if pvs.contains(char::is_whitespace) {
775 Cow::from(format!("{pvs:?}"))
776 } else {
777 pvs
778 }
779 })
780 .collect::<Vec<_>>()
781 .join(" ");
782
783 spec_vals.push(format!("[default: {pvs}]"));
784 }
785
786 let als = a
787 .aliases
788 .iter()
789 .filter(|&als| als.1) .map(|als| als.0.as_str()) .collect::<Vec<_>>()
792 .join(", ");
793 if !als.is_empty() {
794 debug!("HelpTemplate::spec_vals: Found aliases...{:?}", a.aliases);
795 spec_vals.push(format!("[aliases: {als}]"));
796 }
797
798 let als = a
799 .short_aliases
800 .iter()
801 .filter(|&als| als.1) .map(|&als| als.0.to_string()) .collect::<Vec<_>>()
804 .join(", ");
805 if !als.is_empty() {
806 debug!(
807 "HelpTemplate::spec_vals: Found short aliases...{:?}",
808 a.short_aliases
809 );
810 spec_vals.push(format!("[short aliases: {als}]"));
811 }
812
813 if !a.is_hide_possible_values_set() && !self.use_long_pv(a) {
814 let possible_vals = a.get_possible_values();
815 if !possible_vals.is_empty() {
816 debug!("HelpTemplate::spec_vals: Found possible vals...{possible_vals:?}");
817
818 let pvs = possible_vals
819 .iter()
820 .filter_map(PossibleValue::get_visible_quoted_name)
821 .collect::<Vec<_>>()
822 .join(", ");
823
824 spec_vals.push(format!("[possible values: {pvs}]"));
825 }
826 }
827 let connector = if self.use_long { "\n" } else { " " };
828 spec_vals.join(connector)
829 }
830
831 fn get_spaces(&self, n: usize) -> String {
832 " ".repeat(n)
833 }
834
835 fn write_padding(&mut self, amount: usize) {
836 use std::fmt::Write as _;
837 let _ = write!(self.writer, "{:amount$}", "");
838 }
839
840 fn use_long_pv(&self, arg: &Arg) -> bool {
841 self.use_long
842 && arg
843 .get_possible_values()
844 .iter()
845 .any(PossibleValue::should_show_help)
846 }
847}
848
849impl HelpTemplate<'_, '_> {
851 fn write_flat_subcommands(&mut self, cmd: &Command, first: &mut bool) {
853 debug!(
854 "HelpTemplate::write_flat_subcommands, cmd={}, first={}",
855 cmd.get_name(),
856 *first
857 );
858 use std::fmt::Write as _;
859 let header = &self.styles.get_header();
860
861 let mut ord_v = BTreeMap::new();
862 for subcommand in cmd
863 .get_subcommands()
864 .filter(|subcommand| should_show_subcommand(subcommand))
865 {
866 ord_v.insert(
867 (subcommand.get_display_order(), subcommand.get_name()),
868 subcommand,
869 );
870 }
871 for (_, subcommand) in ord_v {
872 if !*first {
873 self.writer.push_str("\n\n");
874 }
875 *first = false;
876
877 let heading = subcommand.get_usage_name_fallback();
878 let about = subcommand
879 .get_about()
880 .or_else(|| subcommand.get_long_about())
881 .unwrap_or_default();
882
883 let _ = write!(self.writer, "{header}{heading}:{header:#}\n",);
884 if !about.is_empty() {
885 let _ = write!(self.writer, "{about}\n",);
886 }
887
888 let mut sub_help = HelpTemplate {
889 writer: self.writer,
890 cmd: subcommand,
891 styles: self.styles,
892 usage: self.usage,
893 next_line_help: self.next_line_help,
894 term_w: self.term_w,
895 use_long: self.use_long,
896 };
897 let args = subcommand
898 .get_arguments()
899 .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set())
900 .collect::<Vec<_>>();
901 sub_help.write_args(&args, heading, option_sort_key);
902 if subcommand.is_flatten_help_set() {
903 sub_help.write_flat_subcommands(subcommand, first);
904 }
905 }
906 }
907
908 fn write_subcommands(&mut self, cmd: &Command) {
910 debug!("HelpTemplate::write_subcommands");
911 use std::fmt::Write as _;
912 let literal = &self.styles.get_literal();
913
914 let mut longest = 2;
916 let mut ord_v = BTreeMap::new();
917 for subcommand in cmd
918 .get_subcommands()
919 .filter(|subcommand| should_show_subcommand(subcommand))
920 {
921 let mut styled = StyledStr::new();
922 let name = subcommand.get_name();
923 let _ = write!(styled, "{literal}{name}{literal:#}",);
924 if let Some(short) = subcommand.get_short_flag() {
925 let _ = write!(styled, ", {literal}-{short}{literal:#}",);
926 }
927 if let Some(long) = subcommand.get_long_flag() {
928 let _ = write!(styled, ", {literal}--{long}{literal:#}",);
929 }
930 longest = longest.max(styled.display_width());
931 ord_v.insert((subcommand.get_display_order(), styled), subcommand);
932 }
933
934 debug!("HelpTemplate::write_subcommands longest = {longest}");
935
936 let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
937
938 for (i, (sc_str, sc)) in ord_v.into_iter().enumerate() {
939 if 0 < i {
940 self.writer.push_str("\n");
941 }
942 self.write_subcommand(sc_str.1, sc, next_line_help, longest);
943 }
944 }
945
946 fn will_subcommands_wrap<'a>(
948 &self,
949 subcommands: impl IntoIterator<Item = &'a Command>,
950 longest: usize,
951 ) -> bool {
952 subcommands
953 .into_iter()
954 .filter(|&subcommand| should_show_subcommand(subcommand))
955 .any(|subcommand| {
956 let spec_vals = &self.sc_spec_vals(subcommand);
957 self.subcommand_next_line_help(subcommand, spec_vals, longest)
958 })
959 }
960
961 fn write_subcommand(
962 &mut self,
963 sc_str: StyledStr,
964 cmd: &Command,
965 next_line_help: bool,
966 longest: usize,
967 ) {
968 debug!("HelpTemplate::write_subcommand");
969
970 let spec_vals = &self.sc_spec_vals(cmd);
971
972 let about = cmd
973 .get_about()
974 .or_else(|| cmd.get_long_about())
975 .unwrap_or_default();
976
977 self.subcmd(sc_str, next_line_help, longest);
978 self.help(None, about, spec_vals, next_line_help, longest);
979 }
980
981 fn sc_spec_vals(&self, a: &Command) -> String {
982 debug!("HelpTemplate::sc_spec_vals: a={}", a.get_name());
983 let mut spec_vals = vec![];
984
985 let mut short_als = a
986 .get_visible_short_flag_aliases()
987 .map(|a| format!("-{a}"))
988 .collect::<Vec<_>>();
989 let als = a.get_visible_aliases().map(|s| s.to_string());
990 short_als.extend(als);
991 let all_als = short_als.join(", ");
992 if !all_als.is_empty() {
993 debug!(
994 "HelpTemplate::spec_vals: Found aliases...{:?}",
995 a.get_all_aliases().collect::<Vec<_>>()
996 );
997 debug!(
998 "HelpTemplate::spec_vals: Found short flag aliases...{:?}",
999 a.get_all_short_flag_aliases().collect::<Vec<_>>()
1000 );
1001 spec_vals.push(format!("[aliases: {all_als}]"));
1002 }
1003
1004 spec_vals.join(" ")
1005 }
1006
1007 fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool {
1008 if self.next_line_help {
1010 true
1012 } else {
1013 let h = cmd
1015 .get_about()
1016 .or_else(|| cmd.get_long_about())
1017 .unwrap_or_default();
1018 let h_w = h.display_width() + display_width(spec_vals);
1019 let taken = longest + TAB_WIDTH * 2;
1020 self.term_w >= taken
1021 && (taken as f32 / self.term_w as f32) > 0.40
1022 && h_w > (self.term_w - taken)
1023 }
1024 }
1025
1026 fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
1028 self.writer.push_str(TAB);
1029 self.writer.push_styled(&sc_str);
1030 if !next_line_help {
1031 let width = sc_str.display_width();
1032 let padding = longest + TAB_WIDTH - width;
1033 self.write_padding(padding);
1034 }
1035 }
1036}
1037
1038const NEXT_LINE_INDENT: &str = " ";
1039
1040type ArgSortKey = fn(arg: &Arg) -> (usize, String);
1041
1042fn positional_sort_key(arg: &Arg) -> (usize, String) {
1043 (arg.get_index().unwrap_or(0), String::new())
1044}
1045
1046fn option_sort_key(arg: &Arg) -> (usize, String) {
1047 let key = if let Some(x) = arg.get_short() {
1056 let mut s = x.to_ascii_lowercase().to_string();
1057 s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
1058 s
1059 } else if let Some(x) = arg.get_long() {
1060 x.to_string()
1061 } else {
1062 let mut s = '{'.to_string();
1063 s.push_str(arg.get_id().as_str());
1064 s
1065 };
1066 (arg.get_display_order(), key)
1067}
1068
1069pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) {
1070 #[cfg(not(feature = "wrap_help"))]
1071 return (None, None);
1072
1073 #[cfg(feature = "wrap_help")]
1074 terminal_size::terminal_size()
1075 .map(|(w, h)| (Some(w.0.into()), Some(h.0.into())))
1076 .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES")))
1077}
1078
1079#[cfg(feature = "wrap_help")]
1080fn parse_env(var: &str) -> Option<usize> {
1081 some!(some!(std::env::var_os(var)).to_str())
1082 .parse::<usize>()
1083 .ok()
1084}
1085
1086fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
1087 debug!(
1088 "should_show_arg: use_long={:?}, arg={}",
1089 use_long,
1090 arg.get_id()
1091 );
1092 if arg.is_hide_set() {
1093 return false;
1094 }
1095 (!arg.is_hide_long_help_set() && use_long)
1096 || (!arg.is_hide_short_help_set() && !use_long)
1097 || arg.is_next_line_help_set()
1098}
1099
1100fn should_show_subcommand(subcommand: &Command) -> bool {
1101 !subcommand.is_hide_set()
1102}
1103
1104fn longest_filter(arg: &Arg) -> bool {
1105 arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none()
1106}
1107
1108#[cfg(test)]
1109mod test {
1110 #[test]
1111 #[cfg(feature = "wrap_help")]
1112 fn wrap_help_last_word() {
1113 use super::*;
1114
1115 let help = String::from("foo bar baz");
1116 assert_eq!(wrap(&help, 5), "foo\nbar\nbaz");
1117 }
1118
1119 #[test]
1120 #[cfg(feature = "unicode")]
1121 fn display_width_handles_non_ascii() {
1122 use super::*;
1123
1124 let text = "rødgrød med fløde";
1126 assert_eq!(display_width(text), 17);
1127 assert_eq!(text.len(), 20);
1130 }
1131
1132 #[test]
1133 #[cfg(feature = "unicode")]
1134 fn display_width_handles_emojis() {
1135 use super::*;
1136
1137 let text = "😂";
1138 assert_eq!(text.chars().count(), 1);
1140 assert_eq!(display_width(text), 2);
1142 assert_eq!(text.len(), 4);
1144 }
1145}