// Std use std::borrow::Cow; use std::cmp; use std::fmt::Write as _; use std::io::{self, Write}; use std::usize; // Internal use crate::builder::{display_arg_val, Arg, Command}; use crate::output::{fmt::Colorizer, Usage}; use crate::PossibleValue; // Third party use indexmap::IndexSet; use textwrap::core::display_width; /// `clap` Help Writer. /// /// Wraps a writer stream providing different methods to generate help for `clap` objects. pub(crate) struct Help<'help, 'cmd, 'writer> { writer: HelpWriter<'writer>, cmd: &'cmd Command<'help>, usage: &'cmd Usage<'help, 'cmd>, next_line_help: bool, term_w: usize, use_long: bool, } // Public Functions impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { #[cfg(feature = "unstable-v4")] const DEFAULT_TEMPLATE: &'static str = "\ {before-help}{name} {version}\n\ {author-with-newline}{about-with-newline}\n\ {usage-heading}\n {usage}\n\ \n\ {all-args}{after-help}\ "; #[cfg(not(feature = "unstable-v4"))] const DEFAULT_TEMPLATE: &'static str = "\ {before-help}{bin} {version}\n\ {author-with-newline}{about-with-newline}\n\ {usage-heading}\n {usage}\n\ \n\ {all-args}{after-help}\ "; #[cfg(feature = "unstable-v4")] const DEFAULT_NO_ARGS_TEMPLATE: &'static str = "\ {before-help}{name} {version}\n\ {author-with-newline}{about-with-newline}\n\ {usage-heading}\n {usage}{after-help}\ "; #[cfg(not(feature = "unstable-v4"))] const DEFAULT_NO_ARGS_TEMPLATE: &'static str = "\ {before-help}{bin} {version}\n\ {author-with-newline}{about-with-newline}\n\ {usage-heading}\n {usage}{after-help}\ "; /// Create a new `Help` instance. pub(crate) fn new( writer: HelpWriter<'writer>, cmd: &'cmd Command<'help>, usage: &'cmd Usage<'help, 'cmd>, use_long: bool, ) -> Self { debug!("Help::new cmd={}, use_long={}", cmd.get_name(), use_long); let term_w = match cmd.get_term_width() { Some(0) => usize::MAX, Some(w) => w, None => cmp::min( dimensions().map_or(100, |(w, _)| w), match cmd.get_max_term_width() { None | Some(0) => usize::MAX, Some(mw) => mw, }, ), }; let next_line_help = cmd.is_next_line_help_set(); Help { writer, cmd, usage, next_line_help, term_w, use_long, } } /// Writes the parser help to the wrapped stream. pub(crate) fn write_help(&mut self) -> io::Result<()> { debug!("Help::write_help"); if let Some(h) = self.cmd.get_override_help() { self.none(h)?; } else if let Some(tmpl) = self.cmd.get_help_template() { self.write_templated_help(tmpl)?; } else { let pos = self .cmd .get_positionals() .any(|arg| should_show_arg(self.use_long, arg)); let non_pos = self .cmd .get_non_positionals() .any(|arg| should_show_arg(self.use_long, arg)); let subcmds = self.cmd.has_visible_subcommands(); if non_pos || pos || subcmds { self.write_templated_help(Self::DEFAULT_TEMPLATE)?; } else { self.write_templated_help(Self::DEFAULT_NO_ARGS_TEMPLATE)?; } } self.none("\n")?; Ok(()) } } macro_rules! write_method { ($_self:ident, $msg:ident, $meth:ident) => { match &mut $_self.writer { HelpWriter::Buffer(c) => { c.$meth(($msg).into()); Ok(()) } HelpWriter::Normal(w) => w.write_all($msg.as_ref()), } }; } // Methods to write Arg help. impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { #[inline(never)] fn good + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> { write_method!(self, msg, good) } #[inline(never)] fn warning + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> { write_method!(self, msg, warning) } #[inline(never)] fn none + AsRef<[u8]>>(&mut self, msg: T) -> io::Result<()> { write_method!(self, msg, none) } #[inline(never)] fn spaces(&mut self, n: usize) -> io::Result<()> { // A string with 64 consecutive spaces. const SHORT_SPACE: &str = " "; if let Some(short) = SHORT_SPACE.get(..n) { self.none(short) } else { self.none(" ".repeat(n)) } } /// Writes help for each argument in the order they were declared to the wrapped stream. fn write_args_unsorted(&mut self, args: &[&Arg<'help>]) -> io::Result<()> { debug!("Help::write_args_unsorted"); // The shortest an arg can legally be is 2 (i.e. '-x') let mut longest = 2; let mut arg_v = Vec::with_capacity(10); for &arg in args .iter() .filter(|arg| should_show_arg(self.use_long, *arg)) { if arg.longest_filter() { longest = longest.max(display_width(&arg.to_string())); debug!( "Help::write_args_unsorted: arg={:?} longest={}", arg.get_id(), longest ); } arg_v.push(arg) } let next_line_help = self.will_args_wrap(args, longest); let argc = arg_v.len(); for (i, arg) in arg_v.iter().enumerate() { self.write_arg(arg, i + 1 == argc, next_line_help, longest)?; } Ok(()) } /// Sorts arguments by length and display order and write their help to the wrapped stream. fn write_args(&mut self, args: &[&Arg<'help>], _category: &str) -> io::Result<()> { debug!("Help::write_args {}", _category); // The shortest an arg can legally be is 2 (i.e. '-x') let mut longest = 2; let mut ord_v = Vec::new(); // Determine the longest for &arg in args.iter().filter(|arg| { // If it's NextLineHelp we don't care to compute how long it is because it may be // NextLineHelp on purpose simply *because* it's so long and would throw off all other // args alignment should_show_arg(self.use_long, *arg) }) { if arg.longest_filter() { longest = longest.max(display_width(&arg.to_string())); debug!( "Help::write_args: arg={:?} longest={}", arg.get_id(), longest ); } // Formatting key like this to ensure that: // 1. Argument has long flags are printed just after short flags. // 2. For two args both have short flags like `-c` and `-C`, the // `-C` arg is printed just after the `-c` arg // 3. For args without short or long flag, print them at last(sorted // by arg name). // Example order: -a, -b, -B, -s, --select-file, --select-folder, -x let key = if let Some(x) = arg.short { let mut s = x.to_ascii_lowercase().to_string(); s.push(if x.is_ascii_lowercase() { '0' } else { '1' }); s } else if let Some(x) = arg.long { x.to_string() } else { let mut s = '{'.to_string(); s.push_str(arg.name); s }; ord_v.push((arg.get_display_order(), key, arg)); } ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); let next_line_help = self.will_args_wrap(args, longest); for (i, (_, _, arg)) in ord_v.iter().enumerate() { let last_arg = i + 1 == ord_v.len(); self.write_arg(arg, last_arg, next_line_help, longest)?; } Ok(()) } /// Writes help for an argument to the wrapped stream. fn write_arg( &mut self, arg: &Arg<'help>, last_arg: bool, next_line_help: bool, longest: usize, ) -> io::Result<()> { let spec_vals = &self.spec_vals(arg); self.short(arg)?; self.long(arg)?; self.val(arg)?; self.align_to_about(arg, next_line_help, longest)?; let about = if self.use_long { arg.long_help.or(arg.help).unwrap_or("") } else { arg.help.or(arg.long_help).unwrap_or("") }; self.help(Some(arg), about, spec_vals, next_line_help, longest)?; if !last_arg { self.none("\n")?; if next_line_help { self.none("\n")?; } } Ok(()) } /// Writes argument's short command to the wrapped stream. fn short(&mut self, arg: &Arg<'help>) -> io::Result<()> { debug!("Help::short"); self.none(TAB)?; if let Some(s) = arg.short { self.good(format!("-{}", s)) } else if !arg.is_positional() { self.none(TAB) } else { Ok(()) } } /// Writes argument's long command to the wrapped stream. fn long(&mut self, arg: &Arg<'help>) -> io::Result<()> { debug!("Help::long"); if let Some(long) = arg.long { if arg.short.is_some() { self.none(", ")?; } self.good(format!("--{}", long))?; } Ok(()) } /// Writes argument's possible values to the wrapped stream. fn val(&mut self, arg: &Arg<'help>) -> io::Result<()> { debug!("Help::val: arg={}", arg.name); let mut need_closing_bracket = false; if arg.is_takes_value_set() && !arg.is_positional() { let is_optional_val = arg.min_vals == Some(0); let sep = if arg.is_require_equals_set() { if is_optional_val { need_closing_bracket = true; "[=" } else { "=" } } else if is_optional_val { need_closing_bracket = true; " [" } else { " " }; self.none(sep)?; } if arg.is_takes_value_set() || arg.is_positional() { display_arg_val( arg, |s, good| if good { self.good(s) } else { self.none(s) }, )?; } if need_closing_bracket { self.none("]")?; } Ok(()) } /// Write alignment padding between arg's switches/values and its about message. fn align_to_about( &mut self, arg: &Arg<'help>, next_line_help: bool, longest: usize, ) -> io::Result<()> { debug!( "Help::align_to_about: arg={}, next_line_help={}, longest={}", arg.name, next_line_help, longest ); if self.use_long || next_line_help { // long help prints messages on the next line so it doesn't need to align text debug!("Help::align_to_about: printing long help so skip alignment"); } else if !arg.is_positional() { let self_len = display_width(&arg.to_string()); // Since we're writing spaces from the tab point we first need to know if we // had a long and short, or just short let padding = if arg.long.is_some() { // Only account 4 after the val 4 } else { // Only account for ', --' + 4 after the val 8 }; let spcs = longest + padding - self_len; debug!( "Help::align_to_about: positional=false arg_len={}, spaces={}", self_len, spcs ); self.spaces(spcs)?; } else { let self_len = display_width(&arg.to_string()); let padding = 4; let spcs = longest + padding - self_len; debug!( "Help::align_to_about: positional=true arg_len={}, spaces={}", self_len, spcs ); self.spaces(spcs)?; } Ok(()) } fn write_before_help(&mut self) -> io::Result<()> { debug!("Help::write_before_help"); let before_help = if self.use_long { self.cmd .get_before_long_help() .or_else(|| self.cmd.get_before_help()) } else { self.cmd.get_before_help() }; if let Some(output) = before_help { self.none(text_wrapper(&output.replace("{n}", "\n"), self.term_w))?; self.none("\n\n")?; } Ok(()) } fn write_after_help(&mut self) -> io::Result<()> { debug!("Help::write_after_help"); let after_help = if self.use_long { self.cmd .get_after_long_help() .or_else(|| self.cmd.get_after_help()) } else { self.cmd.get_after_help() }; if let Some(output) = after_help { self.none("\n\n")?; self.none(text_wrapper(&output.replace("{n}", "\n"), self.term_w))?; } Ok(()) } /// Writes argument's help to the wrapped stream. fn help( &mut self, arg: Option<&Arg<'help>>, about: &str, spec_vals: &str, next_line_help: bool, longest: usize, ) -> io::Result<()> { debug!("Help::help"); let mut help = String::from(about) + spec_vals; debug!("Help::help: Next Line...{:?}", next_line_help); let spaces = if next_line_help { 12 // "tab" * 3 } else { longest + 12 }; let too_long = spaces + display_width(&help) >= self.term_w; // Is help on next line, if so then indent if next_line_help { self.none(format!("\n{}{}{}", TAB, TAB, TAB))?; } debug!("Help::help: Too long..."); if too_long && spaces <= self.term_w || help.contains("{n}") { debug!("Yes"); debug!("Help::help: help...{}", help); debug!("Help::help: help width...{}", display_width(&help)); // Determine how many newlines we need to insert let avail_chars = self.term_w - spaces; debug!("Help::help: Usable space...{}", avail_chars); help = text_wrapper(&help.replace("{n}", "\n"), avail_chars); } else { debug!("No"); } if let Some(part) = help.lines().next() { self.none(part)?; } // indent of help let spaces = if next_line_help { TAB_WIDTH * 3 } else if let Some(true) = arg.map(|a| a.is_positional()) { longest + TAB_WIDTH * 2 } else { longest + TAB_WIDTH * 3 }; for part in help.lines().skip(1) { self.none("\n")?; self.spaces(spaces)?; self.none(part)?; } #[cfg(feature = "unstable-v4")] if let Some(arg) = arg { const DASH_SPACE: usize = "- ".len(); const COLON_SPACE: usize = ": ".len(); let possible_vals = arg.get_possible_values2(); if self.use_long && !arg.is_hide_possible_values_set() && possible_vals.iter().any(PossibleValue::should_show_help) { debug!("Help::help: Found possible vals...{:?}", possible_vals); if !help.is_empty() { self.none("\n\n")?; self.spaces(spaces)?; } self.none("Possible values:")?; let longest = possible_vals .iter() .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name))) .max() .expect("Only called with possible value"); let help_longest = possible_vals .iter() .filter_map(|f| f.get_visible_help().map(display_width)) .max() .expect("Only called with possible value with help"); // should new line let taken = longest + spaces + DASH_SPACE; let possible_value_new_line = self.term_w >= taken && self.term_w < taken + COLON_SPACE + help_longest; let spaces = spaces + TAB_WIDTH - DASH_SPACE; let spaces_help = if possible_value_new_line { spaces + DASH_SPACE } else { spaces + longest + DASH_SPACE + COLON_SPACE }; for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) { self.none("\n")?; self.spaces(spaces)?; self.none("- ")?; self.good(pv.get_name())?; if let Some(help) = pv.get_help() { debug!("Help::help: Possible Value help"); if possible_value_new_line { self.none(":\n")?; self.spaces(spaces_help)?; } else { self.none(": ")?; // To align help messages self.spaces(longest - display_width(pv.get_name()))?; } let avail_chars = if self.term_w > spaces_help { self.term_w - spaces_help } else { usize::MAX }; let help = text_wrapper(help, avail_chars); let mut help = help.lines(); self.none(help.next().unwrap_or_default())?; for part in help { self.none("\n")?; self.spaces(spaces_help)?; self.none(part)?; } } } } } Ok(()) } /// Will use next line help on writing args. fn will_args_wrap(&self, args: &[&Arg<'help>], longest: usize) -> bool { args.iter() .filter(|arg| should_show_arg(self.use_long, *arg)) .any(|arg| { let spec_vals = &self.spec_vals(arg); self.arg_next_line_help(arg, spec_vals, longest) }) } fn arg_next_line_help(&self, arg: &Arg<'help>, spec_vals: &str, longest: usize) -> bool { if self.next_line_help || arg.is_next_line_help_set() || self.use_long { // setting_next_line true } else { // force_next_line let h = arg.help.unwrap_or(""); let h_w = display_width(h) + display_width(spec_vals); let taken = longest + 12; self.term_w >= taken && (taken as f32 / self.term_w as f32) > 0.40 && h_w > (self.term_w - taken) } } fn spec_vals(&self, a: &Arg) -> String { debug!("Help::spec_vals: a={}", a); let mut spec_vals = vec![]; #[cfg(feature = "env")] if let Some(ref env) = a.env { if !a.is_hide_env_set() { debug!( "Help::spec_vals: Found environment variable...[{:?}:{:?}]", env.0, env.1 ); let env_val = if !a.is_hide_env_values_set() { format!( "={}", env.1 .as_ref() .map_or(Cow::Borrowed(""), |val| val.to_string_lossy()) ) } else { String::new() }; let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val); spec_vals.push(env_info); } } if a.is_takes_value_set() && !a.is_hide_default_value_set() && !a.default_vals.is_empty() { debug!( "Help::spec_vals: Found default value...[{:?}]", a.default_vals ); let pvs = a .default_vals .iter() .map(|&pvs| pvs.to_string_lossy()) .map(|pvs| { if pvs.contains(char::is_whitespace) { Cow::from(format!("{:?}", pvs)) } else { pvs } }) .collect::>() .join(" "); spec_vals.push(format!("[default: {}]", pvs)); } if !a.aliases.is_empty() { debug!("Help::spec_vals: Found aliases...{:?}", a.aliases); let als = a .aliases .iter() .filter(|&als| als.1) // visible .map(|&als| als.0) // name .collect::>() .join(", "); if !als.is_empty() { spec_vals.push(format!("[aliases: {}]", als)); } } if !a.short_aliases.is_empty() { debug!( "Help::spec_vals: Found short aliases...{:?}", a.short_aliases ); let als = a .short_aliases .iter() .filter(|&als| als.1) // visible .map(|&als| als.0.to_string()) // name .collect::>() .join(", "); if !als.is_empty() { spec_vals.push(format!("[short aliases: {}]", als)); } } let possible_vals = a.get_possible_values2(); if !(a.is_hide_possible_values_set() || possible_vals.is_empty() || cfg!(feature = "unstable-v4") && self.use_long && possible_vals.iter().any(PossibleValue::should_show_help)) { debug!("Help::spec_vals: Found possible vals...{:?}", possible_vals); let pvs = possible_vals .iter() .filter_map(PossibleValue::get_visible_quoted_name) .collect::>() .join(", "); spec_vals.push(format!("[possible values: {}]", pvs)); } let connector = if self.use_long { "\n" } else { " " }; let prefix = if !spec_vals.is_empty() && !a.get_help().unwrap_or("").is_empty() { if self.use_long { "\n\n" } else { " " } } else { "" }; prefix.to_string() + &spec_vals.join(connector) } fn write_about(&mut self, before_new_line: bool, after_new_line: bool) -> io::Result<()> { let about = if self.use_long { self.cmd.get_long_about().or_else(|| self.cmd.get_about()) } else { self.cmd.get_about() }; if let Some(output) = about { if before_new_line { self.none("\n")?; } self.none(text_wrapper(output, self.term_w))?; if after_new_line { self.none("\n")?; } } Ok(()) } fn write_author(&mut self, before_new_line: bool, after_new_line: bool) -> io::Result<()> { if let Some(author) = self.cmd.get_author() { if before_new_line { self.none("\n")?; } self.none(text_wrapper(author, self.term_w))?; if after_new_line { self.none("\n")?; } } Ok(()) } fn write_version(&mut self) -> io::Result<()> { let version = self .cmd .get_version() .or_else(|| self.cmd.get_long_version()); if let Some(output) = version { self.none(text_wrapper(output, self.term_w))?; } Ok(()) } } /// Methods to write a single subcommand impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { fn write_subcommand( &mut self, sc_str: &str, cmd: &Command<'help>, next_line_help: bool, longest: usize, ) -> io::Result<()> { debug!("Help::write_subcommand"); let spec_vals = &self.sc_spec_vals(cmd); let about = cmd .get_about() .or_else(|| cmd.get_long_about()) .unwrap_or(""); self.subcmd(sc_str, next_line_help, longest)?; self.help(None, about, spec_vals, next_line_help, longest) } fn sc_spec_vals(&self, a: &Command) -> String { debug!("Help::sc_spec_vals: a={}", a.get_name()); let mut spec_vals = vec![]; if 0 < a.get_all_aliases().count() || 0 < a.get_all_short_flag_aliases().count() { debug!( "Help::spec_vals: Found aliases...{:?}", a.get_all_aliases().collect::>() ); debug!( "Help::spec_vals: Found short flag aliases...{:?}", a.get_all_short_flag_aliases().collect::>() ); let mut short_als = a .get_visible_short_flag_aliases() .map(|a| format!("-{}", a)) .collect::>(); let als = a.get_visible_aliases().map(|s| s.to_string()); short_als.extend(als); let all_als = short_als.join(", "); if !all_als.is_empty() { spec_vals.push(format!(" [aliases: {}]", all_als)); } } spec_vals.join(" ") } fn subcommand_next_line_help( &self, cmd: &Command<'help>, spec_vals: &str, longest: usize, ) -> bool { if self.next_line_help | self.use_long { // setting_next_line true } else { // force_next_line let h = cmd.get_about().unwrap_or(""); let h_w = display_width(h) + display_width(spec_vals); let taken = longest + 12; self.term_w >= taken && (taken as f32 / self.term_w as f32) > 0.40 && h_w > (self.term_w - taken) } } /// Writes subcommand to the wrapped stream. fn subcmd(&mut self, sc_str: &str, next_line_help: bool, longest: usize) -> io::Result<()> { self.none(TAB)?; self.good(sc_str)?; if !next_line_help { let width = display_width(sc_str); self.spaces(width.max(longest + 4) - width)?; } Ok(()) } } // Methods to write Parser help. impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { /// Writes help for all arguments (options, flags, args, subcommands) /// including titles of a Parser Object to the wrapped stream. pub(crate) fn write_all_args(&mut self) -> io::Result<()> { debug!("Help::write_all_args"); let pos = self .cmd .get_positionals_with_no_heading() .filter(|arg| should_show_arg(self.use_long, arg)) .collect::>(); let non_pos = self .cmd .get_non_positionals_with_no_heading() .filter(|arg| should_show_arg(self.use_long, arg)) .collect::>(); let subcmds = self.cmd.has_visible_subcommands(); let custom_headings = self .cmd .get_arguments() .filter_map(|arg| arg.get_help_heading()) .collect::>(); let mut first = if !pos.is_empty() { // Write positional args if any self.warning("ARGS:\n")?; self.write_args_unsorted(&pos)?; false } else { true }; if !non_pos.is_empty() { if !first { self.none("\n\n")?; } self.warning("OPTIONS:\n")?; self.write_args(&non_pos, "OPTIONS")?; first = false; } if !custom_headings.is_empty() { for heading in custom_headings { let args = self .cmd .get_arguments() .filter(|a| { if let Some(help_heading) = a.get_help_heading() { return help_heading == heading; } false }) .filter(|arg| should_show_arg(self.use_long, arg)) .collect::>(); if !args.is_empty() { if !first { self.none("\n\n")?; } self.warning(format!("{}:\n", heading))?; self.write_args(&args, heading)?; first = false } } } if subcmds { if !first { self.none("\n\n")?; } self.warning( self.cmd .get_subcommand_help_heading() .unwrap_or("SUBCOMMANDS"), )?; self.warning(":\n")?; self.write_subcommands(self.cmd)?; } Ok(()) } /// Will use next line help on writing subcommands. fn will_subcommands_wrap<'a>( &self, subcommands: impl IntoIterator>, longest: usize, ) -> bool where 'help: 'a, { subcommands .into_iter() .filter(|&subcommand| should_show_subcommand(subcommand)) .any(|subcommand| { let spec_vals = &self.sc_spec_vals(subcommand); self.subcommand_next_line_help(subcommand, spec_vals, longest) }) } /// Writes help for subcommands of a Parser Object to the wrapped stream. fn write_subcommands(&mut self, cmd: &Command<'help>) -> io::Result<()> { debug!("Help::write_subcommands"); // The shortest an arg can legally be is 2 (i.e. '-x') let mut longest = 2; let mut ord_v = Vec::new(); for subcommand in cmd .get_subcommands() .filter(|subcommand| should_show_subcommand(subcommand)) { let mut sc_str = String::new(); sc_str.push_str(subcommand.get_name()); if let Some(short) = subcommand.get_short_flag() { write!(sc_str, " -{}", short).unwrap(); } if let Some(long) = subcommand.get_long_flag() { write!(sc_str, " --{}", long).unwrap(); } longest = longest.max(display_width(&sc_str)); ord_v.push((subcommand.get_display_order(), sc_str, subcommand)); } ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); debug!("Help::write_subcommands longest = {}", longest); let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest); let mut first = true; for (_, sc_str, sc) in &ord_v { if first { first = false; } else { self.none("\n")?; } self.write_subcommand(sc_str, sc, next_line_help, longest)?; } Ok(()) } /// Writes binary name of a Parser Object to the wrapped stream. fn write_display_name(&mut self) -> io::Result<()> { debug!("Help::write_display_name"); let display_name = text_wrapper( &self .cmd .get_display_name() .unwrap_or_else(|| self.cmd.get_name()) .replace("{n}", "\n"), self.term_w, ); self.good(&display_name)?; Ok(()) } /// Writes binary name of a Parser Object to the wrapped stream. fn write_bin_name(&mut self) -> io::Result<()> { debug!("Help::write_bin_name"); let bin_name = if let Some(bn) = self.cmd.get_bin_name() { if bn.contains(' ') { // In case we're dealing with subcommands i.e. git mv is translated to git-mv bn.replace(' ', "-") } else { text_wrapper(&self.cmd.get_name().replace("{n}", "\n"), self.term_w) } } else { text_wrapper(&self.cmd.get_name().replace("{n}", "\n"), self.term_w) }; self.good(&bin_name)?; Ok(()) } } // Methods to write Parser help using templates. impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { /// Write help to stream for the parser in the format defined by the template. /// /// For details about the template language see [`Command::help_template`]. /// /// [`Command::help_template`]: Command::help_template() fn write_templated_help(&mut self, template: &str) -> io::Result<()> { debug!("Help::write_templated_help"); // The strategy is to copy the template from the reader to wrapped stream // until a tag is found. Depending on its value, the appropriate content is copied // to the wrapped stream. // The copy from template is then resumed, repeating this sequence until reading // the complete template. macro_rules! tags { ( match $part:ident { $( $tag:expr => $action:stmt )* } ) => { match $part { $( part if part.starts_with(concat!($tag, "}")) => { $action let rest = &part[$tag.len()+1..]; self.none(rest)?; } )* // Unknown tag, write it back. part => { self.none("{")?; self.none(part)?; } } }; } let mut parts = template.split('{'); if let Some(first) = parts.next() { self.none(first)?; } for part in parts { tags! { match part { "name" => { self.write_display_name()?; } "bin" => { self.write_bin_name()?; } "version" => { self.write_version()?; } "author" => { self.write_author(false, false)?; } "author-with-newline" => { self.write_author(false, true)?; } "author-section" => { self.write_author(true, true)?; } "about" => { self.write_about(false, false)?; } "about-with-newline" => { self.write_about(false, true)?; } "about-section" => { self.write_about(true, true)?; } "usage-heading" => { self.warning("USAGE:")?; } "usage" => { self.none(self.usage.create_usage_no_title(&[]))?; } "all-args" => { self.write_all_args()?; } "options" => { // Include even those with a heading as we don't have a good way of // handling help_heading in the template. self.write_args(&self.cmd.get_non_positionals().collect::>(), "options")?; } "positionals" => { self.write_args(&self.cmd.get_positionals().collect::>(), "positionals")?; } "subcommands" => { self.write_subcommands(self.cmd)?; } "after-help" => { self.write_after_help()?; } "before-help" => { self.write_before_help()?; } } } } Ok(()) } } pub(crate) fn dimensions() -> Option<(usize, usize)> { #[cfg(not(feature = "wrap_help"))] return None; #[cfg(feature = "wrap_help")] terminal_size::terminal_size().map(|(w, h)| (w.0.into(), h.0.into())) } const TAB: &str = " "; const TAB_WIDTH: usize = 4; pub(crate) enum HelpWriter<'writer> { Normal(&'writer mut dyn Write), Buffer(&'writer mut Colorizer), } fn should_show_arg(use_long: bool, arg: &Arg) -> bool { debug!("should_show_arg: use_long={:?}, arg={}", use_long, arg.name); if arg.is_hide_set() { return false; } (!arg.is_hide_long_help_set() && use_long) || (!arg.is_hide_short_help_set() && !use_long) || arg.is_next_line_help_set() } fn should_show_subcommand(subcommand: &Command) -> bool { !subcommand.is_hide_set() } fn text_wrapper(help: &str, width: usize) -> String { let wrapper = textwrap::Options::new(width) .break_words(false) .word_splitter(textwrap::WordSplitter::NoHyphenation); help.lines() .map(|line| textwrap::fill(line, &wrapper)) .collect::>() .join("\n") } #[cfg(test)] mod test { use super::*; #[test] fn wrap_help_last_word() { let help = String::from("foo bar baz"); assert_eq!(text_wrapper(&help, 5), "foo\nbar\nbaz"); } #[test] fn display_width_handles_non_ascii() { // Popular Danish tongue-twister, the name of a fruit dessert. let text = "rødgrød med fløde"; assert_eq!(display_width(text), 17); // Note that the string width is smaller than the string // length. This is due to the precomposed non-ASCII letters: assert_eq!(text.len(), 20); } #[test] fn display_width_handles_emojis() { let text = "😂"; // There is a single `char`... assert_eq!(text.chars().count(), 1); // but it is double-width: assert_eq!(display_width(text), 2); // This is much less than the byte length: assert_eq!(text.len(), 4); } }