| use std::cmp::Ordering; |
| |
| use clap_lex::RawOsStr; |
| |
| use crate::builder::OsStr; |
| use crate::builder::ValueRange; |
| use crate::mkeymap::KeyType; |
| use crate::util::FlatSet; |
| use crate::util::Id; |
| use crate::ArgAction; |
| use crate::INTERNAL_ERROR_MSG; |
| use crate::{Arg, Command, ValueHint}; |
| |
| pub(crate) fn assert_app(cmd: &Command) { |
| debug!("Command::_debug_asserts"); |
| |
| let mut short_flags = vec![]; |
| let mut long_flags = vec![]; |
| |
| // Invalid version flag settings |
| if cmd.get_version().is_none() && cmd.get_long_version().is_none() { |
| // PropagateVersion is meaningless if there is no version |
| assert!( |
| !cmd.is_propagate_version_set(), |
| "Command {}: No version information via Command::version or Command::long_version to propagate", |
| cmd.get_name(), |
| ); |
| |
| // Used `Command::mut_arg("version", ..) but did not provide any version information to display |
| let version_needed = cmd |
| .get_arguments() |
| .filter(|x| matches!(x.get_action(), ArgAction::Version)) |
| .map(|x| x.get_id()) |
| .collect::<Vec<_>>(); |
| |
| assert_eq!(version_needed, Vec::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version" |
| ,cmd.get_name() |
| ); |
| } |
| |
| for sc in cmd.get_subcommands() { |
| if let Some(s) = sc.get_short_flag().as_ref() { |
| short_flags.push(Flag::Command(format!("-{s}"), sc.get_name())); |
| } |
| |
| for short_alias in sc.get_all_short_flag_aliases() { |
| short_flags.push(Flag::Command(format!("-{short_alias}"), sc.get_name())); |
| } |
| |
| if let Some(l) = sc.get_long_flag().as_ref() { |
| assert!(!l.starts_with('-'), "Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser", sc.get_name(), l); |
| long_flags.push(Flag::Command(format!("--{l}"), sc.get_name())); |
| } |
| |
| for long_alias in sc.get_all_long_flag_aliases() { |
| long_flags.push(Flag::Command(format!("--{long_alias}"), sc.get_name())); |
| } |
| } |
| |
| for arg in cmd.get_arguments() { |
| assert_arg(arg); |
| |
| assert!( |
| !cmd.is_multicall_set(), |
| "Command {}: Arguments like {} cannot be set on a multicall command", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| |
| if let Some(s) = arg.get_short() { |
| short_flags.push(Flag::Arg(format!("-{s}"), arg.get_id().as_str())); |
| } |
| |
| for (short_alias, _) in &arg.short_aliases { |
| short_flags.push(Flag::Arg(format!("-{short_alias}"), arg.get_id().as_str())); |
| } |
| |
| if let Some(l) = arg.get_long() { |
| assert!(!l.starts_with('-'), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg.get_id(), l); |
| long_flags.push(Flag::Arg(format!("--{l}"), arg.get_id().as_str())); |
| } |
| |
| for (long_alias, _) in &arg.aliases { |
| long_flags.push(Flag::Arg(format!("--{long_alias}"), arg.get_id().as_str())); |
| } |
| |
| // Name conflicts |
| if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) { |
| panic!( |
| "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}", |
| cmd.get_name(), |
| arg.get_id(), |
| duplicate_tip(cmd, first, second), |
| ); |
| } |
| |
| // Long conflicts |
| if let Some(l) = arg.get_long() { |
| if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) { |
| panic!( |
| "Command {}: Long option names must be unique for each argument, \ |
| but '--{}' is in use by both '{}' and '{}'{}", |
| cmd.get_name(), |
| l, |
| first.get_id(), |
| second.get_id(), |
| duplicate_tip(cmd, first, second) |
| ) |
| } |
| } |
| |
| // Short conflicts |
| if let Some(s) = arg.get_short() { |
| if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) { |
| panic!( |
| "Command {}: Short option names must be unique for each argument, \ |
| but '-{}' is in use by both '{}' and '{}'{}", |
| cmd.get_name(), |
| s, |
| first.get_id(), |
| second.get_id(), |
| duplicate_tip(cmd, first, second), |
| ) |
| } |
| } |
| |
| // Index conflicts |
| if let Some(idx) = arg.index { |
| if let Some((first, second)) = |
| cmd.two_args_of(|x| x.is_positional() && x.get_index() == Some(idx)) |
| { |
| panic!( |
| "Command {}: Argument '{}' has the same index as '{}' \ |
| and they are both positional arguments\n\n\t \ |
| Use `Arg::num_args(1..)` to allow one \ |
| positional argument to take multiple values", |
| cmd.get_name(), |
| first.get_id(), |
| second.get_id() |
| ) |
| } |
| } |
| |
| // requires, r_if, r_unless |
| for req in &arg.requires { |
| assert!( |
| cmd.id_exists(&req.1), |
| "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist", |
| cmd.get_name(), |
| req.1, |
| arg.get_id(), |
| ); |
| } |
| |
| for req in &arg.r_ifs { |
| assert!( |
| !arg.is_required_set(), |
| "Argument {}: `required` conflicts with `required_if_eq*`", |
| arg.get_id() |
| ); |
| assert!( |
| cmd.id_exists(&req.0), |
| "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist", |
| cmd.get_name(), |
| req.0, |
| arg.get_id() |
| ); |
| } |
| |
| for req in &arg.r_ifs_all { |
| assert!( |
| !arg.is_required_set(), |
| "Argument {}: `required` conflicts with `required_if_eq_all`", |
| arg.get_id() |
| ); |
| assert!( |
| cmd.id_exists(&req.0), |
| "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist", |
| cmd.get_name(), |
| req.0, |
| arg.get_id() |
| ); |
| } |
| |
| for req in &arg.r_unless { |
| assert!( |
| !arg.is_required_set(), |
| "Argument {}: `required` conflicts with `required_unless*`", |
| arg.get_id() |
| ); |
| assert!( |
| cmd.id_exists(req), |
| "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist", |
| cmd.get_name(), |
| req, |
| arg.get_id(), |
| ); |
| } |
| |
| for req in &arg.r_unless_all { |
| assert!( |
| !arg.is_required_set(), |
| "Argument {}: `required` conflicts with `required_unless*`", |
| arg.get_id() |
| ); |
| assert!( |
| cmd.id_exists(req), |
| "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist", |
| cmd.get_name(), |
| req, |
| arg.get_id(), |
| ); |
| } |
| |
| // blacklist |
| for req in &arg.blacklist { |
| assert!( |
| cmd.id_exists(req), |
| "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist", |
| cmd.get_name(), |
| req, |
| arg.get_id(), |
| ); |
| } |
| |
| // overrides |
| for req in &arg.overrides { |
| assert!( |
| cmd.id_exists(req), |
| "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist", |
| cmd.get_name(), |
| req, |
| arg.get_id(), |
| ); |
| } |
| |
| if arg.is_last_set() { |
| assert!( |
| arg.get_long().is_none(), |
| "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| assert!( |
| arg.get_short().is_none(), |
| "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| } |
| |
| assert!( |
| !(arg.is_required_set() && arg.is_global_set()), |
| "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| |
| if arg.get_value_hint() == ValueHint::CommandWithArguments { |
| assert!( |
| arg.is_positional(), |
| "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| |
| assert!( |
| arg.is_trailing_var_arg_set() || arg.is_last_set(), |
| "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| } |
| } |
| |
| for group in cmd.get_groups() { |
| let derive_hint = if cfg!(feature = "derive") { |
| " (note: `Args` implicitly creates `ArgGroup`s; disable with `#[group(skip)]`" |
| } else { |
| "" |
| }; |
| |
| // Name conflicts |
| assert!( |
| cmd.get_groups().filter(|x| x.id == group.id).count() < 2, |
| "Command {}: Argument group name must be unique\n\n\t'{}' is already in use{}", |
| cmd.get_name(), |
| group.get_id(), |
| derive_hint |
| ); |
| |
| // Groups should not have naming conflicts with Args |
| assert!( |
| !cmd.get_arguments().any(|x| x.get_id() == group.get_id()), |
| "Command {}: Argument group name '{}' must not conflict with argument name{}", |
| cmd.get_name(), |
| group.get_id(), |
| derive_hint |
| ); |
| |
| for arg in &group.args { |
| // Args listed inside groups should exist |
| assert!( |
| cmd.get_arguments().any(|x| x.get_id() == arg), |
| "Command {}: Argument group '{}' contains non-existent argument '{}'", |
| cmd.get_name(), |
| group.get_id(), |
| arg |
| ); |
| } |
| } |
| |
| // Conflicts between flags and subcommands |
| |
| long_flags.sort_unstable(); |
| short_flags.sort_unstable(); |
| |
| detect_duplicate_flags(&long_flags, "long"); |
| detect_duplicate_flags(&short_flags, "short"); |
| |
| let mut subs = FlatSet::new(); |
| for sc in cmd.get_subcommands() { |
| assert!( |
| subs.insert(sc.get_name()), |
| "Command {}: command name `{}` is duplicated", |
| cmd.get_name(), |
| sc.get_name() |
| ); |
| for alias in sc.get_all_aliases() { |
| assert!( |
| subs.insert(alias), |
| "Command {}: command `{}` alias `{}` is duplicated", |
| cmd.get_name(), |
| sc.get_name(), |
| alias |
| ); |
| } |
| } |
| |
| _verify_positionals(cmd); |
| |
| #[cfg(feature = "help")] |
| if let Some(help_template) = cmd.get_help_template() { |
| assert!( |
| !help_template.to_string().contains("{flags}"), |
| "Command {}: {}", |
| cmd.get_name(), |
| "`{flags}` template variable was removed in clap3, they are now included in `{options}`", |
| ); |
| assert!( |
| !help_template.to_string().contains("{unified}"), |
| "Command {}: {}", |
| cmd.get_name(), |
| "`{unified}` template variable was removed in clap3, use `{options}` instead" |
| ); |
| #[cfg(feature = "unstable-v5")] |
| assert!( |
| !help_template.to_string().contains("{bin}"), |
| "Command {}: {}", |
| cmd.get_name(), |
| "`{bin}` template variable was removed in clap5, use `{name}` instead" |
| ) |
| } |
| |
| cmd._panic_on_missing_help(cmd.is_help_expected_set()); |
| assert_app_flags(cmd); |
| } |
| |
| fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str { |
| if !cmd.is_disable_help_flag_set() |
| && (first.get_id() == Id::HELP || second.get_id() == Id::HELP) |
| { |
| " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)" |
| } else if !cmd.is_disable_version_flag_set() |
| && (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION) |
| { |
| " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)" |
| } else { |
| "" |
| } |
| } |
| |
| #[derive(Eq)] |
| enum Flag<'a> { |
| Command(String, &'a str), |
| Arg(String, &'a str), |
| } |
| |
| impl PartialEq for Flag<'_> { |
| fn eq(&self, other: &Flag) -> bool { |
| self.cmp(other) == Ordering::Equal |
| } |
| } |
| |
| impl PartialOrd for Flag<'_> { |
| fn partial_cmp(&self, other: &Flag) -> Option<Ordering> { |
| use Flag::*; |
| |
| match (self, other) { |
| (Command(s1, _), Command(s2, _)) |
| | (Arg(s1, _), Arg(s2, _)) |
| | (Command(s1, _), Arg(s2, _)) |
| | (Arg(s1, _), Command(s2, _)) => { |
| if s1 == s2 { |
| Some(Ordering::Equal) |
| } else { |
| s1.partial_cmp(s2) |
| } |
| } |
| } |
| } |
| } |
| |
| impl Ord for Flag<'_> { |
| fn cmp(&self, other: &Self) -> Ordering { |
| self.partial_cmp(other).unwrap() |
| } |
| } |
| |
| fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) { |
| use Flag::*; |
| |
| for (one, two) in find_duplicates(flags) { |
| match (one, two) { |
| (Command(flag, one), Command(_, another)) if one != another => panic!( |
| "the '{flag}' {short_or_long} flag is specified for both '{one}' and '{another}' subcommands" |
| ), |
| |
| (Arg(flag, one), Arg(_, another)) if one != another => panic!( |
| "{short_or_long} option names must be unique, but '{flag}' is in use by both '{one}' and '{another}'" |
| ), |
| |
| (Arg(flag, arg), Command(_, sub)) | (Command(flag, sub), Arg(_, arg)) => panic!( |
| "the '{flag}' {short_or_long} flag for the '{arg}' argument conflicts with the short flag \ |
| for '{sub}' subcommand" |
| ), |
| |
| _ => {} |
| } |
| } |
| } |
| |
| /// Find duplicates in a sorted array. |
| /// |
| /// The algorithm is simple: the array is sorted, duplicates |
| /// must be placed next to each other, we can check only adjacent elements. |
| fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> { |
| slice.windows(2).filter_map(|w| { |
| if w[0] == w[1] { |
| Some((&w[0], &w[1])) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| fn assert_app_flags(cmd: &Command) { |
| macro_rules! checker { |
| ($a:ident requires $($b:ident)|+) => { |
| if cmd.$a() { |
| let mut s = String::new(); |
| |
| $( |
| if !cmd.$b() { |
| use std::fmt::Write; |
| write!(&mut s, " AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap(); |
| } |
| )+ |
| |
| if !s.is_empty() { |
| panic!("{}", s) |
| } |
| } |
| }; |
| ($a:ident conflicts $($b:ident)|+) => { |
| if cmd.$a() { |
| let mut s = String::new(); |
| |
| $( |
| if cmd.$b() { |
| use std::fmt::Write; |
| write!(&mut s, " AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap(); |
| } |
| )+ |
| |
| if !s.is_empty() { |
| panic!("{}\n{}", cmd.get_name(), s) |
| } |
| } |
| }; |
| } |
| |
| checker!(is_multicall_set conflicts is_no_binary_name_set); |
| } |
| |
| #[cfg(debug_assertions)] |
| fn _verify_positionals(cmd: &Command) -> bool { |
| debug!("Command::_verify_positionals"); |
| // Because you must wait until all arguments have been supplied, this is the first chance |
| // to make assertions on positional argument indexes |
| // |
| // First we verify that the index highest supplied index, is equal to the number of |
| // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3 |
| // but no 2) |
| |
| let highest_idx = cmd |
| .get_keymap() |
| .keys() |
| .filter_map(|x| { |
| if let KeyType::Position(n) = x { |
| Some(*n) |
| } else { |
| None |
| } |
| }) |
| .max() |
| .unwrap_or(0); |
| |
| let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count(); |
| |
| assert!( |
| highest_idx == num_p, |
| "Found positional argument whose index is {highest_idx} but there \ |
| are only {num_p} positional arguments defined", |
| ); |
| |
| for arg in cmd.get_arguments() { |
| if arg.index.unwrap_or(0) == highest_idx { |
| assert!( |
| !arg.is_trailing_var_arg_set() || !arg.is_last_set(), |
| "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| |
| if arg.is_trailing_var_arg_set() { |
| assert!( |
| arg.is_multiple(), |
| "{}:{}: `Arg::trailing_var_arg` must accept multiple values", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| } |
| } else { |
| assert!( |
| !arg.is_trailing_var_arg_set(), |
| "{}:{}: `Arg::trailing_var_arg` can only apply to last positional", |
| cmd.get_name(), |
| arg.get_id() |
| ); |
| } |
| } |
| |
| // Next we verify that only the highest index has takes multiple arguments (if any) |
| let only_highest = |a: &Arg| a.is_multiple() && (a.get_index().unwrap_or(0) != highest_idx); |
| if cmd.get_positionals().any(only_highest) { |
| // First we make sure if there is a positional that allows multiple values |
| // the one before it (second to last) has one of these: |
| // * a value terminator |
| // * ArgSettings::Last |
| // * The last arg is Required |
| |
| // We can't pass the closure (it.next()) to the macro directly because each call to |
| // find() (iterator, not macro) gets called repeatedly. |
| let last = &cmd.get_keymap()[&KeyType::Position(highest_idx)]; |
| let second_to_last = &cmd.get_keymap()[&KeyType::Position(highest_idx - 1)]; |
| |
| // Either the final positional is required |
| // Or the second to last has a terminator or .last(true) set |
| let ok = last.is_required_set() |
| || (second_to_last.terminator.is_some() || second_to_last.is_last_set()) |
| || last.is_last_set(); |
| assert!( |
| ok, |
| "When using a positional argument with `.num_args(1..)` that is *not the \ |
| last* positional argument, the last positional argument (i.e. the one \ |
| with the highest index) *must* have .required(true) or .last(true) set." |
| ); |
| |
| // We make sure if the second to last is Multiple the last is ArgSettings::Last |
| let ok = second_to_last.is_multiple() || last.is_last_set(); |
| assert!( |
| ok, |
| "Only the last positional argument, or second to last positional \ |
| argument may be set to `.num_args(1..)`" |
| ); |
| |
| // Next we check how many have both Multiple and not a specific number of values set |
| let count = cmd |
| .get_positionals() |
| .filter(|p| { |
| p.is_multiple_values_set() |
| && !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed() |
| }) |
| .count(); |
| let ok = count <= 1 |
| || (last.is_last_set() |
| && last.is_multiple() |
| && second_to_last.is_multiple() |
| && count == 2); |
| assert!( |
| ok, |
| "Only one positional argument with `.num_args(1..)` set is allowed per \ |
| command, unless the second one also has .last(true) set" |
| ); |
| } |
| |
| let mut found = false; |
| |
| if cmd.is_allow_missing_positional_set() { |
| // Check that if a required positional argument is found, all positions with a lower |
| // index are also required. |
| let mut foundx2 = false; |
| |
| for p in cmd.get_positionals() { |
| if foundx2 && !p.is_required_set() { |
| assert!( |
| p.is_required_set(), |
| "Found non-required positional argument with a lower \ |
| index than a required positional argument by two or more: {:?} \ |
| index {:?}", |
| p.get_id(), |
| p.get_index() |
| ); |
| } else if p.is_required_set() && !p.is_last_set() { |
| // Args that .last(true) don't count since they can be required and have |
| // positionals with a lower index that aren't required |
| // Imagine: prog <req1> [opt1] -- <req2> |
| // Both of these are valid invocations: |
| // $ prog r1 -- r2 |
| // $ prog r1 o1 -- r2 |
| if found { |
| foundx2 = true; |
| continue; |
| } |
| found = true; |
| continue; |
| } else { |
| found = false; |
| } |
| } |
| } else { |
| // Check that if a required positional argument is found, all positions with a lower |
| // index are also required |
| for p in (1..=num_p).rev().filter_map(|n| cmd.get_keymap().get(&n)) { |
| if found { |
| assert!( |
| p.is_required_set(), |
| "Found non-required positional argument with a lower \ |
| index than a required positional argument: {:?} index {:?}", |
| p.get_id(), |
| p.get_index() |
| ); |
| } else if p.is_required_set() && !p.is_last_set() { |
| // Args that .last(true) don't count since they can be required and have |
| // positionals with a lower index that aren't required |
| // Imagine: prog <req1> [opt1] -- <req2> |
| // Both of these are valid invocations: |
| // $ prog r1 -- r2 |
| // $ prog r1 o1 -- r2 |
| found = true; |
| continue; |
| } |
| } |
| } |
| assert!( |
| cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2, |
| "Only one positional argument may have last(true) set. Found two." |
| ); |
| if cmd |
| .get_positionals() |
| .any(|p| p.is_last_set() && p.is_required_set()) |
| && cmd.has_subcommands() |
| && !cmd.is_subcommand_negates_reqs_set() |
| { |
| panic!( |
| "Having a required positional argument with .last(true) set *and* child \ |
| subcommands without setting SubcommandsNegateReqs isn't compatible." |
| ); |
| } |
| |
| true |
| } |
| |
| fn assert_arg(arg: &Arg) { |
| debug!("Arg::_debug_asserts:{}", arg.get_id()); |
| |
| // Self conflict |
| // TODO: this check should be recursive |
| assert!( |
| !arg.blacklist.iter().any(|x| x == arg.get_id()), |
| "Argument '{}' cannot conflict with itself", |
| arg.get_id(), |
| ); |
| |
| assert_eq!( |
| arg.get_action().takes_values(), |
| arg.is_takes_value_set(), |
| "Argument `{}`'s selected action {:?} contradicts `takes_value`", |
| arg.get_id(), |
| arg.get_action() |
| ); |
| if let Some(action_type_id) = arg.get_action().value_type_id() { |
| assert_eq!( |
| action_type_id, |
| arg.get_value_parser().type_id(), |
| "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})", |
| arg.get_id(), |
| arg.get_action(), |
| arg.get_value_parser() |
| ); |
| } |
| |
| if arg.get_value_hint() != ValueHint::Unknown { |
| assert!( |
| arg.is_takes_value_set(), |
| "Argument '{}' has value hint but takes no value", |
| arg.get_id() |
| ); |
| |
| if arg.get_value_hint() == ValueHint::CommandWithArguments { |
| assert!( |
| arg.is_multiple_values_set(), |
| "Argument '{}' uses hint CommandWithArguments and must accept multiple values", |
| arg.get_id() |
| ) |
| } |
| } |
| |
| if arg.index.is_some() { |
| assert!( |
| arg.is_positional(), |
| "Argument '{}' is a positional argument and can't have short or long name versions", |
| arg.get_id() |
| ); |
| assert!( |
| arg.is_takes_value_set(), |
| "Argument '{}` is positional, it must take a value{}", |
| arg.get_id(), |
| if arg.get_id() == Id::HELP { |
| " (`mut_arg` no longer works with implicit `--help`)" |
| } else if arg.get_id() == Id::VERSION { |
| " (`mut_arg` no longer works with implicit `--version`)" |
| } else { |
| "" |
| } |
| ); |
| } |
| |
| let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG); |
| // This can be the cause of later asserts, so put this first |
| if num_vals != ValueRange::EMPTY { |
| // HACK: Don't check for flags to make the derive easier |
| let num_val_names = arg.get_value_names().unwrap_or(&[]).len(); |
| if num_vals.max_values() < num_val_names { |
| panic!( |
| "Argument {}: Too many value names ({}) compared to `num_args` ({})", |
| arg.get_id(), |
| num_val_names, |
| num_vals |
| ); |
| } |
| } |
| |
| assert_eq!( |
| num_vals.takes_values(), |
| arg.is_takes_value_set(), |
| "Argument {}: mismatch between `num_args` ({}) and `takes_value`", |
| arg.get_id(), |
| num_vals, |
| ); |
| assert_eq!( |
| num_vals.is_multiple(), |
| arg.is_multiple_values_set(), |
| "Argument {}: mismatch between `num_args` ({}) and `multiple_values`", |
| arg.get_id(), |
| num_vals, |
| ); |
| |
| if 1 < num_vals.min_values() { |
| assert!( |
| !arg.is_require_equals_set(), |
| "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals", |
| arg.get_id(), |
| num_vals |
| ); |
| } |
| |
| if num_vals == ValueRange::SINGLE { |
| assert!( |
| !arg.is_multiple_values_set(), |
| "Argument {}: mismatch between `num_args` and `multiple_values`", |
| arg.get_id() |
| ); |
| } |
| |
| assert_arg_flags(arg); |
| |
| assert_defaults(arg, "default_value", arg.default_vals.iter()); |
| assert_defaults( |
| arg, |
| "default_missing_value", |
| arg.default_missing_vals.iter(), |
| ); |
| assert_defaults( |
| arg, |
| "default_value_if", |
| arg.default_vals_ifs |
| .iter() |
| .filter_map(|(_, _, default)| default.as_ref()), |
| ); |
| } |
| |
| fn assert_arg_flags(arg: &Arg) { |
| macro_rules! checker { |
| ($a:ident requires $($b:ident)|+) => { |
| if arg.$a() { |
| let mut s = String::new(); |
| |
| $( |
| if !arg.$b() { |
| use std::fmt::Write; |
| write!(&mut s, " Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap(); |
| } |
| )+ |
| |
| if !s.is_empty() { |
| panic!("Argument {:?}\n{}", arg.get_id(), s) |
| } |
| } |
| } |
| } |
| |
| checker!(is_hide_possible_values_set requires is_takes_value_set); |
| checker!(is_allow_hyphen_values_set requires is_takes_value_set); |
| checker!(is_allow_negative_numbers_set requires is_takes_value_set); |
| checker!(is_require_equals_set requires is_takes_value_set); |
| checker!(is_last_set requires is_takes_value_set); |
| checker!(is_hide_default_value_set requires is_takes_value_set); |
| checker!(is_multiple_values_set requires is_takes_value_set); |
| checker!(is_ignore_case_set requires is_takes_value_set); |
| } |
| |
| fn assert_defaults<'d>( |
| arg: &Arg, |
| field: &'static str, |
| defaults: impl IntoIterator<Item = &'d OsStr>, |
| ) { |
| for default_os in defaults { |
| let value_parser = arg.get_value_parser(); |
| let assert_cmd = Command::new("assert"); |
| if let Some(delim) = arg.get_value_delimiter() { |
| let default_os = RawOsStr::new(default_os); |
| for part in default_os.split(delim) { |
| if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), &part.to_os_str()) |
| { |
| panic!( |
| "Argument `{}`'s {}={:?} failed validation: {}", |
| arg.get_id(), |
| field, |
| part.to_str_lossy(), |
| err |
| ); |
| } |
| } |
| } else if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), default_os) { |
| panic!( |
| "Argument `{}`'s {}={:?} failed validation: {}", |
| arg.get_id(), |
| field, |
| default_os, |
| err |
| ); |
| } |
| } |
| } |