From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/clap/src/output/fmt.rs | 158 ++++++ vendor/clap/src/output/help.rs | 1176 +++++++++++++++++++++++++++++++++++++++ vendor/clap/src/output/mod.rs | 7 + vendor/clap/src/output/usage.rs | 458 +++++++++++++++ 4 files changed, 1799 insertions(+) create mode 100644 vendor/clap/src/output/fmt.rs create mode 100644 vendor/clap/src/output/help.rs create mode 100644 vendor/clap/src/output/mod.rs create mode 100644 vendor/clap/src/output/usage.rs (limited to 'vendor/clap/src/output') diff --git a/vendor/clap/src/output/fmt.rs b/vendor/clap/src/output/fmt.rs new file mode 100644 index 000000000..dc1f46e08 --- /dev/null +++ b/vendor/clap/src/output/fmt.rs @@ -0,0 +1,158 @@ +use crate::util::color::ColorChoice; + +use std::{ + fmt::{self, Display, Formatter}, + io::{self, Write}, +}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum Stream { + Stdout, + Stderr, +} + +#[derive(Clone, Debug)] +pub(crate) struct Colorizer { + stream: Stream, + #[allow(unused)] + color_when: ColorChoice, + pieces: Vec<(String, Style)>, +} + +impl Colorizer { + #[inline(never)] + pub(crate) fn new(stream: Stream, color_when: ColorChoice) -> Self { + Colorizer { + stream, + color_when, + pieces: vec![], + } + } + + #[inline(never)] + pub(crate) fn good(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Style::Good)); + } + + #[inline(never)] + pub(crate) fn warning(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Style::Warning)); + } + + #[inline(never)] + pub(crate) fn error(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Style::Error)); + } + + #[inline(never)] + #[allow(dead_code)] + pub(crate) fn hint(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Style::Hint)); + } + + #[inline(never)] + pub(crate) fn none(&mut self, msg: impl Into) { + self.pieces.push((msg.into(), Style::Default)); + } +} + +/// Printing methods. +impl Colorizer { + #[cfg(feature = "color")] + pub(crate) fn print(&self) -> io::Result<()> { + use termcolor::{BufferWriter, ColorChoice as DepColorChoice, ColorSpec, WriteColor}; + + let color_when = match self.color_when { + ColorChoice::Always => DepColorChoice::Always, + ColorChoice::Auto if is_a_tty(self.stream) => DepColorChoice::Auto, + _ => DepColorChoice::Never, + }; + + let writer = match self.stream { + Stream::Stderr => BufferWriter::stderr(color_when), + Stream::Stdout => BufferWriter::stdout(color_when), + }; + + let mut buffer = writer.buffer(); + + for piece in &self.pieces { + let mut color = ColorSpec::new(); + match piece.1 { + Style::Good => { + color.set_fg(Some(termcolor::Color::Green)); + } + Style::Warning => { + color.set_fg(Some(termcolor::Color::Yellow)); + } + Style::Error => { + color.set_fg(Some(termcolor::Color::Red)); + color.set_bold(true); + } + Style::Hint => { + color.set_dimmed(true); + } + Style::Default => {} + } + + buffer.set_color(&color)?; + buffer.write_all(piece.0.as_bytes())?; + buffer.reset()?; + } + + writer.print(&buffer) + } + + #[cfg(not(feature = "color"))] + pub(crate) fn print(&self) -> io::Result<()> { + // [e]println can't be used here because it panics + // if something went wrong. We don't want that. + match self.stream { + Stream::Stdout => { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + write!(stdout, "{}", self) + } + Stream::Stderr => { + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); + write!(stderr, "{}", self) + } + } + } +} + +/// Color-unaware printing. Never uses coloring. +impl Display for Colorizer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for piece in &self.pieces { + Display::fmt(&piece.0, f)?; + } + + Ok(()) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Style { + Good, + Warning, + Error, + Hint, + Default, +} + +impl Default for Style { + fn default() -> Self { + Self::Default + } +} + +#[cfg(feature = "color")] +fn is_a_tty(stream: Stream) -> bool { + let stream = match stream { + Stream::Stdout => atty::Stream::Stdout, + Stream::Stderr => atty::Stream::Stderr, + }; + + atty::is(stream) +} diff --git a/vendor/clap/src/output/help.rs b/vendor/clap/src/output/help.rs new file mode 100644 index 000000000..eb53bfa54 --- /dev/null +++ b/vendor/clap/src/output/help.rs @@ -0,0 +1,1176 @@ +// 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); + } +} diff --git a/vendor/clap/src/output/mod.rs b/vendor/clap/src/output/mod.rs new file mode 100644 index 000000000..e32aac26a --- /dev/null +++ b/vendor/clap/src/output/mod.rs @@ -0,0 +1,7 @@ +mod help; +mod usage; + +pub(crate) mod fmt; + +pub(crate) use self::help::{Help, HelpWriter}; +pub(crate) use self::usage::Usage; diff --git a/vendor/clap/src/output/usage.rs b/vendor/clap/src/output/usage.rs new file mode 100644 index 000000000..7adaf58c6 --- /dev/null +++ b/vendor/clap/src/output/usage.rs @@ -0,0 +1,458 @@ +use indexmap::IndexSet; + +// Internal +use crate::builder::AppSettings as AS; +use crate::builder::{Arg, ArgPredicate, Command}; +use crate::parser::ArgMatcher; +use crate::util::ChildGraph; +use crate::util::Id; +use crate::INTERNAL_ERROR_MSG; + +pub(crate) struct Usage<'help, 'cmd> { + cmd: &'cmd Command<'help>, + required: Option<&'cmd ChildGraph>, +} + +impl<'help, 'cmd> Usage<'help, 'cmd> { + pub(crate) fn new(cmd: &'cmd Command<'help>) -> Self { + Usage { + cmd, + required: None, + } + } + + pub(crate) fn required(mut self, required: &'cmd ChildGraph) -> Self { + self.required = Some(required); + self + } + + // Creates a usage string for display. This happens just after all arguments were parsed, but before + // any subcommands have been parsed (so as to give subcommands their own usage recursively) + pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> String { + debug!("Usage::create_usage_with_title"); + let mut usage = String::with_capacity(75); + usage.push_str("USAGE:\n "); + usage.push_str(&*self.create_usage_no_title(used)); + usage + } + + // Creates a usage string (*without title*) if one was not provided by the user manually. + pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> String { + debug!("Usage::create_usage_no_title"); + if let Some(u) = self.cmd.get_override_usage() { + String::from(&*u) + } else if used.is_empty() { + self.create_help_usage(true) + } else { + self.create_smart_usage(used) + } + } + + // Creates a usage string for display in help messages (i.e. not for errors) + fn create_help_usage(&self, incl_reqs: bool) -> String { + debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs); + let mut usage = String::with_capacity(75); + let name = self + .cmd + .get_usage_name() + .or_else(|| self.cmd.get_bin_name()) + .unwrap_or_else(|| self.cmd.get_name()); + usage.push_str(name); + let req_string = if incl_reqs { + self.get_required_usage_from(&[], None, false) + .iter() + .fold(String::new(), |a, s| a + " " + s) + } else { + String::new() + }; + + if self.needs_options_tag() { + usage.push_str(" [OPTIONS]"); + } + + let allow_missing_positional = self.cmd.is_allow_missing_positional_set(); + if !allow_missing_positional { + usage.push_str(&req_string); + } + + let has_last = self.cmd.get_positionals().any(|p| p.is_last_set()); + // places a '--' in the usage string if there are args and options + // supporting multiple values + if self + .cmd + .get_non_positionals() + .any(|o| o.is_multiple_values_set()) + && self.cmd.get_positionals().any(|p| !p.is_required_set()) + && !(self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set()) + && !has_last + { + usage.push_str(" [--]"); + } + let not_req_or_hidden = + |p: &Arg| (!p.is_required_set() || p.is_last_set()) && !p.is_hide_set(); + if self.cmd.get_positionals().any(not_req_or_hidden) { + if let Some(args_tag) = self.get_args_tag(incl_reqs) { + usage.push_str(&*args_tag); + } else { + usage.push_str(" [ARGS]"); + } + if has_last && incl_reqs { + let pos = self + .cmd + .get_positionals() + .find(|p| p.is_last_set()) + .expect(INTERNAL_ERROR_MSG); + debug!("Usage::create_help_usage: '{}' has .last(true)", pos.name); + let req = pos.is_required_set(); + if req && self.cmd.get_positionals().any(|p| !p.is_required_set()) { + usage.push_str(" -- <"); + } else if req { + usage.push_str(" [--] <"); + } else { + usage.push_str(" [-- <"); + } + usage.push_str(&*pos.name_no_brackets()); + usage.push('>'); + usage.push_str(pos.multiple_str()); + if !req { + usage.push(']'); + } + } + } + + if allow_missing_positional { + usage.push_str(&req_string); + } + + // incl_reqs is only false when this function is called recursively + if self.cmd.has_visible_subcommands() && incl_reqs + || self.cmd.is_allow_external_subcommands_set() + { + let placeholder = self.cmd.get_subcommand_value_name().unwrap_or("SUBCOMMAND"); + #[allow(deprecated)] + if self.cmd.is_subcommand_negates_reqs_set() + || self.cmd.is_args_conflicts_with_subcommands_set() + { + usage.push_str("\n "); + if !self.cmd.is_args_conflicts_with_subcommands_set() { + usage.push_str(&*self.create_help_usage(false)); + } else { + usage.push_str(&*name); + } + usage.push_str(" <"); + usage.push_str(placeholder); + usage.push('>'); + } else if self.cmd.is_subcommand_required_set() + || self.cmd.is_set(AS::SubcommandRequiredElseHelp) + { + usage.push_str(" <"); + usage.push_str(placeholder); + usage.push('>'); + } else { + usage.push_str(" ["); + usage.push_str(placeholder); + usage.push(']'); + } + } + let usage = usage.trim().to_owned(); + debug!("Usage::create_help_usage: usage={}", usage); + usage + } + + // Creates a context aware usage string, or "smart usage" from currently used + // args, and requirements + fn create_smart_usage(&self, used: &[Id]) -> String { + debug!("Usage::create_smart_usage"); + let mut usage = String::with_capacity(75); + + let r_string = self + .get_required_usage_from(used, None, true) + .iter() + .fold(String::new(), |acc, s| acc + " " + s); + + usage.push_str( + self.cmd + .get_usage_name() + .or_else(|| self.cmd.get_bin_name()) + .unwrap_or_else(|| self.cmd.get_name()), + ); + usage.push_str(&*r_string); + if self.cmd.is_subcommand_required_set() { + usage.push_str(" <"); + usage.push_str(self.cmd.get_subcommand_value_name().unwrap_or("SUBCOMMAND")); + usage.push('>'); + } + usage.shrink_to_fit(); + usage + } + + // Gets the `[ARGS]` tag for the usage string + fn get_args_tag(&self, incl_reqs: bool) -> Option { + debug!("Usage::get_args_tag; incl_reqs = {:?}", incl_reqs); + let mut count = 0; + for pos in self + .cmd + .get_positionals() + .filter(|pos| !pos.is_required_set()) + .filter(|pos| !pos.is_hide_set()) + .filter(|pos| !pos.is_last_set()) + { + debug!("Usage::get_args_tag:iter:{}", pos.name); + let required = self.cmd.groups_for_arg(&pos.id).any(|grp_s| { + debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); + // if it's part of a required group we don't want to count it + self.cmd.get_groups().any(|g| g.required && (g.id == grp_s)) + }); + if !required { + count += 1; + debug!( + "Usage::get_args_tag:iter: {} Args not required or hidden", + count + ); + } + } + + if !self.cmd.is_dont_collapse_args_in_usage_set() && count > 1 { + debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]"); + + // [ARGS] + None + } else if count == 1 && incl_reqs { + let pos = self + .cmd + .get_positionals() + .find(|pos| { + !pos.is_required_set() + && !pos.is_hide_set() + && !pos.is_last_set() + && !self.cmd.groups_for_arg(&pos.id).any(|grp_s| { + debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); + // if it's part of a required group we don't want to count it + self.cmd.get_groups().any(|g| g.required && (g.id == grp_s)) + }) + }) + .expect(INTERNAL_ERROR_MSG); + + debug!( + "Usage::get_args_tag:iter: Exactly one, returning '{}'", + pos.name + ); + + Some(format!( + " [{}]{}", + pos.name_no_brackets(), + pos.multiple_str() + )) + } else if self.cmd.is_dont_collapse_args_in_usage_set() + && self.cmd.has_positionals() + && incl_reqs + { + debug!("Usage::get_args_tag:iter: Don't collapse returning all"); + Some( + self.cmd + .get_positionals() + .filter(|pos| !pos.is_required_set()) + .filter(|pos| !pos.is_hide_set()) + .filter(|pos| !pos.is_last_set()) + .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())) + .collect::>() + .join(""), + ) + } else if !incl_reqs { + debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); + let highest_req_pos = self + .cmd + .get_positionals() + .filter_map(|pos| { + if pos.is_required_set() && !pos.is_last_set() { + Some(pos.index) + } else { + None + } + }) + .max() + .unwrap_or_else(|| Some(self.cmd.get_positionals().count())); + Some( + self.cmd + .get_positionals() + .filter(|pos| pos.index <= highest_req_pos) + .filter(|pos| !pos.is_required_set()) + .filter(|pos| !pos.is_hide_set()) + .filter(|pos| !pos.is_last_set()) + .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())) + .collect::>() + .join(""), + ) + } else { + Some("".into()) + } + } + + // Determines if we need the `[OPTIONS]` tag in the usage string + fn needs_options_tag(&self) -> bool { + debug!("Usage::needs_options_tag"); + 'outer: for f in self.cmd.get_non_positionals() { + debug!("Usage::needs_options_tag:iter: f={}", f.name); + + // Don't print `[OPTIONS]` just for help or version + if f.long == Some("help") || f.long == Some("version") { + debug!("Usage::needs_options_tag:iter Option is built-in"); + continue; + } + + if f.is_hide_set() { + debug!("Usage::needs_options_tag:iter Option is hidden"); + continue; + } + if f.is_required_set() { + debug!("Usage::needs_options_tag:iter Option is required"); + continue; + } + for grp_s in self.cmd.groups_for_arg(&f.id) { + debug!("Usage::needs_options_tag:iter:iter: grp_s={:?}", grp_s); + if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) { + debug!("Usage::needs_options_tag:iter:iter: Group is required"); + continue 'outer; + } + } + + debug!("Usage::needs_options_tag:iter: [OPTIONS] required"); + return true; + } + + debug!("Usage::needs_options_tag: [OPTIONS] not required"); + false + } + + // Returns the required args in usage string form by fully unrolling all groups + // `incl_last`: should we include args that are Arg::Last? (i.e. `prog [foo] -- [last]). We + // can't do that for required usages being built for subcommands because it would look like: + // `prog [foo] -- [last] ` which is totally wrong. + pub(crate) fn get_required_usage_from( + &self, + incls: &[Id], + matcher: Option<&ArgMatcher>, + incl_last: bool, + ) -> IndexSet { + debug!( + "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}", + incls, + matcher.is_some(), + incl_last + ); + let mut ret_val = IndexSet::new(); + + let mut unrolled_reqs = IndexSet::new(); + + let required_owned; + let required = if let Some(required) = self.required { + required + } else { + required_owned = self.cmd.required_graph(); + &required_owned + }; + + for a in required.iter() { + let is_relevant = |(val, req_arg): &(ArgPredicate<'_>, Id)| -> Option { + let required = match val { + ArgPredicate::Equals(_) => { + if let Some(matcher) = matcher { + matcher.check_explicit(a, *val) + } else { + false + } + } + ArgPredicate::IsPresent => true, + }; + required.then(|| req_arg.clone()) + }; + + for aa in self.cmd.unroll_arg_requires(is_relevant, a) { + // if we don't check for duplicates here this causes duplicate error messages + // see https://github.com/clap-rs/clap/issues/2770 + unrolled_reqs.insert(aa); + } + // always include the required arg itself. it will not be enumerated + // by unroll_requirements_for_arg. + unrolled_reqs.insert(a.clone()); + } + + debug!( + "Usage::get_required_usage_from: unrolled_reqs={:?}", + unrolled_reqs + ); + + let args_in_groups = self + .cmd + .get_groups() + .filter(|gn| required.contains(&gn.id)) + .flat_map(|g| self.cmd.unroll_args_in_group(&g.id)) + .collect::>(); + + for a in unrolled_reqs + .iter() + .chain(incls.iter()) + .filter(|name| !self.cmd.get_positionals().any(|p| &&p.id == name)) + .filter(|name| !self.cmd.get_groups().any(|g| &&g.id == name)) + .filter(|name| !args_in_groups.contains(name)) + .filter(|name| { + !(matcher.is_some() + && matcher + .as_ref() + .unwrap() + .check_explicit(name, ArgPredicate::IsPresent)) + }) + { + debug!("Usage::get_required_usage_from:iter:{:?}", a); + let arg = self.cmd.find(a).expect(INTERNAL_ERROR_MSG).to_string(); + ret_val.insert(arg); + } + let mut g_vec: Vec = vec![]; + for g in unrolled_reqs + .iter() + .filter(|n| self.cmd.get_groups().any(|g| g.id == **n)) + { + // don't print requirement for required groups that have an arg. + if let Some(m) = matcher { + let have_group_entry = self + .cmd + .unroll_args_in_group(g) + .iter() + .any(|arg| m.check_explicit(arg, ArgPredicate::IsPresent)); + if have_group_entry { + continue; + } + } + + let elem = self.cmd.format_group(g); + if !g_vec.contains(&elem) { + g_vec.push(elem); + } + } + ret_val.extend(g_vec); + + let mut pvec = unrolled_reqs + .iter() + .chain(incls.iter()) + .filter(|a| self.cmd.get_positionals().any(|p| &&p.id == a)) + .filter(|&pos| { + matcher.map_or(true, |m| !m.check_explicit(pos, ArgPredicate::IsPresent)) + }) + .filter_map(|pos| self.cmd.find(pos)) + .filter(|&pos| incl_last || !pos.is_last_set()) + .filter(|pos| !args_in_groups.contains(&pos.id)) + .map(|pos| (pos.index.unwrap(), pos)) + .collect::>(); + pvec.sort_by_key(|(ind, _)| *ind); // sort by index + + for (_, p) in pvec { + debug!("Usage::get_required_usage_from:push:{:?}", p.id); + if !args_in_groups.contains(&p.id) { + ret_val.insert(p.to_string()); + } + } + + debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val); + ret_val + } +} -- cgit v1.2.3