diff --git a/src/macros.rs b/src/macros.rs index ed30178d..2712d13d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -626,6 +626,12 @@ macro_rules! app_from_crate { /// `Arg::conflicts_with("FOO")`, `Arg::conflicts_with("BAR")`, and `Arg::conflicts_with("BAZ")` /// (note the lack of quotes around the values in the macro) /// +/// # Shorthand Syntax for Groups +/// +/// * There are short hand syntaxes for `ArgGroup` methods that accept booleans +/// * A plus sign will set that method to `true` such as `+required` = `ArgGroup::required(true)` +/// * An exclamation will set that method to `false` such as `!required` = `ArgGroup::required(false)` +/// /// [`Arg::short`]: ./struct.Arg.html#method.short /// [`Arg::long`]: ./struct.Arg.html#method.long /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple @@ -657,7 +663,7 @@ macro_rules! clap_app { $($tt)* } }; -// Treat the application builder as an argument to set it's attributes +// Treat the application builder as an argument to set its attributes (@app ($builder:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => { clap_app!{ @app (clap_app!{ @arg ($builder) $($attr)* }) $($tt)* } }; @@ -667,6 +673,18 @@ macro_rules! clap_app { $($tt)* } }; + (@app ($builder:expr) (@group $name:ident !$ident:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + (clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name)).$ident(false)) $($tail)* }) + $($tt)* + } + }; + (@app ($builder:expr) (@group $name:ident +$ident:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + (clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name)).$ident(true)) $($tail)* }) + $($tt)* + } + }; // Handle subcommand creation (@app ($builder:expr) (@subcommand $name:ident => $($tail:tt)*) $($tt:tt)*) => { clap_app!{ @app @@ -687,6 +705,7 @@ macro_rules! clap_app { // Add members to group and continue argument handling with the parent builder (@group ($builder:expr, $group:expr)) => { $builder.group($group) }; + // Treat the group builder as an argument to set its attributes (@group ($builder:expr, $group:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => { clap_app!{ @group ($builder, clap_app!{ @arg ($group) (-) $($attr)* }) $($tt)* } }; diff --git a/tests/macros.rs b/tests/macros.rs index 5822986a..948de6c1 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate clap; +use clap::ErrorKind; + #[test] fn basic() { clap_app!(claptests => @@ -151,6 +153,105 @@ fn quoted_arg_name() { assert!(matches.is_present("option2")); } +#[test] +fn group_macro() { + let app = clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@group difficulty => + (@arg hard: -h --hard "Sets hard mode") + (@arg normal: -n --normal "Sets normal mode") + (@arg easy: -e --easy "Sets easy mode") + ) + ); + + let result = app.get_matches_from_safe(vec!["bin_name", "--hard"]); + assert!(result.is_ok()); + let matches = result.expect("Expected to successfully match the given args."); + assert!(matches.is_present("difficulty")); + assert!(matches.is_present("hard")); +} + +#[test] +fn group_macro_set_multiple() { + let app = clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@group difficulty +multiple => + (@arg hard: -h --hard "Sets hard mode") + (@arg normal: -n --normal "Sets normal mode") + (@arg easy: -e --easy "Sets easy mode") + ) + ); + + let result = app.get_matches_from_safe(vec!["bin_name", "--hard", "--easy"]); + assert!(result.is_ok()); + let matches = result.expect("Expected to successfully match the given args."); + assert!(matches.is_present("difficulty")); + assert!(matches.is_present("hard")); + assert!(matches.is_present("easy")); + assert!(!matches.is_present("normal")); +} + +#[test] +fn group_macro_set_not_multiple() { + let app = clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@group difficulty !multiple => + (@arg hard: -h --hard "Sets hard mode") + (@arg normal: -n --normal "Sets normal mode") + (@arg easy: -e --easy "Sets easy mode") + ) + ); + + let result = app.get_matches_from_safe(vec!["bin_name", "--hard", "--easy"]); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.kind, ErrorKind::ArgumentConflict); +} + +#[test] +fn group_macro_set_required() { + let app = clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@group difficulty +required => + (@arg hard: -h --hard "Sets hard mode") + (@arg normal: -n --normal "Sets normal mode") + (@arg easy: -e --easy "Sets easy mode") + ) + ); + + let result = app.get_matches_from_safe(vec!["bin_name"]); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.kind, ErrorKind::MissingRequiredArgument); +} + +#[test] +fn group_macro_set_not_required() { + let app = clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@group difficulty !required => + (@arg hard: -h --hard "Sets hard mode") + (@arg normal: -n --normal "Sets normal mode") + (@arg easy: -e --easy "Sets easy mode") + ) + ); + + let result = app.get_matches_from_safe(vec!["bin_name"]); + assert!(result.is_ok()); + let matches = result.expect("Expected to successfully match the given args."); + assert!(!matches.is_present("difficulty")); +} + #[test] fn arg_enum() { arg_enum!{