1#![allow(clippy::write_with_newline)]
5
6use std::borrow::Cow;
8use std::cmp;
9
10use crate::builder::PossibleValue;
12use crate::builder::Str;
13use crate::builder::StyledStr;
14use crate::builder::Styles;
15use crate::builder::{Arg, Command};
16use crate::output::display_width;
17use crate::output::wrap;
18use crate::output::Usage;
19use crate::output::TAB;
20use crate::output::TAB_WIDTH;
21use crate::util::FlatSet;
22
23pub(crate) struct AutoHelp<'cmd, 'writer> {
25 template: HelpTemplate<'cmd, 'writer>,
26}
27
28impl<'cmd, 'writer> AutoHelp<'cmd, 'writer> {
30 pub(crate) fn new(
32 writer: &'writer mut StyledStr,
33 cmd: &'cmd Command,
34 usage: &'cmd Usage<'cmd>,
35 use_long: bool,
36 ) -> Self {
37 Self {
38 template: HelpTemplate::new(writer, cmd, usage, use_long),
39 }
40 }
41
42 pub(crate) fn write_help(&mut self) {
43 let pos = self
44 .template
45 .cmd
46 .get_positionals()
47 .any(|arg| should_show_arg(self.template.use_long, arg));
48 let non_pos = self
49 .template
50 .cmd
51 .get_non_positionals()
52 .any(|arg| should_show_arg(self.template.use_long, arg));
53 let subcmds = self.template.cmd.has_visible_subcommands();
54
55 let template = if non_pos || pos || subcmds {
56 DEFAULT_TEMPLATE
57 } else {
58 DEFAULT_NO_ARGS_TEMPLATE
59 };
60 self.template.write_templated_help(template);
61 }
62}
63
64const DEFAULT_TEMPLATE: &str = "\
65{before-help}{about-with-newline}
66{usage-heading} {usage}
67
68{all-args}{after-help}\
69 ";
70
71const DEFAULT_NO_ARGS_TEMPLATE: &str = "\
72{before-help}{about-with-newline}
73{usage-heading} {usage}{after-help}\
74 ";
75
76pub(crate) struct HelpTemplate<'cmd, 'writer> {
80 writer: &'writer mut StyledStr,
81 cmd: &'cmd Command,
82 styles: &'cmd Styles,
83 usage: &'cmd Usage<'cmd>,
84 next_line_help: bool,
85 term_w: usize,
86 use_long: bool,
87}
88
89impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
91 pub(crate) fn new(
93 writer: &'writer mut StyledStr,
94 cmd: &'cmd Command,
95 usage: &'cmd Usage<'cmd>,
96 use_long: bool,
97 ) -> Self {
98 debug!(
99 "HelpTemplate::new cmd={}, use_long={}",
100 cmd.get_name(),
101 use_long
102 );
103 let term_w = Self::term_w(cmd);
104 let next_line_help = cmd.is_next_line_help_set();
105
106 HelpTemplate {
107 writer,
108 cmd,
109 styles: cmd.get_styles(),
110 usage,
111 next_line_help,
112 term_w,
113 use_long,
114 }
115 }
116
117 #[cfg(not(feature = "unstable-v5"))]
118 fn term_w(cmd: &'cmd Command) -> usize {
119 match cmd.get_term_width() {
120 Some(0) => usize::MAX,
121 Some(w) => w,
122 None => {
123 let (current_width, _h) = dimensions();
124 let current_width = current_width.unwrap_or(100);
125 let max_width = match cmd.get_max_term_width() {
126 None | Some(0) => usize::MAX,
127 Some(mw) => mw,
128 };
129 cmp::min(current_width, max_width)
130 }
131 }
132 }
133
134 #[cfg(feature = "unstable-v5")]
135 fn term_w(cmd: &'cmd Command) -> usize {
136 let term_w = match cmd.get_term_width() {
137 Some(0) => usize::MAX,
138 Some(w) => w,
139 None => {
140 let (current_width, _h) = dimensions();
141 current_width.unwrap_or(usize::MAX)
142 }
143 };
144
145 let max_term_w = match cmd.get_max_term_width() {
146 Some(0) => usize::MAX,
147 Some(mw) => mw,
148 None => 100,
149 };
150
151 cmp::min(term_w, max_term_w)
152 }
153
154 pub(crate) fn write_templated_help(&mut self, template: &str) {
160 debug!("HelpTemplate::write_templated_help");
161 use std::fmt::Write as _;
162
163 let mut parts = template.split('{');
164 if let Some(first) = parts.next() {
165 self.writer.push_str(first);
166 }
167 for part in parts {
168 if let Some((tag, rest)) = part.split_once('}') {
169 match tag {
170 "name" => {
171 self.write_display_name();
172 }
173 #[cfg(not(feature = "unstable-v5"))]
174 "bin" => {
175 self.write_bin_name();
176 }
177 "version" => {
178 self.write_version();
179 }
180 "author" => {
181 self.write_author(false, false);
182 }
183 "author-with-newline" => {
184 self.write_author(false, true);
185 }
186 "author-section" => {
187 self.write_author(true, true);
188 }
189 "about" => {
190 self.write_about(false, false);
191 }
192 "about-with-newline" => {
193 self.write_about(false, true);
194 }
195 "about-section" => {
196 self.write_about(true, true);
197 }
198 "usage-heading" => {
199 let _ = write!(
200 self.writer,
201 "{}Usage:{}",
202 self.styles.get_usage().render(),
203 self.styles.get_usage().render_reset()
204 );
205 }
206 "usage" => {
207 self.writer.push_styled(
208 &self.usage.create_usage_no_title(&[]).unwrap_or_default(),
209 );
210 }
211 "all-args" => {
212 self.write_all_args();
213 }
214 "options" => {
215 self.write_args(
218 &self.cmd.get_non_positionals().collect::<Vec<_>>(),
219 "options",
220 option_sort_key,
221 );
222 }
223 "positionals" => {
224 self.write_args(
225 &self.cmd.get_positionals().collect::<Vec<_>>(),
226 "positionals",
227 positional_sort_key,
228 );
229 }
230 "subcommands" => {
231 self.write_subcommands(self.cmd);
232 }
233 "tab" => {
234 self.writer.push_str(TAB);
235 }
236 "after-help" => {
237 self.write_after_help();
238 }
239 "before-help" => {
240 self.write_before_help();
241 }
242 _ => {
243 let _ = write!(self.writer, "{{{tag}}}");
244 }
245 }
246 self.writer.push_str(rest);
247 }
248 }
249 }
250}
251
252impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
254 fn write_display_name(&mut self) {
256 debug!("HelpTemplate::write_display_name");
257
258 let display_name = wrap(
259 &self
260 .cmd
261 .get_display_name()
262 .unwrap_or_else(|| self.cmd.get_name())
263 .replace("{n}", "\n"),
264 self.term_w,
265 );
266 self.writer.push_string(display_name);
267 }
268
269 #[cfg(not(feature = "unstable-v5"))]
271 fn write_bin_name(&mut self) {
272 debug!("HelpTemplate::write_bin_name");
273
274 let bin_name = if let Some(bn) = self.cmd.get_bin_name() {
275 if bn.contains(' ') {
276 bn.replace(' ', "-")
278 } else {
279 wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
280 }
281 } else {
282 wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
283 };
284 self.writer.push_string(bin_name);
285 }
286
287 fn write_version(&mut self) {
288 let version = self
289 .cmd
290 .get_version()
291 .or_else(|| self.cmd.get_long_version());
292 if let Some(output) = version {
293 self.writer.push_string(wrap(output, self.term_w));
294 }
295 }
296
297 fn write_author(&mut self, before_new_line: bool, after_new_line: bool) {
298 if let Some(author) = self.cmd.get_author() {
299 if before_new_line {
300 self.writer.push_str("\n");
301 }
302 self.writer.push_string(wrap(author, self.term_w));
303 if after_new_line {
304 self.writer.push_str("\n");
305 }
306 }
307 }
308
309 fn write_about(&mut self, before_new_line: bool, after_new_line: bool) {
310 let about = if self.use_long {
311 self.cmd.get_long_about().or_else(|| self.cmd.get_about())
312 } else {
313 self.cmd.get_about()
314 };
315 if let Some(output) = about {
316 if before_new_line {
317 self.writer.push_str("\n");
318 }
319 let mut output = output.clone();
320 output.replace_newline_var();
321 output.wrap(self.term_w);
322 self.writer.push_styled(&output);
323 if after_new_line {
324 self.writer.push_str("\n");
325 }
326 }
327 }
328
329 fn write_before_help(&mut self) {
330 debug!("HelpTemplate::write_before_help");
331 let before_help = if self.use_long {
332 self.cmd
333 .get_before_long_help()
334 .or_else(|| self.cmd.get_before_help())
335 } else {
336 self.cmd.get_before_help()
337 };
338 if let Some(output) = before_help {
339 let mut output = output.clone();
340 output.replace_newline_var();
341 output.wrap(self.term_w);
342 self.writer.push_styled(&output);
343 self.writer.push_str("\n\n");
344 }
345 }
346
347 fn write_after_help(&mut self) {
348 debug!("HelpTemplate::write_after_help");
349 let after_help = if self.use_long {
350 self.cmd
351 .get_after_long_help()
352 .or_else(|| self.cmd.get_after_help())
353 } else {
354 self.cmd.get_after_help()
355 };
356 if let Some(output) = after_help {
357 self.writer.push_str("\n\n");
358 let mut output = output.clone();
359 output.replace_newline_var();
360 output.wrap(self.term_w);
361 self.writer.push_styled(&output);
362 }
363 }
364}
365
366impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
368 pub(crate) fn write_all_args(&mut self) {
371 debug!("HelpTemplate::write_all_args");
372 use std::fmt::Write as _;
373 let header = &self.styles.get_header();
374
375 let pos = self
376 .cmd
377 .get_positionals()
378 .filter(|a| a.get_help_heading().is_none())
379 .filter(|arg| should_show_arg(self.use_long, arg))
380 .collect::<Vec<_>>();
381 let non_pos = self
382 .cmd
383 .get_non_positionals()
384 .filter(|a| a.get_help_heading().is_none())
385 .filter(|arg| should_show_arg(self.use_long, arg))
386 .collect::<Vec<_>>();
387 let subcmds = self.cmd.has_visible_subcommands();
388
389 let custom_headings = self
390 .cmd
391 .get_arguments()
392 .filter_map(|arg| arg.get_help_heading())
393 .collect::<FlatSet<_>>();
394
395 let flatten = self.cmd.is_flatten_help_set();
396
397 let mut first = true;
398
399 if subcmds && !flatten {
400 if !first {
401 self.writer.push_str("\n\n");
402 }
403 first = false;
404 let default_help_heading = Str::from("Commands");
405 let help_heading = self
406 .cmd
407 .get_subcommand_help_heading()
408 .unwrap_or(&default_help_heading);
409 let _ = write!(self.writer, "{header}{help_heading}:{header:#}\n",);
410
411 self.write_subcommands(self.cmd);
412 }
413
414 if !pos.is_empty() {
415 if !first {
416 self.writer.push_str("\n\n");
417 }
418 first = false;
419 let help_heading = "Arguments";
421 let _ = write!(self.writer, "{header}{help_heading}:{header:#}\n",);
422 self.write_args(&pos, "Arguments", positional_sort_key);
423 }
424
425 if !non_pos.is_empty() {
426 if !first {
427 self.writer.push_str("\n\n");
428 }
429 first = false;
430 let help_heading = "Options";
431 let _ = write!(self.writer, "{header}{help_heading}:{header:#}\n",);
432 self.write_args(&non_pos, "Options", option_sort_key);
433 }
434 if !custom_headings.is_empty() {
435 for heading in custom_headings {
436 let args = self
437 .cmd
438 .get_arguments()
439 .filter(|a| {
440 if let Some(help_heading) = a.get_help_heading() {
441 return help_heading == heading;
442 }
443 false
444 })
445 .filter(|arg| should_show_arg(self.use_long, arg))
446 .collect::<Vec<_>>();
447
448 if !args.is_empty() {
449 if !first {
450 self.writer.push_str("\n\n");
451 }
452 first = false;
453 let _ = write!(self.writer, "{header}{heading}:{header:#}\n",);
454 self.write_args(&args, heading, option_sort_key);
455 }
456 }
457 }
458 if subcmds && flatten {
459 let mut cmd = self.cmd.clone();
460 cmd.build();
461 self.write_flat_subcommands(&cmd, &mut first);
462 }
463 }
464
465 fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) {
467 debug!("HelpTemplate::write_args {_category}");
468 let mut longest = 2;
470 let mut ord_v = Vec::new();
471
472 for &arg in args.iter().filter(|arg| {
474 should_show_arg(self.use_long, arg)
478 }) {
479 if longest_filter(arg) {
480 longest = longest.max(display_width(&arg.to_string()));
481 debug!(
482 "HelpTemplate::write_args: arg={:?} longest={}",
483 arg.get_id(),
484 longest
485 );
486 }
487
488 let key = (sort_key)(arg);
489 ord_v.push((key, arg));
490 }
491 ord_v.sort_by(|a, b| a.0.cmp(&b.0));
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<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
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 = Vec::new();
862 for subcommand in cmd
863 .get_subcommands()
864 .filter(|subcommand| should_show_subcommand(subcommand))
865 {
866 ord_v.push((
867 subcommand.get_display_order(),
868 subcommand.get_name(),
869 subcommand,
870 ));
871 }
872 ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
873 for (_, _, subcommand) in ord_v {
874 if !*first {
875 self.writer.push_str("\n\n");
876 }
877 *first = false;
878
879 let heading = subcommand.get_usage_name_fallback();
880 let about = subcommand
881 .get_about()
882 .or_else(|| subcommand.get_long_about())
883 .unwrap_or_default();
884
885 let _ = write!(self.writer, "{header}{heading}:{header:#}\n",);
886 if !about.is_empty() {
887 let _ = write!(self.writer, "{about}\n",);
888 }
889
890 let mut sub_help = HelpTemplate {
891 writer: self.writer,
892 cmd: subcommand,
893 styles: self.styles,
894 usage: self.usage,
895 next_line_help: self.next_line_help,
896 term_w: self.term_w,
897 use_long: self.use_long,
898 };
899 let args = subcommand
900 .get_arguments()
901 .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set())
902 .collect::<Vec<_>>();
903 sub_help.write_args(&args, heading, option_sort_key);
904 if subcommand.is_flatten_help_set() {
905 sub_help.write_flat_subcommands(subcommand, first);
906 }
907 }
908 }
909
910 fn write_subcommands(&mut self, cmd: &Command) {
912 debug!("HelpTemplate::write_subcommands");
913 use std::fmt::Write as _;
914 let literal = &self.styles.get_literal();
915
916 let mut longest = 2;
918 let mut ord_v = Vec::new();
919 for subcommand in cmd
920 .get_subcommands()
921 .filter(|subcommand| should_show_subcommand(subcommand))
922 {
923 let mut styled = StyledStr::new();
924 let name = subcommand.get_name();
925 let _ = write!(styled, "{literal}{name}{literal:#}",);
926 if let Some(short) = subcommand.get_short_flag() {
927 let _ = write!(styled, ", {literal}-{short}{literal:#}",);
928 }
929 if let Some(long) = subcommand.get_long_flag() {
930 let _ = write!(styled, ", {literal}--{long}{literal:#}",);
931 }
932 longest = longest.max(styled.display_width());
933 ord_v.push((subcommand.get_display_order(), styled, subcommand));
934 }
935 ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
936
937 debug!("HelpTemplate::write_subcommands longest = {longest}");
938
939 let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
940
941 for (i, (_, sc_str, sc)) in ord_v.into_iter().enumerate() {
942 if 0 < i {
943 self.writer.push_str("\n");
944 }
945 self.write_subcommand(sc_str, sc, next_line_help, longest);
946 }
947 }
948
949 fn will_subcommands_wrap<'a>(
951 &self,
952 subcommands: impl IntoIterator<Item = &'a Command>,
953 longest: usize,
954 ) -> bool {
955 subcommands
956 .into_iter()
957 .filter(|&subcommand| should_show_subcommand(subcommand))
958 .any(|subcommand| {
959 let spec_vals = &self.sc_spec_vals(subcommand);
960 self.subcommand_next_line_help(subcommand, spec_vals, longest)
961 })
962 }
963
964 fn write_subcommand(
965 &mut self,
966 sc_str: StyledStr,
967 cmd: &Command,
968 next_line_help: bool,
969 longest: usize,
970 ) {
971 debug!("HelpTemplate::write_subcommand");
972
973 let spec_vals = &self.sc_spec_vals(cmd);
974
975 let about = cmd
976 .get_about()
977 .or_else(|| cmd.get_long_about())
978 .unwrap_or_default();
979
980 self.subcmd(sc_str, next_line_help, longest);
981 self.help(None, about, spec_vals, next_line_help, longest);
982 }
983
984 fn sc_spec_vals(&self, a: &Command) -> String {
985 debug!("HelpTemplate::sc_spec_vals: a={}", a.get_name());
986 let mut spec_vals = vec![];
987
988 let mut short_als = a
989 .get_visible_short_flag_aliases()
990 .map(|a| format!("-{a}"))
991 .collect::<Vec<_>>();
992 let als = a.get_visible_aliases().map(|s| s.to_string());
993 short_als.extend(als);
994 let all_als = short_als.join(", ");
995 if !all_als.is_empty() {
996 debug!(
997 "HelpTemplate::spec_vals: Found aliases...{:?}",
998 a.get_all_aliases().collect::<Vec<_>>()
999 );
1000 debug!(
1001 "HelpTemplate::spec_vals: Found short flag aliases...{:?}",
1002 a.get_all_short_flag_aliases().collect::<Vec<_>>()
1003 );
1004 spec_vals.push(format!("[aliases: {all_als}]"));
1005 }
1006
1007 spec_vals.join(" ")
1008 }
1009
1010 fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool {
1011 if self.next_line_help {
1013 true
1015 } else {
1016 let h = cmd
1018 .get_about()
1019 .or_else(|| cmd.get_long_about())
1020 .unwrap_or_default();
1021 let h_w = h.display_width() + display_width(spec_vals);
1022 let taken = longest + TAB_WIDTH * 2;
1023 self.term_w >= taken
1024 && (taken as f32 / self.term_w as f32) > 0.40
1025 && h_w > (self.term_w - taken)
1026 }
1027 }
1028
1029 fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
1031 self.writer.push_str(TAB);
1032 self.writer.push_styled(&sc_str);
1033 if !next_line_help {
1034 let width = sc_str.display_width();
1035 let padding = longest + TAB_WIDTH - width;
1036 self.write_padding(padding);
1037 }
1038 }
1039}
1040
1041const NEXT_LINE_INDENT: &str = " ";
1042
1043type ArgSortKey = fn(arg: &Arg) -> (usize, String);
1044
1045fn positional_sort_key(arg: &Arg) -> (usize, String) {
1046 (arg.get_index().unwrap_or(0), String::new())
1047}
1048
1049fn option_sort_key(arg: &Arg) -> (usize, String) {
1050 let key = if let Some(x) = arg.get_short() {
1059 let mut s = x.to_ascii_lowercase().to_string();
1060 s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
1061 s
1062 } else if let Some(x) = arg.get_long() {
1063 x.to_string()
1064 } else {
1065 let mut s = '{'.to_string();
1066 s.push_str(arg.get_id().as_str());
1067 s
1068 };
1069 (arg.get_display_order(), key)
1070}
1071
1072pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) {
1073 #[cfg(not(feature = "wrap_help"))]
1074 return (None, None);
1075
1076 #[cfg(feature = "wrap_help")]
1077 terminal_size::terminal_size()
1078 .map(|(w, h)| (Some(w.0.into()), Some(h.0.into())))
1079 .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES")))
1080}
1081
1082#[cfg(feature = "wrap_help")]
1083fn parse_env(var: &str) -> Option<usize> {
1084 some!(some!(std::env::var_os(var)).to_str())
1085 .parse::<usize>()
1086 .ok()
1087}
1088
1089fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
1090 debug!(
1091 "should_show_arg: use_long={:?}, arg={}",
1092 use_long,
1093 arg.get_id()
1094 );
1095 if arg.is_hide_set() {
1096 return false;
1097 }
1098 (!arg.is_hide_long_help_set() && use_long)
1099 || (!arg.is_hide_short_help_set() && !use_long)
1100 || arg.is_next_line_help_set()
1101}
1102
1103fn should_show_subcommand(subcommand: &Command) -> bool {
1104 !subcommand.is_hide_set()
1105}
1106
1107fn longest_filter(arg: &Arg) -> bool {
1108 arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none()
1109}
1110
1111#[cfg(test)]
1112mod test {
1113 #[test]
1114 #[cfg(feature = "wrap_help")]
1115 fn wrap_help_last_word() {
1116 use super::*;
1117
1118 let help = String::from("foo bar baz");
1119 assert_eq!(wrap(&help, 5), "foo\nbar\nbaz");
1120 }
1121
1122 #[test]
1123 #[cfg(feature = "unicode")]
1124 fn display_width_handles_non_ascii() {
1125 use super::*;
1126
1127 let text = "rødgrød med fløde";
1129 assert_eq!(display_width(text), 17);
1130 assert_eq!(text.len(), 20);
1133 }
1134
1135 #[test]
1136 #[cfg(feature = "unicode")]
1137 fn display_width_handles_emojis() {
1138 use super::*;
1139
1140 let text = "😂";
1141 assert_eq!(text.chars().count(), 1);
1143 assert_eq!(display_width(text), 2);
1145 assert_eq!(text.len(), 4);
1147 }
1148}