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