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