diff --git a/src/app/parser.rs b/src/app/parser.rs index 31775d02cea1..a8f093521496 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -143,7 +143,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { self.set(AppSettings::NeedsLongVersion); } } - if a.required { + if a.is_set(ArgSettings::Required) { self.required.push(a.name); } if a.index.is_some() || (a.short.is_none() && a.long.is_none()) { @@ -160,15 +160,15 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { } let pb = PosBuilder::from_arg(&a, i as u8, &mut self.required); self.positionals.insert(i, pb); - } else if a.takes_value { + } else if a.is_set(ArgSettings::TakesValue) { let ob = OptBuilder::from_arg(&a, &mut self.required); self.opts.push(ob); } else { let fb = FlagBuilder::from(a); self.flags.push(fb); } - if a.global { - if a.required { + if a.is_set(ArgSettings::Global) { + if a.is_set(ArgSettings::Required) { panic!("Global arguments cannot be required.\n\n\t'{}' is marked as global and \ required", a.name); diff --git a/src/args/arg.rs b/src/args/arg.rs index 239e267ba89b..4e77406b86ee 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -1,12 +1,13 @@ #[cfg(feature = "yaml")] use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::rc::Rc; #[cfg(feature = "yaml")] use yaml_rust::Yaml; +use vec_map::VecMap; -use usageparser::{UsageParser, UsageToken}; +use usage_parser::UsageParser; +use args::settings::{ArgSettings, ArgFlags}; /// The abstract representation of a command line argument used by the consumer of the library. /// Used to set all the options and relationships that define a valid argument for the program. @@ -36,61 +37,59 @@ use usageparser::{UsageParser, UsageToken}; /// let input = Arg::from_usage("-i --input=[input] 'Provides an input file to the program'"); #[allow(missing_debug_implementations)] pub struct Arg<'a, 'b> where 'a: 'b { - /// The unique name of the argument + // The unique name of the argument + #[doc(hidden)] pub name: &'a str, - /// The short version (i.e. single character) of the argument, no preceding `-` - /// **NOTE:** `short` is mutually exclusive with `index` + // The short version (i.e. single character) of the argument, no preceding `-` + // **NOTE:** `short` is mutually exclusive with `index` + #[doc(hidden)] pub short: Option, - /// The long version of the flag (i.e. word) without the preceding `--` - /// **NOTE:** `long` is mutually exclusive with `index` + // The long version of the flag (i.e. word) without the preceding `--` + // **NOTE:** `long` is mutually exclusive with `index` + #[doc(hidden)] pub long: Option<&'b str>, - /// The string of text that will displayed to the user when the application's - /// `help` text is displayed + // The string of text that will displayed to the user when the application's + // `help` text is displayed + #[doc(hidden)] pub help: Option<&'b str>, - /// If this is a required by default when using the command line program, - /// e.g. 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, - /// Determines if this argument is an option (as opposed to flag or positional) and - /// is mutually exclusive with `index` and `multiple` - pub takes_value: bool, - /// The index of the argument. `index` is mutually exclusive with `takes_value` - /// and `multiple` + // The index of the argument. `index` is mutually exclusive with `takes_value` + // and `multiple` + #[doc(hidden)] pub index: Option, - /// Determines if multiple instances of the same flag are allowed. `multiple` - /// is mutually exclusive with `index`. - /// e.g. `-v -v -v` or `-vvv` or `--option foo --option bar` - pub multiple: bool, - /// A list of names for other arguments that *may not* be used with this flag + // A list of names for other arguments that *may not* be used with this flag + #[doc(hidden)] pub blacklist: Option>, - /// A list of possible values for an option or positional argument + // 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 + // A list of names of other arguments that are *required* to be used when + // this flag is used + #[doc(hidden)] pub requires: Option>, - /// A name of the group the argument belongs to + // A name of the group the argument belongs to + #[doc(hidden)] pub group: Option<&'a str>, - /// A set of names (ordered) for the values to be displayed with the help message - pub val_names: Option>, - /// The exact number of values to satisfy this argument + // A set of names (ordered) for the values to be displayed with the help message + #[doc(hidden)] + pub val_names: Option>, + // The exact number of values to satisfy this argument + #[doc(hidden)] pub num_vals: Option, - /// The maximum number of values possible for this argument + // The maximum number of values possible for this argument + #[doc(hidden)] pub max_vals: Option, - /// The minimum number of values possible to satisfy this argument + // The minimum number of values possible to satisfy this argument + #[doc(hidden)] pub min_vals: Option, - /// Specifies whether or not this argument accepts explicit empty values such as `--option ""` - pub empty_vals: bool, - /// Specifies whether or not this argument is global and should be propagated through all - /// child subcommands - pub global: bool, - /// A function used to check the validity of an argument value. Failing this validation results - /// in failed argument parsing. + // A function used to check the validity of an argument value. Failing this validation results + // in failed argument parsing. + #[doc(hidden)] pub validator: Option Result<(), String>>>, - /// A list of names for other arguments that *mutually override* this flag + // A list of names for other arguments that *mutually override* this flag + #[doc(hidden)] pub overrides: Option>, - /// Specifies whether the argument should show up in the help message - pub hidden: bool, + #[doc(hidden)] + pub settings: ArgFlags, } impl<'a, 'b> Default for Arg<'a, 'b> { @@ -100,10 +99,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { short: None, long: None, help: None, - required: false, - takes_value: false, index: None, - multiple: false, blacklist: None, possible_vals: None, requires: None, @@ -112,11 +108,9 @@ impl<'a, 'b> Default for Arg<'a, 'b> { num_vals: None, max_vals: None, min_vals: None, - empty_vals: true, - global: false, validator: None, overrides: None, - hidden: false, + settings: ArgFlags::new(), } } } @@ -144,7 +138,6 @@ impl<'a, 'b> Arg<'a, 'b> { pub fn with_name(n: &'a str) -> Self { Arg { name: n, - empty_vals: true, ..Default::default() } } @@ -171,11 +164,11 @@ impl<'a, 'b> Arg<'a, 'b> { "short" => a.short(v.as_str().unwrap()), "long" => a.long(v.as_str().unwrap()), "help" => a.help(v.as_str().unwrap()), - "required" => a.required(v.as_bool().unwrap()), - "takes_value" => a.takes_value(v.as_bool().unwrap()), + "required" => a.is_set(ArgSettings::Required)(v.as_bool().unwrap()), + "takes_value" => a.is_set(ArgSettings::TakesValue)(v.as_bool().unwrap()), "index" => a.index(v.as_i64().unwrap() as u8), - "global" => a.global(v.as_bool().unwrap()), - "multiple" => a.multiple(v.as_bool().unwrap()), + "global" => a.is_set(ArgSettings::Global)(v.as_bool().unwrap()), + "multiple" => a.is_set(ArgSettings::Multiple)(v.as_bool().unwrap()), "empty_values" => a.empty_values(v.as_bool().unwrap()), "group" => a.group(v.as_str().unwrap()), "number_of_values" => a.number_of_values(v.as_i64().unwrap() as u8), @@ -284,107 +277,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// ]) /// # .get_matches(); pub fn from_usage(u: &'a str) -> Self { - let mut name = None; - let mut short = None; - let mut long = None; - let mut help = None; - let mut required = false; - let mut takes_value = false; - let mut multiple = false; - let mut num_names = 1; - let mut name_first = false; - let mut consec_names = false; - let mut val_names: BTreeSet<&'a str> = BTreeSet::new(); - - let parser = UsageParser::with_usage(u); - for_match!{ parser, - UsageToken::Name(n, req) => { - if consec_names { - num_names += 1; - } - let mut use_req = false; - let mut use_name = false; - if name.is_none() && long.is_none() && short.is_none() { - name_first = true; - use_name = true; - use_req = true; - } else if let Some(l) = long { - if l == name.unwrap_or("") { - if !name_first { - use_name = true; - use_req = true; - } - } - } else { - // starting with short - if !name_first { - use_name = true; - use_req = true; - } - } - if use_name && !consec_names { - name = Some(n); - } - if use_req && !consec_names { - if let Some(r) = req { - required = r; - } - } - if short.is_some() || long.is_some() { - val_names.insert(n.as_ref()); - takes_value = true; - } - consec_names = true; - }, - UsageToken::Short(s) => { - consec_names = false; - short = Some(s); - }, - UsageToken::Long(l) => { - consec_names = false; - long = Some(l); - if name.is_none() { - name = Some(l); - } - }, - UsageToken::Help(h) => { - help = Some(h); - }, - UsageToken::Multiple => { - multiple = true; - } - } - - if let Some(l) = long { - val_names.remove(l); - if (val_names.len() > 1) && (name.unwrap() != l && !name_first) { - name = Some(l); - } - } - - Arg { - name: name.unwrap_or_else(|| { - panic!("Missing flag name in \"{}\", check from_usage call", u) - }).as_ref(), - short: short, - long: long, - help: help, - required: required, - takes_value: takes_value, - multiple: multiple, - empty_vals: true, - num_vals: if num_names > 1 { - Some(num_names) - } else { - None - }, - val_names: if val_names.len() > 1 { - Some(val_names) - } else { - None - }, - ..Default::default() - } + let parser = UsageParser::from_usage(u); + parser.parse() } /// Sets the short version of the argument without the preceding `-`. @@ -473,9 +367,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # Arg::with_name("config") /// .required(true) /// # ).get_matches(); - pub fn required(mut self, r: bool) -> Self { - self.required = r; - self + pub fn required(self, r: bool) -> Self { + if r { self.set(ArgSettings::Required) } else { self.unset(ArgSettings::Required) } } /// Sets a mutually exclusive argument by name. I.e. when using this argument, @@ -633,9 +526,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # Arg::with_name("config") /// .takes_value(true) /// # ).get_matches(); - pub fn takes_value(mut self, tv: bool) -> Self { - self.takes_value = tv; - self + pub fn takes_value(self, tv: bool) -> Self { + if tv { self.set(ArgSettings::TakesValue) } else { self.unset(ArgSettings::TakesValue) } } /// Specifies the index of a positional argument starting at 1. @@ -680,9 +572,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # Arg::with_name("debug") /// .multiple(true) /// # ).get_matches(); - pub fn multiple(mut self, multi: bool) -> Self { - self.multiple = multi; - self + pub fn multiple(self, multi: bool) -> Self { + if multi { self.set(ArgSettings::Multiple) } else { self.unset(ArgSettings::Multiple) } } /// Specifies that an argument can be matched to all child subcommands. @@ -705,9 +596,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # Arg::with_name("debug") /// .global(true) /// # ).get_matches(); - pub fn global(mut self, g: bool) -> Self { - self.global = g; - self + pub fn global(self, g: bool) -> Self { + if g { self.set(ArgSettings::Global) } else { self.unset(ArgSettings::Global) } } /// Allows an argument to accept explicit empty values. An empty value must be specified at the @@ -725,9 +615,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # Arg::with_name("debug") /// .empty_values(true) /// # ).get_matches(); - pub fn empty_values(mut self, ev: bool) -> Self { - self.empty_vals = ev; - self + pub fn empty_values(self, ev: bool) -> Self { + if ev { self.set(ArgSettings::EmptyValues) } else { self.unset(ArgSettings::EmptyValues) } } /// Hides an argument from help message output. @@ -743,9 +632,8 @@ impl<'a, 'b> Arg<'a, 'b> { /// # Arg::with_name("debug") /// .hidden(true) /// # ).get_matches(); - pub fn hidden(mut self, h: bool) -> Self { - self.hidden = h; - self + pub fn hidden(self, h: bool) -> Self { + if h { self.set(ArgSettings::Hidden) } else { self.unset(ArgSettings::Hidden) } } /// Specifies a list of possible values for this argument. At runtime, clap verifies that only @@ -947,16 +835,49 @@ impl<'a, 'b> Arg<'a, 'b> { /// .value_names(&val_names) /// # ).get_matches(); pub fn value_names(mut self, names: &[&'b str]) -> Self { - if let Some(ref mut vec) = self.val_names { + if let Some(ref mut vals) = self.val_names { + let mut l = vals.len(); for s in names { - vec.insert(s); + vals.insert(l, s); + l += 1; } } else { - self.val_names = Some(names.iter().map(|s| *s).collect::>()); + let mut vm = VecMap::new(); + for (i, n) in names.iter().enumerate() { + vm.insert(i, *n); + } + self.val_names = Some(vm); } self } + #[doc(hidden)] + pub fn setb(&mut self, s: ArgSettings) { + self.settings.set(s); + } + + #[doc(hidden)] + pub fn unsetb(&mut self, s: ArgSettings) { + self.settings.unset(s); + } + + /// Checks if one of the `ArgSettings` settings is set for the argument + pub fn is_set(&self, s: ArgSettings) -> bool { + self.settings.is_set(s) + } + + /// Sets one of the `ArgSettings` settings for the argument + pub fn set(mut self, s: ArgSettings) -> Self { + self.setb(s); + self + } + + /// Unsets one of the `ArgSettings` settings for the argument + pub fn unset(mut self, s: ArgSettings) -> Self { + self.unsetb(s); + self + } + /// Specifies the name for value of option or positional arguments. This name is cosmetic only, /// used for help and usage strings. The name is **not** used to access arguments. /// @@ -971,12 +892,13 @@ impl<'a, 'b> Arg<'a, 'b> { /// .value_name("file") /// # ).get_matches(); pub fn value_name(mut self, name: &'b str) -> Self { - if let Some(ref mut vec) = self.val_names { - vec.insert(name); + if let Some(ref mut vals) = self.val_names { + let l = vals.len(); + vals.insert(l, name); } else { - let mut bts = BTreeSet::new(); - bts.insert(name); - self.val_names = Some(bts); + let mut vm = VecMap::new(); + vm.insert(0, name); + self.val_names = Some(vm); } self } @@ -990,9 +912,6 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> short: a.short, long: a.long, help: a.help, - required: a.required, - takes_value: a.takes_value, - multiple: a.multiple, index: a.index, possible_vals: a.possible_vals.clone(), blacklist: a.blacklist.clone(), @@ -1002,11 +921,9 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> max_vals: a.max_vals, val_names: a.val_names.clone(), group: a.group, - global: a.global, - empty_vals: a.empty_vals, validator: a.validator.clone(), overrides: a.overrides.clone(), - hidden: a.hidden, + settings: a.settings.clone(), } } } diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index a01345e6bc7d..18ede14a9d91 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -100,13 +100,13 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { panic!("The argument '{}' has a validator set, yet was parsed as a flag. Ensure \ .takes_value(true) or .index(u8) is set.", a.name) } - if !a.empty_vals { + if !a.is_set(ArgSettings::EmptyValues) { // 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. \ Perhaps you mean't to set takes_value(true) as well?", a.name); } - if a.required { + if a.is_set(ArgSettings::Required) { panic!("The argument '{}' cannot be required(true) because it has no index() or \ takes_value(true)", a.name); @@ -125,13 +125,13 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { help: a.help, ..Default::default() }; - if a.multiple { + if a.is_set(ArgSettings::Multiple) { fb.settings.set(ArgSettings::Multiple); } - if a.global { + if a.is_set(ArgSettings::Global) { fb.settings.set(ArgSettings::Global); } - if a.hidden { + if a.is_set(ArgSettings::Hidden) { fb.settings.set(ArgSettings::Hidden); } // Check if there is anything in the blacklist (mutually excludes list) and add diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index e04b7b51be4d..30fb88f1f7b8 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -1,9 +1,10 @@ use std::rc::Rc; -use std::collections::BTreeSet; use std::fmt::{Display, Formatter, Result}; use std::result::Result as StdResult; use std::io; +use vec_map::VecMap; + use args::{AnyArg, Arg}; use args::settings::{ArgFlags, ArgSettings}; @@ -27,7 +28,7 @@ pub struct OptBuilder<'n, 'e> { pub num_vals: Option, pub min_vals: Option, pub max_vals: Option, - pub val_names: Option>, + pub val_names: Option>, pub validator: Option StdResult<(), String>>>, /// A list of names for other arguments that *mutually override* this flag pub overrides: Option>, @@ -64,6 +65,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { } pub fn from_arg(a: &Arg<'n, 'e>, reqs: &mut Vec<&'e str>) -> Self { + debugln!("fn=from_arg;"); if a.short.is_none() && a.long.is_none() { panic!("Argument \"{}\" has takes_value(true), yet neither a short() or long() \ was supplied", @@ -81,19 +83,19 @@ impl<'n, 'e> OptBuilder<'n, 'e> { val_names: a.val_names.clone(), ..Default::default() }; - if a.multiple { + if a.is_set(ArgSettings::Multiple) { ob.settings.set(ArgSettings::Multiple); } - if a.required { + if a.is_set(ArgSettings::Required) { ob.settings.set(ArgSettings::Required); } - if a.global { + if a.is_set(ArgSettings::Global) { ob.settings.set(ArgSettings::Global); } - if !a.empty_vals { + if !a.is_set(ArgSettings::EmptyValues) { ob.settings.unset(ArgSettings::Global); } - if a.hidden { + if a.is_set(ArgSettings::Hidden) { ob.settings.set(ArgSettings::Hidden); } if let Some(ref vec) = ob.val_names { @@ -119,7 +121,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { // without derefing n = &&str for n in r { rhs.push(*n); - if a.required { + if a.is_set(ArgSettings::Required) { reqs.push(*n); } } @@ -147,6 +149,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { } pub fn write_help(&self, w: &mut W, tab: &str, longest: usize) -> io::Result<()> { + debugln!("fn=write_help"); // if it supports multiple we add '...' i.e. 3 to the name length try!(write!(w, "{}", tab)); if let Some(s) = self.short { @@ -165,9 +168,13 @@ impl<'n, 'e> OptBuilder<'n, 'e> { l)); } if let Some(ref vec) = self.val_names { - for val in vec { + for (_, val) in vec { try!(write!(w, " <{}>", val)); } + let num = vec.len(); + if self.settings.is_set(ArgSettings::Multiple) && num == 1 { + try!(write!(w, "...")); + } } else if let Some(num) = self.num_vals { for _ in 0..num { try!(write!(w, " <{}>", self.name)); @@ -195,6 +202,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { impl<'n, 'e> Display for OptBuilder<'n, 'e> { fn fmt(&self, f: &mut Formatter) -> Result { + debugln!("fn=fmt"); // Write the name such --long or -l if let Some(l) = self.long { try!(write!(f, "--{}", l)); @@ -204,9 +212,14 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> { // Write the values such as if let Some(ref vec) = self.val_names { - for n in vec.iter() { + for (_, n) in vec { + debugln!("writing val_name: {}", n); try!(write!(f, " <{}>", n)); } + let num = vec.len(); + if self.settings.is_set(ArgSettings::Multiple) && num == 1 { + try!(write!(f, "...")); + } } else { let num = self.num_vals.unwrap_or(1); for _ in 0..num { @@ -243,24 +256,41 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { #[cfg(test)] mod test { use super::OptBuilder; - use std::collections::BTreeSet; + use vec_map::VecMap; use args::settings::ArgSettings; #[test] - fn optbuilder_display() { + fn optbuilder_display1() { let mut o = OptBuilder::new("opt"); o.long = Some("option"); o.settings.set(ArgSettings::Multiple); assert_eq!(&*format!("{}", o), "--option ..."); + } + + #[test] + fn optbuilder_display2() { + let mut v_names = VecMap::new(); + v_names.insert(0, "file"); + v_names.insert(1, "name"); - let mut v_names = BTreeSet::new(); - v_names.insert("file"); - v_names.insert("name"); + let mut o2 = OptBuilder::new("opt"); + o2.short = Some('o'); + o2.val_names = Some(v_names); + + assert_eq!(&*format!("{}", o2), "-o "); + } + + #[test] + fn optbuilder_display3() { + let mut v_names = VecMap::new(); + v_names.insert(0, "file"); + v_names.insert(1, "name"); let mut o2 = OptBuilder::new("opt"); o2.short = Some('o'); o2.val_names = Some(v_names); + o2.settings.set(ArgSettings::Multiple); assert_eq!(&*format!("{}", o2), "-o "); } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 547a08a7ff9b..cb684e69a9cd 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -66,7 +66,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { a.name); } - if a.takes_value { + if a.is_set(ArgSettings::TakesValue) { 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", @@ -91,16 +91,16 @@ impl<'n, 'e> PosBuilder<'n, 'e> { help: a.help, ..Default::default() }; - if a.multiple || a.num_vals.is_some() || a.max_vals.is_some() || a.min_vals.is_some() { + if a.is_set(ArgSettings::Multiple) || a.num_vals.is_some() || a.max_vals.is_some() || a.min_vals.is_some() { pb.settings.set(ArgSettings::Multiple); } - if a.required { + if a.is_set(ArgSettings::Required) { pb.settings.set(ArgSettings::Required); } - if a.global { + if a.is_set(ArgSettings::Global) { pb.settings.set(ArgSettings::Global); } - if a.hidden { + if a.is_set(ArgSettings::Hidden) { pb.settings.set(ArgSettings::Hidden); } // Check if there is anything in the blacklist (mutually excludes list) and add @@ -140,7 +140,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { // without derefing n = &&str for n in r { rhs.push(n); - if a.required { + if a.is_set(ArgSettings::Required) { reqs.push(n); } } diff --git a/src/args/group.rs b/src/args/group.rs index 47bc9eaf9ae1..1814186fb8ec 100644 --- a/src/args/group.rs +++ b/src/args/group.rs @@ -95,7 +95,7 @@ impl<'a> ArgGroup<'a> { for (k, v) in group_settings.iter() { a = match k.as_str().unwrap() { - "required" => a.required(v.as_bool().unwrap()), + "required" => a.is_set(ArgSettings::Required)(v.as_bool().unwrap()), "args" => { for ys in v.as_vec().unwrap() { if let Some(s) = ys.as_str() { diff --git a/src/args/mod.rs b/src/args/mod.rs index 511e8e395902..98b2330b8f5d 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -6,6 +6,7 @@ pub use self::arg_builder::{FlagBuilder, OptBuilder, PosBuilder}; pub use self::matched_arg::MatchedArg; pub use self::group::ArgGroup; pub use self::any_arg::AnyArg; +pub use self::settings::ArgSettings; mod arg; pub mod any_arg; diff --git a/src/args/settings.rs b/src/args/settings.rs index f53ef9ed927c..a6fcfff54112 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -3,15 +3,16 @@ use std::ascii::AsciiExt; bitflags! { flags Flags: u8 { - const REQUIRED = 0b00001, - const MULTIPLE = 0b00010, - const EMPTY_VALS = 0b00100, - const GLOBAL = 0b01000, - const HIDDEN = 0b10000, + const REQUIRED = 0b000001, + const MULTIPLE = 0b000010, + const EMPTY_VALS = 0b000100, + const GLOBAL = 0b001000, + const HIDDEN = 0b010000, + const TAKES_VAL = 0b100000, } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ArgFlags(Flags); impl ArgFlags { @@ -26,6 +27,7 @@ impl ArgFlags { ArgSettings::EmptyValues => self.0.insert(EMPTY_VALS), ArgSettings::Global => self.0.insert(GLOBAL), ArgSettings::Hidden => self.0.insert(HIDDEN), + ArgSettings::TakesValue => self.0.insert(TAKES_VAL), } } @@ -36,6 +38,7 @@ impl ArgFlags { ArgSettings::EmptyValues => self.0.remove(EMPTY_VALS), ArgSettings::Global => self.0.remove(GLOBAL), ArgSettings::Hidden => self.0.remove(HIDDEN), + ArgSettings::TakesValue => self.0.remove(TAKES_VAL), } } @@ -46,6 +49,7 @@ impl ArgFlags { ArgSettings::EmptyValues => self.0.contains(EMPTY_VALS), ArgSettings::Global => self.0.contains(GLOBAL), ArgSettings::Hidden => self.0.contains(HIDDEN), + ArgSettings::TakesValue => self.0.contains(TAKES_VAL), } } } @@ -56,14 +60,22 @@ impl Default for ArgFlags { } } -#[doc(hidden)] -#[derive(Debug, PartialEq)] +/// Various settings that apply to arguments and may be set, unset, and checked via getter/setter +/// methods `Arg::set`, `Arg::unset`, and `Arg::is_set` +#[derive(Debug, PartialEq, Copy, Clone)] pub enum ArgSettings { + /// The argument must be used Required, + /// The argument may be used multiple times such as `--flag --flag` Multiple, + /// The argument allows empty values such as `--option ""` EmptyValues, + /// The argument should be propagated down through all child subcommands Global, + /// The argument should **not** be shown in help text Hidden, + /// The argument accepts a value, such as `--option ` + TakesValue, } impl FromStr for ArgSettings { @@ -75,6 +87,7 @@ impl FromStr for ArgSettings { "global" => Ok(ArgSettings::Global), "emptyvalues" => Ok(ArgSettings::EmptyValues), "hidden" => Ok(ArgSettings::Hidden), + "takesvalue" => Ok(ArgSettings::TakesValue), _ => Err("unknown ArgSetting, cannot convert from str".to_owned()), } } diff --git a/src/errors.rs b/src/errors.rs index f05946e70401..1d9f80babefb 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -596,7 +596,7 @@ impl Error { { let a = arg.into(); Error { - message: format!("{} The argument '{}' wasn't recognized, or isn't valid in this context{}\n\ + message: format!("{} Found argument '{}' which wasn't expected, or isn't valid in this context{}\n\ {}\n\n\ For more information try {}", Format::Error("error:"), diff --git a/src/lib.rs b/src/lib.rs index 469ae832d2ce..1e96fb77ee07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -672,7 +672,7 @@ extern crate vec_map; #[cfg(feature = "yaml")] pub use yaml_rust::YamlLoader; -pub use args::{Arg, ArgGroup, ArgMatches, SubCommand}; +pub use args::{Arg, ArgGroup, ArgMatches, SubCommand, ArgSettings}; pub use app::{App, AppSettings}; pub use fmt::Format; pub use errors::{Error, ErrorKind}; @@ -681,7 +681,7 @@ pub use errors::{Error, ErrorKind}; mod macros; mod app; mod args; -mod usageparser; +mod usage_parser; mod fmt; mod suggestions; mod errors; diff --git a/src/macros.rs b/src/macros.rs index 697fe95b98b6..47675ac2f329 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -124,23 +124,6 @@ macro_rules! vec_remove { } } -// Thanks to bluss and flan3002 in #rust IRC -// -// Helps with rightward drift when iterating over something and matching each -// item. -macro_rules! for_match { - ($it:ident, $($p:pat => $($e:expr);+),*) => { - debugln!("macro=for_match!;"); - for i in $it { - match i { - $( - $p => { $($e)+ } - )* - } - } - }; -} - /// Convenience macro getting a typed value `T` where `T` implements `std::str::FromStr` /// This macro returns a `Result` which allows you as the developer to decide /// what you'd like to do on a failed parse. There are two types of errors, parse failures diff --git a/src/usage_parser.rs b/src/usage_parser.rs new file mode 100644 index 000000000000..193688caa3e5 --- /dev/null +++ b/src/usage_parser.rs @@ -0,0 +1,201 @@ +use vec_map::VecMap; + +use args::Arg; +use args::settings::ArgSettings; +use INTERNAL_ERROR_MSG; + +type ParseResult = Result<(), ()>; + +#[derive(PartialEq, Debug)] +enum UsageToken { + Name, + ValName, + Short, + Long, + Help, + Multiple, + Unknown +} + +#[derive(Debug)] +pub struct UsageParser<'a> { + usage: &'a str, + pos: usize, + start: usize, + prev: UsageToken, + explicit_name_set: bool +} + +impl<'a> UsageParser<'a> { + fn new(usage: &'a str) -> Self { + debugln!("exec=new; usage={:?}", usage); + UsageParser { + usage: usage, + pos: 0, + start: 0, + prev: UsageToken::Unknown, + explicit_name_set: false, + } + } + + pub fn from_usage(usage: &'a str) -> Self { + debugln!("fn=from_usage;"); + UsageParser::new(usage) + } + + pub fn parse(mut self) -> Arg<'a, 'a> { + debugln!("fn=parse;"); + let mut arg = Arg::default(); + loop { + debugln!("iter; pos={};", self.pos); + self.stop_at(token); + if self.pos < self.usage.len() { + if let Some(c) = self.usage.chars().nth(self.pos) { + match c { + '-' => self.short_or_long(&mut arg), + '.' => self.multiple(&mut arg), + '\'' => self.help(&mut arg), + _ => self.name(&mut arg), + } + } + } else { break; } + } + if arg.name.is_empty() { panic!("No name found for Arg when parsing usage string: {}", self.usage) } + let n_vals = if let Some(ref v) = arg.val_names { v.len() } else { 0 }; + if n_vals > 1 { + arg.num_vals = Some(n_vals as u8); + } + debugln!("vals: {:?}", arg.val_names); + arg + } + + fn name(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("fn=name;"); + if self.usage.chars().nth(self.pos).expect(INTERNAL_ERROR_MSG) == '<' && !self.explicit_name_set { arg.setb(ArgSettings::Required); } + self.pos += 1; + self.stop_at(name_end); + let name = &self.usage[self.start..self.pos]; + if self.prev != UsageToken::Unknown { + debugln!("setting val name: {}", name); + if let Some(ref mut v) = arg.val_names { + let len = v.len(); + v.insert(len, name); + } else { + let mut v = VecMap::new(); + v.insert(0, name); + arg.val_names = Some(v); + arg.setb(ArgSettings::TakesValue); + } + self.prev = UsageToken::ValName; + } else { + debugln!("setting name: {}", name); + arg.name = name; + if arg.long.is_none() && arg.short.is_none() { + debugln!("explicit name set..."); + self.explicit_name_set = true; + self.prev = UsageToken::Name; + } + } + } + + fn stop_at(&mut self, f: F) where F: Fn(u32) -> bool { + debugln!("fn=stop_at;"); + self.start = self.pos; + for c in self.usage[self.start..].chars() { + if f(c as u32) { self.pos += 1; continue; } + break; + } + } + + fn short_or_long(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("fn=short_or_long;"); + self.pos += 1; + if self.usage.chars().nth(self.pos).expect(INTERNAL_ERROR_MSG) == '-' { + self.pos += 1; + self.long(arg); + return; + } + self.short(arg) + } + + fn long(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("fn=long;"); + self.stop_at(long_end); + let name = &self.usage[self.start..self.pos]; + if arg.name.is_empty() || (self.prev == UsageToken::Short && arg.name.len() == 1) { + debugln!("setting name: {}", name); + arg.name = name; + } + debugln!("setting long: {}", name); + arg.long = Some(name); + self.prev = UsageToken::Long; + } + + fn short(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("fn=short;"); + let name = &self.usage[self.pos..self.pos + 1]; + debugln!("setting short: {}", name); + arg.short = Some(name.chars().nth(0).expect(INTERNAL_ERROR_MSG)); + if arg.name.is_empty() { + debugln!("setting name: {}", name); + arg.name = name; + } + self.prev = UsageToken::Short; + } + + fn multiple(&mut self, arg: &mut Arg) { + debugln!("fn=multiple;"); + let mut dot_counter = 1; + let start = self.pos; + for c in self.usage[start..].chars() { + match c { + '.' => { + dot_counter += 1; + self.pos += 1; + if dot_counter == 3 { + debugln!("setting multiple"); + arg.setb(ArgSettings::Multiple); + self.prev = UsageToken::Multiple; + self.pos += 1; + break; + } + }, + _ => break, + } + } + } + + fn help(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("fn=help;"); + self.pos += 1; + self.stop_at(help_end); + debugln!("setting help: {}", &self.usage[self.start..self.pos]); + arg.help = Some(&self.usage[self.start..self.pos]); + self.pos += 1; // Move to next byte to keep from thinking ending ' is a start + self.prev = UsageToken::Help; + } +} + + #[inline] + fn name_end(b: u32) -> bool { + // 93(]), 62(>) + b > b']' as u32 || b < b'>' as u32 || (b > b'>' as u32 && b < b']' as u32) + } + + #[inline] + fn token(b: u32) -> bool { + // 39('), 45(-), 46(.), 60(<), 91([) + b < 39 || b > 91 || (b > 46 && b < 91 && b != b'<' as u32) || (b > 39 && b < 45) + } + + #[inline] + fn long_end(b: u32) -> bool { + // 39('), 46(.), 60(<), 61(=), 91([) + (b < 39 && (b > 13 && b != b' ' as u32)) || b > 91 || (b > 61 && b < 91) || (b > 39 && b < 60 && b != 46) + } + + #[inline] + fn help_end(b: u32) -> bool { + // 39(') + b > 39 || b < 39 + } diff --git a/src/usageparser.rs b/src/usageparser.rs deleted file mode 100644 index 61f968356da6..000000000000 --- a/src/usageparser.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::str::Chars; - -pub enum UsageToken<'u> { - Name(&'u str, Option), - Short(char), - Long(&'u str), - Help(&'u str), - Multiple, -} - -pub struct UsageParser<'u> { - usage: &'u str, - chars: Chars<'u>, - s: usize, - e: usize, -} - -impl<'u> UsageParser<'u> { - pub fn with_usage(u: &'u str) -> UsageParser<'u> { - UsageParser { - usage: u, - chars: u.chars(), - s: 0, - e: 0, - } - } - - #[cfg_attr(feature = "lints", allow(while_let_on_iterator))] - fn name(&mut self, c: char) -> Option> { - if self.e != 0 { - self.e += 1; - } - self.s = self.e + 1; - let closing = match c { - '[' => ']', - '<' => '>', - _ => unreachable!(), - }; - while let Some(c) = self.chars.next() { - self.e += 1; - if c == closing { - break; - } - } - if self.e > self.usage.len() { - return None; - } - - let name = &self.usage[self.s..self.e]; - - Some(UsageToken::Name(name, - if c == '<' { - Some(true) - } else { - None - })) - } - - #[cfg_attr(feature = "lints", allow(while_let_on_iterator))] - fn help(&mut self) -> Option> { - self.s = self.e + 2; - self.e = self.usage.len() - 1; - - while let Some(_) = self.chars.next() { - continue; - } - - Some(UsageToken::Help(&self.usage[self.s..self.e])) - } - - #[cfg_attr(feature = "lints", allow(while_let_on_iterator))] - fn long_arg(&mut self) -> Option> { - if self.e != 1 { - self.e += 1; - } - - self.s = self.e + 1; - - while let Some(c) = self.chars.next() { - self.e += 1; - if c == ' ' || c == '=' || c == '.' { - break; - } - } - - if self.e > self.usage.len() { - return None; - } else if self.e == self.usage.len() - 1 { - return Some(UsageToken::Long(&self.usage[self.s..])); - } - - Some(UsageToken::Long(&self.usage[self.s..self.e])) - } - - fn short_arg(&mut self, c: char) -> Option> { - // When short is first don't increment e - if self.e != 1 { - self.e += 1; - } - if !c.is_alphanumeric() { - return None; - } - Some(UsageToken::Short(c)) - } - - fn multiple(&mut self) -> bool { - self.e += 1; - let mut mult = false; - for _ in 0..2 { - self.e += 1; - match self.chars.next() { - // longs consume one '.' so they match '.. ' whereas shorts can - // match '...' - Some('.') | Some(' ') => { - mult = true; - } - _ => { - // if there is no help or following space all we can match is '..' - if self.e == self.usage.len() - 1 { - mult = true; - } - break; - } - } - } - - mult - } -} - -impl<'u> Iterator for UsageParser<'u> { - type Item = UsageToken<'u>; - - fn next(&mut self) -> Option> { - loop { - match self.chars.next() { - Some(c) if c == '[' || c == '<' => { - return self.name(c); - } - Some('\'') => { - return self.help(); - } - Some('-') => { - self.e += 1; - match self.chars.next() { - Some('-') => { - return self.long_arg(); - } - Some(c) => { - return self.short_arg(c); - } - _ => { - return None; - } - } - } - Some('.') => { - if self.multiple() { - return Some(UsageToken::Multiple); - } - } - Some(' ') | Some('=') | Some(']') | Some('>') | Some('\t') | Some(',') => { - self.e += 1; - continue; - } - None => { - return None; - } - Some(c) => panic!("Usage parser error, unexpected \ - \"{}\" at \"{}\", check from_usage call", - c, - self.usage), - } - } - } -}