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