From 44ed8b663c92d31ceabe5136e7fc33889f8e4eaa Mon Sep 17 00:00:00 2001 From: Kevin K Date: Thu, 9 Mar 2017 15:51:38 -0500 Subject: [PATCH] refactor: moves validation code into it's own module --- src/app/mod.rs | 1 + src/app/parser.rs | 428 ++---------------------------------------- src/app/validator.rs | 430 +++++++++++++++++++++++++++++++++++++++++++ src/macros.rs | 9 +- 4 files changed, 446 insertions(+), 422 deletions(-) create mode 100644 src/app/validator.rs diff --git a/src/app/mod.rs b/src/app/mod.rs index 5c25dc89..96e393db 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -4,6 +4,7 @@ mod macros; pub mod parser; mod meta; mod help; +mod validator; // Std use std::env; diff --git a/src/app/parser.rs b/src/app/parser.rs index d13a7f59..982fc764 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -21,17 +21,17 @@ use app::App; use app::help::Help; use app::meta::AppMeta; use app::settings::AppFlags; -use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder, - MatchedArg}; +use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder}; use args::settings::ArgSettings; use completions::ComplGen; use errors::{Error, ErrorKind}; use errors::Result as ClapResult; -use fmt::{Colorizer, ColorWhen}; +use fmt::ColorWhen; use osstringext::OsStrExt2; use completions::Shell; use suggestions; use app::settings::AppSettings as AS; +use app::validator::Validator; #[allow(missing_debug_implementations)] #[doc(hidden)] @@ -46,12 +46,12 @@ pub struct Parser<'a, 'b> pub opts: Vec>, pub positionals: VecMap>, pub subcommands: Vec>, - groups: Vec>, + pub groups: Vec>, pub global_args: Vec>, - required: Vec<&'a str>, - r_ifs: Vec<(&'a str, &'b str, &'a str)>, - blacklist: Vec<&'b str>, - overrides: Vec<&'b str>, + 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>, help_short: Option, version_short: Option, cache: Option<&'a str>, @@ -1097,54 +1097,9 @@ impl<'a, 'b> Parser<'a, 'b> }); } - self.validate(needs_val_of, subcmd_name, matcher) + Validator::new(self).validate(needs_val_of, subcmd_name, matcher) } - fn validate(&mut self, - needs_val_of: Option<&'a str>, - subcmd_name: Option, - matcher: &mut ArgMatcher<'a>) - -> ClapResult<()> { - debugln!("Parser::validate;"); - let mut reqs_validated = false; - try!(self.add_defaults(matcher)); - if let Some(a) = needs_val_of { - debugln!("Parser::validate: needs_val_of={:?}", a); - if let Some(o) = find_by_name!(self, &a, opts, iter) { - try!(self.validate_required(matcher)); - reqs_validated = true; - let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { - v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) - } else { - true - }; - if should_err { - return Err(Error::empty_value(o, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - } - - try!(self.validate_blacklist(matcher)); - if !(self.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { - try!(self.validate_required(matcher)); - } - try!(self.validate_matched_args(matcher)); - matcher.usage(self.create_usage(&[])); - - if matcher.is_empty() && matcher.subcommand_name().is_none() && - self.is_set(AS::ArgRequiredElseHelp) { - let mut out = vec![]; - try!(self.write_help_err(&mut out)); - return Err(Error { - message: String::from_utf8_lossy(&*out).into_owned(), - kind: ErrorKind::MissingArgumentOrSubcommand, - info: None, - }); - } - Ok(()) - } fn propogate_help_version(&mut self) { debugln!("Parser::propogate_help_version;"); @@ -1294,7 +1249,7 @@ impl<'a, 'b> Parser<'a, 'b> args.iter().map(ToOwned::to_owned).collect() } - fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { + pub fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { let mut g_vec = vec![]; let mut args = vec![]; @@ -1697,62 +1652,6 @@ impl<'a, 'b> Parser<'a, 'b> Ok(None) } - fn validate_values(&self, - arg: &A, - ma: &MatchedArg, - matcher: &ArgMatcher<'a>) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_values: arg={:?}", arg.name()); - for val in &ma.vals { - if self.is_set(AS::StrictUtf8) && val.to_str().is_none() { - debugln!("Parser::validate_values: invalid UTF-8 found in val {:?}", - val); - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), - self.color())); - } - if let Some(p_vals) = arg.possible_vals() { - debugln!("Parser::validate_values: possible_vals={:?}", p_vals); - let val_str = val.to_string_lossy(); - if !p_vals.contains(&&*val_str) { - return Err(Error::invalid_value(val_str, - p_vals, - arg, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && - matcher.contains(&*arg.name()) { - debugln!("Parser::validate_values: illegal empty val found"); - return Err(Error::empty_value(arg, - &*self.create_current_usage(matcher, None), - self.color())); - } - if let Some(vtor) = arg.validator() { - debug!("Parser::validate_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.color())); - } else { - sdebugln!("good"); - } - } - if let Some(vtor) = arg.validator_os() { - debug!("Parser::validate_values: checking validator_os..."); - if let Err(e) = vtor(&val) { - sdebugln!("error"); - return Err(Error::value_validation(Some(arg), - (*e).to_string_lossy().to_string(), - self.color())); - } else { - sdebugln!("good"); - } - } - } - Ok(()) - } fn parse_flag(&self, flag: &FlagBuilder<'a, 'b>, @@ -1767,311 +1666,6 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { - debugln!("Parser::validate_blacklist: blacklist={:?}", self.blacklist); - macro_rules! build_err { - ($me:ident, $name:expr, $matcher:ident) => ({ - debugln!("build_err!: name={}", $name); - let mut c_with = find_from!($me, $name, blacklist, &$matcher); - c_with = c_with.or( - $me.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| $me.find_any_arg(an)) - .map_or(None, |aa| Some(format!("{}", aa))) - ); - debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, $name); - $matcher.remove($name); - let usg = $me.create_current_usage($matcher, None); - if let Some(f) = find_by_name!($me, $name, flags, iter) { - debugln!("build_err!: It was a flag..."); - Error::argument_conflict(f, c_with, &*usg, self.color()) - } else if let Some(o) = find_by_name!($me, $name, opts, iter) { - debugln!("build_err!: It was an option..."); - Error::argument_conflict(o, c_with, &*usg, self.color()) - } else { - match find_by_name!($me, $name, positionals, values) { - Some(p) => { - debugln!("build_err!: It was a positional..."); - Error::argument_conflict(p, c_with, &*usg, self.color()) - }, - None => panic!(INTERNAL_ERROR_MSG) - } - } - }); - } - - for name in &self.blacklist { - debugln!("Parser::validate_blacklist:iter: Checking blacklisted name: {}", - name); - if self.groups.iter().any(|g| &g.name == name) { - debugln!("Parser::validate_blacklist:iter: groups contains it..."); - for n in self.arg_names_in_group(name) { - debugln!("Parser::validate_blacklist:iter:iter: Checking arg '{}' in group...", - n); - if matcher.contains(n) { - debugln!("Parser::validate_blacklist:iter:iter: matcher contains it..."); - return Err(build_err!(self, &n, matcher)); - } - } - } else if matcher.contains(name) { - debugln!("Parser::validate_blacklist:iter: matcher contains it..."); - return Err(build_err!(self, name, matcher)); - } - } - Ok(()) - } - - fn validate_matched_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { - debugln!("Parser::validate_matched_args;"); - for (name, ma) in matcher.iter() { - debugln!("Parser::validate_matched_args:iter:{}: vals={:#?}", - name, - ma.vals); - if let Some(opt) = find_by_name!(self, name, opts, iter) { - try!(self.validate_arg_num_vals(opt, ma, matcher)); - try!(self.validate_values(opt, ma, matcher)); - try!(self.validate_arg_requires(opt, ma, matcher)); - try!(self.validate_arg_num_occurs(opt, ma, matcher)); - } else if let Some(flag) = find_by_name!(self, name, flags, iter) { - try!(self.validate_arg_requires(flag, ma, matcher)); - try!(self.validate_arg_num_occurs(flag, ma, matcher)); - } else if let Some(pos) = find_by_name!(self, name, positionals, values) { - try!(self.validate_arg_num_vals(pos, ma, matcher)); - try!(self.validate_arg_num_occurs(pos, ma, matcher)); - try!(self.validate_values(pos, ma, matcher)); - try!(self.validate_arg_requires(pos, ma, matcher)); - } else { - let grp = self.groups.iter().find(|g| &g.name == name).expect(INTERNAL_ERROR_MSG); - if let Some(ref g_reqs) = grp.requires { - if g_reqs.iter().any(|&n| !matcher.contains(n)) { - return self.missing_required_error(matcher, None); - } - } - } - } - Ok(()) - } - - fn validate_arg_num_occurs(&self, - a: &A, - ma: &MatchedArg, - matcher: &ArgMatcher) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_arg_num_occurs: a={};", a.name()); - if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) { - // Not the first time, and we don't allow multiples - return Err(Error::unexpected_multiple_usage(a, - &*self.create_current_usage(matcher, None), - self.color())); - } - Ok(()) - } - - fn validate_arg_num_vals(&self, - a: &A, - ma: &MatchedArg, - matcher: &ArgMatcher) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_arg_num_vals;"); - if let Some(num) = a.num_vals() { - debugln!("Parser::validate_arg_num_vals: num_vals set...{}", num); - let should_err = if a.is_set(ArgSettings::Multiple) { - ((ma.vals.len() as u64) % num) != 0 - } else { - num != (ma.vals.len() as u64) - }; - if should_err { - debugln!("Parser::validate_arg_num_vals: Sending error WrongNumberOfValues"); - return Err(Error::wrong_number_of_values(a, - num, - if a.is_set(ArgSettings::Multiple) { - (ma.vals.len() % num as usize) - } else { - ma.vals.len() - }, - if ma.vals.len() == 1 || - (a.is_set(ArgSettings::Multiple) && - (ma.vals.len() % num as usize) == - 1) { - "as" - } else { - "ere" - }, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if let Some(num) = a.max_vals() { - debugln!("Parser::validate_arg_num_vals: max_vals set...{}", num); - if (ma.vals.len() as u64) > num { - debugln!("Parser::validate_arg_num_vals: Sending error TooManyValues"); - return Err(Error::too_many_values(ma.vals - .iter() - .last() - .expect(INTERNAL_ERROR_MSG) - .to_str() - .expect(INVALID_UTF8), - a, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if let Some(num) = a.min_vals() { - debugln!("Parser::validate_arg_num_vals: min_vals set: {}", num); - if (ma.vals.len() as u64) < num { - debugln!("Parser::validate_arg_num_vals: Sending error TooFewValues"); - return Err(Error::too_few_values(a, - num, - ma.vals.len(), - &*self.create_current_usage(matcher, None), - self.color())); - } - } - // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) - if a.takes_value() && !a.is_set(ArgSettings::EmptyValues) && ma.vals.is_empty() { - return Err(Error::empty_value(a, - &*self.create_current_usage(matcher, None), - self.color())); - } - Ok(()) - } - - fn validate_arg_requires(&self, - a: &A, - ma: &MatchedArg, - matcher: &ArgMatcher) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_arg_requires;"); - if let Some(a_reqs) = a.requires() { - for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { - if ma.vals - .iter() - .any(|v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name)) { - return self.missing_required_error(matcher, None); - } - } - } - Ok(()) - } - - #[inline] - fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { - debugln!("Parser::missing_required_error: extra={:?}", extra); - let c = Colorizer { - use_stderr: true, - when: self.color(), - }; - let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); - if let Some(r) = extra { - reqs.push(r); - } - reqs.retain(|n| !matcher.contains(n)); - reqs.dedup(); - debugln!("Parser::missing_required_error: reqs={:#?}", reqs); - Err(Error::missing_required_argument(&*self.get_required_from(&reqs[..], - Some(matcher), - extra) - .iter() - .fold(String::new(), |acc, s| { - acc + &format!("\n {}", c.error(s))[..] - }), - &*self.create_current_usage(matcher, extra), - self.color())) - } - - fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { - debugln!("Parser::validate_required: required={:?};", self.required); - 'outer: for name in &self.required { - debugln!("Parser::validate_required:iter:{}:", name); - if matcher.contains(name) { - continue 'outer; - } - if let Some(a) = find_by_name!(self, name, flags, iter) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } else if let Some(a) = find_by_name!(self, name, opts, iter) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } else if let Some(a) = find_by_name!(self, name, positionals, values) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } - return self.missing_required_error(matcher, None); - } - - // Validate the conditionally required args - for &(a, v, r) in &self.r_ifs { - if let Some(ma) = matcher.get(a) { - if matcher.get(r).is_none() { - if ma.vals.iter().any(|val| val == v) { - return self.missing_required_error(matcher, Some(r)); - } - } - } - } - Ok(()) - } - - fn check_conflicts(&self, a: &A, matcher: &ArgMatcher) -> Option - where A: AnyArg<'a, 'b> - { - debugln!("Parser::check_conflicts: a={:?};", a.name()); - a.blacklist().map(|bl| { - bl.iter().any(|conf| { - matcher.contains(conf) || - self.groups - .iter() - .find(|g| &g.name == conf) - .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) - }) - }) - } - - fn check_required_unless(&self, a: &A, matcher: &ArgMatcher) -> Option - where A: AnyArg<'a, 'b> - { - debugln!("Parser::check_required_unless: a={:?};", a.name()); - macro_rules! check { - ($how:ident, $_self:ident, $a:ident, $m:ident) => {{ - $a.required_unless().map(|ru| { - ru.iter().$how(|n| { - $m.contains(n) || { - if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { - grp.args.iter().any(|arg| $m.contains(arg)) - } else { - false - } - } - }) - }) - }}; - } - if a.is_set(ArgSettings::RequiredUnlessAll) { - check!(all, self, a, matcher) - } else { - check!(any, self, a, matcher) - } - } - - #[inline] - fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool - where A: AnyArg<'a, 'b> - { - debugln!("Parser::is_missing_required_ok: a={}", a.name()); - self.check_conflicts(a, matcher).unwrap_or(false) || - self.check_required_unless(a, matcher).unwrap_or(false) - } - fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { // Didn't match a flag or option...maybe it was a typo and close to one let suffix = @@ -2261,7 +1855,7 @@ impl<'a, 'b> Parser<'a, 'b> Help::write_parser_help_to_stderr(w, self) } - fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + pub fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { macro_rules! add_val { (@default $_self:ident, $a:ident, $m:ident) => { if let Some(ref val) = $a.v.default_val { diff --git a/src/app/validator.rs b/src/app/validator.rs new file mode 100644 index 00000000..afce3089 --- /dev/null +++ b/src/app/validator.rs @@ -0,0 +1,430 @@ +// std +use std::fmt::Display; + +// Internal +use INTERNAL_ERROR_MSG; +use INVALID_UTF8; +use args::{AnyArg, ArgMatcher, MatchedArg}; +use args::settings::ArgSettings; +use errors::{Error, ErrorKind}; +use errors::Result as ClapResult; +use osstringext::OsStrExt2; +use app::settings::AppSettings as AS; +use app::parser::Parser; +use fmt::Colorizer; + +pub struct Validator<'a, 'b, 'z>(&'z mut Parser<'a, 'b>) where 'a: 'b, 'b: 'z; + +impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { + pub fn new(p: &'z mut Parser<'a, 'b>) -> Self { + Validator(p) + } + + pub fn validate(&mut self, + needs_val_of: Option<&'a str>, + subcmd_name: Option, + matcher: &mut ArgMatcher<'a>) + -> ClapResult<()> { + debugln!("Validator::validate;"); + let mut reqs_validated = false; + try!(self.0.add_defaults(matcher)); + if let Some(a) = needs_val_of { + debugln!("Validator::validate: needs_val_of={:?}", a); + if let Some(o) = find_by_name!(self.0, &a, opts, iter) { + try!(self.validate_required(matcher)); + reqs_validated = true; + let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { + v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) + } else { + true + }; + if should_err { + return Err(Error::empty_value(o, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + } + } + + try!(self.validate_blacklist(matcher)); + if !(self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { + try!(self.validate_required(matcher)); + } + try!(self.validate_matched_args(matcher)); + matcher.usage(self.0.create_usage(&[])); + + if matcher.is_empty() && matcher.subcommand_name().is_none() && + self.0.is_set(AS::ArgRequiredElseHelp) { + let mut out = vec![]; + try!(self.0.write_help_err(&mut out)); + return Err(Error { + message: String::from_utf8_lossy(&*out).into_owned(), + kind: ErrorKind::MissingArgumentOrSubcommand, + info: None, + }); + } + Ok(()) + } + + fn validate_values(&self, + arg: &A, + ma: &MatchedArg, + matcher: &ArgMatcher<'a>) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_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 {:?}", + val); + return Err(Error::invalid_utf8(&*self.0.create_current_usage(matcher, None), + self.0.color())); + } + if let Some(p_vals) = arg.possible_vals() { + debugln!("Validator::validate_values: possible_vals={:?}", p_vals); + let val_str = val.to_string_lossy(); + if !p_vals.contains(&&*val_str) { + return Err(Error::invalid_value(val_str, + p_vals, + arg, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + } + if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && + matcher.contains(&*arg.name()) { + debugln!("Validator::validate_values: illegal empty val found"); + return Err(Error::empty_value(arg, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + if let Some(vtor) = arg.validator() { + debug!("Validator::validate_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())); + } else { + sdebugln!("good"); + } + } + if let Some(vtor) = arg.validator_os() { + debug!("Validator::validate_values: checking validator_os..."); + if let Err(e) = vtor(&val) { + sdebugln!("error"); + return Err(Error::value_validation(Some(arg), + (*e).to_string_lossy().to_string(), + self.0.color())); + } else { + sdebugln!("good"); + } + } + } + Ok(()) + } + + fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + debugln!("Validator::validate_blacklist: blacklist={:?}", self.0.blacklist); + 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 = $p.create_current_usage($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) + } + } + }); + } + + for name in &self.0.blacklist { + debugln!("Validator::validate_blacklist:iter: Checking blacklisted name: {}", + name); + if self.0.groups.iter().any(|g| &g.name == name) { + debugln!("Validator::validate_blacklist:iter: groups contains it..."); + for n in self.0.arg_names_in_group(name) { + debugln!("Validator::validate_blacklist:iter:iter: Checking arg '{}' in group...", + n); + if matcher.contains(n) { + debugln!("Validator::validate_blacklist:iter:iter: matcher contains it..."); + return Err(build_err!(self.0, &n, matcher)); + } + } + } else if matcher.contains(name) { + debugln!("Validator::validate_blacklist:iter: matcher contains it..."); + return Err(build_err!(self.0, name, matcher)); + } + } + Ok(()) + } + + fn validate_matched_args(&self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + debugln!("Validator::validate_matched_args;"); + for (name, ma) in matcher.iter() { + debugln!("Validator::validate_matched_args:iter:{}: vals={:#?}", + name, + ma.vals); + if let Some(opt) = find_by_name!(self.0, name, opts, iter) { + try!(self.validate_arg_num_vals(opt, ma, matcher)); + try!(self.validate_values(opt, ma, matcher)); + try!(self.validate_arg_requires(opt, ma, matcher)); + try!(self.validate_arg_num_occurs(opt, ma, matcher)); + } else if let Some(flag) = find_by_name!(self.0, name, flags, iter) { + try!(self.validate_arg_requires(flag, ma, matcher)); + try!(self.validate_arg_num_occurs(flag, ma, matcher)); + } else if let Some(pos) = find_by_name!(self.0, name, positionals, values) { + try!(self.validate_arg_num_vals(pos, ma, matcher)); + try!(self.validate_arg_num_occurs(pos, ma, matcher)); + try!(self.validate_values(pos, ma, matcher)); + try!(self.validate_arg_requires(pos, ma, matcher)); + } else { + let grp = self.0.groups.iter().find(|g| &g.name == name).expect(INTERNAL_ERROR_MSG); + if let Some(ref g_reqs) = grp.requires { + if g_reqs.iter().any(|&n| !matcher.contains(n)) { + return self.missing_required_error(matcher, None); + } + } + } + } + Ok(()) + } + + fn validate_arg_num_occurs(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_arg_num_occurs: a={};", a.name()); + if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) { + // Not the first time, and we don't allow multiples + return Err(Error::unexpected_multiple_usage(a, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + Ok(()) + } + + fn validate_arg_num_vals(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_arg_num_vals;"); + if let Some(num) = a.num_vals() { + debugln!("Validator::validate_arg_num_vals: num_vals set...{}", num); + let should_err = if a.is_set(ArgSettings::Multiple) { + ((ma.vals.len() as u64) % num) != 0 + } else { + num != (ma.vals.len() as u64) + }; + if should_err { + debugln!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues"); + return Err(Error::wrong_number_of_values(a, + num, + if a.is_set(ArgSettings::Multiple) { + (ma.vals.len() % num as usize) + } else { + ma.vals.len() + }, + if ma.vals.len() == 1 || + (a.is_set(ArgSettings::Multiple) && + (ma.vals.len() % num as usize) == + 1) { + "as" + } else { + "ere" + }, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + } + if let Some(num) = a.max_vals() { + debugln!("Validator::validate_arg_num_vals: max_vals set...{}", num); + if (ma.vals.len() as u64) > num { + debugln!("Validator::validate_arg_num_vals: Sending error TooManyValues"); + return Err(Error::too_many_values(ma.vals + .iter() + .last() + .expect(INTERNAL_ERROR_MSG) + .to_str() + .expect(INVALID_UTF8), + a, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + } + if let Some(num) = a.min_vals() { + debugln!("Validator::validate_arg_num_vals: min_vals set: {}", num); + if (ma.vals.len() as u64) < num { + debugln!("Validator::validate_arg_num_vals: Sending error TooFewValues"); + return Err(Error::too_few_values(a, + num, + ma.vals.len(), + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + } + // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) + if a.takes_value() && !a.is_set(ArgSettings::EmptyValues) && ma.vals.is_empty() { + return Err(Error::empty_value(a, + &*self.0.create_current_usage(matcher, None), + self.0.color())); + } + Ok(()) + } + + fn validate_arg_requires(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_arg_requires;"); + if let Some(a_reqs) = a.requires() { + for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { + if ma.vals + .iter() + .any(|v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name)) { + return self.missing_required_error(matcher, None); + } + } + } + Ok(()) + } + + fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { + debugln!("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 self.is_missing_required_ok(a, matcher) { + continue 'outer; + } + } + return self.missing_required_error(matcher, None); + } + + // Validate the conditionally required args + for &(a, v, r) in &self.0.r_ifs { + if let Some(ma) = matcher.get(a) { + if matcher.get(r).is_none() { + if ma.vals.iter().any(|val| val == v) { + return self.missing_required_error(matcher, Some(r)); + } + } + } + } + Ok(()) + } + + fn validate_conflicts(&self, a: &A, matcher: &ArgMatcher) -> Option + where A: AnyArg<'a, 'b> + { + debugln!("Validator::validate_conflicts: a={:?};", a.name()); + a.blacklist().map(|bl| { + bl.iter().any(|conf| { + matcher.contains(conf) || + self.0.groups + .iter() + .find(|g| &g.name == conf) + .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) + }) + }) + } + + fn validate_required_unless(&self, a: &A, matcher: &ArgMatcher) -> Option + where A: AnyArg<'a, 'b> + { + debugln!("Validator::validate_required_unless: a={:?};", a.name()); + macro_rules! check { + ($how:ident, $_self:expr, $a:ident, $m:ident) => {{ + $a.required_unless().map(|ru| { + ru.iter().$how(|n| { + $m.contains(n) || { + if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { + grp.args.iter().any(|arg| $m.contains(arg)) + } else { + false + } + } + }) + }) + }}; + } + if a.is_set(ArgSettings::RequiredUnlessAll) { + check!(all, self.0, a, matcher) + } else { + check!(any, self.0, a, matcher) + } + } + + #[inline] + fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { + debugln!("Validator::missing_required_error: extra={:?}", extra); + let c = Colorizer { + use_stderr: true, + when: self.0.color(), + }; + let mut reqs = self.0.required.iter().map(|&r| &*r).collect::>(); + if let Some(r) = extra { + reqs.push(r); + } + reqs.retain(|n| !matcher.contains(n)); + reqs.dedup(); + debugln!("Validator::missing_required_error: reqs={:#?}", reqs); + Err(Error::missing_required_argument(&*self.0.get_required_from(&reqs[..], + Some(matcher), + extra) + .iter() + .fold(String::new(), |acc, s| { + acc + &format!("\n {}", c.error(s))[..] + }), + &*self.0.create_current_usage(matcher, extra), + self.0.color())) + } + + #[inline] + fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool + where A: AnyArg<'a, 'b> + { + debugln!("Validator::is_missing_required_ok: a={}", a.name()); + self.validate_conflicts(a, matcher).unwrap_or(false) || + self.validate_required_unless(a, matcher).unwrap_or(false) + } +} \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs index e5483741..6fc7763a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -847,7 +847,7 @@ macro_rules! vec_remove_all { }; } macro_rules! find_from { - ($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{ + ($_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) { @@ -877,7 +877,7 @@ macro_rules! find_from { } macro_rules! find_name_from { - ($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{ + ($_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) { @@ -908,8 +908,8 @@ macro_rules! find_name_from { // Finds an arg by name macro_rules! find_by_name { - ($_self:ident, $name:expr, $what:ident, $how:ident) => { - $_self.$what.$how().find(|o| &o.b.name == $name) + ($p:expr, $name:expr, $what:ident, $how:ident) => { + $p.$what.$how().find(|o| &o.b.name == $name) } } @@ -1057,7 +1057,6 @@ macro_rules! _names { .iter() .filter(|s| s.p.meta.aliases.is_some()) .flat_map(|s| s.p.meta.aliases.as_ref().unwrap().iter().map(|&(n, _)| n))) - // .map(|n| &n) }} }