diff options
Diffstat (limited to 'vendor/clap_builder/src/output')
-rw-r--r-- | vendor/clap_builder/src/output/fmt.rs | 83 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/help.rs | 39 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/help_template.rs | 1198 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/mod.rs | 23 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/textwrap/core.rs | 158 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/textwrap/mod.rs | 122 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/textwrap/word_separators.rs | 92 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/textwrap/wrap_algorithms.rs | 63 | ||||
-rw-r--r-- | vendor/clap_builder/src/output/usage.rs | 529 |
9 files changed, 2307 insertions, 0 deletions
diff --git a/vendor/clap_builder/src/output/fmt.rs b/vendor/clap_builder/src/output/fmt.rs new file mode 100644 index 0000000..0c9a24f --- /dev/null +++ b/vendor/clap_builder/src/output/fmt.rs @@ -0,0 +1,83 @@ +use crate::builder::StyledStr; +use crate::util::color::ColorChoice; + +#[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, + content: StyledStr, +} + +impl Colorizer { + pub(crate) fn new(stream: Stream, color_when: ColorChoice) -> Self { + Colorizer { + stream, + color_when, + content: Default::default(), + } + } + + pub(crate) fn with_content(mut self, content: StyledStr) -> Self { + self.content = content; + self + } +} + +/// Printing methods. +impl Colorizer { + #[cfg(feature = "color")] + pub(crate) fn print(&self) -> std::io::Result<()> { + let color_when = match self.color_when { + ColorChoice::Always => anstream::ColorChoice::Always, + ColorChoice::Auto => anstream::ColorChoice::Auto, + ColorChoice::Never => anstream::ColorChoice::Never, + }; + + let mut stdout; + let mut stderr; + let writer: &mut dyn std::io::Write = match self.stream { + Stream::Stderr => { + stderr = anstream::AutoStream::new(std::io::stderr().lock(), color_when); + &mut stderr + } + Stream::Stdout => { + stdout = anstream::AutoStream::new(std::io::stdout().lock(), color_when); + &mut stdout + } + }; + + self.content.write_to(writer) + } + + #[cfg(not(feature = "color"))] + pub(crate) fn print(&self) -> std::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(); + self.content.write_to(&mut stdout) + } + Stream::Stderr => { + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); + self.content.write_to(&mut stderr) + } + } + } +} + +/// Color-unaware printing. Never uses coloring. +impl std::fmt::Display for Colorizer { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.content.fmt(f) + } +} diff --git a/vendor/clap_builder/src/output/help.rs b/vendor/clap_builder/src/output/help.rs new file mode 100644 index 0000000..a5073a9 --- /dev/null +++ b/vendor/clap_builder/src/output/help.rs @@ -0,0 +1,39 @@ +#![cfg_attr(not(feature = "help"), allow(unused_variables))] + +// Internal +use crate::builder::Command; +use crate::builder::StyledStr; +use crate::output::Usage; + +/// Writes the parser help to the wrapped stream. +pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_>, use_long: bool) { + debug!("write_help"); + + if let Some(h) = cmd.get_override_help() { + writer.push_styled(h); + } else { + #[cfg(feature = "help")] + { + use super::AutoHelp; + use super::HelpTemplate; + if let Some(tmpl) = cmd.get_help_template() { + HelpTemplate::new(writer, cmd, usage, use_long) + .write_templated_help(tmpl.as_styled_str()); + } else { + AutoHelp::new(writer, cmd, usage, use_long).write_help(); + } + } + + #[cfg(not(feature = "help"))] + { + debug!("write_help: no help, `Command::override_help` and `help` is missing"); + } + } + + // Remove any lines from unused sections + writer.trim_start_lines(); + // Remove any whitespace caused by book keeping + writer.trim_end(); + // Ensure there is still a trailing newline + writer.push_str("\n"); +} diff --git a/vendor/clap_builder/src/output/help_template.rs b/vendor/clap_builder/src/output/help_template.rs new file mode 100644 index 0000000..dff15ff --- /dev/null +++ b/vendor/clap_builder/src/output/help_template.rs @@ -0,0 +1,1198 @@ +// HACK: for rust 1.64 (1.68 doesn't need this since this is in lib.rs) +// +// Wanting consistency in our calls +#![allow(clippy::write_with_newline)] + +// Std +use std::borrow::Cow; +use std::cmp; +use std::usize; + +// Internal +use crate::builder::PossibleValue; +use crate::builder::Str; +use crate::builder::StyledStr; +use crate::builder::Styles; +use crate::builder::{Arg, Command}; +use crate::output::display_width; +use crate::output::wrap; +use crate::output::Usage; +use crate::output::TAB; +use crate::output::TAB_WIDTH; +use crate::util::FlatSet; + +/// `clap` auto-generated help writer +pub(crate) struct AutoHelp<'cmd, 'writer> { + template: HelpTemplate<'cmd, 'writer>, +} + +// Public Functions +impl<'cmd, 'writer> AutoHelp<'cmd, 'writer> { + /// Create a new `HelpTemplate` instance. + pub(crate) fn new( + writer: &'writer mut StyledStr, + cmd: &'cmd Command, + usage: &'cmd Usage<'cmd>, + use_long: bool, + ) -> Self { + Self { + template: HelpTemplate::new(writer, cmd, usage, use_long), + } + } + + pub(crate) fn write_help(&mut self) { + let pos = self + .template + .cmd + .get_positionals() + .any(|arg| should_show_arg(self.template.use_long, arg)); + let non_pos = self + .template + .cmd + .get_non_positionals() + .any(|arg| should_show_arg(self.template.use_long, arg)); + let subcmds = self.template.cmd.has_visible_subcommands(); + + let template = if non_pos || pos || subcmds { + DEFAULT_TEMPLATE + } else { + DEFAULT_NO_ARGS_TEMPLATE + }; + self.template.write_templated_help(template); + } +} + +const DEFAULT_TEMPLATE: &str = "\ +{before-help}{about-with-newline} +{usage-heading} {usage} + +{all-args}{after-help}\ + "; + +const DEFAULT_NO_ARGS_TEMPLATE: &str = "\ +{before-help}{about-with-newline} +{usage-heading} {usage}{after-help}\ + "; + +/// `clap` HelpTemplate Writer. +/// +/// Wraps a writer stream providing different methods to generate help for `clap` objects. +pub(crate) struct HelpTemplate<'cmd, 'writer> { + writer: &'writer mut StyledStr, + cmd: &'cmd Command, + styles: &'cmd Styles, + usage: &'cmd Usage<'cmd>, + next_line_help: bool, + term_w: usize, + use_long: bool, +} + +// Public Functions +impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { + /// Create a new `HelpTemplate` instance. + pub(crate) fn new( + writer: &'writer mut StyledStr, + cmd: &'cmd Command, + usage: &'cmd Usage<'cmd>, + use_long: bool, + ) -> Self { + debug!( + "HelpTemplate::new cmd={}, use_long={}", + cmd.get_name(), + use_long + ); + let term_w = Self::term_w(cmd); + let next_line_help = cmd.is_next_line_help_set(); + + HelpTemplate { + writer, + cmd, + styles: cmd.get_styles(), + usage, + next_line_help, + term_w, + use_long, + } + } + + #[cfg(not(feature = "unstable-v5"))] + fn term_w(cmd: &'cmd Command) -> usize { + match cmd.get_term_width() { + Some(0) => usize::MAX, + Some(w) => w, + None => { + let (current_width, _h) = dimensions(); + let current_width = current_width.unwrap_or(100); + let max_width = match cmd.get_max_term_width() { + None | Some(0) => usize::MAX, + Some(mw) => mw, + }; + cmp::min(current_width, max_width) + } + } + } + + #[cfg(feature = "unstable-v5")] + fn term_w(cmd: &'cmd Command) -> usize { + let term_w = match cmd.get_term_width() { + Some(0) => usize::MAX, + Some(w) => w, + None => { + let (current_width, _h) = dimensions(); + current_width.unwrap_or(usize::MAX) + } + }; + + let max_term_w = match cmd.get_max_term_width() { + Some(0) => usize::MAX, + Some(mw) => mw, + None => 100, + }; + + cmp::min(term_w, max_term_w) + } + + /// 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() + pub(crate) fn write_templated_help(&mut self, template: &str) { + debug!("HelpTemplate::write_templated_help"); + use std::fmt::Write as _; + + let mut parts = template.split('{'); + if let Some(first) = parts.next() { + self.writer.push_str(first); + } + for part in parts { + if let Some((tag, rest)) = part.split_once('}') { + match tag { + "name" => { + self.write_display_name(); + } + #[cfg(not(feature = "unstable-v5"))] + "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" => { + let _ = write!( + self.writer, + "{}Usage:{}", + self.styles.get_usage().render(), + self.styles.get_usage().render_reset() + ); + } + "usage" => { + self.writer.push_styled( + &self.usage.create_usage_no_title(&[]).unwrap_or_default(), + ); + } + "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::<Vec<_>>(), + "options", + option_sort_key, + ); + } + "positionals" => { + self.write_args( + &self.cmd.get_positionals().collect::<Vec<_>>(), + "positionals", + positional_sort_key, + ); + } + "subcommands" => { + self.write_subcommands(self.cmd); + } + "tab" => { + self.writer.push_str(TAB); + } + "after-help" => { + self.write_after_help(); + } + "before-help" => { + self.write_before_help(); + } + _ => { + let _ = write!(self.writer, "{{{tag}}}"); + } + } + self.writer.push_str(rest); + } + } + } +} + +/// Basic template methods +impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { + /// Writes binary name of a Parser Object to the wrapped stream. + fn write_display_name(&mut self) { + debug!("HelpTemplate::write_display_name"); + + let display_name = wrap( + &self + .cmd + .get_display_name() + .unwrap_or_else(|| self.cmd.get_name()) + .replace("{n}", "\n"), + self.term_w, + ); + self.writer.push_string(display_name); + } + + /// Writes binary name of a Parser Object to the wrapped stream. + #[cfg(not(feature = "unstable-v5"))] + fn write_bin_name(&mut self) { + debug!("HelpTemplate::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 { + wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w) + } + } else { + wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w) + }; + self.writer.push_string(bin_name); + } + + fn write_version(&mut self) { + let version = self + .cmd + .get_version() + .or_else(|| self.cmd.get_long_version()); + if let Some(output) = version { + self.writer.push_string(wrap(output, self.term_w)); + } + } + + fn write_author(&mut self, before_new_line: bool, after_new_line: bool) { + if let Some(author) = self.cmd.get_author() { + if before_new_line { + self.writer.push_str("\n"); + } + self.writer.push_string(wrap(author, self.term_w)); + if after_new_line { + self.writer.push_str("\n"); + } + } + } + + fn write_about(&mut self, before_new_line: bool, after_new_line: bool) { + 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.writer.push_str("\n"); + } + let mut output = output.clone(); + output.replace_newline_var(); + output.wrap(self.term_w); + self.writer.push_styled(&output); + if after_new_line { + self.writer.push_str("\n"); + } + } + } + + fn write_before_help(&mut self) { + debug!("HelpTemplate::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 { + let mut output = output.clone(); + output.replace_newline_var(); + output.wrap(self.term_w); + self.writer.push_styled(&output); + self.writer.push_str("\n\n"); + } + } + + fn write_after_help(&mut self) { + debug!("HelpTemplate::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.writer.push_str("\n\n"); + let mut output = output.clone(); + output.replace_newline_var(); + output.wrap(self.term_w); + self.writer.push_styled(&output); + } + } +} + +/// Arg handling +impl<'cmd, 'writer> HelpTemplate<'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) { + debug!("HelpTemplate::write_all_args"); + use std::fmt::Write as _; + let header = &self.styles.get_header(); + + let pos = self + .cmd + .get_positionals() + .filter(|a| a.get_help_heading().is_none()) + .filter(|arg| should_show_arg(self.use_long, arg)) + .collect::<Vec<_>>(); + let non_pos = self + .cmd + .get_non_positionals() + .filter(|a| a.get_help_heading().is_none()) + .filter(|arg| should_show_arg(self.use_long, arg)) + .collect::<Vec<_>>(); + let subcmds = self.cmd.has_visible_subcommands(); + + let custom_headings = self + .cmd + .get_arguments() + .filter_map(|arg| arg.get_help_heading()) + .collect::<FlatSet<_>>(); + + let flatten = self.cmd.is_flatten_help_set(); + + let mut first = true; + + if subcmds && !flatten { + if !first { + self.writer.push_str("\n\n"); + } + first = false; + let default_help_heading = Str::from("Commands"); + let help_heading = self + .cmd + .get_subcommand_help_heading() + .unwrap_or(&default_help_heading); + let _ = write!( + self.writer, + "{}{help_heading}:{}\n", + header.render(), + header.render_reset() + ); + + self.write_subcommands(self.cmd); + } + + if !pos.is_empty() { + if !first { + self.writer.push_str("\n\n"); + } + first = false; + // Write positional args if any + let help_heading = "Arguments"; + let _ = write!( + self.writer, + "{}{help_heading}:{}\n", + header.render(), + header.render_reset() + ); + self.write_args(&pos, "Arguments", positional_sort_key); + } + + if !non_pos.is_empty() { + if !first { + self.writer.push_str("\n\n"); + } + first = false; + let help_heading = "Options"; + let _ = write!( + self.writer, + "{}{help_heading}:{}\n", + header.render(), + header.render_reset() + ); + self.write_args(&non_pos, "Options", option_sort_key); + } + 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::<Vec<_>>(); + + if !args.is_empty() { + if !first { + self.writer.push_str("\n\n"); + } + first = false; + let _ = write!( + self.writer, + "{}{heading}:{}\n", + header.render(), + header.render_reset() + ); + self.write_args(&args, heading, option_sort_key); + } + } + } + if subcmds && flatten { + let mut cmd = self.cmd.clone(); + cmd.build(); + self.write_flat_subcommands(&cmd, &mut first); + } + } + + /// Sorts arguments by length and display order and write their help to the wrapped stream. + fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) { + debug!("HelpTemplate::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 longest_filter(arg) { + longest = longest.max(display_width(&arg.to_string())); + debug!( + "HelpTemplate::write_args: arg={:?} longest={}", + arg.get_id(), + longest + ); + } + + let key = (sort_key)(arg); + ord_v.push((key, arg)); + } + ord_v.sort_by(|a, b| a.0.cmp(&b.0)); + + let next_line_help = self.will_args_wrap(args, longest); + + for (i, (_, arg)) in ord_v.iter().enumerate() { + if i != 0 { + self.writer.push_str("\n"); + if next_line_help && self.use_long { + self.writer.push_str("\n"); + } + } + self.write_arg(arg, next_line_help, longest); + } + } + + /// Writes help for an argument to the wrapped stream. + fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) { + let spec_vals = &self.spec_vals(arg); + + self.writer.push_str(TAB); + self.short(arg); + self.long(arg); + self.writer + .push_styled(&arg.stylize_arg_suffix(self.styles, None)); + self.align_to_about(arg, next_line_help, longest); + + let about = if self.use_long { + arg.get_long_help() + .or_else(|| arg.get_help()) + .unwrap_or_default() + } else { + arg.get_help() + .or_else(|| arg.get_long_help()) + .unwrap_or_default() + }; + + self.help(Some(arg), about, spec_vals, next_line_help, longest); + } + + /// Writes argument's short command to the wrapped stream. + fn short(&mut self, arg: &Arg) { + debug!("HelpTemplate::short"); + use std::fmt::Write as _; + let literal = &self.styles.get_literal(); + + if let Some(s) = arg.get_short() { + let _ = write!( + self.writer, + "{}-{s}{}", + literal.render(), + literal.render_reset() + ); + } else if arg.get_long().is_some() { + self.writer.push_str(" "); + } + } + + /// Writes argument's long command to the wrapped stream. + fn long(&mut self, arg: &Arg) { + debug!("HelpTemplate::long"); + use std::fmt::Write as _; + let literal = &self.styles.get_literal(); + + if let Some(long) = arg.get_long() { + if arg.get_short().is_some() { + self.writer.push_str(", "); + } + let _ = write!( + self.writer, + "{}--{long}{}", + literal.render(), + literal.render_reset() + ); + } + } + + /// Write alignment padding between arg's switches/values and its about message. + fn align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize) { + debug!( + "HelpTemplate::align_to_about: arg={}, next_line_help={}, longest={}", + arg.get_id(), + next_line_help, + longest + ); + let padding = if self.use_long || next_line_help { + // long help prints messages on the next line so it doesn't need to align text + debug!("HelpTemplate::align_to_about: printing long help so skip alignment"); + 0 + } 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.get_long().is_some() { + // Only account 4 after the val + TAB_WIDTH + } else { + // Only account for ', --' + 4 after the val + TAB_WIDTH + 4 + }; + let spcs = longest + padding - self_len; + debug!( + "HelpTemplate::align_to_about: positional=false arg_len={self_len}, spaces={spcs}" + ); + + spcs + } else { + let self_len = display_width(&arg.to_string()); + let padding = TAB_WIDTH; + let spcs = longest + padding - self_len; + debug!( + "HelpTemplate::align_to_about: positional=true arg_len={self_len}, spaces={spcs}", + ); + + spcs + }; + + self.write_padding(padding); + } + + /// Writes argument's help to the wrapped stream. + fn help( + &mut self, + arg: Option<&Arg>, + about: &StyledStr, + spec_vals: &str, + next_line_help: bool, + longest: usize, + ) { + debug!("HelpTemplate::help"); + use std::fmt::Write as _; + let literal = &self.styles.get_literal(); + + // Is help on next line, if so then indent + if next_line_help { + debug!("HelpTemplate::help: Next Line...{next_line_help:?}"); + self.writer.push_str("\n"); + self.writer.push_str(TAB); + self.writer.push_str(NEXT_LINE_INDENT); + } + + let spaces = if next_line_help { + TAB.len() + NEXT_LINE_INDENT.len() + } else if let Some(true) = arg.map(|a| a.is_positional()) { + longest + TAB_WIDTH * 2 + } else { + longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4 + }; + let trailing_indent = spaces; // Don't indent any further than the first line is indented + let trailing_indent = self.get_spaces(trailing_indent); + + let mut help = about.clone(); + help.replace_newline_var(); + if !spec_vals.is_empty() { + if !help.is_empty() { + let sep = if self.use_long && arg.is_some() { + "\n\n" + } else { + " " + }; + help.push_str(sep); + } + help.push_str(spec_vals); + } + let avail_chars = self.term_w.saturating_sub(spaces); + debug!( + "HelpTemplate::help: help_width={}, spaces={}, avail={}", + spaces, + help.display_width(), + avail_chars + ); + help.wrap(avail_chars); + help.indent("", &trailing_indent); + let help_is_empty = help.is_empty(); + self.writer.push_styled(&help); + if let Some(arg) = arg { + if !arg.is_hide_possible_values_set() && self.use_long_pv(arg) { + const DASH_SPACE: usize = "- ".len(); + let possible_vals = arg.get_possible_values(); + if !possible_vals.is_empty() { + debug!("HelpTemplate::help: Found possible vals...{possible_vals:?}"); + let longest = possible_vals + .iter() + .filter(|f| !f.is_hide_set()) + .map(|f| display_width(f.get_name())) + .max() + .expect("Only called with possible value"); + + let spaces = spaces + TAB_WIDTH - DASH_SPACE; + let trailing_indent = spaces + DASH_SPACE; + let trailing_indent = self.get_spaces(trailing_indent); + + if !help_is_empty { + let _ = write!(self.writer, "\n\n{:spaces$}", ""); + } + self.writer.push_str("Possible values:"); + for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) { + let name = pv.get_name(); + + let mut descr = StyledStr::new(); + let _ = write!( + &mut descr, + "{}{name}{}", + literal.render(), + literal.render_reset() + ); + if let Some(help) = pv.get_help() { + debug!("HelpTemplate::help: Possible Value help"); + // To align help messages + let padding = longest - display_width(name); + let _ = write!(&mut descr, ": {:padding$}", ""); + descr.push_styled(help); + } + + let avail_chars = if self.term_w > trailing_indent.len() { + self.term_w - trailing_indent.len() + } else { + usize::MAX + }; + descr.replace_newline_var(); + descr.wrap(avail_chars); + descr.indent("", &trailing_indent); + + let _ = write!(self.writer, "\n{:spaces$}- ", "",); + self.writer.push_styled(&descr); + } + } + } + } + } + + /// Will use next line help on writing args. + fn will_args_wrap(&self, args: &[&Arg], 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, 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.get_help().unwrap_or_default(); + let h_w = h.display_width() + display_width(spec_vals); + let taken = if arg.is_positional() { + longest + TAB_WIDTH * 2 + } else { + longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4 + }; + 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!("HelpTemplate::spec_vals: a={a}"); + let mut spec_vals = Vec::new(); + #[cfg(feature = "env")] + if let Some(ref env) = a.env { + if !a.is_hide_env_set() { + debug!( + "HelpTemplate::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(|s| s.to_string_lossy()) + .unwrap_or_default() + ) + } else { + Default::default() + }; + 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!( + "HelpTemplate::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::<Vec<_>>() + .join(" "); + + spec_vals.push(format!("[default: {pvs}]")); + } + + let als = a + .aliases + .iter() + .filter(|&als| als.1) // visible + .map(|als| als.0.as_str()) // name + .collect::<Vec<_>>() + .join(", "); + if !als.is_empty() { + debug!("HelpTemplate::spec_vals: Found aliases...{:?}", a.aliases); + spec_vals.push(format!("[aliases: {als}]")); + } + + let als = a + .short_aliases + .iter() + .filter(|&als| als.1) // visible + .map(|&als| als.0.to_string()) // name + .collect::<Vec<_>>() + .join(", "); + if !als.is_empty() { + debug!( + "HelpTemplate::spec_vals: Found short aliases...{:?}", + a.short_aliases + ); + spec_vals.push(format!("[short aliases: {als}]")); + } + + if !a.is_hide_possible_values_set() && !self.use_long_pv(a) { + let possible_vals = a.get_possible_values(); + if !possible_vals.is_empty() { + debug!("HelpTemplate::spec_vals: Found possible vals...{possible_vals:?}"); + + let pvs = possible_vals + .iter() + .filter_map(PossibleValue::get_visible_quoted_name) + .collect::<Vec<_>>() + .join(", "); + + spec_vals.push(format!("[possible values: {pvs}]")); + } + } + let connector = if self.use_long { "\n" } else { " " }; + spec_vals.join(connector) + } + + fn get_spaces(&self, n: usize) -> String { + " ".repeat(n) + } + + fn write_padding(&mut self, amount: usize) { + use std::fmt::Write as _; + let _ = write!(self.writer, "{:amount$}", ""); + } + + fn use_long_pv(&self, arg: &Arg) -> bool { + self.use_long + && arg + .get_possible_values() + .iter() + .any(PossibleValue::should_show_help) + } +} + +/// Subcommand handling +impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { + /// Writes help for subcommands of a Parser Object to the wrapped stream. + fn write_flat_subcommands(&mut self, cmd: &Command, first: &mut bool) { + debug!( + "HelpTemplate::write_flat_subcommands, cmd={}, first={}", + cmd.get_name(), + *first + ); + use std::fmt::Write as _; + let header = &self.styles.get_header(); + + let mut ord_v = Vec::new(); + for subcommand in cmd + .get_subcommands() + .filter(|subcommand| should_show_subcommand(subcommand)) + { + ord_v.push(( + subcommand.get_display_order(), + subcommand.get_name(), + subcommand, + )); + } + ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); + for (_, _, subcommand) in ord_v { + if !*first { + self.writer.push_str("\n\n"); + } + *first = false; + + let heading = subcommand.get_usage_name_fallback(); + let about = subcommand + .get_about() + .or_else(|| subcommand.get_long_about()) + .unwrap_or_default(); + + let _ = write!( + self.writer, + "{}{heading}:{}\n", + header.render(), + header.render_reset() + ); + if !about.is_empty() { + let _ = write!(self.writer, "{about}\n",); + } + + let mut sub_help = HelpTemplate { + writer: self.writer, + cmd: subcommand, + styles: self.styles, + usage: self.usage, + next_line_help: self.next_line_help, + term_w: self.term_w, + use_long: self.use_long, + }; + let args = subcommand + .get_arguments() + .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set()) + .collect::<Vec<_>>(); + sub_help.write_args(&args, heading, option_sort_key); + if subcommand.is_flatten_help_set() { + sub_help.write_flat_subcommands(subcommand, first); + } + } + } + + /// Writes help for subcommands of a Parser Object to the wrapped stream. + fn write_subcommands(&mut self, cmd: &Command) { + debug!("HelpTemplate::write_subcommands"); + use std::fmt::Write as _; + let literal = &self.styles.get_literal(); + + // 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 styled = StyledStr::new(); + let name = subcommand.get_name(); + let _ = write!( + styled, + "{}{name}{}", + literal.render(), + literal.render_reset() + ); + if let Some(short) = subcommand.get_short_flag() { + let _ = write!( + styled, + ", {}-{short}{}", + literal.render(), + literal.render_reset() + ); + } + if let Some(long) = subcommand.get_long_flag() { + let _ = write!( + styled, + ", {}--{long}{}", + literal.render(), + literal.render_reset() + ); + } + longest = longest.max(styled.display_width()); + ord_v.push((subcommand.get_display_order(), styled, subcommand)); + } + ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); + + debug!("HelpTemplate::write_subcommands longest = {longest}"); + + let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest); + + for (i, (_, sc_str, sc)) in ord_v.into_iter().enumerate() { + if 0 < i { + self.writer.push_str("\n"); + } + self.write_subcommand(sc_str, sc, next_line_help, longest); + } + } + + /// Will use next line help on writing subcommands. + fn will_subcommands_wrap<'a>( + &self, + subcommands: impl IntoIterator<Item = &'a Command>, + longest: usize, + ) -> bool { + 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) + }) + } + + fn write_subcommand( + &mut self, + sc_str: StyledStr, + cmd: &Command, + next_line_help: bool, + longest: usize, + ) { + debug!("HelpTemplate::write_subcommand"); + + let spec_vals = &self.sc_spec_vals(cmd); + + let about = cmd + .get_about() + .or_else(|| cmd.get_long_about()) + .unwrap_or_default(); + + 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!("HelpTemplate::sc_spec_vals: a={}", a.get_name()); + let mut spec_vals = vec![]; + + let mut short_als = a + .get_visible_short_flag_aliases() + .map(|a| format!("-{a}")) + .collect::<Vec<_>>(); + 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() { + debug!( + "HelpTemplate::spec_vals: Found aliases...{:?}", + a.get_all_aliases().collect::<Vec<_>>() + ); + debug!( + "HelpTemplate::spec_vals: Found short flag aliases...{:?}", + a.get_all_short_flag_aliases().collect::<Vec<_>>() + ); + spec_vals.push(format!("[aliases: {all_als}]")); + } + + spec_vals.join(" ") + } + + fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool { + // Ignore `self.use_long` since subcommands are only shown as short help + if self.next_line_help { + // setting_next_line + true + } else { + // force_next_line + let h = cmd.get_about().unwrap_or_default(); + let h_w = h.display_width() + display_width(spec_vals); + let taken = longest + TAB_WIDTH * 2; + 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: StyledStr, next_line_help: bool, longest: usize) { + self.writer.push_str(TAB); + self.writer.push_styled(&sc_str); + if !next_line_help { + let width = sc_str.display_width(); + let padding = longest + TAB_WIDTH - width; + self.write_padding(padding); + } + } +} + +const NEXT_LINE_INDENT: &str = " "; + +type ArgSortKey = fn(arg: &Arg) -> (usize, String); + +fn positional_sort_key(arg: &Arg) -> (usize, String) { + (arg.get_index().unwrap_or(0), String::new()) +} + +fn option_sort_key(arg: &Arg) -> (usize, String) { + // 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.get_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.get_long() { + x.to_string() + } else { + let mut s = '{'.to_string(); + s.push_str(arg.get_id().as_str()); + s + }; + (arg.get_display_order(), key) +} + +pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) { + #[cfg(not(feature = "wrap_help"))] + return (None, None); + + #[cfg(feature = "wrap_help")] + terminal_size::terminal_size() + .map(|(w, h)| (Some(w.0.into()), Some(h.0.into()))) + .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES"))) +} + +#[cfg(feature = "wrap_help")] +fn parse_env(var: &str) -> Option<usize> { + some!(some!(std::env::var_os(var)).to_str()) + .parse::<usize>() + .ok() +} + +fn should_show_arg(use_long: bool, arg: &Arg) -> bool { + debug!( + "should_show_arg: use_long={:?}, arg={}", + use_long, + arg.get_id() + ); + 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 longest_filter(arg: &Arg) -> bool { + arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none() +} + +#[cfg(test)] +mod test { + #[test] + #[cfg(feature = "wrap_help")] + fn wrap_help_last_word() { + use super::*; + + let help = String::from("foo bar baz"); + assert_eq!(wrap(&help, 5), "foo\nbar\nbaz"); + } + + #[test] + #[cfg(feature = "unicode")] + fn display_width_handles_non_ascii() { + use super::*; + + // 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] + #[cfg(feature = "unicode")] + fn display_width_handles_emojis() { + use super::*; + + 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_builder/src/output/mod.rs b/vendor/clap_builder/src/output/mod.rs new file mode 100644 index 0000000..b358711 --- /dev/null +++ b/vendor/clap_builder/src/output/mod.rs @@ -0,0 +1,23 @@ +mod help; +#[cfg(feature = "help")] +mod help_template; +mod usage; + +pub(crate) mod fmt; +#[cfg(feature = "help")] +pub(crate) mod textwrap; + +pub(crate) use self::help::write_help; +#[cfg(feature = "help")] +pub(crate) use self::help_template::AutoHelp; +#[cfg(feature = "help")] +pub(crate) use self::help_template::HelpTemplate; +#[cfg(feature = "help")] +pub(crate) use self::textwrap::core::display_width; +#[cfg(feature = "help")] +pub(crate) use self::textwrap::wrap; +pub(crate) use self::usage::Usage; + +pub(crate) const TAB: &str = " "; +#[cfg(feature = "help")] +pub(crate) const TAB_WIDTH: usize = TAB.len(); diff --git a/vendor/clap_builder/src/output/textwrap/core.rs b/vendor/clap_builder/src/output/textwrap/core.rs new file mode 100644 index 0000000..2f6004c --- /dev/null +++ b/vendor/clap_builder/src/output/textwrap/core.rs @@ -0,0 +1,158 @@ +/// Compute the display width of `text` +/// +/// # Examples +/// +/// **Note:** When the `unicode` Cargo feature is disabled, all characters are presumed to take up +/// 1 width. With the feature enabled, function will correctly deal with [combining characters] in +/// their decomposed form (see [Unicode equivalence]). +/// +/// An example of a decomposed character is “é”, which can be decomposed into: “e” followed by a +/// combining acute accent: “◌́”. Without the `unicode` Cargo feature, every `char` has a width of +/// 1. This includes the combining accent: +/// +/// ## Emojis and CJK Characters +/// +/// Characters such as emojis and [CJK characters] used in the +/// Chinese, Japanese, and Korean languages are seen as double-width, +/// even if the `unicode-width` feature is disabled: +/// +/// # Limitations +/// +/// The displayed width of a string cannot always be computed from the +/// string alone. This is because the width depends on the rendering +/// engine used. This is particularly visible with [emoji modifier +/// sequences] where a base emoji is modified with, e.g., skin tone or +/// hair color modifiers. It is up to the rendering engine to detect +/// this and to produce a suitable emoji. +/// +/// A simple example is “❤️”, which consists of “❤” (U+2764: Black +/// Heart Symbol) followed by U+FE0F (Variation Selector-16). By +/// itself, “❤” is a black heart, but if you follow it with the +/// variant selector, you may get a wider red heart. +/// +/// A more complex example would be “👨🦰” which should depict a man +/// with red hair. Here the computed width is too large — and the +/// width differs depending on the use of the `unicode-width` feature: +/// +/// This happens because the grapheme consists of three code points: +/// “👨” (U+1F468: Man), Zero Width Joiner (U+200D), and “🦰” +/// (U+1F9B0: Red Hair). You can see them above in the test. With +/// `unicode-width` enabled, the ZWJ is correctly seen as having zero +/// width, without it is counted as a double-width character. +/// +/// ## Terminal Support +/// +/// Modern browsers typically do a great job at combining characters +/// as shown above, but terminals often struggle more. As an example, +/// Gnome Terminal version 3.38.1, shows “❤️” as a big red heart, but +/// shows "👨🦰" as “👨🦰”. +/// +/// [combining characters]: https://en.wikipedia.org/wiki/Combining_character +/// [Unicode equivalence]: https://en.wikipedia.org/wiki/Unicode_equivalence +/// [CJK characters]: https://en.wikipedia.org/wiki/CJK_characters +/// [emoji modifier sequences]: https://unicode.org/emoji/charts/full-emoji-modifiers.html +#[inline(never)] +pub(crate) fn display_width(text: &str) -> usize { + let mut width = 0; + + let mut control_sequence = false; + let control_terminate: char = 'm'; + + for ch in text.chars() { + if ch.is_ascii_control() { + control_sequence = true; + } else if control_sequence && ch == control_terminate { + control_sequence = false; + continue; + } + + if !control_sequence { + width += ch_width(ch); + } + } + width +} + +#[cfg(feature = "unicode")] +fn ch_width(ch: char) -> usize { + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) +} + +#[cfg(not(feature = "unicode"))] +fn ch_width(_: char) -> usize { + 1 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "unicode")] + use unicode_width::UnicodeWidthChar; + + #[test] + fn emojis_have_correct_width() { + use unic_emoji_char::is_emoji; + + // Emojis in the Basic Latin (ASCII) and Latin-1 Supplement + // blocks all have a width of 1 column. This includes + // characters such as '#' and '©'. + for ch in '\u{1}'..'\u{FF}' { + if is_emoji(ch) { + let desc = format!("{:?} U+{:04X}", ch, ch as u32); + + #[cfg(feature = "unicode")] + assert_eq!(ch.width().unwrap(), 1, "char: {desc}"); + + #[cfg(not(feature = "unicode"))] + assert_eq!(ch_width(ch), 1, "char: {desc}"); + } + } + + // Emojis in the remaining blocks of the Basic Multilingual + // Plane (BMP), in the Supplementary Multilingual Plane (SMP), + // and in the Supplementary Ideographic Plane (SIP), are all 1 + // or 2 columns wide when unicode-width is used, and always 2 + // columns wide otherwise. This includes all of our favorite + // emojis such as 😊. + for ch in '\u{FF}'..'\u{2FFFF}' { + if is_emoji(ch) { + let desc = format!("{:?} U+{:04X}", ch, ch as u32); + + #[cfg(feature = "unicode")] + assert!(ch.width().unwrap() <= 2, "char: {desc}"); + + #[cfg(not(feature = "unicode"))] + assert_eq!(ch_width(ch), 1, "char: {desc}"); + } + } + + // The remaining planes contain almost no assigned code points + // and thus also no emojis. + } + + #[test] + #[cfg(feature = "unicode")] + fn display_width_works() { + assert_eq!("Café Plain".len(), 11); // “é” is two bytes + assert_eq!(display_width("Café Plain"), 10); + } + + #[test] + #[cfg(feature = "unicode")] + fn display_width_narrow_emojis() { + assert_eq!(display_width("⁉"), 1); + } + + #[test] + #[cfg(feature = "unicode")] + fn display_width_narrow_emojis_variant_selector() { + assert_eq!(display_width("⁉\u{fe0f}"), 1); + } + + #[test] + #[cfg(feature = "unicode")] + fn display_width_emojis() { + assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20); + } +} diff --git a/vendor/clap_builder/src/output/textwrap/mod.rs b/vendor/clap_builder/src/output/textwrap/mod.rs new file mode 100644 index 0000000..fe8139f --- /dev/null +++ b/vendor/clap_builder/src/output/textwrap/mod.rs @@ -0,0 +1,122 @@ +//! Fork of `textwrap` crate +//! +//! Benefits of forking: +//! - Pull in only what we need rather than relying on the compiler to remove what we don't need +//! - `LineWrapper` is able to incrementally wrap which will help with `StyledStr + +pub(crate) mod core; +#[cfg(feature = "wrap_help")] +pub(crate) mod word_separators; +#[cfg(feature = "wrap_help")] +pub(crate) mod wrap_algorithms; + +#[cfg(feature = "wrap_help")] +pub(crate) fn wrap(content: &str, hard_width: usize) -> String { + let mut wrapper = wrap_algorithms::LineWrapper::new(hard_width); + let mut total = Vec::new(); + for line in content.split_inclusive('\n') { + wrapper.reset(); + let line = word_separators::find_words_ascii_space(line).collect::<Vec<_>>(); + total.extend(wrapper.wrap(line)); + } + total.join("") +} + +#[cfg(not(feature = "wrap_help"))] +pub(crate) fn wrap(content: &str, _hard_width: usize) -> String { + content.to_owned() +} + +#[cfg(test)] +#[cfg(feature = "wrap_help")] +mod test { + /// Compatibility shim to keep textwrap's tests + fn wrap(content: &str, hard_width: usize) -> Vec<String> { + super::wrap(content, hard_width) + .trim_end() + .split('\n') + .map(|s| s.to_owned()) + .collect::<Vec<_>>() + } + + #[test] + fn no_wrap() { + assert_eq!(wrap("foo", 10), vec!["foo"]); + } + + #[test] + fn wrap_simple() { + assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]); + } + + #[test] + fn to_be_or_not() { + assert_eq!( + wrap("To be, or not to be, that is the question.", 10), + vec!["To be, or", "not to be,", "that is", "the", "question."] + ); + } + + #[test] + fn multiple_words_on_first_line() { + assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]); + } + + #[test] + fn long_word() { + assert_eq!(wrap("foo", 0), vec!["foo"]); + } + + #[test] + fn long_words() { + assert_eq!(wrap("foo bar", 0), vec!["foo", "bar"]); + } + + #[test] + fn max_width() { + assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]); + + let text = "Hello there! This is some English text. \ + It should not be wrapped given the extents below."; + assert_eq!(wrap(text, usize::MAX), vec![text]); + } + + #[test] + fn leading_whitespace() { + assert_eq!(wrap(" foo bar", 6), vec![" foo", " bar"]); + } + + #[test] + fn leading_whitespace_empty_first_line() { + // If there is no space for the first word, the first line + // will be empty. This is because the string is split into + // words like [" ", "foobar ", "baz"], which puts "foobar " on + // the second line. We never output trailing whitespace + assert_eq!(wrap(" foobar baz", 6), vec!["", " foobar", " baz"]); + } + + #[test] + fn trailing_whitespace() { + // Whitespace is only significant inside a line. After a line + // gets too long and is broken, the first word starts in + // column zero and is not indented. + assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]); + } + + #[test] + fn issue_99() { + // We did not reset the in_whitespace flag correctly and did + // not handle single-character words after a line break. + assert_eq!( + wrap("aaabbbccc x yyyzzzwww", 9), + vec!["aaabbbccc", "x", "yyyzzzwww"] + ); + } + + #[test] + fn issue_129() { + // The dash is an em-dash which takes up four bytes. We used + // to panic since we tried to index into the character. + assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]); + } +} diff --git a/vendor/clap_builder/src/output/textwrap/word_separators.rs b/vendor/clap_builder/src/output/textwrap/word_separators.rs new file mode 100644 index 0000000..cb8250b --- /dev/null +++ b/vendor/clap_builder/src/output/textwrap/word_separators.rs @@ -0,0 +1,92 @@ +pub(crate) fn find_words_ascii_space(line: &str) -> impl Iterator<Item = &'_ str> + '_ { + let mut start = 0; + let mut in_whitespace = false; + let mut char_indices = line.char_indices(); + + std::iter::from_fn(move || { + for (idx, ch) in char_indices.by_ref() { + let next_whitespace = ch == ' '; + if in_whitespace && !next_whitespace { + let word = &line[start..idx]; + start = idx; + in_whitespace = next_whitespace; + return Some(word); + } + + in_whitespace = next_whitespace; + } + + if start < line.len() { + let word = &line[start..]; + start = line.len(); + return Some(word); + } + + None + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! test_find_words { + ($ascii_name:ident, + $([ $line:expr, $ascii_words:expr ]),+) => { + #[test] + fn $ascii_name() { + $( + let expected_words: Vec<&str> = $ascii_words.to_vec(); + let actual_words = find_words_ascii_space($line) + .collect::<Vec<_>>(); + assert_eq!(actual_words, expected_words, "Line: {:?}", $line); + )+ + } + }; + } + + test_find_words!(ascii_space_empty, ["", []]); + + test_find_words!(ascii_single_word, ["foo", ["foo"]]); + + test_find_words!(ascii_two_words, ["foo bar", ["foo ", "bar"]]); + + test_find_words!( + ascii_multiple_words, + ["foo bar", ["foo ", "bar"]], + ["x y z", ["x ", "y ", "z"]] + ); + + test_find_words!(ascii_only_whitespace, [" ", [" "]], [" ", [" "]]); + + test_find_words!( + ascii_inter_word_whitespace, + ["foo bar", ["foo ", "bar"]] + ); + + test_find_words!(ascii_trailing_whitespace, ["foo ", ["foo "]]); + + test_find_words!(ascii_leading_whitespace, [" foo", [" ", "foo"]]); + + test_find_words!( + ascii_multi_column_char, + ["\u{1f920}", ["\u{1f920}"]] // cowboy emoji 🤠 + ); + + test_find_words!( + ascii_hyphens, + ["foo-bar", ["foo-bar"]], + ["foo- bar", ["foo- ", "bar"]], + ["foo - bar", ["foo ", "- ", "bar"]], + ["foo -bar", ["foo ", "-bar"]] + ); + + test_find_words!(ascii_newline, ["foo\nbar", ["foo\nbar"]]); + + test_find_words!(ascii_tab, ["foo\tbar", ["foo\tbar"]]); + + test_find_words!( + ascii_non_breaking_space, + ["foo\u{00A0}bar", ["foo\u{00A0}bar"]] + ); +} diff --git a/vendor/clap_builder/src/output/textwrap/wrap_algorithms.rs b/vendor/clap_builder/src/output/textwrap/wrap_algorithms.rs new file mode 100644 index 0000000..34b4fd2 --- /dev/null +++ b/vendor/clap_builder/src/output/textwrap/wrap_algorithms.rs @@ -0,0 +1,63 @@ +use super::core::display_width; + +#[derive(Debug)] +pub(crate) struct LineWrapper<'w> { + hard_width: usize, + line_width: usize, + carryover: Option<&'w str>, +} + +impl<'w> LineWrapper<'w> { + pub(crate) fn new(hard_width: usize) -> Self { + Self { + hard_width, + line_width: 0, + carryover: None, + } + } + + pub(crate) fn reset(&mut self) { + self.line_width = 0; + self.carryover = None; + } + + pub(crate) fn wrap(&mut self, mut words: Vec<&'w str>) -> Vec<&'w str> { + if self.carryover.is_none() { + if let Some(word) = words.first() { + if word.trim().is_empty() { + self.carryover = Some(*word); + } else { + self.carryover = Some(""); + } + } + } + + let mut i = 0; + while i < words.len() { + let word = &words[i]; + let trimmed = word.trim_end(); + let word_width = display_width(trimmed); + let trimmed_delta = word.len() - trimmed.len(); + if i != 0 && self.hard_width < self.line_width + word_width { + if 0 < i { + let last = i - 1; + let trimmed = words[last].trim_end(); + words[last] = trimmed; + } + + self.line_width = 0; + words.insert(i, "\n"); + i += 1; + if let Some(carryover) = self.carryover { + words.insert(i, carryover); + self.line_width += carryover.len(); + i += 1; + } + } + self.line_width += word_width + trimmed_delta; + + i += 1; + } + words + } +} diff --git a/vendor/clap_builder/src/output/usage.rs b/vendor/clap_builder/src/output/usage.rs new file mode 100644 index 0000000..d75b704 --- /dev/null +++ b/vendor/clap_builder/src/output/usage.rs @@ -0,0 +1,529 @@ +#![cfg_attr(not(feature = "usage"), allow(unused_imports))] +#![cfg_attr(not(feature = "usage"), allow(unused_variables))] +#![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))] +#![cfg_attr(not(feature = "usage"), allow(dead_code))] + +// Internal +use crate::builder::ArgAction; +use crate::builder::StyledStr; +use crate::builder::Styles; +use crate::builder::{ArgPredicate, Command}; +use crate::parser::ArgMatcher; +use crate::util::ChildGraph; +use crate::util::FlatSet; +use crate::util::Id; + +static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND"; +const USAGE_SEP: &str = "\n "; + +pub(crate) struct Usage<'cmd> { + cmd: &'cmd Command, + styles: &'cmd Styles, + required: Option<&'cmd ChildGraph<Id>>, +} + +impl<'cmd> Usage<'cmd> { + pub(crate) fn new(cmd: &'cmd Command) -> Self { + Usage { + cmd, + styles: cmd.get_styles(), + required: None, + } + } + + pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> 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]) -> Option<StyledStr> { + debug!("Usage::create_usage_with_title"); + use std::fmt::Write as _; + let mut styled = StyledStr::new(); + let _ = write!( + styled, + "{}Usage:{} ", + self.styles.get_usage().render(), + self.styles.get_usage().render_reset() + ); + if self.write_usage_no_title(&mut styled, used) { + styled.trim_end(); + } else { + return None; + } + debug!("Usage::create_usage_with_title: usage={styled}"); + Some(styled) + } + + // 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]) -> Option<StyledStr> { + debug!("Usage::create_usage_no_title"); + + let mut styled = StyledStr::new(); + if self.write_usage_no_title(&mut styled, used) { + styled.trim_end(); + debug!("Usage::create_usage_no_title: usage={styled}"); + Some(styled) + } else { + None + } + } + + // Creates a usage string (*without title*) if one was not provided by the user manually. + fn write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool { + debug!("Usage::create_usage_no_title"); + if let Some(u) = self.cmd.get_override_usage() { + styled.push_styled(u); + true + } else { + #[cfg(feature = "usage")] + { + if used.is_empty() { + self.write_help_usage(styled); + } else { + self.write_smart_usage(styled, used); + } + true + } + + #[cfg(not(feature = "usage"))] + { + false + } + } + } +} + +#[cfg(feature = "usage")] +impl<'cmd> Usage<'cmd> { + // Creates a usage string for display in help messages (i.e. not for errors) + fn write_help_usage(&self, styled: &mut StyledStr) { + debug!("Usage::write_help_usage"); + use std::fmt::Write; + + if self.cmd.has_visible_subcommands() && self.cmd.is_flatten_help_set() { + if !self.cmd.is_subcommand_required_set() + || self.cmd.is_args_conflicts_with_subcommands_set() + { + self.write_arg_usage(styled, &[], true); + styled.trim_end(); + let _ = write!(styled, "{}", USAGE_SEP); + } + let mut cmd = self.cmd.clone(); + cmd.build(); + for (i, sub) in cmd + .get_subcommands() + .filter(|c| !c.is_hide_set()) + .enumerate() + { + if i != 0 { + styled.trim_end(); + let _ = write!(styled, "{}", USAGE_SEP); + } + Usage::new(sub).write_usage_no_title(styled, &[]); + } + } else { + self.write_arg_usage(styled, &[], true); + self.write_subcommand_usage(styled); + } + } + + // Creates a context aware usage string, or "smart usage" from currently used + // args, and requirements + fn write_smart_usage(&self, styled: &mut StyledStr, used: &[Id]) { + debug!("Usage::create_smart_usage"); + use std::fmt::Write; + let placeholder = &self.styles.get_placeholder(); + + self.write_arg_usage(styled, used, true); + + if self.cmd.is_subcommand_required_set() { + let value_name = self + .cmd + .get_subcommand_value_name() + .unwrap_or(DEFAULT_SUB_VALUE_NAME); + let _ = write!( + styled, + "{}<{value_name}>{}", + placeholder.render(), + placeholder.render_reset() + ); + } + } + + fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) { + debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}"); + use std::fmt::Write as _; + let literal = &self.styles.get_literal(); + let placeholder = &self.styles.get_placeholder(); + + let bin_name = self.cmd.get_usage_name_fallback(); + if !bin_name.is_empty() { + // the trim won't properly remove a leading space due to the formatting + let _ = write!( + styled, + "{}{bin_name}{} ", + literal.render(), + literal.render_reset() + ); + } + + if used.is_empty() && self.needs_options_tag() { + let _ = write!( + styled, + "{}[OPTIONS]{} ", + placeholder.render(), + placeholder.render_reset() + ); + } + + self.write_args(styled, used, !incl_reqs); + } + + fn write_subcommand_usage(&self, styled: &mut StyledStr) { + debug!("Usage::write_subcommand_usage"); + use std::fmt::Write as _; + + // incl_reqs is only false when this function is called recursively + if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() { + let literal = &self.styles.get_literal(); + let placeholder = &self.styles.get_placeholder(); + let value_name = self + .cmd + .get_subcommand_value_name() + .unwrap_or(DEFAULT_SUB_VALUE_NAME); + if self.cmd.is_subcommand_negates_reqs_set() + || self.cmd.is_args_conflicts_with_subcommands_set() + { + styled.trim_end(); + let _ = write!(styled, "{}", USAGE_SEP); + if self.cmd.is_args_conflicts_with_subcommands_set() { + let bin_name = self.cmd.get_usage_name_fallback(); + // Short-circuit full usage creation since no args will be relevant + let _ = write!( + styled, + "{}{bin_name}{} ", + literal.render(), + literal.render_reset() + ); + } else { + self.write_arg_usage(styled, &[], false); + } + let _ = write!( + styled, + "{}<{value_name}>{}", + placeholder.render(), + placeholder.render_reset() + ); + } else if self.cmd.is_subcommand_required_set() { + let _ = write!( + styled, + "{}<{value_name}>{}", + placeholder.render(), + placeholder.render_reset() + ); + } else { + let _ = write!( + styled, + "{}[{value_name}]{}", + placeholder.render(), + placeholder.render_reset() + ); + } + } + } + + // 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.get_id()); + + // Don't print `[OPTIONS]` just for help or version + if f.get_long() == Some("help") || f.get_long() == Some("version") { + debug!("Usage::needs_options_tag:iter Option is built-in"); + continue; + } + match f.get_action() { + ArgAction::Set + | ArgAction::Append + | ArgAction::SetTrue + | ArgAction::SetFalse + | ArgAction::Count => {} + ArgAction::Help + | ArgAction::HelpShort + | ArgAction::HelpLong + | ArgAction::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.get_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 + pub(crate) fn write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool) { + debug!("Usage::write_args: incls={incls:?}",); + use std::fmt::Write as _; + let literal = &self.styles.get_literal(); + + let required_owned; + let required = if let Some(required) = self.required { + required + } else { + required_owned = self.cmd.required_graph(); + &required_owned + }; + + let mut unrolled_reqs = Vec::new(); + for a in required.iter() { + let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { + let required = match val { + ArgPredicate::Equals(_) => 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.push(aa); + } + // always include the required arg itself. it will not be enumerated + // by unroll_requirements_for_arg. + unrolled_reqs.push(a.clone()); + } + debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}"); + + let mut required_groups_members = FlatSet::new(); + let mut required_groups = FlatSet::new(); + for req in unrolled_reqs.iter().chain(incls.iter()) { + if self.cmd.find_group(req).is_some() { + let group_members = self.cmd.unroll_args_in_group(req); + let elem = self.cmd.format_group(req); + required_groups.insert(elem); + required_groups_members.extend(group_members); + } else { + debug_assert!(self.cmd.find(req).is_some()); + } + } + + let mut required_opts = FlatSet::new(); + let mut required_positionals = Vec::new(); + for req in unrolled_reqs.iter().chain(incls.iter()) { + if let Some(arg) = self.cmd.find(req) { + if required_groups_members.contains(arg.get_id()) { + continue; + } + + let stylized = arg.stylized(self.styles, Some(!force_optional)); + if let Some(index) = arg.get_index() { + let new_len = index + 1; + if required_positionals.len() < new_len { + required_positionals.resize(new_len, None); + } + required_positionals[index] = Some(stylized); + } else { + required_opts.insert(stylized); + } + } else { + debug_assert!(self.cmd.find_group(req).is_some()); + } + } + + for pos in self.cmd.get_positionals() { + if pos.is_hide_set() { + continue; + } + if required_groups_members.contains(pos.get_id()) { + continue; + } + + let index = pos.get_index().unwrap(); + let new_len = index + 1; + if required_positionals.len() < new_len { + required_positionals.resize(new_len, None); + } + if required_positionals[index].is_some() { + if pos.is_last_set() { + let styled = required_positionals[index].take().unwrap(); + let mut new = StyledStr::new(); + let _ = write!(new, "{}--{} ", literal.render(), literal.render_reset()); + new.push_styled(&styled); + required_positionals[index] = Some(new); + } + } else { + let mut styled; + if pos.is_last_set() { + styled = StyledStr::new(); + let _ = write!(styled, "{}[--{} ", literal.render(), literal.render_reset()); + styled.push_styled(&pos.stylized(self.styles, Some(true))); + let _ = write!(styled, "{}]{}", literal.render(), literal.render_reset()); + } else { + styled = pos.stylized(self.styles, Some(false)); + } + required_positionals[index] = Some(styled); + } + if pos.is_last_set() && force_optional { + required_positionals[index] = None; + } + } + + if !force_optional { + for arg in required_opts { + styled.push_styled(&arg); + styled.push_str(" "); + } + for arg in required_groups { + styled.push_styled(&arg); + styled.push_str(" "); + } + } + for arg in required_positionals.into_iter().flatten() { + styled.push_styled(&arg); + styled.push_str(" "); + } + } + + pub(crate) fn get_required_usage_from( + &self, + incls: &[Id], + matcher: Option<&ArgMatcher>, + incl_last: bool, + ) -> Vec<StyledStr> { + debug!( + "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}", + incls, + matcher.is_some(), + incl_last + ); + + let required_owned; + let required = if let Some(required) = self.required { + required + } else { + required_owned = self.cmd.required_graph(); + &required_owned + }; + + let mut unrolled_reqs = Vec::new(); + for a in required.iter() { + let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { + 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.push(aa); + } + // always include the required arg itself. it will not be enumerated + // by unroll_requirements_for_arg. + unrolled_reqs.push(a.clone()); + } + debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}"); + + let mut required_groups_members = FlatSet::new(); + let mut required_groups = FlatSet::new(); + for req in unrolled_reqs.iter().chain(incls.iter()) { + if self.cmd.find_group(req).is_some() { + let group_members = self.cmd.unroll_args_in_group(req); + let is_present = matcher + .map(|m| { + group_members + .iter() + .any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent)) + }) + .unwrap_or(false); + debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}"); + if is_present { + continue; + } + + let elem = self.cmd.format_group(req); + required_groups.insert(elem); + required_groups_members.extend(group_members); + } else { + debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist"); + } + } + + let mut required_opts = FlatSet::new(); + let mut required_positionals = Vec::new(); + for req in unrolled_reqs.iter().chain(incls.iter()) { + if let Some(arg) = self.cmd.find(req) { + if required_groups_members.contains(arg.get_id()) { + continue; + } + + let is_present = matcher + .map(|m| m.check_explicit(req, &ArgPredicate::IsPresent)) + .unwrap_or(false); + debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}"); + if is_present { + continue; + } + + let stylized = arg.stylized(self.styles, Some(true)); + if let Some(index) = arg.get_index() { + if !arg.is_last_set() || incl_last { + let new_len = index + 1; + if required_positionals.len() < new_len { + required_positionals.resize(new_len, None); + } + required_positionals[index] = Some(stylized); + } + } else { + required_opts.insert(stylized); + } + } else { + debug_assert!(self.cmd.find_group(req).is_some()); + } + } + + let mut ret_val = Vec::new(); + ret_val.extend(required_opts); + ret_val.extend(required_groups); + for pos in required_positionals.into_iter().flatten() { + ret_val.push(pos); + } + + debug!("Usage::get_required_usage_from: ret_val={ret_val:?}"); + ret_val + } +} |