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