chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,238 @@
// Std
use std::ffi::OsString;
use std::mem;
use std::ops::Deref;
// Internal
use crate::INTERNAL_ERROR_MSG;
use crate::builder::{Arg, ArgPredicate, Command};
use crate::parser::Identifier;
use crate::parser::PendingArg;
use crate::parser::{ArgMatches, MatchedArg, SubCommand, ValueSource};
use crate::util::AnyValue;
use crate::util::FlatMap;
use crate::util::Id;
#[derive(Debug, Default)]
pub(crate) struct ArgMatcher {
matches: ArgMatches,
pending: Option<PendingArg>,
}
impl ArgMatcher {
pub(crate) fn new(_cmd: &Command) -> Self {
ArgMatcher {
matches: ArgMatches {
#[cfg(debug_assertions)]
valid_args: {
let args = _cmd.get_arguments().map(|a| a.get_id().clone());
let groups = _cmd.get_groups().map(|g| g.get_id().clone());
args.chain(groups).collect()
},
#[cfg(debug_assertions)]
valid_subcommands: _cmd
.get_subcommands()
.map(|sc| sc.get_name_str().clone())
.collect(),
..Default::default()
},
pending: None,
}
}
pub(crate) fn into_inner(self) -> ArgMatches {
self.matches
}
pub(crate) fn propagate_globals(&mut self, global_arg_vec: &[Id]) {
debug!("ArgMatcher::get_global_values: global_arg_vec={global_arg_vec:?}");
let mut vals_map = FlatMap::new();
self.fill_in_global_values(global_arg_vec, &mut vals_map);
}
fn fill_in_global_values(
&mut self,
global_arg_vec: &[Id],
vals_map: &mut FlatMap<Id, MatchedArg>,
) {
for global_arg in global_arg_vec {
if let Some(ma) = self.get(global_arg) {
// We have to check if the parent's global arg wasn't used but still exists
// such as from a default value.
//
// For example, `myprog subcommand --global-arg=value` where `--global-arg` defines
// a default value of `other` myprog would have an existing MatchedArg for
// `--global-arg` where the value is `other`
let to_update = if let Some(parent_ma) = vals_map.get(global_arg) {
if parent_ma.source() > ma.source() {
parent_ma
} else {
ma
}
} else {
ma
}
.clone();
vals_map.insert(global_arg.clone(), to_update);
}
}
if let Some(ref mut sc) = self.matches.subcommand {
let mut am = ArgMatcher {
matches: mem::take(&mut sc.matches),
pending: None,
};
am.fill_in_global_values(global_arg_vec, vals_map);
mem::swap(&mut am.matches, &mut sc.matches);
}
for (name, matched_arg) in vals_map.iter_mut() {
self.matches.args.insert(name.clone(), matched_arg.clone());
}
}
pub(crate) fn get(&self, arg: &Id) -> Option<&MatchedArg> {
self.matches.args.get(arg)
}
pub(crate) fn get_mut(&mut self, arg: &Id) -> Option<&mut MatchedArg> {
self.matches.args.get_mut(arg)
}
pub(crate) fn remove(&mut self, arg: &Id) -> bool {
self.matches.args.remove(arg).is_some()
}
pub(crate) fn contains(&self, arg: &Id) -> bool {
self.matches.args.contains_key(arg)
}
pub(crate) fn arg_ids(&self) -> std::slice::Iter<'_, Id> {
self.matches.args.keys()
}
pub(crate) fn args(&self) -> crate::util::flat_map::Iter<'_, Id, MatchedArg> {
self.matches.args.iter()
}
pub(crate) fn entry(&mut self, arg: Id) -> crate::util::Entry<'_, Id, MatchedArg> {
self.matches.args.entry(arg)
}
pub(crate) fn subcommand(&mut self, sc: SubCommand) {
self.matches.subcommand = Some(Box::new(sc));
}
pub(crate) fn subcommand_name(&self) -> Option<&str> {
self.matches.subcommand_name()
}
pub(crate) fn check_explicit(&self, arg: &Id, predicate: &ArgPredicate) -> bool {
self.get(arg)
.map(|a| a.check_explicit(predicate))
.unwrap_or_default()
}
pub(crate) fn start_custom_arg(&mut self, arg: &Arg, source: ValueSource) {
let id = arg.get_id().clone();
debug!("ArgMatcher::start_custom_arg: id={id:?}, source={source:?}");
let ma = self.entry(id).or_insert(MatchedArg::new_arg(arg));
debug_assert_eq!(ma.type_id(), Some(arg.get_value_parser().type_id()));
ma.set_source(source);
ma.new_val_group();
}
pub(crate) fn start_custom_group(&mut self, id: Id, source: ValueSource) {
debug!("ArgMatcher::start_custom_arg: id={id:?}, source={source:?}");
let ma = self.entry(id).or_insert(MatchedArg::new_group());
debug_assert_eq!(ma.type_id(), None);
ma.set_source(source);
ma.new_val_group();
}
pub(crate) fn start_occurrence_of_external(&mut self, cmd: &Command) {
let id = Id::from_static_ref(Id::EXTERNAL);
debug!("ArgMatcher::start_occurrence_of_external: id={id:?}");
let ma = self.entry(id).or_insert(MatchedArg::new_external(cmd));
debug_assert_eq!(
ma.type_id(),
Some(
cmd.get_external_subcommand_value_parser()
.expect(INTERNAL_ERROR_MSG)
.type_id()
)
);
ma.set_source(ValueSource::CommandLine);
ma.new_val_group();
}
pub(crate) fn add_val_to(&mut self, arg: &Id, val: AnyValue, raw_val: OsString) {
let ma = self.get_mut(arg).expect(INTERNAL_ERROR_MSG);
ma.append_val(val, raw_val);
}
pub(crate) fn add_index_to(&mut self, arg: &Id, idx: usize) {
let ma = self.get_mut(arg).expect(INTERNAL_ERROR_MSG);
ma.push_index(idx);
}
pub(crate) fn needs_more_vals(&self, o: &Arg) -> bool {
let num_pending = self
.pending
.as_ref()
.and_then(|p| (p.id == *o.get_id()).then_some(p.raw_vals.len()))
.unwrap_or(0);
debug!(
"ArgMatcher::needs_more_vals: o={}, pending={}",
o.get_id(),
num_pending
);
let expected = o.get_num_args().expect(INTERNAL_ERROR_MSG);
debug!("ArgMatcher::needs_more_vals: expected={expected}, actual={num_pending}");
expected.accepts_more(num_pending)
}
pub(crate) fn pending_arg_id(&self) -> Option<&Id> {
self.pending.as_ref().map(|p| &p.id)
}
pub(crate) fn pending_values_mut(
&mut self,
id: &Id,
ident: Option<Identifier>,
trailing_values: bool,
) -> &mut Vec<OsString> {
let pending = self.pending.get_or_insert_with(|| PendingArg {
id: id.clone(),
ident,
raw_vals: Default::default(),
trailing_idx: None,
});
debug_assert_eq!(pending.id, *id, "{INTERNAL_ERROR_MSG}");
if ident.is_some() {
debug_assert_eq!(pending.ident, ident, "{INTERNAL_ERROR_MSG}");
}
if trailing_values {
pending.trailing_idx.get_or_insert(pending.raw_vals.len());
}
&mut pending.raw_vals
}
pub(crate) fn start_trailing(&mut self) {
if let Some(pending) = &mut self.pending {
// Allow asserting its started on subsequent calls
pending.trailing_idx.get_or_insert(pending.raw_vals.len());
}
}
pub(crate) fn take_pending(&mut self) -> Option<PendingArg> {
self.pending.take()
}
}
impl Deref for ArgMatcher {
type Target = ArgMatches;
fn deref(&self) -> &Self::Target {
&self.matches
}
}

