diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/clap_builder/src/parser/validator.rs | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/third_party/rust/clap_builder/src/parser/validator.rs b/third_party/rust/clap_builder/src/parser/validator.rs new file mode 100644 index 0000000000..17fb7c1e2b --- /dev/null +++ b/third_party/rust/clap_builder/src/parser/validator.rs @@ -0,0 +1,564 @@ +// Internal +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, ParseState}; +use crate::util::ChildGraph; +use crate::util::FlatMap; +use crate::util::FlatSet; +use crate::util::Id; +use crate::INTERNAL_ERROR_MSG; + +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, + parse_state: ParseState, + matcher: &mut ArgMatcher, + ) -> ClapResult<()> { + debug!("Validator::validate"); + let conflicts = Conflicts::with_args(self.cmd, matcher); + let has_subcmd = matcher.subcommand_name().is_some(); + + if let ParseState::Opt(a) = parse_state { + debug!("Validator::validate: needs_val_of={a:?}"); + + let o = &self.cmd[&a]; + let should_err = if let Some(v) = matcher.args.get(o.get_id()) { + v.all_val_groups_empty() && o.get_min_vals() != 0 + } else { + true + }; + if should_err { + return Err(Error::empty_value( + self.cmd, + &get_possible_values_cli(o) + .iter() + .filter(|pv| !pv.is_hide_set()) + .map(|n| n.get_name().to_owned()) + .collect::<Vec<_>>(), + o.to_string(), + )); + } + } + + 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() + .unwrap_or_else(|| self.cmd.get_name()); + 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(&crate::builder::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(&crate::builder::ArgPredicate::IsPresent)) + .filter_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) + }) + .next() + .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 mut seen = FlatSet::new(); + let conflicts = 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()] + } + }) + .filter_map(|c_id| { + seen.insert(c_id.clone()).then(|| { + 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 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::<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() + } +} |