perf: debloats clap by deduplicating logic and refactors

This commit removes heavy use of macros in certain functions which
drastically increased code size. Some of the macros could be turned
into functions, while others could be removed entirely.

Examples were removing arg_post_processing! which did things like
checked overrides, requirements, groups, etc. This would happen
after every argument was parsed. This macro also had several other
macros inside it, and would expand to several tens or hundreds of
lines of code.

Then add that due to borrowck and branch issues, this macro may be
included in multiple parts of a function. Unlike traditional functions
each of these uses expanded into TONS of code (just like agressive
inlining).

This commit primarily removes those arg_post_processing! calls and
breaks up the functionality into two types. The first must happen at
ever new argument (not new value, but new argument). This is pretty
cheap. The next type was moved to the end of parsing validation section
which is more expensive, but only happens once.

i.e. clap was validating each argument/value as it saw them, now it's
lazy and validates them all at once at the end. This MUCH more
efficient!
This commit is contained in:
Kevin K 2018-01-09 10:18:36 -05:00
parent d26ab2b97c
commit 03e413d717
6 changed files with 325 additions and 359 deletions

View file

@ -1,165 +0,0 @@
macro_rules! remove_overriden {
(@remove_requires $rem_from:expr, $a:ident.$ov:ident) => {
if let Some(ora) = $a.$ov() {
for i in (0 .. $rem_from.len()).rev() {
let should_remove = ora.iter().any(|&(_, ref name)| name == &$rem_from[i]);
if should_remove { $rem_from.swap_remove(i); }
}
}
};
(@remove $rem_from:expr, $a:ident.$ov:ident) => {
if let Some(ora) = $a.$ov() {
vec_remove_all!($rem_from, ora.iter());
}
};
(@arg $_self:ident, $arg:ident) => {
remove_overriden!(@remove_requires $_self.required, $arg.requires);
remove_overriden!(@remove $_self.blacklist, $arg.blacklist);
remove_overriden!(@remove $_self.overrides, $arg.overrides);
};
($_self:ident, $name:expr) => {
debugln!("remove_overriden!;");
if let Some(o) = $_self.opts.iter() .find(|o| o.b.name == *$name) {
remove_overriden!(@arg $_self, o);
} else if let Some(f) = $_self.flags.iter() .find(|f| f.b.name == *$name) {
remove_overriden!(@arg $_self, f);
} else {
let p = $_self.positionals.values()
.find(|p| p.b.name == *$name)
.expect(INTERNAL_ERROR_MSG);
remove_overriden!(@arg $_self, p);
}
};
}
macro_rules! arg_post_processing {
($me:ident, $arg:ident, $matcher:ident) => {
debugln!("arg_post_processing!;");
// Handle POSIX overrides
debug!("arg_post_processing!: Is '{}' in overrides...", $arg.to_string());
if $me.overrides.contains(&$arg.name()) {
if let Some(ref name) = find_name_from!($me, &$arg.name(), overrides, $matcher) {
sdebugln!("Yes by {}", name);
$matcher.remove(name);
remove_overriden!($me, name);
}
} else { sdebugln!("No"); }
// Add overrides
debug!("arg_post_processing!: Does '{}' have overrides...", $arg.to_string());
if let Some(or) = $arg.overrides() {
sdebugln!("Yes");
$matcher.remove_all(or);
for pa in or { remove_overriden!($me, pa); }
$me.overrides.extend(or);
vec_remove_all!($me.required, or.iter());
} else { sdebugln!("No"); }
// Handle conflicts
debug!("arg_post_processing!: Does '{}' have conflicts...", $arg.to_string());
if let Some(bl) = $arg.blacklist() {
sdebugln!("Yes");
for c in bl {
// Inject two-way conflicts
debug!("arg_post_processing!: Has '{}' already been matched...", c);
if $matcher.contains(c) {
sdebugln!("Yes");
// find who blacklisted us...
$me.blacklist.push(&$arg.b.name);
} else {
sdebugln!("No");
}
}
$me.blacklist.extend_from_slice(bl);
vec_remove_all!($me.overrides, bl.iter());
// vec_remove_all!($me.required, bl.iter());
} else { sdebugln!("No"); }
// Add all required args which aren't already found in matcher to the master
// list
debug!("arg_post_processing!: Does '{}' have requirements...", $arg.to_string());
if let Some(reqs) = $arg.requires() {
for n in reqs.iter()
.filter(|&&(val, _)| val.is_none())
.filter(|&&(_, req)| !$matcher.contains(&req))
.map(|&(_, name)| name) {
$me.required.push(n);
}
} else { sdebugln!("No"); }
_handle_group_reqs!($me, $arg);
};
}
macro_rules! _handle_group_reqs{
($me:ident, $arg:ident) => ({
use args::AnyArg;
debugln!("_handle_group_reqs!;");
for grp in &$me.groups {
let found = if grp.args.contains(&$arg.name()) {
if let Some(ref reqs) = grp.requires {
debugln!("_handle_group_reqs!: Adding {:?} to the required list", reqs);
$me.required.extend(reqs);
}
if let Some(ref bl) = grp.conflicts {
$me.blacklist.extend(bl);
}
true // What if arg is in more than one group with different reqs?
} else {
false
};
debugln!("_handle_group_reqs!:iter: grp={}, found={:?}", grp.name, found);
if found {
for i in (0 .. $me.required.len()).rev() {
let should_remove = grp.args.contains(&$me.required[i]);
if should_remove { $me.required.swap_remove(i); }
}
debugln!("_handle_group_reqs!:iter: Adding args from group to blacklist...{:?}", grp.args);
if !grp.multiple {
$me.blacklist.extend(&grp.args);
debugln!("_handle_group_reqs!: removing {:?} from blacklist", $arg.name());
for i in (0 .. $me.blacklist.len()).rev() {
let should_remove = $me.blacklist[i] == $arg.name();
if should_remove { $me.blacklist.swap_remove(i); }
}
}
}
}
})
}
macro_rules! parse_positional {
(
$_self:ident,
$p:ident,
$arg_os:ident,
$pos_counter:ident,
$matcher:ident
) => {
debugln!("parse_positional!;");
if !$_self.is_set(AS::TrailingValues) &&
($_self.is_set(AS::TrailingVarArg) &&
$pos_counter == $_self.positionals.len()) {
$_self.settings.set(AS::TrailingValues);
}
let _ = $_self.add_val_to_arg($p, &$arg_os, $matcher)?;
$matcher.inc_occurrence_of($p.b.name);
let _ = $_self.groups_for_arg($p.b.name)
.and_then(|vec| Some($matcher.inc_occurrences_of(&*vec)));
if $_self.cache.map_or(true, |name| name != $p.b.name) {
arg_post_processing!($_self, $p, $matcher);
$_self.cache = Some($p.b.name);
}
$_self.settings.set(AS::ValidArgFound);
// Only increment the positional counter if it doesn't allow multiples
if !$p.b.settings.is_set(ArgSettings::Multiple) {
$pos_counter += 1;
}
};
}

