From 09eb4d9893af40c347e50e2b717e1adef552357d Mon Sep 17 00:00:00 2001 From: Kevin K Date: Sun, 26 Apr 2015 22:03:11 -0400 Subject: [PATCH] feat(ArgGroups): add ability to create arg groups --- src/app.rs | 236 +++++++++++++++++++++++------ src/args/arg.rs | 65 +++++--- src/args/argbuilder/flag.rs | 7 + src/args/argbuilder/option.rs | 7 + src/args/argbuilder/positional.rs | 7 + src/args/group.rs | 240 ++++++++++++++++++++++++++++++ src/args/mod.rs | 4 +- src/lib.rs | 2 +- src/macros.rs | 33 ++++ 9 files changed, 535 insertions(+), 66 deletions(-) create mode 100644 src/args/group.rs diff --git a/src/app.rs b/src/app.rs index 8937fbf1fda..4db028f2097 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::collections::BTreeSet; use std::collections::HashSet; +use std::collections::HashMap; use std::env; use std::path::Path; use std::vec::IntoIter; @@ -10,6 +11,7 @@ use std::fmt::Write; use args::{ ArgMatches, Arg, SubCommand, MatchedArg}; use args::{ FlagBuilder, OptBuilder, PosBuilder}; +use args::ArgGroup; /// Used to create a representation of a command line program and all possible command line /// arguments for parsing at runtime. @@ -70,8 +72,8 @@ pub struct App<'a, 'v, 'ab, 'u, 'h, 'ar> { long_list: HashSet<&'ar str>, blacklist: HashSet<&'ar str>, usage_str: Option<&'u str>, - bin_name: Option - + bin_name: Option, + groups: HashMap<&'ar str, ArgGroup<'ar, 'ar>> } impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ @@ -112,6 +114,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ usage_str: None, blacklist: HashSet::new(), bin_name: None, + groups: HashMap::new(), } } @@ -230,13 +233,21 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ /// ``` pub fn arg(mut self, a: Arg<'ar, 'ar, 'ar, 'ar, 'ar, 'ar>) -> App<'a, 'v, 'ab, 'u, 'h, 'ar> { if self.arg_list.contains(a.name) { - panic!("Argument name must be unique, \"{}\" is already in use", a.name); + panic!("Argument name must be unique\n\n\t\"{}\" is already in use", a.name); } else { self.arg_list.insert(a.name); } + if let Some(grp) = a.group { + let ag = self.groups.entry(grp).or_insert(ArgGroup::with_name(grp)); + ag.args.insert(a.name); + // Leaving this commented out for now...I'm not sure if having a required argument in + // a in required group is bad...It has it's uses + // assert!(!a.required, + // format!("Arguments may not be required AND part of a required group\n\n\t{} is required and also part of the {} group\n\n\tEither remove the requirement from the group, or the argument.", a.name, grp)); + } if let Some(s) = a.short { if self.short_list.contains(&s) { - panic!("Argument short must be unique, -{} is already in use", s); + panic!("Argument short must be unique\n\n\t-{} is already in use", s); } else { self.short_list.insert(s); } @@ -248,7 +259,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } if let Some(l) = a.long { if self.long_list.contains(l) { - panic!("Argument long must be unique, --{} is already in use", l); + panic!("Argument long must be unique\n\n\t--{} is already in use", l); } else { self.long_list.insert(l); } @@ -269,10 +280,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } if self.positionals_idx.contains_key(&i) { - panic!("Argument \"{}\" has the same index as another positional argument", a.name); + panic!("Argument \"{}\" has the same index as another positional argument\n\n\tPerhaps try .multiple(true) to allow one positional argument to take multiple values", a.name); } if a.takes_value { - panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied", a.name); + panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied\n\n\tArguments with an index automatically take a value, you do not need to specify it manually", a.name); } @@ -454,6 +465,106 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self } + /// Adds an ArgGroup to the application. ArgGroups are a family of related arguments. By + /// placing them in a logical group, you make easier requirement and exclusion rules. For + /// instance, you can make an ArgGroup required, this means that one (and *only* one) argument + /// from that group must be present. Using more than one argument from an ArgGroup causes a + /// failure (graceful exit). + /// + /// You can also do things such as name an ArgGroup as a confliction, meaning any of the + /// arguments that belong to that group will cause a failure if present. + /// + /// Perhaps the most common use of ArgGroups is to require one and *only* one argument to be + /// present out of a given set. For example, lets say that you were building an application + /// where one could set a given version number by supplying a string using an option argument, + /// such as `--set-ver v1.2.3`, you also wanted to support automatically using a previous + /// version numer and simply incrementing one of the three numbers, so you create three flags + /// `--major`, `--minor`, and `--patch`. All of these arguments shouldn't be used at one time + /// but perhaps you want to specify that *at least one* of them is used. You can create a + /// group + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let _ = App::new("app") + /// .args_from_usage("--set-ver [ver] 'set the version manually' + /// --major 'auto increase major' + /// --minor 'auto increase minor' + /// --patch 'auto increase patch") + /// .arg_group(ArgGroup::with_name("vers") + /// .add_all(vec!["ver", "major", "minor","patch"]) + /// .required(true)) + /// # .get_matches(); + pub fn arg_group(mut self, group: ArgGroup<'ar, 'ar>) -> App<'a, 'v, 'ab, 'u, 'h, 'ar> { + if group.required { + self.required.insert(group.name); + if let Some(ref reqs) = group.requires { + for r in reqs { + self.required.insert(r); + } + } + if let Some(ref bl) = group.conflicts { + for b in bl { + self.blacklist.insert(b); + } + } + } + let mut found = false; + if let Some(ref mut grp) = self.groups.get_mut(group.name) { + for a in group.args.iter() { + grp.args.insert(a); + } + grp.requires = group.requires.clone(); + grp.conflicts = group.conflicts.clone(); + grp.required = group.required; + found = true; + } + if !found { + self.groups.insert(group.name, group); + } + self + } + + /// Adds a ArgGroups to the application. ArgGroups are a family of related arguments. By + /// placing them in a logical group, you make easier requirement and exclusion rules. For + /// instance, you can make an ArgGroup required, this means that one (and *only* one) argument + /// from that group must be present. Using more than one argument from an ArgGroup causes a + /// failure (graceful exit). + /// + /// You can also do things such as name an ArgGroup as a confliction, meaning any of the + /// arguments that belong to that group will cause a failure if present. + /// + /// Perhaps the most common use of ArgGroups is to require one and *only* one argument to be + /// present out of a given set. For example, lets say that you were building an application + /// where one could set a given version number by supplying a string using an option argument, + /// such as `--set-ver v1.2.3`, you also wanted to support automatically using a previous + /// version numer and simply incrementing one of the three numbers, so you create three flags + /// `--major`, `--minor`, and `--patch`. All of these arguments shouldn't be used at one time + /// but perhaps you want to specify that *at least one* of them is used. You can create a + /// group + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let _ = App::new("app") + /// .args_from_usage("--set-ver [ver] 'set the version manually' + /// --major 'auto increase major' + /// --minor 'auto increase minor' + /// --patch 'auto increase patch") + /// .arg_group(ArgGroup::with_name("vers") + /// .add_all(vec!["ver", "major", "minor","patch"]) + /// .required(true)) + /// # .get_matches(); + pub fn arg_groups(mut self, groups: Vec>) -> App<'a, 'v, 'ab, 'u, 'h, 'ar> { + for g in groups { + self = self.arg_group(g); + } + self + } /// Adds a subcommand to the list of valid possibilties. Subcommands are effectively sub apps, /// because they can contain their own arguments, subcommands, version, usage, etc. They also @@ -512,6 +623,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ let pos = !self.positionals_idx.is_empty(); let opts = !self.opts.is_empty(); let subcmds = !self.subcommands.is_empty(); + let groups = !self.groups.is_empty(); let mut num_req_pos = 0; let mut matched_pos_reqs = HashSet::new(); // If it's required we also need to ensure all previous positionals are required too @@ -529,11 +641,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ let mut req_pos = self.positionals_idx.values().filter_map(|ref x| if x.required || matched_pos_reqs.contains(x.name) { num_req_pos += 1; - if x.multiple { - Some(format!("<{}>...", x.name)) - } else { - Some(format!("<{}>", x.name)) - } + Some(format!("{}", x)) } else { None } ) @@ -546,11 +654,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ }else { None }) - .fold(String::with_capacity(50), |acc, ref o| acc + &format!("-{}{} ",if let Some(l) = o.long { - format!("-{} ", l) - } else { - format!("{}=",o.short.unwrap()) - },format!("<{}>", o.name))); + .fold(String::with_capacity(50), |acc, ref o| acc + &format!("{} ",o)); req_opts.shrink_to_fit(); usage.push_str(&self.bin_name.clone().unwrap_or(self.name.clone())[..]); @@ -577,6 +681,30 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ req_pos } ).unwrap_or_else(|e| self.report_error(format!("internal error: {}", e),false,true)); } + if groups { + let req_grps = self.groups.values() // Iterator + .filter_map(|g| if g.required {Some(g.args.clone())} else {None}) // Iterator> + .map(|hs| hs.into_iter().collect::>() ) // Iterator> + .fold(vec![], |acc, n| acc + &n[..]) // Vec<&str> + .iter() // Iterator + .fold(String::new(), |acc, n| { + if let Some(ref o) = self.opts.get(n) { + acc + &format!("{}|", o)[..] + } else if let Some(ref f) = self.flags.get(n) { + acc + &format!("{}|", f)[..] + } else { + acc + &format!("{}|", match self.positionals_idx.values().rev().filter_map(|ref p| if &p.name == n {Some(format!("{}", p))}else{None}).next(){ + Some(name) => name, + None => panic!(format!("Error parsing a required group which contains argument \"{}\"\n\n\tArgument couldn't be found. Check the arguments settings.", n)) + })[..] + } + }); + + // There may be no required groups, so we check + if req_grps.len() > 0 { + write!(&mut usage, " [{}]", &req_grps[..req_grps.len() - 1]).unwrap_or_else(|e| self.report_error(format!("internal error: {}", e),false,true)); + } + } if subcmds { usage.push_str(" [SUBCOMMANDS]"); } @@ -921,7 +1049,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // let mut req_pos_from_name = None; if let Some(p) = self.positionals_idx.get(&pos_counter) { if self.blacklist.contains(p.name) { - self.report_error(format!("The argument \"{}\" cannot be used with one or more of the other specified arguments", arg), + self.report_error(format!("The argument \"{}\" cannot be used with one or more of the other specified arguments", p), true, true); } @@ -930,8 +1058,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if !p_vals.contains(arg_slice) { self.report_error(format!("\"{}\" isn't a valid value for {}{}", arg_slice, - p.name, - format!("\n [valid values:{}]", p_vals.iter().fold(String::new(), |acc, name| acc + &format!(" {}",name)[..] )) ), true, true); + p, + format!("\n\t[valid values:{}]", p_vals.iter().fold(String::new(), |acc, name| acc + &format!(" {}",name)[..] )) ), true, true); } } } @@ -953,7 +1081,6 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ // Was an update made, or is this the first occurrence? if !done { matches.args.insert(p.name, MatchedArg{ - // name: p.name.to_owned(), occurrences: 1, values: Some(vec![arg.clone()]), }); @@ -966,10 +1093,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ } } - // No need to check for existance, returns None if not found - // if self.required.contains(p.name) { - self.required.remove(p.name); - // } + self.required.remove(p.name); if let Some(ref reqs) = p.requires { // Add all required args which aren't already found in matches to the // final required list @@ -980,6 +1104,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.required.insert(n); } } + + parse_group_reqs!(self, p); + } else { self.report_error(format!("Argument \"{}\" isn't a valid argument for {}", arg, self.bin_name.clone().unwrap_or(self.name.clone())), true, true); } @@ -997,6 +1124,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.validate_blacklist(&matches); if !self.required.is_empty() { + println!("reqs: {:?}", self.required); + println!("bls: {:?}", self.blacklist); + println!("grps: {:?}", self.groups); self.report_error("One or more required arguments were not supplied".to_owned(), true, true); } @@ -1142,7 +1272,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.required.insert(n); } - } + } + + parse_group_reqs!(self, v); + match arg_val { None => { return Some(v.name); }, _ => { return None; } @@ -1152,13 +1285,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if let Some(v) = self.flags.values().filter(|&v| v.long.is_some()).filter(|&v| v.long.unwrap() == arg).nth(0) { // Ensure this flag isn't on the mutually excludes list if self.blacklist.contains(v.name) { - self.report_error(format!("The argument --{} cannot be used with one or more of the other specified arguments", arg), + self.report_error(format!("The argument {} cannot be used with one or more of the other specified arguments", v), true, true); } // Make sure this isn't one being added multiple times if it doesn't suppor it if matches.args.contains_key(v.name) && !v.multiple { - self.report_error(format!("Argument --{} was supplied more than once, but does not support multiple values", arg), true, true); + self.report_error(format!("Argument {} was supplied more than once, but does not support multiple values", v), true, true); } let mut @@ -1196,6 +1329,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.required.insert(n); } } + + parse_group_reqs!(self, v); + return None; } @@ -1266,6 +1402,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.required.insert(n); } } + + parse_group_reqs!(self, v); + return Some(v.name) } @@ -1322,6 +1461,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ self.required.insert(n); } } + + parse_group_reqs!(self, v); + return true; } false @@ -1332,24 +1474,32 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{ if matches.args.contains_key(name) { self.report_error(format!("The argument {} cannot be used with one or more of the other specified arguments", if let Some(ref flag) = self.flags.get(name) { - if let Some(short) = flag.short { - format!("-{}", short) - } else if let Some(long) = flag.long { - format!("--{}", long) - } else { - format!("\"{}\"", flag.name) - } + format!("{}", flag) } else if let Some(ref opt) = self.opts.get(name) { - if let Some(short) = opt.short { - format!("-{}", short) - } else if let Some(long) = opt.long { - format!("--{}", long) - } else { - format!("\"{}\"", opt.name) - } + format!("{}", opt) } else { - format!("\"{}\"", name) + match self.positionals_idx.values().filter(|p| p.name == *name).next() { + Some(pos) => format!("{}", pos), + None => format!("\"{}\"", name) + } }), true, true); + } else if self.groups.contains_key(name) { + let grp = self.groups.get(name).unwrap(); + for n in grp.args.iter() { + if matches.args.contains_key(n) { + self.report_error(format!("The argument {} cannot be used with one or more of the other specified arguments", + if let Some(ref flag) = self.flags.get(n) { + format!("{}", flag) + } else if let Some(ref opt) = self.opts.get(n) { + format!("{}", opt) + } else { + match self.positionals_idx.values().filter(|p| p.name == *name).next() { + Some(pos) => format!("{}", pos), + None => format!("\"{}\"", n) + } + }), true, true); + } + } } } } diff --git a/src/args/arg.rs b/src/args/arg.rs index 9266e01ce3c..b05e2aa02ad 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -28,7 +28,7 @@ use usageparser::{UsageParser, UsageToken}; /// // Using a usage string (setting a similar argument to the one above) /// Arg::from_usage("-i --input=[input] 'Provides an input file to the program'") /// # ).get_matches(); -pub struct Arg<'n, 'l, 'h, 'b, 'p, 'r> { +pub struct Arg<'n, 'l, 'h, 'g, 'p, 'r> { /// The unique name of the argument, required #[doc(hidden)] pub name: &'n str, @@ -65,17 +65,20 @@ pub struct Arg<'n, 'l, 'h, 'b, 'p, 'r> { pub multiple: bool, /// A list of names for other arguments that *may not* be used with this flag #[doc(hidden)] - pub blacklist: Option>, + pub blacklist: Option>, /// A list of possible values for an option or positional argument #[doc(hidden)] pub possible_vals: Option>, /// A list of names of other arguments that are *required* to be used when /// this flag is used #[doc(hidden)] - pub requires: Option> + pub requires: Option>, + /// A name of the group the argument belongs to + #[doc(hidden)] + pub group: Option<&'g str> } -impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { +impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, '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. @@ -97,7 +100,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// Arg::new("conifg") /// # .short("c") /// # ).get_matches(); - pub fn new(n: &'n str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn new(n: &'n str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { Arg { name: n, short: None, @@ -110,6 +113,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { possible_vals: None, blacklist: None, requires: None, + group: None, } } @@ -131,7 +135,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// Arg::with_name("conifg") /// # .short("c") /// # ).get_matches(); - pub fn with_name(n: &'n str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn with_name(n: &'n str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { Arg { name: n, short: None, @@ -144,6 +148,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { possible_vals: None, blacklist: None, requires: None, + group: None, } } @@ -195,7 +200,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// Arg::from_usage(" 'the input file to use'") /// ]) /// # .get_matches(); - pub fn from_usage(u: &'n str) -> Arg<'n, 'n, 'n, 'b, 'p, 'r> { + pub fn from_usage(u: &'n str) -> Arg<'n, 'n, 'n, 'g, 'p, 'r> { assert!(u.len() > 0, "Arg::from_usage() requires a non-zero-length usage string but none was provided"); let mut name = None; @@ -258,6 +263,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { possible_vals: None, blacklist: None, requires: None, + group: None, } } @@ -281,7 +287,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::new("conifg") /// .short("c") /// # ).get_matches(); - pub fn short(mut self, s: &str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn short(mut self, s: &str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.short = s.trim_left_matches(|c| c == '-').chars().nth(0); self } @@ -305,7 +311,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::new("conifg") /// .long("config") /// # ).get_matches(); - pub fn long(mut self, l: &'l str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn long(mut self, l: &'l str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.long = Some(l.trim_left_matches(|c| c == '-')); self } @@ -323,7 +329,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, '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, 'p, 'r> { + pub fn help(mut self, h: &'h str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.help = Some(h); self } @@ -347,7 +353,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::with_name("conifg") /// .required(true) /// # ).get_matches(); - pub fn required(mut self, r: bool) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn required(mut self, r: bool) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.required = r; self } @@ -369,7 +375,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # let myprog = App::new("myprog").arg(Arg::with_name("conifg") /// .mutually_excludes("debug") /// # ).get_matches(); - pub fn mutually_excludes(mut self, name: &'b str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn mutually_excludes(mut self, name: &'r str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.blacklist { vec.push(name); } else { @@ -396,7 +402,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, '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, 'p, 'r> { + pub fn mutually_excludes_all(mut self, names: Vec<&'r str>) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.blacklist { for n in names { vec.push(n); @@ -422,7 +428,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # let myprog = App::new("myprog").arg(Arg::with_name("conifg") /// .conflicts_with("debug") /// # ).get_matches(); - pub fn conflicts_with(mut self, name: &'b str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn conflicts_with(mut self, name: &'r str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.blacklist { vec.push(name); } else { @@ -447,7 +453,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// .conflicts_with_all( /// vec!["debug", "input"]) /// # ).get_matches(); - pub fn conflicts_with_all(mut self, names: Vec<&'b str>) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn conflicts_with_all(mut self, names: Vec<&'r str>) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.blacklist { for n in names { vec.push(n); @@ -471,7 +477,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # let myprog = App::new("myprog").arg(Arg::with_name("conifg") /// .requires("debug") /// # ).get_matches(); - pub fn requires(mut self, name: &'r str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn requires(mut self, name: &'r str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.requires { vec.push(name); } else { @@ -495,7 +501,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// .requires_all( /// vec!["debug", "input"]) /// # ).get_matches(); - pub fn requires_all(mut self, names: Vec<&'r str>) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn requires_all(mut self, names: Vec<&'r str>) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.requires { for n in names { vec.push(n); @@ -521,7 +527,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::with_name("conifg") /// .takes_value(true) /// # ).get_matches(); - pub fn takes_value(mut self, tv: bool) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn takes_value(mut self, tv: bool) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.takes_value = tv; self } @@ -543,7 +549,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::with_name("conifg") /// .index(1) /// # ).get_matches(); - pub fn index(mut self, idx: u8) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn index(mut self, idx: u8) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.index = Some(idx); self } @@ -566,7 +572,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::with_name("debug") /// .multiple(true) /// # ).get_matches(); - pub fn multiple(mut self, multi: bool) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + pub fn multiple(mut self, multi: bool) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { self.multiple = multi; self } @@ -586,7 +592,7 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// # Arg::with_name("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> { + pub fn possible_values(mut self, names: Vec<&'p str>) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { if let Some(ref mut vec) = self.possible_vals { for n in names { vec.push(n); @@ -596,4 +602,21 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { } self } + + /// Specifies the name of the group the argument belongs to. + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let matches = App::new("myprog") + /// # .arg( + /// # Arg::with_name("debug").index(1) + /// .group("mode") + /// # ).get_matches(); + pub fn group(mut self, name: &'g str) -> Arg<'n, 'l, 'h, 'g, 'p, 'r> { + self.group = Some(name); + self + } } diff --git a/src/args/argbuilder/flag.rs b/src/args/argbuilder/flag.rs index 00abd5cfbaf..ab6e5088e26 100644 --- a/src/args/argbuilder/flag.rs +++ b/src/args/argbuilder/flag.rs @@ -1,4 +1,5 @@ use std::collections::HashSet; +use std::fmt::{ Display, Formatter, Result }; pub struct FlagBuilder<'n> { pub name: &'n str, @@ -23,4 +24,10 @@ pub struct FlagBuilder<'n> { /// The short version (i.e. single character) /// of the argument, no preceding `-` pub short: Option, +} + +impl<'n> Display for FlagBuilder<'n> { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{}", if self.long.is_some() { format!("--{}", self.long.unwrap())} else {format!("-{}", self.short.unwrap())}) + } } \ No newline at end of file diff --git a/src/args/argbuilder/option.rs b/src/args/argbuilder/option.rs index 652c4f839d6..f1319289710 100644 --- a/src/args/argbuilder/option.rs +++ b/src/args/argbuilder/option.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::collections::BTreeSet; +use std::fmt::{ Display, Formatter, Result }; pub struct OptBuilder<'n> { pub name: &'n str, @@ -25,3 +26,9 @@ pub struct OptBuilder<'n> { /// this flag is used pub requires: Option>, } + +impl<'n> Display for OptBuilder<'n> { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{} <{}>{}", if self.long.is_some() { format!("--{}", self.long.unwrap())} else {format!("-{}", self.short.unwrap())}, self.name, if self.multiple{"..."}else{""}) + } +} \ No newline at end of file diff --git a/src/args/argbuilder/positional.rs b/src/args/argbuilder/positional.rs index 56bb24d4414..366d0a70722 100644 --- a/src/args/argbuilder/positional.rs +++ b/src/args/argbuilder/positional.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::collections::BTreeSet; +use std::fmt::{ Display, Formatter, Result }; pub struct PosBuilder<'n> { pub name: &'n str, @@ -22,4 +23,10 @@ pub struct PosBuilder<'n> { pub possible_vals: Option>, /// The index of the argument pub index: u8 +} + +impl<'n> Display for PosBuilder<'n> { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{}{}{}{}", if self.required { "<" } else {"["}, self.name,if self.required { ">" } else {"]"}, if self.multiple {"..."}else{""}) + } } \ No newline at end of file diff --git a/src/args/group.rs b/src/args/group.rs new file mode 100644 index 00000000000..5c655425b2a --- /dev/null +++ b/src/args/group.rs @@ -0,0 +1,240 @@ +use std::collections::HashSet; +use std::fmt::{Debug, Formatter, Result}; + +/// ArgGroups are a family of related arguments and provide a few useful features for you. By +/// placing arguments in a logical group, you can make easier requirement and exclusion rules. For +/// instance, you can make an ArgGroup required, this means that one (and *only* one) argument +/// from that group must be present. Using more than one argument from an ArgGroup causes a +/// failure (graceful exit). +/// +/// You can also do things such as name an ArgGroup as a confliction or requirement, meaning any +/// of the arguments that belong to that group will cause a failure if present, or must present +/// respectively. +/// +/// Perhaps the most common use of ArgGroups is to require one and *only* one argument to be +/// present out of a given set. For example, lets say that you were building an application +/// where one could set a given version number by supplying a string using an option argument, +/// such as `--set-ver v1.2.3`, you also wanted to support automatically using a previous +/// version numer and simply incrementing one of the three numbers, so you create three flags +/// `--major`, `--minor`, and `--patch`. All of these arguments shouldn't be used at one time +/// but perhaps you want to specify that *at least one* of them is used. You can create a +/// group +/// +/// +/// # Example +/// ```no_run +/// # use clap::{App, ArgGroup}; +/// # let _ = App::new("app") +/// .args_from_usage("--set-ver [ver] 'set the version manually' +/// --major 'auto increase major' +/// --minor 'auto increase minor' +/// --patch 'auto increase patch") +/// .arg_group(ArgGroup::with_name("vers") +/// .add_all(vec!["ver", "major", "minor","patch"]) +/// .required(true)) +/// # .get_matches(); +pub struct ArgGroup<'n, 'ar> { + #[doc(hidden)] + pub name: &'n str, + #[doc(hidden)] + pub args: HashSet<&'ar str>, + #[doc(hidden)] + pub required: bool, + #[doc(hidden)] + pub requires: Option>, + #[doc(hidden)] + pub conflicts: Option> +} + +impl<'n, 'ar> ArgGroup<'n, 'ar> { + /// Creates a new instace of `ArgGroup` using a unique string name. + /// The name will only be used by the library consumer and not displayed to the user + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// ArgGroup::with_name("conifg") + /// # ).get_matches(); + pub fn with_name(n: &'n str) -> ArgGroup<'n, 'ar> { + ArgGroup { + name: n, + required: false, + args: HashSet::new(), + requires: None, + conflicts: None + } + } + + /// Adds an argument to this group by name + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .add("config") + /// # ).get_matches(); + pub fn add(mut self, n: &'ar str) -> ArgGroup<'n, 'ar> { + self.args.insert(n); + self + } + + /// Adds multiple arguments to this group by name inside a Vec + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .add_all(vec!["config", "input", "output"]) + /// # ).get_matches(); + pub fn add_all(mut self, ns: Vec<&'ar str>) -> ArgGroup<'n, 'ar> { + for n in ns { + self = self.add(n); + } + self + } + + /// Sets the requirement of this group. A required group will be displayed in the usage string + /// of the application in the format `[arg|arg2|arg3]`. A required `ArgGroup` simply states + /// that one, and only one argument from this group *must* be present at runtime (unless + /// conflicting with another argument). + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .required(true) + /// # ).get_matches(); + pub fn required(mut self, r: bool) -> ArgGroup<'n, 'ar> { + self.required = r; + self + } + + /// Sets the requirement rules of this group. This is not to be confused with a required group. + /// Requirement rules function just like argument requirement rules, you can name other arguments + /// or groups that must be present when one of the arguments from this group is used. + /// + /// **NOTE:** The name provided may be an argument, or group name + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .requires("config") + /// # ).get_matches(); + pub fn requires(mut self, n: &'ar str) -> ArgGroup<'n, 'ar> { + if let Some(ref mut reqs) = self.requires { + reqs.insert(n); + } else { + let mut hs = HashSet::new(); + hs.insert(n); + self.requires = Some(hs); + } + self + } + + /// Sets the requirement rules of this group. This is not to be confused with a required group. + /// Requirement rules function just like argument requirement rules, you can name other arguments + /// or groups that must be present when one of the arguments from this group is used. + /// + /// **NOTE:** The names provided may be an argument, or group name + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .requires_all(vec!["config", "input"]) + /// # ).get_matches(); + pub fn requires_all(mut self, ns: Vec<&'ar str>) -> ArgGroup<'n, 'ar> { + for n in ns { + self = self.requires(n); + } + self + } + + /// Sets the exclusion rules of this group. Exclusion rules function just like argument exclusion + /// rules, you can name other arguments or groups that must not be present when one of the arguments + /// from this group are used. + /// + /// **NOTE:** The name provided may be an argument, or group name + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .conflicts_with("config") + /// # ).get_matches(); + pub fn conflicts_with(mut self, n: &'ar str) -> ArgGroup<'n, 'ar> { + if let Some(ref mut confs) = self.conflicts { + confs.insert(n); + } else { + let mut hs = HashSet::new(); + hs.insert(n); + self.conflicts = Some(hs); + } + self + } + + /// Sets the exclusion rules of this group. Exclusion rules function just like argument exclusion + /// rules, you can name other arguments or groups that must not be present when one of the arguments + /// from this group are used. + /// + /// **NOTE:** The names provided may be an argument, or group name + /// + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// # let matches = App::new("myprog") + /// # .arg_group( + /// # ArgGroup::with_name("conifg") + /// .conflicts_with_all(vec!["config", "input"]) + /// # ).get_matches(); + pub fn conflicts_with_all(mut self, ns: Vec<&'ar str>) -> ArgGroup<'n, 'ar> { + for n in ns { + self = self.conflicts_with(n); + } + self + } +} + +impl<'n, 'ar> Debug for ArgGroup<'n, 'ar> { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{{ + name:{:?}, + args: {:?}, + required: {:?}, + requires: {:?}, + conflicts: {:?}, +}}", self.name, self.args, self.required, self.requires, self.conflicts) + } +} + diff --git a/src/args/mod.rs b/src/args/mod.rs index 01b2388eeb4..797820aa435 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -3,9 +3,11 @@ pub use self::argmatches::ArgMatches; pub use self::subcommand::SubCommand; pub use self::argbuilder::{FlagBuilder, OptBuilder, PosBuilder}; pub use self::matchedarg::MatchedArg; +pub use self::group::ArgGroup; mod arg; mod argmatches; mod subcommand; mod argbuilder; -mod matchedarg; \ No newline at end of file +mod matchedarg; +mod group; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 7165713d4ba..124839bae7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ // DOCS -pub use args::{Arg, SubCommand, ArgMatches}; +pub use args::{Arg, SubCommand, ArgMatches, ArgGroup}; pub use app::App; #[macro_use] diff --git a/src/macros.rs b/src/macros.rs index d427cf20ebe..5946b11db74 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,6 +14,39 @@ macro_rules! get_help { }; } +// De-duplication macro used in src/app.rs +macro_rules! parse_group_reqs { + ($me:ident, $arg:ident) => { + for ag in $me.groups.values() { + let mut found = false; + for name in ag.args.iter() { + if name == &$arg.name { + $me.required.remove(ag.name); + if let Some(ref reqs) = ag.requires { + for r in reqs { + $me.required.insert(r); + } + } + if let Some(ref bl) = ag.conflicts { + for b in bl { + $me.blacklist.insert(b); + } + } + found = true; + break; + } + } + if found { + for name in ag.args.iter() { + if name == &$arg.name { continue } + $me.required.remove(name); + $me.blacklist.insert(name); + } + } + } + }; +} + // Thanks to bluss and flan3002 in #rust IRC // // Helps with rightward drift when iterating over something and matching each item.