mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat(Args): allows for custom argument value validations to be defined
Closes #170
This commit is contained in:
parent
23aca97072
commit
84ae2ddbce
4 changed files with 94 additions and 8 deletions
47
src/app.rs
47
src/app.rs
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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,12 +29,13 @@ 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> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
if self.required {
|
||||
if self.required {
|
||||
try!(write!(f, "<{}>", self.name));
|
||||
} else {
|
||||
try!(write!(f, "[{}]", self.name));
|
||||
|
|
Loading…
Reference in a new issue