66
vendor/clap_builder/src/parser/error.rs vendored Normal file
View File

@@ -0,0 +1,66 @@
use crate::util::AnyValueId;
/// Violation of [`ArgMatches`][crate::ArgMatches] assumptions
#[derive(Clone, Debug)]
#[allow(missing_copy_implementations)] // We might add non-Copy types in the future
#[non_exhaustive]
pub enum MatchesError {
/// Failed to downcast `AnyValue` to the specified type
#[non_exhaustive]
Downcast {
/// Type for value stored in [`ArgMatches`][crate::ArgMatches]
actual: AnyValueId,
/// The target type to downcast to
expected: AnyValueId,
},
/// Argument not defined in [`Command`][crate::Command]
#[non_exhaustive]
UnknownArgument {
// Missing `id` but blocked on a public id type which will hopefully come with `unstable-v4`
},
}
impl MatchesError {
#[cfg_attr(debug_assertions, track_caller)]
pub(crate) fn unwrap<T>(id: &str, r: Result<T, MatchesError>) -> T {
let err = match r {
Ok(t) => {
return t;
}
Err(err) => err,
};
panic!("Mismatch between definition and access of `{id}`. {err}",)
}
}
impl std::error::Error for MatchesError {}
impl std::fmt::Display for MatchesError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Downcast { actual, expected } => {
writeln!(
f,
"Could not downcast to {expected:?}, need to downcast to {actual:?}"
)
}
Self::UnknownArgument {} => {
writeln!(
f,
"Unknown argument or group id. Make sure you are using the argument id and not the short or long flags"
)
}
}
}
}
#[test]
fn check_auto_traits() {
static_assertions::assert_impl_all!(
MatchesError: Send,
Sync,
std::panic::RefUnwindSafe,
std::panic::UnwindSafe,
Unpin
);
}

