diff --git a/clap-tests/run_tests.py b/clap-tests/run_tests.py index 4f1092e9..558114b2 100755 --- a/clap-tests/run_tests.py +++ b/clap-tests/run_tests.py @@ -18,14 +18,14 @@ FLAGS: -v, --version Prints version information OPTIONS: - -o, --option=option... tests options - --long-option-2=option2 tests long options with exclusions and requirements - -O option3 test options with specific value sets [values: fast slow] + -o, --option=opt... tests options + --long-option-2=option2 tests long options with exclusions + -O option3 tests options with specific value sets [values: fast slow] POSITIONAL ARGUMENTS: positional tests positionals - positional2 tests positionals with exclusions and multiple - positional3... tests positionals with specific value sets [values: emacs vi] + positional2 tests positionals with exclusions + positional3... tests positionals with specific values [values: emacs vi] SUBCOMMANDS: help Prints this message diff --git a/clap-tests/src/main.rs b/clap-tests/src/main.rs index 65cbcd44..d6cdd4bb 100644 --- a/clap-tests/src/main.rs +++ b/clap-tests/src/main.rs @@ -3,6 +3,10 @@ extern crate clap; use clap::{App, Arg, SubCommand}; fn main() { + let args = +"-f --flag... 'tests flags' +-o --option=[opt]... 'tests options' +[positional] 'tests positionals'"; // Test version from Cargo.toml let version = format!("{}.{}.{}{}", env!("CARGO_PKG_VERSION_MAJOR"), @@ -13,45 +17,21 @@ fn main() { .version(&version[..]) .about("tests clap library") .author("Kevin K. ") - .arg(Arg::new("flag") - .short("f") - .long("flag") - .help("tests flags") - .multiple(true)) - .arg(Arg::new("option") - .short("o") - .long("option") - .help("tests options") - .takes_value(true) - .multiple(true)) - .arg(Arg::new("positional") - .index(1) - .help("tests positionals")) + .args_from_usage(args) .args(vec![ - Arg::new("flag2").short("F").mutually_excludes("flag").help("tests flags with exclusions").requires("option2"), - Arg::new("option2").takes_value(true).long("long-option-2").mutually_excludes("option").help("tests long options with exclusions and requirements").requires("positional2"), - Arg::new("positional2").index(2).help("tests positionals with exclusions and multiple"), - Arg::new("option3").takes_value(true).short("O").possible_values(vec!["fast", "slow"]).help("test options with specific value sets"), - Arg::new("positional3").index(3).multiple(true).possible_values(vec!["vi", "emacs"]).help("tests positionals with specific value sets") + Arg::from_usage("[flag2] -F 'tests flags with exclusions'").mutually_excludes("flag").requires("option2"), + Arg::from_usage("--long-option-2 [option2] 'tests long options with exclusions'").mutually_excludes("option").requires("positional2"), + Arg::from_usage("[positional2] 'tests positionals with exclusions'"), + Arg::from_usage("-O [option3] 'tests options with specific value sets'").possible_values(vec!["fast", "slow"]), + Arg::from_usage("[positional3]... 'tests positionals with specific values'").possible_values(vec!["vi", "emacs"]) ]) .subcommand(SubCommand::new("subcmd") .about("tests subcommands") .version("0.1") .author("Kevin K. ") - .arg(Arg::new("scflag") - .short("f") - .long("flag") - .help("tests flags") - .multiple(true)) - .arg(Arg::new("scoption") - .short("o") - .long("option") - .help("tests options") - .takes_value(true) - .multiple(true)) - .arg(Arg::new("scpositional") - .index(1) - .help("tests positionals"))) + .arg_from_usage("[scflag] -f --flag... 'tests flags'") + .arg_from_usage("-o --option [scoption]... 'tests options'") + .arg_from_usage("[scpositional] 'tests positionals'")) .get_matches(); if matches.is_present("flag") { @@ -60,11 +40,11 @@ fn main() { println!("flag NOT present"); } - if matches.is_present("option") { - if let Some(v) = matches.value_of("option") { - println!("option present {} times with value: {}",matches.occurrences_of("option"), v); + if matches.is_present("opt") { + if let Some(v) = matches.value_of("opt") { + println!("option present {} times with value: {}",matches.occurrences_of("opt"), v); } - if let Some(ref ov) = matches.values_of("option") { + if let Some(ref ov) = matches.values_of("opt") { for o in ov { println!("An option: {}", o); } @@ -101,11 +81,11 @@ fn main() { _ => println!("positional3 NOT present") } - if matches.is_present("option") { - if let Some(v) = matches.value_of("option") { - println!("option present {} times with value: {}",matches.occurrences_of("option"), v); + if matches.is_present("opt") { + if let Some(v) = matches.value_of("opt") { + println!("option present {} times with value: {}",matches.occurrences_of("opt"), v); } - if let Some(ref ov) = matches.values_of("option") { + if let Some(ref ov) = matches.values_of("opt") { for o in ov { println!("An option: {}", o); } 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..8025bdcf 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); @@ -360,6 +365,43 @@ impl<'a, 'v, 'ab, 'u, 'ar> App<'a, 'v, 'ab, 'u, 'ar>{ self } + /// Adds an argument from a usage string. See Arg::from_usage() for details + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let app = App::new("myprog") + /// .arg_from_usage("-c --conf= 'Sets a configuration file to use'") + /// # .get_matches(); + /// ``` + pub fn arg_from_usage(mut self, usage: &'ar str) -> App<'a, 'v, 'ab, 'u, 'ar> { + self = self.arg(Arg::from_usage(usage)); + self + } + + /// Adds multiple arguments from a usage string, one per line. See Arg::from_usage() for + /// details + /// + /// # Example + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let app = App::new("myprog") + /// .args_from_usage( + /// "-c --conf=[config] 'Sets a configuration file to use' + /// [debug]... -d 'Sets the debugging level' + /// 'The input file to use'") + /// # .get_matches(); + /// ``` + pub fn args_from_usage(mut self, usage: &'ar str) -> App<'a, 'v, 'ab, 'u, 'ar> { + for l in usage.lines() { + self = self.arg(Arg::from_usage(l)); + } + 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 /// function just like apps, in that they get their own auto generated help and version diff --git a/src/args/arg.rs b/src/args/arg.rs index 57547e0c..811ae720 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,148 @@ 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, + } + } + + /// Creates a new instace of `Arg` using a usage string. Allows creation of basic settings + /// for Arg (i.e. everything except relational rules). The syntax is flexible, but there are + /// some rules to follow. + /// + /// The syntax should be as follows (only properties which you wish to set must be present): + /// + /// 1. Name (arguments with a `long` or that take a value can ommit this if desired), + /// use `[]` for non-required arguments, or `<>` for required arguments. + /// 2. Short preceded by a `-` + /// 3. Long preceded by a `--` (this may be used as the name, if the name is omitted. If the + /// name is *not* omittied, the name takes precedence) + /// 4. Value (this can be used as the name, if the name is not manually specified. If the name + /// is manually specified, it takes precence. If this value is used as the name, it uses the + /// same `[]` and `<>` requirement rules. If it is *not* used as the name, it still needs to + /// be surrounded by either `[]` or `<>` but the effect is the same, as the requirement rule + /// is determined by the name. The value may follow the `short` or `long`. If it + /// follows the `long`, it may follow either a `=` or ` ` with the same effect, personal + /// preference only, but may only follow a ` ` after a `short`) + /// 5. Multiple specifier `...` (for flags or positional arguments the `...` may follow the + /// name or `short` or `long`) + /// 6. The help info surrounded by `'` + /// 7. The index of a positional argument will be the next available index (you don't need to + /// specify one) + /// + /// + /// Example: + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// # let matches = App::new("myprog") + /// .args(vec![ + /// // A option argument with a long, named "conf" (note: because the name was specified + /// // the portion after the long can be called anything, only the first name will be displayed + /// // to the user. Also, requirement is set with the *name*, so the portion after the long could + /// // be either <> or [] and it wouldn't matter, so long as it's one of them. Had the name been + /// // omitted, the name would have been derived from the portion after the long and those rules + /// // would have mattered) + /// Arg::from_usage("[conf] --config=[c] 'a required file for the configuration'"), + /// // A flag with a short, a long, named "debug", and accepts multiple values + /// Arg::from_usage("-d --debug... 'turns on debugging information"), + /// // A required positional argument named "input" + /// Arg::from_usage(" 'the input file to use'") + /// ]) + /// # + /// # .get_matches(); + 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("