diff --git a/src/app.rs b/src/app.rs index 3ff54bea..2a0dd2e5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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>) -> App<'a, 'v, 'ab, 'u, 'ar> { + pub fn args(mut self, args: Vec>) -> 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 }; diff --git a/src/args/arg.rs b/src/args/arg.rs index ff3500f2..3c51054b 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -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>, + /// A list of possible values for an option or positional argument + pub possible_vals: Option>, /// A list of names of other arguments that are *required* to be used when /// this flag is used pub requires: Option> } -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 + } } diff --git a/src/args/flagarg.rs b/src/args/flagarg.rs index b466e186..9a88f00d 100644 --- a/src/args/flagarg.rs +++ b/src/args/flagarg.rs @@ -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>, + pub blacklist: Option>, /// A list of names of other arguments that /// are *required* to be used when this /// flag is used - pub requires: Option>, + pub requires: Option>, /// The short version (i.e. single character) /// of the argument, no preceding `-` pub short: Option, diff --git a/src/args/optarg.rs b/src/args/optarg.rs index d82b1711..51be27f8 100644 --- a/src/args/optarg.rs +++ b/src/args/optarg.rs @@ -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>, + pub blacklist: Option>, /// 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>, /// A list of names of other arguments that are *required* to be used when /// this flag is used - pub requires: Option>, + pub requires: Option>, } diff --git a/src/args/posarg.rs b/src/args/posarg.rs index ef7d619b..cac2d95a 100644 --- a/src/args/posarg.rs +++ b/src/args/posarg.rs @@ -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>, + pub requires: Option>, /// A list of names for other arguments that *may not* be used with this flag - pub blacklist: Option>, + pub blacklist: Option>, + /// A list of possible values for this argument + pub possible_vals: Option>, /// The index of the argument pub index: u8 } \ No newline at end of file