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