feat(Args): allows for custom argument value validations to be defined

Closes #170
This commit is contained in:
Kevin K 2015-08-13 23:22:48 -04:00
parent 23aca97072
commit 84ae2ddbce
4 changed files with 94 additions and 8 deletions

View file

@ -663,7 +663,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
max_vals: a.max_vals,
help: a.help,
global: a.global,
empty_vals: a.empty_vals
empty_vals: a.empty_vals,
validator: None
};
if pb.min_vals.is_some() && !pb.multiple {
panic!("Argument \"{}\" does not allow multiple values, yet it is expecting {} \
@ -700,6 +701,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
for n in p { phs.insert(*n); }
pb.possible_vals = Some(phs);
}
if let Some(ref p) = a.validator {
pb.validator = Some(p.clone());
}
self.positionals_idx.insert(i, pb);
} else if a.takes_value {
if a.short.is_none() && a.long.is_none() {
@ -722,7 +726,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
val_names: a.val_names.clone(),
requires: None,
required: a.required,
empty_vals: a.empty_vals
empty_vals: a.empty_vals,
validator: None
};
if let Some(ref vec) = ob.val_names {
ob.num_vals = Some(vec.len() as u8);
@ -743,6 +748,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
for n in bl { bhs.insert(*n); }
ob.blacklist = Some(bhs);
}
if let Some(ref p) = a.validator {
ob.validator = Some(p.clone());
}
// Check if there is anything in the requires list and add any values
if let Some(ref r) = a.requires {
let mut rhs = HashSet::new();
@ -764,6 +772,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
}
self.opts.insert(a.name, ob);
} else {
if a.validator.is_some() {
panic!("The argument '{}' has a validator set, yet was parsed as a flag. Ensure \
.takes_value(true) or .index(u8) is set.")
}
if !a.empty_vals {
// Empty vals defaults to true, so if it's false it was manually set
panic!("The argument '{}' cannot have empty_values() set because it is a flag. \
@ -1758,6 +1770,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if let Some(ref mut o) = matches.args.get_mut(opt.name) {
// Options have values, so we can unwrap()
if let Some(ref mut vals) = o.values {
if let Some(ref vtor) = opt.validator {
if let Err(e) = vtor(arg_slice.to_owned()) {
self.report_error(e,
true,
Some(vec![opt.name]));
}
}
let len = vals.len() as u8 + 1;
vals.insert(len, arg_slice.to_owned());
}
@ -1928,6 +1947,14 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
Some(matches.args.keys()
.map(|k| *k).collect()));
}
if let Some(ref vtor) = p.validator {
let f = &*vtor;
if let Err(ref e) = f(arg_slice.to_owned()) {
self.report_error(e.clone(),
true,
Some(matches.args.keys().map(|k| *k).collect()));
}
}
bm.insert(1, arg_slice.to_owned());
matches.args.insert(p.name, MatchedArg{
occurrences: 1,
@ -2234,6 +2261,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
Some(matches.args.keys()
.map(|k| *k).collect()));
}
if let Some(ref vtor) = v.validator {
if let Err(e) = vtor(arg_val.clone().unwrap()) {
self.report_error(e,
true,
Some(matches.args.keys().map(|k| *k).collect()));
}
}
if let Some(ref mut o) = matches.args.get_mut(v.name) {
o.occurrences += 1;
if let Some(ref mut vals) = o.values {
@ -2250,6 +2284,15 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
Some(matches.args.keys()
.map(|k| *k).collect()));
}
if let Some(ref val) = arg_val {
if let Some(ref vtor) = v.validator {
if let Err(e) = vtor(val.clone()) {
self.report_error(e,
true,
Some(matches.args.keys().map(|k| *k).collect()));
}
}
}
matches.args.insert(v.name, MatchedArg{
occurrences: if arg_val.is_some() { 1 } else { 0 },
values: if arg_val.is_some() {

View file

@ -1,5 +1,6 @@
use std::iter::IntoIterator;
use std::collections::HashSet;
use std::rc::Rc;
use usageparser::{UsageParser, UsageToken};
@ -90,7 +91,9 @@ pub struct Arg<'n, 'l, 'h, 'g, 'p, 'r> {
#[doc(hidden)]
pub empty_vals: bool,
#[doc(hidden)]
pub global: bool
pub global: bool,
#[doc(hidden)]
pub validator: Option<Rc<Fn(String) -> Result<(), String>>>
}
impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
@ -131,7 +134,8 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
val_names: None,
group: None,
global: false,
empty_vals: true
empty_vals: true,
validator: None
}
}
@ -289,7 +293,8 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
min_vals: None,
group: None,
global: false,
empty_vals: true
empty_vals: true,
validator: None,
}
}
@ -663,6 +668,37 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
self
}
/// Allows one to perform a validation on the argument value. You provide a closure which
/// accepts a `String` value, a `Result` where the `Err(String)` is a message displayed to the
/// user.
///
/// **NOTE:** The error message does *not* need to contain the `error:` portion, only the
/// message.
///
/// **NOTE:** There is a small performance hit for using validators, as they are implemented
/// with `Rc` pointers. And the value to be checked will be allocated an extra time in order to
/// to be passed to the closure.
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// # let matches = App::new("myprog")
/// # .arg(
/// # Arg::with_name("debug").index(1)
/// .validator(|val| {
/// if val.contains("@") {
/// Ok(())
/// } else {
/// Err(String::from("the value must contain at lesat one '@' character"))
/// }
/// })
/// # ).get_matches();
pub fn validator<F>(mut self, f: F) -> Self where F: Fn(String) -> Result<(), String> + 'static {
self.validator = Some(Rc::new(f));
self
}
/// Specifies the *maximum* number of values are for this argument. For example, if you had a
/// `-f <file>` argument where you wanted up to 3 'files' you would set
/// `.max_values(3)`, and this argument would be satisfied if the user provided, 1, 2, or 3
@ -780,7 +816,8 @@ impl<'n, 'l, 'h, 'g, 'p, 'r, 'z> From<&'z Arg<'n, 'l, 'h, 'g, 'p, 'r>> for Arg<'
val_names: a.val_names.clone(),
group: a.group,
global: a.global,
empty_vals: a.empty_vals
empty_vals: a.empty_vals,
validator: a.validator.clone()
}
}
}

View file

@ -1,6 +1,8 @@
use std::rc::Rc;
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::fmt::{ Display, Formatter, Result };
use std::result::Result as StdResult;
pub struct OptBuilder<'n> {
pub name: &'n str,
@ -31,6 +33,7 @@ pub struct OptBuilder<'n> {
pub val_names: Option<Vec<&'n str>>,
pub empty_vals: bool,
pub global: bool,
pub validator: Option<Rc<Fn(String) -> StdResult<(), String>>>
}
impl<'n> Display for OptBuilder<'n> {

View file

@ -1,6 +1,8 @@
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::fmt::{ Display, Formatter, Result };
use std::result::Result as StdResult;
use std::rc::Rc;
pub struct PosBuilder<'n> {
pub name: &'n str,
@ -27,7 +29,8 @@ pub struct PosBuilder<'n> {
pub max_vals: Option<u8>,
pub min_vals: Option<u8>,
pub empty_vals: bool,
pub global: bool
pub global: bool,
pub validator: Option<Rc<Fn(String) -> StdResult<(), String>>>
}
impl<'n> Display for PosBuilder<'n> {