diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/clap_builder/src/output/usage.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/clap_builder/src/output/usage.rs')
-rw-r--r-- | vendor/clap_builder/src/output/usage.rs | 529 |
1 files changed, 529 insertions, 0 deletions
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 + } +} |