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(":");
372 self.none("\n");
373
374 self.write_subcommands(self.cmd);
375 }
376
377 if !pos.is_empty() {
378 if !first {
379 self.none("\n\n");
380 }
381 first = false;
382 // Write positional args if any
383 self.header("Arguments:");
384 self.none("\n");
385 self.write_args(&pos, "Arguments", positional_sort_key);
386 }
387
388 if !non_pos.is_empty() {
389 if !first {
390 self.none("\n\n");
391 }
392 first = false;
393 self.header("Options:");
394 self.none("\n");
395 self.write_args(&non_pos, "Options", option_sort_key);
396 }
397 if !custom_headings.is_empty() {
398 for heading in custom_headings {
399 let args = self
400 .cmd
401 .get_arguments()
402 .filter(|a| {
403 if let Some(help_heading) = a.get_help_heading() {
404 return help_heading == heading;
405 }
406 false
407 })
408 .filter(|arg| should_show_arg(self.use_long, arg))
409 .collect::<Vec<_>>();
410
411 if !args.is_empty() {
412 if !first {
413 self.none("\n\n");
414 }
415 first = false;
416 self.header(heading);
417 self.header(":");
418 self.none("\n");
419 self.write_args(&args, heading, option_sort_key);
420 }
421 }
422 }
423 }
424 /// 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)425 fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) {
426 debug!("HelpTemplate::write_args {}", _category);
427 // The shortest an arg can legally be is 2 (i.e. '-x')
428 let mut longest = 2;
429 let mut ord_v = Vec::new();
430
431 // Determine the longest
432 for &arg in args.iter().filter(|arg| {
433 // If it's NextLineHelp we don't care to compute how long it is because it may be
434 // NextLineHelp on purpose simply *because* it's so long and would throw off all other
435 // args alignment
436 should_show_arg(self.use_long, arg)
437 }) {
438 if longest_filter(arg) {
439 longest = longest.max(display_width(&arg.to_string()));
440 debug!(
441 "HelpTemplate::write_args: arg={:?} longest={}",
442 arg.get_id(),
443 longest
444 );
445 }
446
447 let key = (sort_key)(arg);
448 ord_v.push((key, arg));
449 }
450 ord_v.sort_by(|a, b| a.0.cmp(&b.0));
451
452 let next_line_help = self.will_args_wrap(args, longest);
453
454 for (i, (_, arg)) in ord_v.iter().enumerate() {
455 if i != 0 {
456 self.none("\n");
457 if next_line_help && self.use_long {
458 self.none("\n");
459 }
460 }
461 self.write_arg(arg, next_line_help, longest);
462 }
463 }
464
465 /// Writes help for an argument to the wrapped stream.
write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize)466 fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
467 let spec_vals = &self.spec_vals(arg);
468
469 self.none(TAB);
470 self.short(arg);
471 self.long(arg);
472 self.writer.extend(arg.stylize_arg_suffix(None).into_iter());
473 self.align_to_about(arg, next_line_help, longest);
474
475 let about = if self.use_long {
476 arg.get_long_help()
477 .or_else(|| arg.get_help())
478 .unwrap_or_default()
479 } else {
480 arg.get_help()
481 .or_else(|| arg.get_long_help())
482 .unwrap_or_default()
483 };
484
485 self.help(Some(arg), about, spec_vals, next_line_help, longest);
486 }
487
488 /// Writes argument's short command to the wrapped stream.
short(&mut self, arg: &Arg)489 fn short(&mut self, arg: &Arg) {
490 debug!("HelpTemplate::short");
491
492 if let Some(s) = arg.get_short() {
493 self.literal(format!("-{s}"));
494 } else if arg.get_long().is_some() {
495 self.none(" ");
496 }
497 }
498
499 /// Writes argument's long command to the wrapped stream.
long(&mut self, arg: &Arg)500 fn long(&mut self, arg: &Arg) {
501 debug!("HelpTemplate::long");
502 if let Some(long) = arg.get_long() {
503 if arg.get_short().is_some() {
504 self.none(", ");
505 }
506 self.literal(format!("--{long}"));
507 }
508 }
509
510 /// 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)511 fn align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
512 debug!(
513 "HelpTemplate::align_to_about: arg={}, next_line_help={}, longest={}",
514 arg.get_id(),
515 next_line_help,
516 longest
517 );
518 if self.use_long || next_line_help {
519 // long help prints messages on the next line so it doesn't need to align text
520 debug!("HelpTemplate::align_to_about: printing long help so skip alignment");
521 } else if !arg.is_positional() {
522 let self_len = display_width(&arg.to_string());
523 // Since we're writing spaces from the tab point we first need to know if we
524 // had a long and short, or just short
525 let padding = if arg.get_long().is_some() {
526 // Only account 4 after the val
527 TAB_WIDTH
528 } else {
529 // Only account for ', --' + 4 after the val
530 TAB_WIDTH + 4
531 };
532 let spcs = longest + padding - self_len;
533 debug!(
534 "HelpTemplate::align_to_about: positional=false arg_len={}, spaces={}",
535 self_len, spcs
536 );
537
538 self.spaces(spcs);
539 } else {
540 let self_len = display_width(&arg.to_string());
541 let padding = TAB_WIDTH;
542 let spcs = longest + padding - self_len;
543 debug!(
544 "HelpTemplate::align_to_about: positional=true arg_len={}, spaces={}",
545 self_len, spcs
546 );
547
548 self.spaces(spcs);
549 }
550 }
551
552 /// 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, )553 fn help(
554 &mut self,
555 arg: Option<&Arg>,
556 about: &StyledStr,
557 spec_vals: &str,
558 next_line_help: bool,
559 longest: usize,
560 ) {
561 debug!("HelpTemplate::help");
562
563 // Is help on next line, if so then indent
564 if next_line_help {
565 debug!("HelpTemplate::help: Next Line...{:?}", next_line_help);
566 self.none("\n");
567 self.none(TAB);
568 self.none(NEXT_LINE_INDENT);
569 }
570
571 let spaces = if next_line_help {
572 TAB.len() + NEXT_LINE_INDENT.len()
573 } else if let Some(true) = arg.map(|a| a.is_positional()) {
574 longest + TAB_WIDTH * 2
575 } else {
576 longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4
577 };
578 let trailing_indent = spaces; // Don't indent any further than the first line is indented
579 let trailing_indent = self.get_spaces(trailing_indent);
580
581 let mut help = about.clone();
582 replace_newline_var(&mut help);
583 if !spec_vals.is_empty() {
584 if !help.is_empty() {
585 let sep = if self.use_long && arg.is_some() {
586 "\n\n"
587 } else {
588 " "
589 };
590 help.none(sep);
591 }
592 help.none(spec_vals);
593 }
594 let avail_chars = self.term_w.saturating_sub(spaces);
595 debug!(
596 "HelpTemplate::help: help_width={}, spaces={}, avail={}",
597 spaces,
598 help.display_width(),
599 avail_chars
600 );
601 help.wrap(avail_chars);
602 help.indent("", &trailing_indent);
603 let help_is_empty = help.is_empty();
604 self.writer.extend(help.into_iter());
605 if let Some(arg) = arg {
606 const DASH_SPACE: usize = "- ".len();
607 const COLON_SPACE: usize = ": ".len();
608 let possible_vals = arg.get_possible_values();
609 if self.use_long
610 && !arg.is_hide_possible_values_set()
611 && possible_vals.iter().any(PossibleValue::should_show_help)
612 {
613 debug!(
614 "HelpTemplate::help: Found possible vals...{:?}",
615 possible_vals
616 );
617 if !help_is_empty {
618 self.none("\n\n");
619 self.spaces(spaces);
620 }
621 self.none("Possible values:");
622 let longest = possible_vals
623 .iter()
624 .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name)))
625 .max()
626 .expect("Only called with possible value");
627 let help_longest = possible_vals
628 .iter()
629 .filter_map(|f| f.get_visible_help().map(|h| h.display_width()))
630 .max()
631 .expect("Only called with possible value with help");
632 // should new line
633 let taken = longest + spaces + DASH_SPACE;
634
635 let possible_value_new_line =
636 self.term_w >= taken && self.term_w < taken + COLON_SPACE + help_longest;
637
638 let spaces = spaces + TAB_WIDTH - DASH_SPACE;
639 let trailing_indent = if possible_value_new_line {
640 spaces + DASH_SPACE
641 } else {
642 spaces + longest + DASH_SPACE + COLON_SPACE
643 };
644 let trailing_indent = self.get_spaces(trailing_indent);
645
646 for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) {
647 self.none("\n");
648 self.spaces(spaces);
649 self.none("- ");
650 self.literal(pv.get_name());
651 if let Some(help) = pv.get_help() {
652 debug!("HelpTemplate::help: Possible Value help");
653
654 if possible_value_new_line {
655 self.none(":\n");
656 self.spaces(trailing_indent.len());
657 } else {
658 self.none(": ");
659 // To align help messages
660 self.spaces(longest - display_width(pv.get_name()));
661 }
662
663 let avail_chars = if self.term_w > trailing_indent.len() {
664 self.term_w - trailing_indent.len()
665 } else {
666 usize::MAX
667 };
668
669 let mut help = help.clone();
670 replace_newline_var(&mut help);
671 help.wrap(avail_chars);
672 help.indent("", &trailing_indent);
673 self.writer.extend(help.into_iter());
674 }
675 }
676 }
677 }
678 }
679
680 /// Will use next line help on writing args.
will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool681 fn will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool {
682 args.iter()
683 .filter(|arg| should_show_arg(self.use_long, arg))
684 .any(|arg| {
685 let spec_vals = &self.spec_vals(arg);
686 self.arg_next_line_help(arg, spec_vals, longest)
687 })
688 }
689
arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool690 fn arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool {
691 if self.next_line_help || arg.is_next_line_help_set() || self.use_long {
692 // setting_next_line
693 true
694 } else {
695 // force_next_line
696 let h = arg.get_help().unwrap_or_default();
697 let h_w = h.display_width() + display_width(spec_vals);
698 let taken = if arg.is_positional() {
699 longest + TAB_WIDTH * 2
700 } else {
701 longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4
702 };
703 self.term_w >= taken
704 && (taken as f32 / self.term_w as f32) > 0.40
705 && h_w > (self.term_w - taken)
706 }
707 }
708
spec_vals(&self, a: &Arg) -> String709 fn spec_vals(&self, a: &Arg) -> String {
710 debug!("HelpTemplate::spec_vals: a={}", a);
711 let mut spec_vals = Vec::new();
712 #[cfg(feature = "env")]
713 if let Some(ref env) = a.env {
714 if !a.is_hide_env_set() {
715 debug!(
716 "HelpTemplate::spec_vals: Found environment variable...[{:?}:{:?}]",
717 env.0, env.1
718 );
719 let env_val = if !a.is_hide_env_values_set() {
720 format!(
721 "={}",
722 env.1
723 .as_ref()
724 .map(|s| s.to_string_lossy())
725 .unwrap_or_default()
726 )
727 } else {
728 Default::default()
729 };
730 let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
731 spec_vals.push(env_info);
732 }
733 }
734 if a.is_takes_value_set() && !a.is_hide_default_value_set() && !a.default_vals.is_empty() {
735 debug!(
736 "HelpTemplate::spec_vals: Found default value...[{:?}]",
737 a.default_vals
738 );
739
740 let pvs = a
741 .default_vals
742 .iter()
743 .map(|pvs| pvs.to_string_lossy())
744 .map(|pvs| {
745 if pvs.contains(char::is_whitespace) {
746 Cow::from(format!("{pvs:?}"))
747 } else {
748 pvs
749 }
750 })
751 .collect::<Vec<_>>()
752 .join(" ");
753
754 spec_vals.push(format!("[default: {pvs}]"));
755 }
756
757 let als = a
758 .aliases
759 .iter()
760 .filter(|&als| als.1) // visible
761 .map(|als| als.0.as_str()) // name
762 .collect::<Vec<_>>()
763 .join(", ");
764 if !als.is_empty() {
765 debug!("HelpTemplate::spec_vals: Found aliases...{:?}", a.aliases);
766 spec_vals.push(format!("[aliases: {als}]"));
767 }
768
769 let als = a
770 .short_aliases
771 .iter()
772 .filter(|&als| als.1) // visible
773 .map(|&als| als.0.to_string()) // name
774 .collect::<Vec<_>>()
775 .join(", ");
776 if !als.is_empty() {
777 debug!(
778 "HelpTemplate::spec_vals: Found short aliases...{:?}",
779 a.short_aliases
780 );
781 spec_vals.push(format!("[short aliases: {als}]"));
782 }
783
784 let possible_vals = a.get_possible_values();
785 if !(a.is_hide_possible_values_set()
786 || possible_vals.is_empty()
787 || self.use_long && possible_vals.iter().any(PossibleValue::should_show_help))
788 {
789 debug!(
790 "HelpTemplate::spec_vals: Found possible vals...{:?}",
791 possible_vals
792 );
793
794 let pvs = possible_vals
795 .iter()
796 .filter_map(PossibleValue::get_visible_quoted_name)
797 .collect::<Vec<_>>()
798 .join(", ");
799
800 spec_vals.push(format!("[possible values: {pvs}]"));
801 }
802 let connector = if self.use_long { "\n" } else { " " };
803 spec_vals.join(connector)
804 }
805
header<T: Into<String>>(&mut self, msg: T)806 fn header<T: Into<String>>(&mut self, msg: T) {
807 self.writer.header(msg);
808 }
809
literal<T: Into<String>>(&mut self, msg: T)810 fn literal<T: Into<String>>(&mut self, msg: T) {
811 self.writer.literal(msg);
812 }
813
none<T: Into<String>>(&mut self, msg: T)814 fn none<T: Into<String>>(&mut self, msg: T) {
815 self.writer.none(msg);
816 }
817
get_spaces(&self, n: usize) -> String818 fn get_spaces(&self, n: usize) -> String {
819 " ".repeat(n)
820 }
821
spaces(&mut self, n: usize)822 fn spaces(&mut self, n: usize) {
823 self.none(self.get_spaces(n));
824 }
825 }
826
827 /// Subcommand handling
828 impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
829 /// Writes help for subcommands of a Parser Object to the wrapped stream.
write_subcommands(&mut self, cmd: &Command)830 fn write_subcommands(&mut self, cmd: &Command) {
831 debug!("HelpTemplate::write_subcommands");
832 // The shortest an arg can legally be is 2 (i.e. '-x')
833 let mut longest = 2;
834 let mut ord_v = Vec::new();
835 for subcommand in cmd
836 .get_subcommands()
837 .filter(|subcommand| should_show_subcommand(subcommand))
838 {
839 let mut styled = StyledStr::new();
840 styled.literal(subcommand.get_name());
841 if let Some(short) = subcommand.get_short_flag() {
842 styled.none(", ");
843 styled.literal(format!("-{short}"));
844 }
845 if let Some(long) = subcommand.get_long_flag() {
846 styled.none(", ");
847 styled.literal(format!("--{long}"));
848 }
849 longest = longest.max(styled.display_width());
850 ord_v.push((subcommand.get_display_order(), styled, subcommand));
851 }
852 ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
853
854 debug!("HelpTemplate::write_subcommands longest = {}", longest);
855
856 let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
857
858 let mut first = true;
859 for (_, sc_str, sc) in ord_v {
860 if first {
861 first = false;
862 } else {
863 self.none("\n");
864 }
865 self.write_subcommand(sc_str, sc, next_line_help, longest);
866 }
867 }
868
869 /// Will use next line help on writing subcommands.
will_subcommands_wrap<'a>( &self, subcommands: impl IntoIterator<Item = &'a Command>, longest: usize, ) -> bool870 fn will_subcommands_wrap<'a>(
871 &self,
872 subcommands: impl IntoIterator<Item = &'a Command>,
873 longest: usize,
874 ) -> bool {
875 subcommands
876 .into_iter()
877 .filter(|&subcommand| should_show_subcommand(subcommand))
878 .any(|subcommand| {
879 let spec_vals = &self.sc_spec_vals(subcommand);
880 self.subcommand_next_line_help(subcommand, spec_vals, longest)
881 })
882 }
883
write_subcommand( &mut self, sc_str: StyledStr, cmd: &Command, next_line_help: bool, longest: usize, )884 fn write_subcommand(
885 &mut self,
886 sc_str: StyledStr,
887 cmd: &Command,
888 next_line_help: bool,
889 longest: usize,
890 ) {
891 debug!("HelpTemplate::write_subcommand");
892
893 let spec_vals = &self.sc_spec_vals(cmd);
894
895 let about = cmd
896 .get_about()
897 .or_else(|| cmd.get_long_about())
898 .unwrap_or_default();
899
900 self.subcmd(sc_str, next_line_help, longest);
901 self.help(None, about, spec_vals, next_line_help, longest)
902 }
903
sc_spec_vals(&self, a: &Command) -> String904 fn sc_spec_vals(&self, a: &Command) -> String {
905 debug!("HelpTemplate::sc_spec_vals: a={}", a.get_name());
906 let mut spec_vals = vec![];
907
908 let mut short_als = a
909 .get_visible_short_flag_aliases()
910 .map(|a| format!("-{a}"))
911 .collect::<Vec<_>>();
912 let als = a.get_visible_aliases().map(|s| s.to_string());
913 short_als.extend(als);
914 let all_als = short_als.join(", ");
915 if !all_als.is_empty() {
916 debug!(
917 "HelpTemplate::spec_vals: Found aliases...{:?}",
918 a.get_all_aliases().collect::<Vec<_>>()
919 );
920 debug!(
921 "HelpTemplate::spec_vals: Found short flag aliases...{:?}",
922 a.get_all_short_flag_aliases().collect::<Vec<_>>()
923 );
924 spec_vals.push(format!("[aliases: {all_als}]"));
925 }
926
927 spec_vals.join(" ")
928 }
929
subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool930 fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool {
931 if self.next_line_help | self.use_long {
932 // setting_next_line
933 true
934 } else {
935 // force_next_line
936 let h = cmd.get_about().unwrap_or_default();
937 let h_w = h.display_width() + display_width(spec_vals);
938 let taken = longest + TAB_WIDTH * 2;
939 self.term_w >= taken
940 && (taken as f32 / self.term_w as f32) > 0.40
941 && h_w > (self.term_w - taken)
942 }
943 }
944
945 /// Writes subcommand to the wrapped stream.
subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize)946 fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
947 let width = sc_str.display_width();
948
949 self.none(TAB);
950 self.writer.extend(sc_str.into_iter());
951 if !next_line_help {
952 self.spaces(longest + TAB_WIDTH - width);
953 }
954 }
955 }
956
957 const NEXT_LINE_INDENT: &str = " ";
958
959 type ArgSortKey = fn(arg: &Arg) -> (usize, String);
960
positional_sort_key(arg: &Arg) -> (usize, String)961 fn positional_sort_key(arg: &Arg) -> (usize, String) {
962 (arg.get_index().unwrap_or(0), String::new())
963 }
964
option_sort_key(arg: &Arg) -> (usize, String)965 fn option_sort_key(arg: &Arg) -> (usize, String) {
966 // Formatting key like this to ensure that:
967 // 1. Argument has long flags are printed just after short flags.
968 // 2. For two args both have short flags like `-c` and `-C`, the
969 // `-C` arg is printed just after the `-c` arg
970 // 3. For args without short or long flag, print them at last(sorted
971 // by arg name).
972 // Example order: -a, -b, -B, -s, --select-file, --select-folder, -x
973
974 let key = if let Some(x) = arg.get_short() {
975 let mut s = x.to_ascii_lowercase().to_string();
976 s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
977 s
978 } else if let Some(x) = arg.get_long() {
979 x.to_string()
980 } else {
981 let mut s = '{'.to_string();
982 s.push_str(arg.get_id().as_str());
983 s
984 };
985 (arg.get_display_order(), key)
986 }
987
dimensions() -> (Option<usize>, Option<usize>)988 pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) {
989 #[cfg(not(feature = "wrap_help"))]
990 return (None, None);
991
992 #[cfg(feature = "wrap_help")]
993 terminal_size::terminal_size()
994 .map(|(w, h)| (Some(w.0.into()), Some(h.0.into())))
995 .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES")))
996 }
997
998 #[cfg(feature = "wrap_help")]
parse_env(var: &str) -> Option<usize>999 fn parse_env(var: &str) -> Option<usize> {
1000 some!(some!(std::env::var_os(var)).to_str())
1001 .parse::<usize>()
1002 .ok()
1003 }
1004
should_show_arg(use_long: bool, arg: &Arg) -> bool1005 fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
1006 debug!(
1007 "should_show_arg: use_long={:?}, arg={}",
1008 use_long,
1009 arg.get_id()
1010 );
1011 if arg.is_hide_set() {
1012 return false;
1013 }
1014 (!arg.is_hide_long_help_set() && use_long)
1015 || (!arg.is_hide_short_help_set() && !use_long)
1016 || arg.is_next_line_help_set()
1017 }
1018
should_show_subcommand(subcommand: &Command) -> bool1019 fn should_show_subcommand(subcommand: &Command) -> bool {
1020 !subcommand.is_hide_set()
1021 }
1022
replace_newline_var(styled: &mut StyledStr)1023 fn replace_newline_var(styled: &mut StyledStr) {
1024 for (_, content) in styled.iter_mut() {
1025 *content = content.replace("{n}", "\n");
1026 }
1027 }
1028
longest_filter(arg: &Arg) -> bool1029 fn longest_filter(arg: &Arg) -> bool {
1030 arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none()
1031 }
1032
1033 #[cfg(test)]
1034 mod test {
1035 #[test]
1036 #[cfg(feature = "wrap_help")]
wrap_help_last_word()1037 fn wrap_help_last_word() {
1038 use super::*;
1039
1040 let help = String::from("foo bar baz");
1041 assert_eq!(wrap(&help, 5), "foo\nbar\nbaz");
1042 }
1043
1044 #[test]
1045 #[cfg(feature = "unicode")]
display_width_handles_non_ascii()1046 fn display_width_handles_non_ascii() {
1047 use super::*;
1048
1049 // Popular Danish tongue-twister, the name of a fruit dessert.
1050 let text = "rødgrød med fløde";
1051 assert_eq!(display_width(text), 17);
1052 // Note that the string width is smaller than the string
1053 // length. This is due to the precomposed non-ASCII letters:
1054 assert_eq!(text.len(), 20);
1055 }
1056
1057 #[test]
1058 #[cfg(feature = "unicode")]
display_width_handles_emojis()1059 fn display_width_handles_emojis() {
1060 use super::*;
1061
1062 let text = "";
1063 // There is a single `char`...
1064 assert_eq!(text.chars().count(), 1);
1065 // but it is double-width:
1066 assert_eq!(display_width(text), 2);
1067 // This is much less than the byte length:
1068 assert_eq!(text.len(), 4);
1069 }
1070 }
1071