• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
new( writer: HelpWriter<'writer>, cmd: &'cmd Command<'help>, usage: &'cmd Usage<'help, 'cmd>, use_long: bool, ) -> Self62     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.
write_help(&mut self) -> io::Result<()>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)]
good<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()>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)]
warning<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()>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)]
none<T: Into<String> + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()>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)]
spaces(&mut self, n: usize) -> io::Result<()>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.
write_args_unsorted(&mut self, args: &[&Arg<'help>]) -> io::Result<()>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.
write_args(&mut self, args: &[&Arg<'help>], _category: &str) -> io::Result<()>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.
write_arg( &mut self, arg: &Arg<'help>, last_arg: bool, next_line_help: bool, longest: usize, ) -> io::Result<()>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.
short(&mut self, arg: &Arg<'help>) -> io::Result<()>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.
long(&mut self, arg: &Arg<'help>) -> io::Result<()>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.
val(&mut self, arg: &Arg<'help>) -> io::Result<()>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.
align_to_about( &mut self, arg: &Arg<'help>, next_line_help: bool, longest: usize, ) -> io::Result<()>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 
write_before_help(&mut self) -> io::Result<()>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 
write_after_help(&mut self) -> io::Result<()>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.
help( &mut self, arg: Option<&Arg<'help>>, about: &str, spec_vals: &str, next_line_help: bool, longest: usize, ) -> io::Result<()>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.
will_args_wrap(&self, args: &[&Arg<'help>], longest: usize) -> bool558     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 
arg_next_line_help(&self, arg: &Arg<'help>, spec_vals: &str, longest: usize) -> bool567     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 
spec_vals(&self, a: &Arg) -> String582     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 
write_about(&mut self, before_new_line: bool, after_new_line: bool) -> io::Result<()>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 
write_author(&mut self, before_new_line: bool, after_new_line: bool) -> io::Result<()>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 
write_version(&mut self) -> io::Result<()>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> {
write_subcommand( &mut self, sc_str: &str, cmd: &Command<'help>, next_line_help: bool, longest: usize, ) -> io::Result<()>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 
sc_spec_vals(&self, a: &Command) -> String758     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 
subcommand_next_line_help( &self, cmd: &Command<'help>, spec_vals: &str, longest: usize, ) -> bool789     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.
subcmd(&mut self, sc_str: &str, next_line_help: bool, longest: usize) -> io::Result<()>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.
write_all_args(&mut self) -> io::Result<()>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.
will_subcommands_wrap<'a>( &self, subcommands: impl IntoIterator<Item = &'a Command<'help>>, longest: usize, ) -> bool where 'help: 'a,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.
write_subcommands(&mut self, cmd: &Command<'help>) -> io::Result<()>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.
write_display_name(&mut self) -> io::Result<()>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.
write_bin_name(&mut self) -> io::Result<()>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()
write_templated_help(&mut self, template: &str) -> io::Result<()>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 
dimensions() -> Option<(usize, usize)>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 
should_show_arg(use_long: bool, arg: &Arg) -> bool1122 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 
should_show_subcommand(subcommand: &Command) -> bool1132 fn should_show_subcommand(subcommand: &Command) -> bool {
1133     !subcommand.is_hide_set()
1134 }
1135 
text_wrapper(help: &str, width: usize) -> String1136 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]
wrap_help_last_word()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]
display_width_handles_non_ascii()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]
display_width_handles_emojis()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 }
1177