From ab409a8f1db9e37cc70200f6f4a84a162692e618 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 13 Apr 2015 00:37:59 -0400 Subject: [PATCH 1/4] feat(args): add ability to create basic arguments from a usage string --- gede.ini | 12 ++ src/app.rs | 9 +- src/args/arg.rs | 104 ++++++++++ src/lib.rs | 506 +++++++++++++++++++++++++++++++++++++++++++++ src/macros.rs | 13 ++ src/usageparser.rs | 126 +++++++++++ 6 files changed, 768 insertions(+), 2 deletions(-) create mode 100644 gede.ini create mode 100644 src/usageparser.rs diff --git a/gede.ini b/gede.ini new file mode 100644 index 00000000..d0085694 --- /dev/null +++ b/gede.ini @@ -0,0 +1,12 @@ +TcpPort="2000" +TcpHost="localhost" +Mode="0" +LastProgram="/home/kevin/Projects/clap-rs/target/debug/clap-fd2d4002ca38536a" +TcpProgram="" +InitCommands="" +GdpPath="gdb" +LastProgramArguments="" +Font="Monospace" +FontSize="8" +ReuseBreakpoints="0" +Breakpoints=":0;:0;:0;:0;:0;:0;:0;:0;/home/kevin/Projects/clap-rs/src/usageparser.rs:51;:0" diff --git a/src/app.rs b/src/app.rs index 3650c472..f4ef9c9e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -219,10 +219,13 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{ if a.required { self.required.insert(a.name); } - if let Some(i) = a.index { + if a.index.is_some() || (a.short.is_none() && a.long.is_none()) { + let i = if a.index.is_none() {(self.positionals_idx.len() + 1) as u8 } else { a.index.unwrap() }; + if a.short.is_some() || a.long.is_some() { panic!("Argument \"{}\" has conflicting requirements, both index() and short(), or long(), were supplied", a.name); } + if self.positionals_idx.contains_key(&i) { panic!("Argument \"{}\" has the same index as another positional argument", a.name); } @@ -230,6 +233,7 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{ panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied", a.name); } + // Create the Positional Arguemnt Builder with each HashSet = None to only allocate those that require it let mut pb = PosBuilder { name: a.name, @@ -304,7 +308,8 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{ self.opts.insert(a.name, ob); } else { if a.short.is_none() && a.long.is_none() { - panic!("Argument \"{}\" must have either a short() and/or long() supplied since no index() or takes_value() were found", a.name); + // Could be a posistional constructed from usage string + } if a.required { panic!("Argument \"{}\" cannot be required(true) because it has no index() or takes_value(true)", a.name); diff --git a/src/args/arg.rs b/src/args/arg.rs index 57547e0c..37dcf1c4 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -1,3 +1,5 @@ +use usageparser::{UsageParser, UsageToken}; + /// 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. /// @@ -74,6 +76,9 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// and positional arguments (i.e. those without a `-` or `--`) the name will also /// be displayed when the user prints the usage/help information of the program. /// + /// **NOTE:** this function is deprecated in favor of Arg::with_name() to stay in line with + /// Rust APIs + /// /// Example: /// /// ```no_run @@ -83,6 +88,8 @@ impl<'n, 'l, 'h, 'b, 'p, 'r> Arg<'n, 'l, 'h, 'b, 'p, 'r> { /// Arg::new("conifg") /// # .short("c") /// # ).get_matches(); + #[deprecated(since = "0.5.15", + reason = "use Arg::with_name() instead")] pub fn new(n: &'n str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { Arg { name: n, @@ -99,6 +106,103 @@ 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. + /// + /// **NOTE:** in the case of arguments that take values (i.e. `takes_value(true)`) + /// and positional arguments (i.e. those without a `-` or `--`) the name will also + /// be displayed when the user prints the usage/help information of the program. + /// + /// Example: + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let matches = App::new("myprog") + /// # .arg( + /// Arg::with_name("conifg") + /// # .short("c") + /// # ).get_matches(); + pub fn with_name(n: &'n str) -> Arg<'n, 'l, 'h, 'b, 'p, 'r> { + Arg { + name: n, + short: None, + long: None, + help: None, + required: false, + takes_value: false, + multiple: false, + index: None, + possible_vals: None, + blacklist: None, + requires: None, + } + } + + pub fn from_usage(u: &'n str) -> Arg<'n, 'n, 'n, 'b, 'p, 'r> { + assert!(u.len() > 0, "Arg::from_usage() requires a non-zero-length usage string but none was provided"); + + 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 parser = UsageParser::with_usage(u); + for_match!{ parser, + UsageToken::Name(n, req) => { + if name.is_none() { + name = Some(n); + if let Some(m) = req { + required = m; + } + } + if short.is_some() || long.is_some() { + takes_value = true; + } + if let Some(l) = long { + if n != name.unwrap() && name.unwrap() == l { + name = Some(n); + if let Some(m) = req { + required = m; + } + } + } + }, + UsageToken::Short(s) => { + short = Some(s); + }, + UsageToken::Long(l) => { + long = Some(l); + if name.is_none() { + name = Some(l); + } + }, + UsageToken::Help(h) => { + help = Some(h); + }, + UsageToken::Multiple => { + multiple = true; + } + } + + Arg { + name: name.unwrap(), + short: short, + long: long, + help: help, + required: required, + takes_value: takes_value, + multiple: multiple, + index: None, + possible_vals: None, + blacklist: None, + requires: None, + } + } + /// Sets the short version of the argument without the preceding `-`. /// /// diff --git a/src/lib.rs b/src/lib.rs index d38a6529..7e7917be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub use app::App; mod macros; mod app; mod args; +mod usageparser; #[cfg(test)] mod tests { @@ -38,6 +39,58 @@ mod tests { .get_matches(); } + #[test] + fn create_flag_usage() { + let a = Arg::from_usage("[flag] -f 'some help info'"); + assert_eq!(a.name, "flag"); + assert_eq!(a.short.unwrap(), 'f'); + assert!(a.long.is_none()); + assert_eq!(a.help.unwrap(), "some help info"); + assert!(!a.multiple); + + let b = Arg::from_usage("[flag] --flag 'some help info'"); + assert_eq!(b.name, "flag"); + assert_eq!(b.long.unwrap(), "flag"); + assert!(b.short.is_none()); + assert_eq!(b.help.unwrap(), "some help info"); + assert!(!b.multiple); + + let b = Arg::from_usage("--flag 'some help info'"); + assert_eq!(b.name, "flag"); + assert_eq!(b.long.unwrap(), "flag"); + assert!(b.short.is_none()); + assert_eq!(b.help.unwrap(), "some help info"); + assert!(!b.multiple); + + let c = Arg::from_usage("[flag] -f --flag 'some help info'"); + assert_eq!(c.name, "flag"); + assert_eq!(c.short.unwrap(), 'f'); + assert_eq!(c.long.unwrap(), "flag"); + assert_eq!(c.help.unwrap(), "some help info"); + assert!(!c.multiple); + + let d = Arg::from_usage("[flag] -f... 'some help info'"); + assert_eq!(d.name, "flag"); + assert_eq!(d.short.unwrap(), 'f'); + assert!(d.long.is_none()); + assert_eq!(d.help.unwrap(), "some help info"); + assert!(d.multiple); + + let e = Arg::from_usage("[flag] -f --flag... 'some help info'"); + assert_eq!(e.name, "flag"); + assert_eq!(e.long.unwrap(), "flag"); + assert_eq!(e.short.unwrap(), 'f'); + assert_eq!(e.help.unwrap(), "some help info"); + assert!(e.multiple); + + let e = Arg::from_usage("-f --flag... 'some help info'"); + assert_eq!(e.name, "flag"); + assert_eq!(e.long.unwrap(), "flag"); + assert_eq!(e.short.unwrap(), 'f'); + assert_eq!(e.help.unwrap(), "some help info"); + assert!(e.multiple); + } + #[test] fn create_positional() { let _ = App::new("test") @@ -47,6 +100,33 @@ mod tests { .get_matches(); } + #[test] + fn create_positional_usage() { + let a = Arg::from_usage("[pos] 'some help info'"); + assert_eq!(a.name, "pos"); + assert_eq!(a.help.unwrap(), "some help info"); + assert!(!a.multiple); + assert!(!a.required); + + let b = Arg::from_usage(" 'some help info'"); + assert_eq!(b.name, "pos"); + assert_eq!(b.help.unwrap(), "some help info"); + assert!(!b.multiple); + assert!(b.required); + + let c = Arg::from_usage("[pos]... 'some help info'"); + assert_eq!(c.name, "pos"); + assert_eq!(c.help.unwrap(), "some help info"); + assert!(c.multiple); + assert!(!c.required); + + let d = Arg::from_usage("... 'some help info'"); + assert_eq!(d.name, "pos"); + assert_eq!(d.help.unwrap(), "some help info"); + assert!(d.multiple); + assert!(d.required); + } + #[test] fn create_option() { let _ = App::new("test") @@ -58,6 +138,432 @@ mod tests { .get_matches(); } + #[test] + fn create_option_usage() { + // Short only + let a = Arg::from_usage("[option] -o [opt] 'some help info'"); + assert_eq!(a.name, "option"); + assert_eq!(a.short.unwrap(), 'o'); + assert!(a.long.is_none()); + assert_eq!(a.help.unwrap(), "some help info"); + assert!(!a.multiple); + assert!(a.takes_value); + assert!(!a.required); + + let b = Arg::from_usage("-o [opt] 'some help info'"); + assert_eq!(b.name, "opt"); + assert_eq!(b.short.unwrap(), 'o'); + assert!(b.long.is_none()); + assert_eq!(b.help.unwrap(), "some help info"); + assert!(!b.multiple); + assert!(b.takes_value); + assert!(!b.required); + + let c = Arg::from_usage("