feat(args): add support for a specific set of allowed values on options

or positional arguments
This commit is contained in:
Kevin K 2015-03-28 19:33:20 -04:00
parent bcd1fa728e
commit 270eb88925
5 changed files with 202 additions and 47 deletions

View file

@ -180,7 +180,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
/// )
/// # .get_matches();
/// ```
pub fn arg<'l, 'h, 'b, 'r>(mut self, a: Arg<'ar, 'ar, 'ar, 'ar, 'ar>) -> App<'a, 'v, 'ab, 'u, 'ar> {
pub fn arg<'l, 'h, 'b, 'r>(mut self, a: Arg<'ar, 'ar, 'ar, 'ar, 'ar, 'ar>) -> App<'a, 'v, 'ab, 'u, 'ar> {
if self.arg_list.contains(a.name) {
panic!("Argument name must be unique, \"{}\" is already in use", a.name);
} else {
@ -213,30 +213,76 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
if a.takes_value {
panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied", a.name);
}
self.positionals_idx.insert(i, PosBuilder {
// Create the Positional Arguemnt Builder with each HashSet = None to only allocate those that require it
let mut pb = PosBuilder {
name: a.name,
index: i,
required: a.required,
blacklist: a.blacklist,
requires: a.requires,
blacklist: None,
requires: None,
possible_vals: None,
help: a.help,
});
};
// Check if there is anything in the blacklist (mutually excludes list) and add any values
if let Some(ref bl) = a.blacklist {
let mut bhs = HashSet::new();
// without derefing n = &&str
for n in bl { bhs.insert(*n); }
pb.blacklist = Some(bhs);
}
// 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();
// without derefing n = &&str
for n in r { rhs.insert(*n); }
pb.requires = Some(rhs);
}
// Check if there is anything in the possible values and add those as well
if let Some(ref p) = a.possible_vals {
let mut phs = HashSet::new();
// without derefing n = &&str
for n in p { phs.insert(*n); }
pb.possible_vals = Some(phs);
}
self.positionals_idx.insert(i, pb);
} else if a.takes_value {
if a.short.is_none() && a.long.is_none() {
panic!("Argument \"{}\" has take_value(true), yet neither a short() or long() were supplied", a.name);
}
// No need to check for .index() as that is handled above
self.opts.insert(a.name, OptBuilder {
let mut ob = OptBuilder {
name: a.name,
short: a.short,
long: a.long,
multiple: a.multiple,
blacklist: a.blacklist,
blacklist: None,
help: a.help,
requires: a.requires,
possible_vals: None,
requires: None,
required: a.required,
});
};
// Check if there is anything in the blacklist (mutually excludes list) and add any values
if let Some(ref bl) = a.blacklist {
let mut bhs = HashSet::new();
// without derefing n = &&str
for n in bl { bhs.insert(*n); }
ob.blacklist = Some(bhs);
}
// 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();
// without derefing n = &&str
for n in r { rhs.insert(*n); }
ob.requires = Some(rhs);
}
// Check if there is anything in the possible values and add those as well
if let Some(ref p) = a.possible_vals {
let mut phs = HashSet::new();
// without derefing n = &&str
for n in p { phs.insert(*n); }
ob.possible_vals = Some(phs);
}
self.opts.insert(a.name, ob);
} else {
if let Some(ref l) = a.long {
if *l == "help" {
@ -265,15 +311,30 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
// if self.required.contains(a.name) {
// self.required.remove(a.name);
// }
self.flags.insert(a.name, FlagBuilder {
let mut fb = FlagBuilder {
name: a.name,
short: a.short,
long: a.long,
help: a.help,
blacklist: a.blacklist,
blacklist: None,
multiple: a.multiple,
requires: a.requires,
});
requires: None,
};
// Check if there is anything in the blacklist (mutually excludes list) and add any values
if let Some(ref bl) = a.blacklist {
let mut bhs = HashSet::new();
// without derefing n = &&str
for n in bl { bhs.insert(*n); }
fb.blacklist = Some(bhs);
}
// 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();
// without derefing n = &&str
for n in r { rhs.insert(*n); }
fb.requires = Some(rhs);
}
self.flags.insert(a.name, fb);
}
self
}
@ -289,7 +350,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
/// Arg::new("debug").short("d")])
/// # .get_matches();
/// ```
pub fn args(mut self, args: Vec<Arg<'ar, 'ar, 'ar, 'ar, 'ar>>) -> App<'a, 'v, 'ab, 'u, 'ar> {
pub fn args(mut self, args: Vec<Arg<'ar, 'ar, 'ar, 'ar, 'ar, 'ar>>) -> App<'a, 'v, 'ab, 'u, 'ar> {
for arg in args.into_iter() {
self = self.arg(arg);
}
@ -411,7 +472,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
println!("\t{}{}\t{}",
if let Some(s) = v.short{format!("-{}",s)}else{" ".to_owned()},
if let Some(l) = v.long {format!(",--{}",l)}else {" \t".to_owned()},
if let Some(h) = v.help {h} else {" "} );
v.help.unwrap_or(" ") );
}
}
if opts {
@ -423,7 +484,16 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
if let Some(s) = v.short{format!("-{} ",s)}else{" ".to_owned()},
if let Some(l) = v.long {format!(",--{}=",l)}else {needs_tab = true; " ".to_owned()},
format!("{}", v.name),
if let Some(h) = v.help {if needs_tab {format!("\t{}", h)} else { format!("{}", h) } } else {" ".to_owned()} );
if let Some(h) = v.help {
format!("{}{}{}",
if needs_tab { "\t" } else { "" },
h,
if let Some(ref pv) = v.possible_vals {
format!(" [values:{}]", pv.iter().fold(String::new(), |acc, name| acc + &format!("{}",name)[..] ))
}else{"".to_owned()})
} else {
" ".to_owned()
} );
}
}
if pos {
@ -431,7 +501,15 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
println!("POSITIONAL ARGUMENTS:");
for v in self.positionals_idx.values() {
println!("\t{}\t\t{}", v.name,
if let Some(h) = v.help {h} else {" "} );
if let Some(h) = v.help {
format!("{}{}",
h,
if let Some(ref pv) = v.possible_vals {
format!(" [values:{}]", pv.iter().fold(String::new(), |acc, name| acc + &format!(" {}",name)[..] ))
}else{"".to_owned()})
} else {
" ".to_owned()
} );
}
}
if subcmds {
@ -455,9 +533,9 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
unsafe { libc::exit(0); }
}
fn report_error(&self, msg: String, help: bool, quit: bool) {
fn report_error(&self, msg: String, usage: bool, quit: bool) {
println!("{}", msg);
if help { self.print_usage(true); }
if usage { self.print_usage(true); }
if quit { env::set_exit_status(1); self.exit(); }
}
@ -492,6 +570,20 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
if !pos_only {
if let Some(nvo) = needs_val_of {
if let Some(ref opt) = self.opts.get(nvo) {
if let Some(ref p_vals) = opt.possible_vals {
if !p_vals.is_empty() {
if !p_vals.contains(arg_slice) {
self.report_error(format!("\"{}\" isn't a valid value for {}{}",
arg_slice,
if opt.long.is_some() {
format!("--{}",opt.long.unwrap())
}else{
format!("-{}", opt.short.unwrap())
},
format!("\n\t[valid values:{}]", p_vals.iter().fold(String::new(), |acc, name| acc + &format!(" {}",name)[..] )) ), true, true);
}
}
}
if let Some(ref mut o) = matches.opts.get_mut(opt.name) {
o.values.push(arg.clone());
// if it's multiple the occurrences are increased when originall found
@ -536,6 +628,16 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
true, true);
}
if let Some(ref p_vals) = p.possible_vals {
if !p_vals.is_empty() {
if !p_vals.contains(arg_slice) {
self.report_error(format!("\"{}\" isn't a valid value for {}{}",
arg_slice,
p.name,
format!("\n\t[valid values:{}]", p_vals.iter().fold(String::new(), |acc, name| acc + &format!(" {}",name)[..] )) ), true, true);
}
}
}
matches.positionals.insert(p.name, PosArg{
name: p.name.to_owned(),
value: arg.clone(),
@ -661,6 +763,20 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
if !v.multiple {
self.report_error(format!("Argument --{} was supplied more than once, but does not support multiple values", arg), true, true);
}
if let Some(ref p_vals) = v.possible_vals {
if let Some(ref av) = arg_val {
if !p_vals.contains(&av[..]) {
self.report_error(format!("\"{}\" isn't a valid value for {}{}",
arg_val.clone().unwrap_or(arg.to_owned()),
if v.long.is_some() {
format!("--{}", v.long.unwrap())
}else{
format!("-{}", v.short.unwrap())
},
format!("\n\t[valid values:{}]", p_vals.iter().fold(String::new(), |acc, name| acc + &format!(" {}",name)[..] )) ), true, true);
}
}
}
if arg_val.is_some() {
if let Some(ref mut o) = matches.opts.get_mut(v.name) {
o.occurrences += 1;
@ -712,7 +828,8 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{
self.report_error(format!("Argument --{} was supplied more than once, but does not support multiple values", arg), true, true);
}
let mut done = false;
let mut
done = false;
if let Some(ref mut f) = matches.flags.get_mut(v.name) {
done = true;
f.occurrences = if v.multiple { f.occurrences + 1 } else { 1 };

View file

@ -18,7 +18,7 @@
/// .takes_value(true)
/// .help("Provides a config file to myprog")
/// # ).get_matches();
pub struct Arg<'n, 'l, 'h, 'b, 'r> {
pub struct Arg<'n, 'l, 'h, 'b, 'p, 'r> {
/// The unique name of the argument, required
pub name: &'n str,
/// The short version (i.e. single character) of the argument, no preceding `-`
@ -47,12 +47,14 @@ pub struct Arg<'n, 'l, 'h, 'b, 'r> {
pub multiple: bool,
/// A list of names for other arguments that *may not* be used with this flag
pub blacklist: Option<Vec<&'b str>>,
/// A list of possible values for an option or positional argument
pub possible_vals: Option<Vec<&'p str>>,
/// A list of names of other arguments that are *required* to be used when
/// this flag is used
pub requires: Option<Vec<&'r str>>
}
impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
/// Creates a new instace of `Arg` using a unique string name.
/// The name will be used by the library consumer to get information about
/// whether or not the argument was used at runtime.
@ -70,7 +72,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// Arg::new("conifg")
/// # .short("c")
/// # ).get_matches();
pub fn new(n: &'n str) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn new(n: &'n str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
Arg {
name: n,
short: None,
@ -80,8 +82,9 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
takes_value: false,
multiple: false,
index: None,
blacklist: Some(vec![]),
requires: Some(vec![]),
possible_vals: None,
blacklist: None,
requires: None,
}
}
@ -104,7 +107,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("conifg")
/// .short("c")
/// # ).get_matches();
pub fn short(mut self, s: &str) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn short(mut self, s: &str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.short = s.trim_left_matches(|c| c == '-').chars().nth(0);
self
}
@ -128,7 +131,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("conifg")
/// .long("config")
/// # ).get_matches();
pub fn long(mut self, l: &'l str) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn long(mut self, l: &'l str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.long = Some(l.trim_left_matches(|c| c == '-'));
self
}
@ -145,7 +148,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("conifg")
/// .help("The config file used by the myprog")
/// # ).get_matches();
pub fn help(mut self, h: &'h str) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn help(mut self, h: &'h str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.help = Some(h);
self
}
@ -168,7 +171,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("conifg")
/// .required(true)
/// # ).get_matches();
pub fn required(mut self, r: bool) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn required(mut self, r: bool) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.required = r;
self
}
@ -187,11 +190,11 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # let myprog = App::new("myprog").arg(Arg::new("conifg")
/// .mutually_excludes("debug")
/// # ).get_matches();
pub fn mutually_excludes(mut self, name: &'b str) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn mutually_excludes(mut self, name: &'b str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
if let Some(ref mut vec) = self.blacklist {
vec.push(name);
} else {
self.blacklist = Some(vec![]);
self.blacklist = Some(vec![name]);
}
self
}
@ -211,13 +214,13 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// .mutually_excludes_all(
/// vec!["debug", "input"])
/// # ).get_matches();
pub fn mutually_excludes_all(mut self, names: Vec<&'b str>) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn mutually_excludes_all(mut self, names: Vec<&'b str>) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
if let Some(ref mut vec) = self.blacklist {
for n in names {
vec.push(n);
}
} else {
self.blacklist = Some(vec![]);
self.blacklist = Some(names);
}
self
}
@ -234,11 +237,11 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # let myprog = App::new("myprog").arg(Arg::new("conifg")
/// .requires("debug")
/// # ).get_matches();
pub fn requires(mut self, name: &'r str) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn requires(mut self, name: &'r str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
if let Some(ref mut vec) = self.requires {
vec.push(name);
} else {
self.requires = Some(vec![]);
self.requires = Some(vec![name]);
}
self
}
@ -257,13 +260,13 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// .requires_all(
/// vec!["debug", "input"])
/// # ).get_matches();
pub fn requires_all(mut self, names: Vec<&'r str>) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn requires_all(mut self, names: Vec<&'r str>) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
if let Some(ref mut vec) = self.requires {
for n in names {
vec.push(n);
}
} else {
self.requires = Some(vec![]);
self.requires = Some(names);
}
self
}
@ -282,7 +285,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("conifg")
/// .takes_value(true)
/// # ).get_matches();
pub fn takes_value(mut self, tv: bool) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn takes_value(mut self, tv: bool) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.takes_value = tv;
self
}
@ -303,7 +306,7 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("conifg")
/// .index(1)
/// # ).get_matches();
pub fn index(mut self, idx: u8) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn index(mut self, idx: u8) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.index = Some(idx);
self
}
@ -325,8 +328,33 @@ impl<'n, 'l, 'h, 'b, 'r> Arg<'n, 'l, 'h, 'b, 'r> {
/// # Arg::new("debug")
/// .multiple(true)
/// # ).get_matches();
pub fn multiple(mut self, multi: bool) -> Arg<'n, 'l, 'h, 'b, 'r> {
pub fn multiple(mut self, multi: bool) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
self.multiple = multi;
self
}
/// Specifies a list of possible values for this argument. At runtime, clap verifies that only
/// one of the specified values was used, or fails with a usage string.
///
/// **NOTE:** This setting only applies to options and positional arguments
///
/// Example:
///
/// ```no_run
/// # use clap::{App, Arg};
/// # let matches = App::new("myprog")
/// # .arg(
/// # Arg::new("debug").index(1)
/// .possible_values(vec!["fast", "slow"])
/// # ).get_matches();
pub fn possible_values(mut self, names: Vec<&'p str>) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> {
if let Some(ref mut vec) = self.possible_vals {
for n in names {
vec.push(n);
}
} else {
self.possible_vals = Some(names);
}
self
}
}

View file

@ -1,3 +1,5 @@
use std::collections::HashSet;
/// `FlagArg` represents a flag argument for command line applications. Flag arguments
/// take no additional values, and are always preceded by either a `-` (single character)
/// or `--` (single word, no spaces). `FlagArg` isn't directly used by the end application
@ -31,11 +33,11 @@ pub struct FlagBuilder<'n> {
pub multiple: bool,
/// A list of names for other arguments that
/// *may not* be used with this flag
pub blacklist: Option<Vec<&'n str>>,
pub blacklist: Option<HashSet<&'n str>>,
/// A list of names of other arguments that
/// are *required* to be used when this
/// flag is used
pub requires: Option<Vec<&'n str>>,
pub requires: Option<HashSet<&'n str>>,
/// The short version (i.e. single character)
/// of the argument, no preceding `-`
pub short: Option<char>,

View file

@ -1,3 +1,5 @@
use std::collections::HashSet;
/// `OptArg` represents a option argument for command line applications, which is one that
/// takes an additional value. Option arguments are always preceded by either a `-`
/// (single character) or `--` (single word, no spaces) then followed by a space and the
@ -32,13 +34,15 @@ pub struct OptBuilder<'n> {
/// Allow multiple occurrences of an option argument such as "-c some -c other"
pub multiple: bool,
/// A list of names for other arguments that *may not* be used with this flag
pub blacklist: Option<Vec<&'n str>>,
pub blacklist: Option<HashSet<&'n str>>,
/// If this is a required by default when using the command line program
/// i.e. a configuration file that's required for the program to function
/// **NOTE:** required by default means, it is required *until* mutually
/// exclusive arguments are evaluated.
pub required: bool,
/// A list of possible values for this argument
pub possible_vals: Option<HashSet<&'n str>>,
/// A list of names of other arguments that are *required* to be used when
/// this flag is used
pub requires: Option<Vec<&'n str>>,
pub requires: Option<HashSet<&'n str>>,
}

View file

@ -1,3 +1,5 @@
use std::collections::HashSet;
/// `PosArg` represents a positional argument, i.e. one that isn't preceded
/// by a `-` or `--`. `PosArg` isn't directly used by the end application
/// writer, only internally to the `clap` library.
@ -30,9 +32,11 @@ pub struct PosBuilder<'n> {
pub required: bool,
/// A list of names of other arguments that are *required* to be used when
/// this flag is used
pub requires: Option<Vec<&'n str>>,
pub requires: Option<HashSet<&'n str>>,
/// A list of names for other arguments that *may not* be used with this flag
pub blacklist: Option<Vec<&'n str>>,
pub blacklist: Option<HashSet<&'n str>>,
/// A list of possible values for this argument
pub possible_vals: Option<HashSet<&'n str>>,
/// The index of the argument
pub index: u8
}