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