View File

@@ -0,0 +1 @@
pub(crate) mod suggestions;

View File

@@ -0,0 +1,178 @@
// Internal
use crate::builder::Command;
/// Find strings from an iterable of `possible_values` similar to a given value `v`
/// Returns a Vec of all possible values that exceed a similarity threshold
/// sorted by ascending similarity, most similar comes last
#[cfg(feature = "suggestions")]
pub(crate) fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String>
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
use std::cmp::Ordering;
let mut candidates: Vec<(f64, String)> = Vec::new();
for pv in possible_values {
// GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong
// causing strings with common prefix >=10 to be considered perfectly similar
let confidence = strsim::jaro(v, pv.as_ref());
if confidence > 0.7 {
let new_elem = (confidence, pv.as_ref().to_owned());
let pos = candidates
.binary_search_by(|probe| {
if probe.0 > confidence {
Ordering::Greater
} else {
Ordering::Less
}
})
.unwrap_or_else(|e| e);
candidates.insert(pos, new_elem);
}
}
candidates.into_iter().map(|(_, pv)| pv).collect()
}
#[cfg(not(feature = "suggestions"))]
pub(crate) fn did_you_mean<T, I>(_: &str, _: I) -> Vec<String>
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
Vec::new()
}
/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase
pub(crate) fn did_you_mean_flag<'a, 'help, I, T>(
arg: &str,
remaining_args: &[&std::ffi::OsStr],
longs: I,
subcommands: impl IntoIterator<Item = &'a mut Command>,
) -> Option<(String, Option<String>)>
where
'help: 'a,
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
use crate::mkeymap::KeyType;
match did_you_mean(arg, longs).pop() {
Some(candidate) => Some((candidate, None)),
None => subcommands
.into_iter()
.filter_map(|subcommand| {
subcommand._build_self(false);
let longs = subcommand.get_keymap().keys().filter_map(|a| {
if let KeyType::Long(v) = a {
Some(v.to_string_lossy().into_owned())
} else {
None
}
});
let subcommand_name = subcommand.get_name();
let candidate = some!(did_you_mean(arg, longs).pop());
let score = some!(remaining_args.iter().position(|x| subcommand_name == *x));
Some((score, (candidate, Some(subcommand_name.to_string()))))
})
.min_by_key(|(x, _)| *x)
.map(|(_, suggestion)| suggestion),
}
}
#[cfg(all(test, feature = "suggestions"))]
mod test {
use super::*;
#[test]
fn missing_letter() {
let p_vals = ["test", "possible", "values"];
assert_eq!(did_you_mean("tst", p_vals.iter()), vec!["test"]);
}
#[test]
fn ambiguous() {
let p_vals = ["test", "temp", "possible", "values"];
assert_eq!(did_you_mean("te", p_vals.iter()), vec!["test", "temp"]);
}
#[test]
fn unrelated() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
did_you_mean("hahaahahah", p_vals.iter()),
Vec::<String>::new()
);
}
#[test]
fn best_fit() {
let p_vals = [
"test",
"possible",
"values",
"alignmentStart",
"alignmentScore",
];
assert_eq!(
did_you_mean("alignmentScorr", p_vals.iter()),
vec!["alignmentStart", "alignmentScore"]
);
}
#[test]
fn best_fit_long_common_prefix_issue_4660() {
let p_vals = ["alignmentScore", "alignmentStart"];
assert_eq!(
did_you_mean("alignmentScorr", p_vals.iter()),
vec!["alignmentStart", "alignmentScore"]
);
}
#[test]
fn flag_missing_letter() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
did_you_mean_flag("tst", &[], p_vals.iter(), []),
Some(("test".to_owned(), None))
);
}
#[test]
fn flag_ambiguous() {
let p_vals = ["test", "temp", "possible", "values"];
assert_eq!(
did_you_mean_flag("te", &[], p_vals.iter(), []),
Some(("temp".to_owned(), None))
);
}
#[test]
fn flag_unrelated() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
did_you_mean_flag("hahaahahah", &[], p_vals.iter(), []),
None
);
}
#[test]
fn flag_best_fit() {
let p_vals = [
"test",
"possible",
"values",
"alignmentStart",
"alignmentScore",
];
assert_eq!(
did_you_mean_flag("alignmentScorr", &[], p_vals.iter(), []),
Some(("alignmentScore".to_owned(), None))
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
// Std
use std::{
ffi::{OsStr, OsString},
iter::{Cloned, Flatten},
slice::Iter,
};
use crate::INTERNAL_ERROR_MSG;
use crate::builder::ArgPredicate;
use crate::parser::ValueSource;
use crate::util::AnyValue;
use crate::util::AnyValueId;
use crate::util::eq_ignore_case;
#[derive(Debug, Clone)]
pub(crate) struct MatchedArg {
source: Option<ValueSource>,
indices: Vec<usize>,
type_id: Option<AnyValueId>,
vals: Vec<Vec<AnyValue>>,
raw_vals: Vec<Vec<OsString>>,
ignore_case: bool,
}
impl MatchedArg {
pub(crate) fn new_arg(arg: &crate::Arg) -> Self {
let ignore_case = arg.is_ignore_case_set();
Self {
source: None,
indices: Vec::new(),
type_id: Some(arg.get_value_parser().type_id()),
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn new_group() -> Self {
let ignore_case = false;
Self {
source: None,
indices: Vec::new(),
type_id: None,
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn new_external(cmd: &crate::Command) -> Self {
let ignore_case = false;
Self {
source: None,
indices: Vec::new(),
type_id: Some(
cmd.get_external_subcommand_value_parser()
.expect(INTERNAL_ERROR_MSG)
.type_id(),
),
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn indices(&self) -> Cloned<Iter<'_, usize>> {
self.indices.iter().cloned()
}
pub(crate) fn get_index(&self, index: usize) -> Option<usize> {
self.indices.get(index).cloned()
}
pub(crate) fn push_index(&mut self, index: usize) {
self.indices.push(index);
}
pub(crate) fn vals(&self) -> Iter<'_, Vec<AnyValue>> {
self.vals.iter()
}
pub(crate) fn into_vals(self) -> Vec<Vec<AnyValue>> {
self.vals
}
pub(crate) fn vals_flatten(&self) -> Flatten<Iter<'_, Vec<AnyValue>>> {
self.vals.iter().flatten()
}
pub(crate) fn into_vals_flatten(self) -> Flatten<std::vec::IntoIter<Vec<AnyValue>>> {
self.vals.into_iter().flatten()
}
pub(crate) fn raw_vals(&self) -> Iter<'_, Vec<OsString>> {
self.raw_vals.iter()
}
pub(crate) fn raw_vals_flatten(&self) -> Flatten<Iter<'_, Vec<OsString>>> {
self.raw_vals.iter().flatten()
}
pub(crate) fn first(&self) -> Option<&AnyValue> {
self.vals_flatten().next()
}
#[cfg(test)]
pub(crate) fn first_raw(&self) -> Option<&OsString> {
self.raw_vals_flatten().next()
}
pub(crate) fn new_val_group(&mut self) {
self.vals.push(vec![]);
self.raw_vals.push(vec![]);
}
pub(crate) fn append_val(&mut self, val: AnyValue, raw_val: OsString) {
// We assume there is always a group created before.
self.vals.last_mut().expect(INTERNAL_ERROR_MSG).push(val);
self.raw_vals
.last_mut()
.expect(INTERNAL_ERROR_MSG)
.push(raw_val);
}
pub(crate) fn num_vals(&self) -> usize {
self.vals.iter().map(|v| v.len()).sum()
}
// Will be used later
#[allow(dead_code)]
pub(crate) fn num_vals_last_group(&self) -> usize {
self.vals.last().map(|x| x.len()).unwrap_or(0)
}
pub(crate) fn check_explicit(&self, predicate: &ArgPredicate) -> bool {
if self.source.map(|s| !s.is_explicit()).unwrap_or(false) {
return false;
}
match predicate {
ArgPredicate::Equals(val) => self.raw_vals_flatten().any(|v| {
if self.ignore_case {
// If `v` isn't utf8, it can't match `val`, so `OsStr::to_str` should be fine
eq_ignore_case(&v.to_string_lossy(), &val.to_string_lossy())
} else {
OsString::as_os_str(v) == OsStr::new(val)
}
}),
ArgPredicate::IsPresent => true,
}
}
pub(crate) fn source(&self) -> Option<ValueSource> {
self.source
}
pub(crate) fn set_source(&mut self, source: ValueSource) {
if let Some(existing) = self.source {
self.source = Some(existing.max(source));
} else {
self.source = Some(source);
}
}
pub(crate) fn type_id(&self) -> Option<AnyValueId> {
self.type_id
}
pub(crate) fn infer_type_id(&self, expected: AnyValueId) -> AnyValueId {
self.type_id()
.or_else(|| {
self.vals_flatten()
.map(|v| v.type_id())
.find(|actual| *actual != expected)
})
.unwrap_or(expected)
}
}
impl PartialEq for MatchedArg {
fn eq(&self, other: &MatchedArg) -> bool {
let MatchedArg {
source: self_source,
indices: self_indices,
type_id: self_type_id,
vals: _,
raw_vals: self_raw_vals,
ignore_case: self_ignore_case,
} = self;
let MatchedArg {
source: other_source,
indices: other_indices,
type_id: other_type_id,
vals: _,
raw_vals: other_raw_vals,
ignore_case: other_ignore_case,
} = other;
self_source == other_source
&& self_indices == other_indices
&& self_type_id == other_type_id
&& self_raw_vals == other_raw_vals
&& self_ignore_case == other_ignore_case
}
}
impl Eq for MatchedArg {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grouped_vals_first() {
let mut m = MatchedArg::new_group();
m.new_val_group();
m.new_val_group();
m.append_val(AnyValue::new(String::from("bbb")), "bbb".into());
m.append_val(AnyValue::new(String::from("ccc")), "ccc".into());
assert_eq!(m.first_raw(), Some(&OsString::from("bbb")));
}
}

View File

@@ -0,0 +1,13 @@
mod arg_matches;
mod matched_arg;
mod value_source;
pub use arg_matches::IdsRef;
pub use arg_matches::RawValues;
pub use arg_matches::Values;
pub use arg_matches::ValuesRef;
pub use arg_matches::{ArgMatches, Indices};
pub use value_source::ValueSource;
pub(crate) use arg_matches::SubCommand;
pub(crate) use matched_arg::MatchedArg;

View File

@@ -0,0 +1,17 @@
/// Origin of the argument's value
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ValueSource {
/// Value came [`Arg::default_value`][crate::Arg::default_value]
DefaultValue,
/// Value came [`Arg::env`][crate::Arg::env]
EnvVariable,
/// Value was passed in on the command-line
CommandLine,
}
impl ValueSource {
pub(crate) fn is_explicit(self) -> bool {
self != Self::DefaultValue
}
}

25
vendor/clap_builder/src/parser/mod.rs vendored Normal file
View File

@@ -0,0 +1,25 @@
//! [`Command`][crate::Command] line argument parser
mod arg_matcher;
mod error;
mod matches;
#[allow(clippy::module_inception)]
mod parser;
mod validator;
pub(crate) mod features;
pub(crate) use self::arg_matcher::ArgMatcher;
pub(crate) use self::matches::{MatchedArg, SubCommand};
pub(crate) use self::parser::Identifier;
pub(crate) use self::parser::Parser;
pub(crate) use self::parser::PendingArg;
pub(crate) use self::validator::Validator;
pub(crate) use self::validator::get_possible_values_cli;
pub use self::matches::IdsRef;
pub use self::matches::RawValues;
pub use self::matches::Values;
pub use self::matches::ValuesRef;
pub use self::matches::{ArgMatches, Indices, ValueSource};
pub use error::MatchesError;

1685
vendor/clap_builder/src/parser/parser.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,537 @@
// Internal
use crate::INTERNAL_ERROR_MSG;
use crate::builder::StyledStr;
use crate::builder::{Arg, ArgGroup, ArgPredicate, Command, PossibleValue};
use crate::error::{Error, Result as ClapResult};
use crate::output::Usage;
use crate::parser::ArgMatcher;
use crate::util::ChildGraph;
use crate::util::FlatMap;
use crate::util::FlatSet;
use crate::util::Id;
pub(crate) struct Validator<'cmd> {
cmd: &'cmd Command,
required: ChildGraph<Id>,
}
impl<'cmd> Validator<'cmd> {
pub(crate) fn new(cmd: &'cmd Command) -> Self {
let required = cmd.required_graph();
Validator { cmd, required }
}
pub(crate) fn validate(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate");
let conflicts = Conflicts::with_args(self.cmd, matcher);
let has_subcmd = matcher.subcommand_name().is_some();
if !has_subcmd && self.cmd.is_arg_required_else_help_set() {
let num_user_values = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.count();
if num_user_values == 0 {
let message = self.cmd.write_help_err(false);
return Err(Error::display_help_error(self.cmd, message));
}
}
if !has_subcmd && self.cmd.is_subcommand_required_set() {
let bn = self.cmd.get_bin_name_fallback();
return Err(Error::missing_subcommand(
self.cmd,
bn.to_string(),
self.cmd
.all_subcommand_names()
.map(|s| s.to_owned())
.collect::<Vec<_>>(),
Usage::new(self.cmd)
.required(&self.required)
.create_usage_with_title(&[]),
));
}
ok!(self.validate_conflicts(matcher, &conflicts));
if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) {
ok!(self.validate_required(matcher, &conflicts));
}
Ok(())
}
fn validate_conflicts(
&mut self,
matcher: &ArgMatcher,
conflicts: &Conflicts,
) -> ClapResult<()> {
debug!("Validator::validate_conflicts");
ok!(self.validate_exclusive(matcher));
for (arg_id, _) in matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.filter(|(arg_id, _)| self.cmd.find(arg_id).is_some())
{
debug!("Validator::validate_conflicts::iter: id={arg_id:?}");
let conflicts = conflicts.gather_conflicts(self.cmd, arg_id);
ok!(self.build_conflict_err(arg_id, &conflicts, matcher));
}
Ok(())
}
fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate_exclusive");
let args_count = matcher
.args()
.filter(|(arg_id, matched)| {
matched.check_explicit(&ArgPredicate::IsPresent)
// Avoid including our own groups by checking none of them. If a group is present, the
// args for the group will be.
&& self.cmd.find(arg_id).is_some()
})
.count();
if args_count <= 1 {
// Nothing present to conflict with
return Ok(());
}
matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.find_map(|(id, _)| {
debug!("Validator::validate_exclusive:iter:{id:?}");
self.cmd
.find(id)
// Find `arg`s which are exclusive but also appear with other args.
.filter(|&arg| arg.is_exclusive_set() && args_count > 1)
})
.map(|arg| {
// Throw an error for the first conflict found.
Err(Error::argument_conflict(
self.cmd,
arg.to_string(),
Vec::new(),
Usage::new(self.cmd)
.required(&self.required)
.create_usage_with_title(&[]),
))
})
.unwrap_or(Ok(()))
}
fn build_conflict_err(
&self,
name: &Id,
conflict_ids: &[Id],
matcher: &ArgMatcher,
) -> ClapResult<()> {
if conflict_ids.is_empty() {
return Ok(());
}
debug!("Validator::build_conflict_err: name={name:?}");
let conflict_ids = conflict_ids
.iter()
.flat_map(|c_id| {
if self.cmd.find_group(c_id).is_some() {
self.cmd.unroll_args_in_group(c_id)
} else {
vec![c_id.clone()]
}
})
.collect::<FlatSet<_>>()
.into_vec();
let conflicts = conflict_ids
.iter()
.map(|c_id| {
let c_arg = self.cmd.find(c_id).expect(INTERNAL_ERROR_MSG);
c_arg.to_string()
})
.collect();
let former_arg = self.cmd.find(name).expect(INTERNAL_ERROR_MSG);
let usg = self.build_conflict_err_usage(matcher, &conflict_ids);
Err(Error::argument_conflict(
self.cmd,
former_arg.to_string(),
conflicts,
usg,
))
}
fn build_conflict_err_usage(
&self,
matcher: &ArgMatcher,
conflicting_keys: &[Id],
) -> Option<StyledStr> {
let used_filtered: Vec<Id> = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.map(|(n, _)| n)
.filter(|n| {
// Filter out the args we don't want to specify.
self.cmd
.find(n)
.map(|a| !a.is_hide_set())
.unwrap_or_default()
})
.filter(|key| !conflicting_keys.contains(key))
.cloned()
.collect();
let required: Vec<Id> = used_filtered
.iter()
.filter_map(|key| self.cmd.find(key))
.flat_map(|arg| arg.requires.iter().map(|item| &item.1))
.filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key))
.chain(used_filtered.iter())
.cloned()
.collect();
Usage::new(self.cmd)
.required(&self.required)
.create_usage_with_title(&required)
}
fn gather_requires(&mut self, matcher: &ArgMatcher) {
debug!("Validator::gather_requires");
for (name, matched) in matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
{
debug!("Validator::gather_requires:iter:{name:?}");
if let Some(arg) = self.cmd.find(name) {
let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
let required = matched.check_explicit(val);
required.then(|| req_arg.clone())
};
for req in self.cmd.unroll_arg_requires(is_relevant, arg.get_id()) {
self.required.insert(req);
}
} else if let Some(g) = self.cmd.find_group(name) {
debug!("Validator::gather_requires:iter:{name:?}:group");
for r in &g.requires {
self.required.insert(r.clone());
}
}
}
}
fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> {
debug!("Validator::validate_required: required={:?}", self.required);
self.gather_requires(matcher);
let mut missing_required = Vec::new();
let mut highest_index = 0;
let is_exclusive_present = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.any(|(id, _)| {
self.cmd
.find(id)
.map(|arg| arg.is_exclusive_set())
.unwrap_or_default()
});
debug!("Validator::validate_required: is_exclusive_present={is_exclusive_present}");
for arg_or_group in self
.required
.iter()
.filter(|r| !matcher.check_explicit(r, &ArgPredicate::IsPresent))
{
debug!("Validator::validate_required:iter:aog={arg_or_group:?}");
if let Some(arg) = self.cmd.find(arg_or_group) {
debug!("Validator::validate_required:iter: This is an arg");
if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts) {
debug!(
"Validator::validate_required:iter: Missing {:?}",
arg.get_id()
);
missing_required.push(arg.get_id().clone());
if !arg.is_last_set() {
highest_index = highest_index.max(arg.get_index().unwrap_or(0));
}
}
} else if let Some(group) = self.cmd.find_group(arg_or_group) {
debug!("Validator::validate_required:iter: This is a group");
if !self
.cmd
.unroll_args_in_group(&group.id)
.iter()
.any(|a| matcher.check_explicit(a, &ArgPredicate::IsPresent))
{
debug!(
"Validator::validate_required:iter: Missing {:?}",
group.get_id()
);
missing_required.push(group.get_id().clone());
}
}
}
// Validate the conditionally required args
for a in self
.cmd
.get_arguments()
.filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent))
{
let mut required = false;
for (other, val) in &a.r_ifs {
if matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) {
debug!(
"Validator::validate_required:iter: Missing {:?}",
a.get_id()
);
required = true;
}
}
let match_all = a.r_ifs_all.iter().all(|(other, val)| {
matcher.check_explicit(other, &ArgPredicate::Equals(val.into()))
});
if match_all && !a.r_ifs_all.is_empty() {
debug!(
"Validator::validate_required:iter: Missing {:?}",
a.get_id()
);
required = true;
}
if (!a.r_unless.is_empty() || !a.r_unless_all.is_empty())
&& self.fails_arg_required_unless(a, matcher)
{
debug!(
"Validator::validate_required:iter: Missing {:?}",
a.get_id()
);
required = true;
}
if !is_exclusive_present && required {
missing_required.push(a.get_id().clone());
if !a.is_last_set() {
highest_index = highest_index.max(a.get_index().unwrap_or(0));
}
}
}
// For display purposes, include all of the preceding positional arguments
if !self.cmd.is_allow_missing_positional_set() {
for pos in self
.cmd
.get_positionals()
.filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent))
{
if pos.get_index() < Some(highest_index) {
debug!(
"Validator::validate_required:iter: Missing {:?}",
pos.get_id()
);
missing_required.push(pos.get_id().clone());
}
}
}
if !missing_required.is_empty() {
ok!(self.missing_required_error(matcher, missing_required));
}
Ok(())
}
fn is_missing_required_ok(&self, a: &Arg, conflicts: &Conflicts) -> bool {
debug!("Validator::is_missing_required_ok: {}", a.get_id());
if !conflicts.gather_conflicts(self.cmd, a.get_id()).is_empty() {
debug!("Validator::is_missing_required_ok: true (self)");
return true;
}
for group_id in self.cmd.groups_for_arg(a.get_id()) {
if !conflicts.gather_conflicts(self.cmd, &group_id).is_empty() {
debug!("Validator::is_missing_required_ok: true ({group_id})");
return true;
}
}
false
}
// Failing a required unless means, the arg's "unless" wasn't present, and neither were they
fn fails_arg_required_unless(&self, a: &Arg, matcher: &ArgMatcher) -> bool {
debug!("Validator::fails_arg_required_unless: a={:?}", a.get_id());
let exists = |id| matcher.check_explicit(id, &ArgPredicate::IsPresent);
(a.r_unless_all.is_empty() || !a.r_unless_all.iter().all(exists))
&& !a.r_unless.iter().any(exists)
}
// `req_args`: an arg to include in the error even if not used
fn missing_required_error(
&self,
matcher: &ArgMatcher,
raw_req_args: Vec<Id>,
) -> ClapResult<()> {
debug!("Validator::missing_required_error; incl={raw_req_args:?}");
debug!(
"Validator::missing_required_error: reqs={:?}",
self.required
);
let usg = Usage::new(self.cmd).required(&self.required);
let req_args = {
#[cfg(feature = "usage")]
{
usg.get_required_usage_from(&raw_req_args, Some(matcher), true)
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
}
#[cfg(not(feature = "usage"))]
{
raw_req_args
.iter()
.map(|id| {
if let Some(arg) = self.cmd.find(id) {
arg.to_string()
} else if let Some(_group) = self.cmd.find_group(id) {
self.cmd.format_group(id).to_string()
} else {
debug_assert!(false, "id={id:?} is unknown");
"".to_owned()
}
})
.collect::<FlatSet<_>>()
.into_iter()
.collect::<Vec<_>>()
}
};
debug!("Validator::missing_required_error: req_args={req_args:#?}");
let used: Vec<Id> = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.map(|(n, _)| n)
.filter(|n| {
// Filter out the args we don't want to specify.
self.cmd
.find(n)
.map(|a| !a.is_hide_set())
.unwrap_or_default()
})
.cloned()
.chain(raw_req_args)
.collect();
Err(Error::missing_required_argument(
self.cmd,
req_args,
usg.create_usage_with_title(&used),
))
}
}
#[derive(Default, Clone, Debug)]
struct Conflicts {
potential: FlatMap<Id, Vec<Id>>,
}
impl Conflicts {
fn with_args(cmd: &Command, matcher: &ArgMatcher) -> Self {
let mut potential = FlatMap::new();
potential.extend_unchecked(
matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.map(|(id, _)| {
let conf = gather_direct_conflicts(cmd, id);
(id.clone(), conf)
}),
);
Self { potential }
}
fn gather_conflicts(&self, cmd: &Command, arg_id: &Id) -> Vec<Id> {
debug!("Conflicts::gather_conflicts: arg={arg_id:?}");
let mut conflicts = Vec::new();
let arg_id_conflicts_storage;
let arg_id_conflicts = if let Some(arg_id_conflicts) = self.get_direct_conflicts(arg_id) {
arg_id_conflicts
} else {
// `is_missing_required_ok` is a case where we check not-present args for conflicts
arg_id_conflicts_storage = gather_direct_conflicts(cmd, arg_id);
&arg_id_conflicts_storage
};
for (other_arg_id, other_arg_id_conflicts) in self.potential.iter() {
if arg_id == other_arg_id {
continue;
}
if arg_id_conflicts.contains(other_arg_id) {
conflicts.push(other_arg_id.clone());
}
if other_arg_id_conflicts.contains(arg_id) {
conflicts.push(other_arg_id.clone());
}
}
debug!("Conflicts::gather_conflicts: conflicts={conflicts:?}");
conflicts
}
fn get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> {
self.potential.get(arg_id).map(Vec::as_slice)
}
}
fn gather_direct_conflicts(cmd: &Command, id: &Id) -> Vec<Id> {
let conf = if let Some(arg) = cmd.find(id) {
gather_arg_direct_conflicts(cmd, arg)
} else if let Some(group) = cmd.find_group(id) {
gather_group_direct_conflicts(group)
} else {
debug_assert!(false, "id={id:?} is unknown");
Vec::new()
};
debug!("Conflicts::gather_direct_conflicts id={id:?}, conflicts={conf:?}",);
conf
}
fn gather_arg_direct_conflicts(cmd: &Command, arg: &Arg) -> Vec<Id> {
let mut conf = arg.blacklist.clone();
for group_id in cmd.groups_for_arg(arg.get_id()) {
let group = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG);
conf.extend(group.conflicts.iter().cloned());
if !group.multiple {
for member_id in &group.args {
if member_id != arg.get_id() {
conf.push(member_id.clone());
}
}
}
}
// Overrides are implicitly conflicts
conf.extend(arg.overrides.iter().cloned());
conf
}
fn gather_group_direct_conflicts(group: &ArgGroup) -> Vec<Id> {
group.conflicts.clone()
}
pub(crate) fn get_possible_values_cli(a: &Arg) -> Vec<PossibleValue> {
if !a.is_takes_value_set() {
vec![]
} else {
a.get_value_parser()
.possible_values()
.map(|pvs| pvs.collect())
.unwrap_or_default()
}
}