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