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