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