Repository: kbknapp/clap-rs
Pull request #1238 (2018-03-30 20:56:11)

imp(macros): Support shorthand syntax for ArgGroups
Fixes https://github.com/kbknapp/clap-rs/issues/1231.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/kbknapp/clap-rs/1238)
<!-- Reviewable:end -->


mention-bot: @LegNeato, thanks for your PR! By analyzing the history of the files in this pull request, we identified @kbknapp to be a potential reviewer.

Patch:
diff --git a/src/macros.rs b/src/macros.rs
index 97aedb94..e55554b8 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)* }
     };


Test patch:
diff --git a/tests/macros.rs b/tests/macros.rs
index 97c421d6..fcd62c6f 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 =>
@@ -148,6 +150,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. <kbknapp@gmail.com>")
+             (@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. <kbknapp@gmail.com>")
+             (@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. <kbknapp@gmail.com>")
+             (@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. <kbknapp@gmail.com>")
+             (@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. <kbknapp@gmail.com>")
+             (@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!{