View file

@ -61,8 +61,7 @@ where
pub global_args: Vec<Arg<'a, 'b>>,
pub required: Vec<&'a str>,
pub r_ifs: Vec<(&'a str, &'b str, &'a str)>,
pub blacklist: Vec<&'b str>,
pub overrides: Vec<&'b str>,
pub overrides: Vec<(&'b str, &'a str)>,
help_short: Option<char>,
version_short: Option<char>,
cache: Option<&'a str>,
@ -346,9 +345,9 @@ where
if let Some(ref reqs) = group.requires {
self.required.extend_from_slice(reqs);
}
if let Some(ref bl) = group.conflicts {
self.blacklist.extend_from_slice(bl);
}
// if let Some(ref bl) = group.conflicts {
// self.blacklist.extend_from_slice(bl);
// }
}
if self.groups.iter().any(|g| g.name == group.name) {
let grp = self.groups
@ -773,12 +772,8 @@ where
// allow wrong self convention due to self.valid_neg_num = true and it's a private method
#[cfg_attr(feature = "lints", allow(wrong_self_convention))]
fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult<'a>) -> bool {
debugln!(
"Parser::is_new_arg: arg={:?}, Needs Val of={:?}",
arg_os,
needs_val_of
);
fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult) -> bool {
debugln!( "Parser::is_new_arg:{:?}:{:?}", arg_os, needs_val_of);
let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) {
true
} else if self.is_set(AS::AllowNegativeNumbers) {
@ -807,12 +802,10 @@ where
.expect(INTERNAL_ERROR_MSG);
(p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
}
ParseResult::ValuesDone => return true,
_ => false,
};
debugln!(
"Parser::is_new_arg: Arg::allow_leading_hyphen({:?})",
arg_allows_tac
);
debugln!( "Parser::is_new_arg: arg_allows_tac={:?}", arg_allows_tac );
// Is this a new argument, or values from a previous option?
let mut ret = if arg_os.starts_with(b"--") {
@ -913,7 +906,52 @@ where
}
}
if !starts_new_arg {
if starts_new_arg {
{
let any_arg = find_any_by_name!(self, self.cache.unwrap_or(""));
matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required);
}
if arg_os.starts_with(b"--") {
needs_val_of = self.parse_long_arg(matcher, &arg_os)?;
debugln!( "Parser:get_matches_with: After parse_long_arg {:?}", needs_val_of );
match needs_val_of {
ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => {
continue
}
_ => (),
}
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
// Try to parse short args like normal, if AllowLeadingHyphen or
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
// an error, and instead return Ok(None)
needs_val_of = self.parse_short_arg(matcher, &arg_os)?;
// If it's None, we then check if one of those two AppSettings was set
debugln!(
"Parser:get_matches_with: After parse_short_arg {:?}",
needs_val_of
);
match needs_val_of {
ParseResult::MaybeNegNum => {
if !(arg_os.to_string_lossy().parse::<i64>().is_ok()
|| arg_os.to_string_lossy().parse::<f64>().is_ok())
{
return Err(Error::unknown_argument(
&*arg_os.to_string_lossy(),
"",
&*usage::create_error_usage(self, matcher, None),
self.color(),
));
}
},
ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => {
continue
}
_ => (),
}
}
} else {
if let ParseResult::Opt(name) = needs_val_of {
// Check to see if parsing a value from a previous arg
let arg = self.opts
@ -925,62 +963,20 @@ where
// get the next value from the iterator
continue;
}
} else if arg_os.starts_with(b"--") {
needs_val_of = self.parse_long_arg(matcher, &arg_os)?;
debugln!(
"Parser:get_matches_with: After parse_long_arg {:?}",
needs_val_of
);
match needs_val_of {
ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => {
continue
}
_ => (),
}
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
// Try to parse short args like normal, if AllowLeadingHyphen or
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
// an error, and instead return Ok(None)
needs_val_of = self.parse_short_arg(matcher, &arg_os)?;
// If it's None, we then check if one of those two AppSettings was set
debugln!(
"Parser:get_matches_with: After parse_short_arg {:?}",
needs_val_of
);
match needs_val_of {
ParseResult::MaybeNegNum => {
if !(arg_os.to_string_lossy().parse::<i64>().is_ok()
|| arg_os.to_string_lossy().parse::<f64>().is_ok())
{
return Err(Error::unknown_argument(
&*arg_os.to_string_lossy(),
"",
&*usage::create_error_usage(self, matcher, None),
self.color(),
));
}
}
ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => {
continue
}
_ => (),
}
}
}
if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound))
&& !self.is_set(AS::InferSubcommands)
{
if let Some(cdate) =
suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self))
{
return Err(Error::invalid_subcommand(
arg_os.to_string_lossy().into_owned(),
cdate,
self.meta.bin_name.as_ref().unwrap_or(&self.meta.name),
&*usage::create_error_usage(self, matcher, None),
self.color(),
));
}
if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound))
&& !self.is_set(AS::InferSubcommands)
{
if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) {
return Err(Error::invalid_subcommand(
arg_os.to_string_lossy().into_owned(),
cdate,
self.meta.bin_name.as_ref().unwrap_or(&self.meta.name),
&*usage::create_error_usage(self, matcher, None),
self.color(),
));
}
}
@ -1035,7 +1031,29 @@ where
self.color(),
));
}
parse_positional!(self, p, arg_os, pos_counter, matcher);
if !self.is_set(AS::TrailingValues) &&
(self.is_set(AS::TrailingVarArg) &&
pos_counter == self.positionals.len()) {
self.settings.set(AS::TrailingValues);
}
if self.cache.map_or(true, |name| name != p.b.name) {
{
let any_arg = find_any_by_name!(self, self.cache.unwrap_or(""));
matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required);
}
self.cache = Some(p.b.name);
}
let _ = self.add_val_to_arg(p, &arg_os, matcher)?;
matcher.inc_occurrence_of(p.b.name);
let _ = self.groups_for_arg(p.b.name)
.and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
self.settings.set(AS::ValidArgFound);
// Only increment the positional counter if it doesn't allow multiples
if !p.b.settings.is_set(ArgSettings::Multiple) {
pos_counter += 1;
}
self.settings.set(AS::ValidArgFound);
} else if self.is_set(AS::AllowExternalSubcommands) {
// Get external subcommand name
@ -1136,9 +1154,34 @@ where
});
}
// In case the last arg was new, we need to process it's overrides
{
let any_arg = find_any_by_name!(self, self.cache.unwrap_or(""));
matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required);
}
self.remove_overrides(matcher);
Validator::new(self).validate(needs_val_of, subcmd_name, matcher)
}
fn remove_overrides(&mut self, matcher: &mut ArgMatcher) {
debugln!("Parser::remove_overrides:{:?};", self.overrides);
for &(overr, name) in &self.overrides {
debugln!("Parser::remove_overrides:iter:({},{});", overr, name);
if matcher.is_present(overr) {
debugln!("Parser::remove_overrides:iter:({},{}): removing {};", overr, name, name);
matcher.remove(name);
for i in (0 .. self.required.len()).rev() {
debugln!("Parser::remove_overrides:iter:({},{}): removing required {};", overr, name, name);
if self.required[i] == name {
self.required.swap_remove(i);
break;
}
}
}
}
}
fn propagate_help_version(&mut self) {
debugln!("Parser::propagate_help_version;");
@ -1483,7 +1526,6 @@ where
self.settings.set(AS::ValidArgFound);
let ret = self.parse_opt(val, opt, val.is_some(), matcher)?;
if self.cache.map_or(true, |name| name != opt.b.name) {
arg_post_processing!(self, opt, matcher);
self.cache = Some(opt.b.name);
}
@ -1501,10 +1543,9 @@ where
self.parse_flag(flag, matcher)?;
// Handle conflicts, requirements, etc.
// if self.cache.map_or(true, |name| name != flag.b.name) {
arg_post_processing!(self, flag, matcher);
// self.cache = Some(flag.b.name);
// }
if self.cache.map_or(true, |name| name != flag.b.name) {
self.cache = Some(flag.b.name);
}
return Ok(ParseResult::Flag);
} else if self.is_set(AS::AllowLeadingHyphen) {
@ -1580,7 +1621,6 @@ where
let ret = self.parse_opt(val, opt, false, matcher)?;
if self.cache.map_or(true, |name| name != opt.b.name) {
arg_post_processing!(self, opt, matcher);
self.cache = Some(opt.b.name);
}
@ -1595,7 +1635,6 @@ where
// Handle conflicts, requirements, overrides, etc.
// Must be called here due to mutablilty
if self.cache.map_or(true, |name| name != flag.b.name) {
arg_post_processing!(self, flag, matcher);
self.cache = Some(flag.b.name);
}
} else {
@ -1844,7 +1883,6 @@ where
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
} else if $m.get($a.b.name).is_some() {
@ -1855,7 +1893,6 @@ where
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
}
@ -1881,7 +1918,6 @@ where
if add {
$_self.add_val_to_arg($a, OsStr::new(default), $m)?;
if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
done = true;
@ -1920,7 +1956,6 @@ where
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
}
@ -1929,7 +1964,6 @@ where
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
}
@ -1972,7 +2006,7 @@ where
}
}
pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg> {
pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg<'a, 'b>> {
if let Some(f) = find_by_name!(self, name, flags, iter) {
return Some(f);
}

View file

@ -78,7 +78,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
Ok(())
}
fn validate_values<A>(
fn validate_arg_values<A>(
&self,
arg: &A,
ma: &MatchedArg,
@ -87,11 +87,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
where
A: AnyArg<'a, 'b> + Display,
{
debugln!("Validator::validate_values: arg={:?}", arg.name());
debugln!("Validator::validate_arg_values: arg={:?}", arg.name());
for val in &ma.vals {
if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() {
debugln!(
"Validator::validate_values: invalid UTF-8 found in val {:?}",
"Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
val
);
return Err(Error::invalid_utf8(
@ -100,7 +100,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
));
}
if let Some(p_vals) = arg.possible_vals() {
debugln!("Validator::validate_values: possible_vals={:?}", p_vals);
debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals);
let val_str = val.to_string_lossy();
let ok = if arg.is_set(ArgSettings::CaseInsensitive) {
p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str))
@ -120,7 +120,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_()
&& matcher.contains(&*arg.name())
{
debugln!("Validator::validate_values: illegal empty val found");
debugln!("Validator::validate_arg_values: illegal empty val found");
return Err(Error::empty_value(
arg,
&*usage::create_error_usage(self.0, matcher, None),
@ -128,7 +128,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
));
}
if let Some(vtor) = arg.validator() {
debug!("Validator::validate_values: checking validator...");
debug!("Validator::validate_arg_values: checking validator...");
if let Err(e) = vtor(val.to_string_lossy().into_owned()) {
sdebugln!("error");
return Err(Error::value_validation(Some(arg), e, self.0.color()));
@ -137,7 +137,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
}
}
if let Some(vtor) = arg.validator_os() {
debug!("Validator::validate_values: checking validator_os...");
debug!("Validator::validate_arg_values: checking validator_os...");
if let Err(e) = vtor(val) {
sdebugln!("error");
return Err(Error::value_validation(
@ -153,44 +153,83 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
Ok(())
}
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debugln!(
"Validator::validate_blacklist: blacklist={:?}",
self.0.blacklist
fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> {
debugln!("build_err!: name={}", name);
let mut c_with = find_from!(self.0, &name, blacklist, &matcher);
c_with = c_with.or(
self.0.find_any_arg(&name).map_or(None, |aa| aa.blacklist())
.map_or(None,
|bl| bl.iter().find(|arg| matcher.contains(arg)))
.map_or(None, |an| self.0.find_any_arg(an))
.map_or(None, |aa| Some(format!("{}", aa)))
);
macro_rules! build_err {
($p:expr, $name:expr, $matcher:ident) => ({
debugln!("build_err!: name={}", $name);
let mut c_with = find_from!($p, &$name, blacklist, &$matcher);
c_with = c_with.or(
$p.find_any_arg(&$name).map_or(None, |aa| aa.blacklist())
.map_or(None,
|bl| bl.iter().find(|arg| $matcher.contains(arg)))
.map_or(None, |an| $p.find_any_arg(an))
.map_or(None, |aa| Some(format!("{}", aa)))
);
debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &$name);
$matcher.remove(&$name);
let usg = usage::create_error_usage($p, $matcher, None);
if let Some(f) = find_by_name!($p, $name, flags, iter) {
debugln!("build_err!: It was a flag...");
Error::argument_conflict(f, c_with, &*usg, self.0.color())
} else if let Some(o) = find_by_name!($p, $name, opts, iter) {
debugln!("build_err!: It was an option...");
Error::argument_conflict(o, c_with, &*usg, self.0.color())
} else {
match find_by_name!($p, $name, positionals, values) {
Some(p) => {
debugln!("build_err!: It was a positional...");
Error::argument_conflict(p, c_with, &*usg, self.0.color())
},
None => panic!(INTERNAL_ERROR_MSG)
debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name);
// matcher.remove(&name);
let usg = usage::create_error_usage(self.0, matcher, None);
if let Some(f) = find_by_name!(self.0, name, flags, iter) {
debugln!("build_err!: It was a flag...");
Err(Error::argument_conflict(f, c_with, &*usg, self.0.color()))
} else if let Some(o) = find_by_name!(self.0, name, opts, iter) {
debugln!("build_err!: It was an option...");
Err(Error::argument_conflict(o, c_with, &*usg, self.0.color()))
} else {
match find_by_name!(self.0, name, positionals, values) {
Some(p) => {
debugln!("build_err!: It was a positional...");
Err(Error::argument_conflict(p, c_with, &*usg, self.0.color()))
},
None => panic!(INTERNAL_ERROR_MSG)
}
}
}
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debugln!("Validator::validate_blacklist;");
let mut conflicts: Vec<&str> = vec![];
for (&name, _) in matcher.iter() {
debugln!("Validator::validate_blacklist:iter:{};", name);
if let Some(grps) = self.0.groups_for_arg(name) {
for grp in &grps {
if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) {
if !g.multiple {
for arg in &g.args {
if arg == &name {
continue;
}
conflicts.push(arg);
}
}
if let Some(ref gc) = g.conflicts {
conflicts.extend(&*gc);
}
}
}
});
}
if let Some(arg) = find_any_by_name!(self.0, name) {
if let Some(bl) = arg.blacklist() {
for conf in bl {
if matcher.get(conf).is_some() {
conflicts.push(conf);
}
}
}
} else {
debugln!("Validator::validate_blacklist:iter:{}:group;", name);
let args = self.0.arg_names_in_group(name);
for arg in &args {
debugln!("Validator::validate_blacklist:iter:{}:group:iter:{};", name, arg);
if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() {
for conf in bl {
if matcher.get(conf).is_some() {
conflicts.push(conf);
}
}
}
}
}
}
for name in &self.0.blacklist {
for name in &conflicts {
debugln!(
"Validator::validate_blacklist:iter:{}: Checking blacklisted arg",
name
@ -213,7 +252,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
name,
n
);
return Err(build_err!(self.0, n, matcher));
return self.build_err(n, matcher);
}
}
} else if let Some(ma) = matcher.get(name) {
@ -224,7 +263,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
should_err = ma.occurs > 0;
}
if should_err {
return Err(build_err!(self.0, *name, matcher));
return self.build_err(*name, matcher);
}
}
Ok(())
@ -240,7 +279,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
);
if let Some(opt) = find_by_name!(self.0, *name, opts, iter) {
self.validate_arg_num_vals(opt, ma, matcher)?;
self.validate_values(opt, ma, matcher)?;
self.validate_arg_values(opt, ma, matcher)?;
self.validate_arg_requires(opt, ma, matcher)?;
self.validate_arg_num_occurs(opt, ma, matcher)?;
} else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) {
@ -249,7 +288,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
} else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) {
self.validate_arg_num_vals(pos, ma, matcher)?;
self.validate_arg_num_occurs(pos, ma, matcher)?;
self.validate_values(pos, ma, matcher)?;
self.validate_arg_values(pos, ma, matcher)?;
self.validate_arg_requires(pos, ma, matcher)?;
} else {
let grp = self.0
@ -381,7 +420,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
where
A: AnyArg<'a, 'b> + Display,
{
debugln!("Validator::validate_arg_requires;");
debugln!("Validator::validate_arg_requires:{};", a.name());
if let Some(a_reqs) = a.requires() {
for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) {
let missing_req =
@ -390,6 +429,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
return self.missing_required_error(matcher, None);
}
}
for &(_, name) in a_reqs.iter().filter(|&&(val, _)| val.is_none()) {
if !matcher.contains(name) {
return self.missing_required_error(matcher, Some(name));
}
}
}
Ok(())
}
@ -399,20 +443,13 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
"Validator::validate_required: required={:?};",
self.0.required
);
'outer: for name in &self.0.required {
debugln!("Validator::validate_required:iter:{}:", name);
if matcher.contains(name) {
continue 'outer;
}
if let Some(a) = find_by_name!(self.0, *name, flags, iter) {
if self.is_missing_required_ok(a, matcher) {
continue 'outer;
}
} else if let Some(a) = find_by_name!(self.0, *name, opts, iter) {
if self.is_missing_required_ok(a, matcher) {
continue 'outer;
}
} else if let Some(a) = find_by_name!(self.0, *name, positionals, values) {
if let Some(a) = find_any_by_name!(self.0, *name) {
if self.is_missing_required_ok(a, matcher) {
continue 'outer;
}
@ -431,11 +468,8 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
Ok(())
}
fn validate_conflicts<A>(&self, a: &A, matcher: &ArgMatcher) -> Option<bool>
where
A: AnyArg<'a, 'b>,
{
debugln!("Validator::validate_conflicts: a={:?};", a.name());
fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
debugln!("Validator::validate_arg_conflicts: a={:?};", a.name());
a.blacklist().map(|bl| {
bl.iter().any(|conf| {
matcher.contains(conf)
@ -448,10 +482,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
})
}
fn validate_required_unless<A>(&self, a: &A, matcher: &ArgMatcher) -> Option<bool>
where
A: AnyArg<'a, 'b>,
{
fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
debugln!("Validator::validate_required_unless: a={:?};", a.name());
macro_rules! check {
($how:ident, $_self:expr, $a:ident, $m:ident) => {{
@ -506,12 +537,9 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
}
#[inline]
fn is_missing_required_ok<A>(&self, a: &A, matcher: &ArgMatcher) -> bool
where
A: AnyArg<'a, 'b>,
{
fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool {
debugln!("Validator::is_missing_required_ok: a={}", a.name());
self.validate_conflicts(a, matcher).unwrap_or(false)
self.validate_arg_conflicts(a, matcher).unwrap_or(false)
|| self.validate_required_unless(a, matcher).unwrap_or(false)
}
}

View file

@ -6,6 +6,7 @@ use std::ffi::{OsStr, OsString};
// Internal
use args::settings::ArgSettings;
use map::{self, VecMap};
use INTERNAL_ERROR_MSG;
#[doc(hidden)]
pub trait AnyArg<'n, 'e>: std_fmt::Display {
@ -41,3 +42,33 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display {
pub trait DispOrder {
fn disp_ord(&self) -> usize;
}
impl<'n, 'e, 'z, T: ?Sized> AnyArg<'n, 'e> for &'z T where T: AnyArg<'n, 'e> + 'z {
fn name(&self) -> &'n str { (*self).name() }
fn overrides(&self) -> Option<&[&'e str]> { (*self).overrides() }
fn aliases(&self) -> Option<Vec<&'e str>> { (*self).aliases() }
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { (*self).requires() }
fn blacklist(&self) -> Option<&[&'e str]> { (*self).blacklist() }
fn required_unless(&self) -> Option<&[&'e str]> { (*self).required_unless() }
fn is_set(&self, a: ArgSettings) -> bool { (*self).is_set(a) }
fn set(&mut self, _: ArgSettings) { panic!(INTERNAL_ERROR_MSG) }
fn has_switch(&self) -> bool { (*self).has_switch() }
fn max_vals(&self) -> Option<u64> { (*self).max_vals() }
fn min_vals(&self) -> Option<u64> { (*self).min_vals() }
fn num_vals(&self) -> Option<u64> { (*self).num_vals() }
fn possible_vals(&self) -> Option<&[&'e str]> { (*self).possible_vals() }
fn validator(&self) -> Option<&Rc<Fn(String) -> Result<(), String>>> { (*self).validator() }
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> Result<(), OsString>>> { (*self).validator_os() }
fn short(&self) -> Option<char> { (*self).short() }
fn long(&self) -> Option<&'e str> { (*self).long() }
fn val_delim(&self) -> Option<char> { (*self).val_delim() }
fn takes_value(&self) -> bool { (*self).takes_value() }
fn val_names(&self) -> Option<&VecMap<&'e str>> { (*self).val_names() }
fn help(&self) -> Option<&'e str> { (*self).help() }
fn long_help(&self) -> Option<&'e str> { (*self).long_help() }
fn default_val(&self) -> Option<&'e OsStr> { (*self).default_val() }
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> { (*self).default_vals_ifs() }
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { (*self).env() }
fn longest_filter(&self) -> bool { (*self).longest_filter() }
fn val_terminator(&self) -> Option<&'e str> { (*self).val_terminator() }
}

View file

@ -21,11 +21,36 @@ impl<'a> Default for ArgMatcher<'a> {
impl<'a> ArgMatcher<'a> {
pub fn new() -> Self { ArgMatcher::default() }
pub fn process_arg_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>, overrides: &mut Vec<(&'b str, &'a str)>, required: &mut Vec<&'a str>) {
debugln!("ArgMatcher::process_arg_overrides:{:?};", a.map_or(None, |a| Some(a.name())));
if let Some(aa) = a {
if let Some(a_overrides) = aa.overrides() {
for overr in a_overrides {
debugln!("ArgMatcher::process_arg_overrides:iter:{};", overr);
if self.is_present(overr) {
debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing from matches;", overr);
self.remove(overr);
for i in (0 .. required.len()).rev() {
if &required[i] == overr {
debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing required;", overr);
required.swap_remove(i);
break;
}
}
} else {
overrides.push((overr, aa.name()));
}
}
}
}
}
pub fn is_present(&self, name: &str) -> bool {
self.0.is_present(name)
}
pub fn propagate_globals(&mut self, global_arg_vec: &[&'a str]) {
debugln!(
"ArgMatcher::get_global_values: global_arg_vec={:?}",
global_arg_vec
);
debugln!( "ArgMatcher::get_global_values: global_arg_vec={:?}", global_arg_vec );
let mut vals_map = HashMap::new();
self.fill_in_global_values(global_arg_vec, &mut vals_map);
}

View file

@ -853,15 +853,15 @@ macro_rules! write_nspaces {
}
// convenience macro for remove an item from a vec
macro_rules! vec_remove_all {
($vec:expr, $to_rem:expr) => {
debugln!("vec_remove_all! to_rem={:?}", $to_rem);
for i in (0 .. $vec.len()).rev() {
let should_remove = $to_rem.any(|name| name == &$vec[i]);
if should_remove { $vec.swap_remove(i); }
}
};
}
//macro_rules! vec_remove_all {
// ($vec:expr, $to_rem:expr) => {
// debugln!("vec_remove_all! to_rem={:?}", $to_rem);
// for i in (0 .. $vec.len()).rev() {
// let should_remove = $to_rem.any(|name| name == &$vec[i]);
// if should_remove { $vec.swap_remove(i); }
// }
// };
//}
macro_rules! find_from {
($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
let mut ret = None;
@ -892,36 +892,49 @@ macro_rules! find_from {
}};
}
macro_rules! find_name_from {
($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
let mut ret = None;
for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, k, flags, iter) {
if let Some(ref v) = f.$from() {
if v.contains($arg_name) {
ret = Some(f.b.name);
}
}
}
if let Some(o) = find_by_name!($_self, k, opts, iter) {
if let Some(ref v) = o.$from() {
if v.contains(&$arg_name) {
ret = Some(o.b.name);
}
}
}
if let Some(pos) = find_by_name!($_self, k, positionals, values) {
if let Some(ref v) = pos.$from() {
if v.contains($arg_name) {
ret = Some(pos.b.name);
}
}
}
}
ret
}};
}
//macro_rules! find_name_from {
// ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
// let mut ret = None;
// for k in $matcher.arg_names() {
// if let Some(f) = find_by_name!($_self, k, flags, iter) {
// if let Some(ref v) = f.$from() {
// if v.contains($arg_name) {
// ret = Some(f.b.name);
// }
// }
// }
// if let Some(o) = find_by_name!($_self, k, opts, iter) {
// if let Some(ref v) = o.$from() {
// if v.contains(&$arg_name) {
// ret = Some(o.b.name);
// }
// }
// }
// if let Some(pos) = find_by_name!($_self, k, positionals, values) {
// if let Some(ref v) = pos.$from() {
// if v.contains($arg_name) {
// ret = Some(pos.b.name);
// }
// }
// }
// }
// ret
// }};
//}
macro_rules! find_any_by_name {
($p:expr, $name:expr) => {
{
fn as_trait_obj<'a, 'b, T: AnyArg<'a, 'b>>(x: &T) -> &AnyArg<'a, 'b> { x }
find_by_name!($p, $name, flags, iter).map(as_trait_obj).or(
find_by_name!($p, $name, opts, iter).map(as_trait_obj).or(
find_by_name!($p, $name, positionals, values).map(as_trait_obj)
)
)
}
}
}
// Finds an arg by name
macro_rules! find_by_name {
($p:expr, $name:expr, $what:ident, $how:ident) => {