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