Repository: kbknapp/clap-rs
Pull request #1241 (2018-04-04 03:24:30)

V3 dev
Implements #1235 and #1238 on v3

<!-- 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/1241)
<!-- Reviewable:end -->



Patch:
diff --git a/src/app/help.rs b/src/app/help.rs
index ccae8c76..2ed1e0db 100644
--- a/src/app/help.rs
+++ b/src/app/help.rs
@@ -174,16 +174,17 @@ impl<'w> Help<'w> {
 impl<'w> Help<'w> {
     /// Writes help for each argument in the order they were declared to the wrapped stream.
     fn write_args_unsorted<'a, 'b, I>(&mut self, args: I) -> io::Result<()>
-    where
-        'a: 'b,
-        I: Iterator<Item = &'b Arg<'a, 'b>>,
+        where
+            'a: 'b,
+            I: Iterator<Item=&'b Arg<'a, 'b>>,
     {
         debugln!("Help::write_args_unsorted;");
         // The shortest an arg can legally be is 2 (i.e. '-x')
         self.longest = 2;
         let mut arg_v = Vec::with_capacity(10);
+        let use_long = self.use_long;
         for arg in args.filter(|arg| {
-            !(arg.is_set(ArgSettings::Hidden)) || arg.is_set(ArgSettings::NextLineHelp)
+            should_show_arg(use_long, *arg)
         }) {
             if arg.longest_filter() {
                 self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str()));
@@ -207,20 +208,21 @@ impl<'w> Help<'w> {
 
     /// Sorts arguments by length and display order and write their help to the wrapped stream.
     fn write_args<'a, 'b, I>(&mut self, args: I) -> io::Result<()>
-    where
-        'a: 'b,
-        I: Iterator<Item = &'b Arg<'a, 'b>>,
+        where
+            'a: 'b,
+            I: Iterator<Item=&'b Arg<'a, 'b>>,
     {
         debugln!("Help::write_args;");
         // The shortest an arg can legally be is 2 (i.e. '-x')
         self.longest = 2;
         let mut ord_m = VecMap::new();
+        let use_long = self.use_long;
         // Determine the longest
         for arg in args.filter(|arg| {
             // If it's NextLineHelp we don't care to compute how long it is because it may be
             // NextLineHelp on purpose simply *because* it's so long and would throw off all other
             // args alignment
-            !arg.is_set(ArgSettings::Hidden) || arg.is_set(ArgSettings::NextLineHelp)
+            should_show_arg(use_long, *arg)
         }) {
             if arg.longest_filter() {
                 debugln!("Help::write_args: Current Longest...{}", self.longest);
@@ -682,10 +684,10 @@ impl<'w> Help<'w> {
         let flags = parser.has_flags();
         // Strange filter/count vs fold... https://github.com/rust-lang/rust/issues/33038
         let pos = positionals!(parser.app).fold(0, |acc, arg| {
-            if arg.is_set(ArgSettings::Hidden) {
-                acc
-            } else {
+            if should_show_arg(self.use_long, arg) {
                 acc + 1
+            } else {
+                acc
             }
         }) > 0;
         let opts = parser.has_opts();
@@ -1099,6 +1101,17 @@ impl<'w> Help<'w> {
     }
 }
 
+fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
+    debugln!("Help::should_show_arg: use_long={:?}, arg={}", use_long, arg.name);
+    if arg.is_set(ArgSettings::Hidden) {
+        return false;
+    }
+    (!arg.is_set(ArgSettings::HiddenLongHelp) && use_long) ||
+        (!arg.is_set(ArgSettings::HiddenShortHelp) && !use_long) ||
+        arg.is_set(ArgSettings::NextLineHelp)
+}
+
+
 fn wrap_help(help: &str, avail_chars: usize) -> String {
     let wrapper = textwrap::Wrapper::new(avail_chars).break_words(false);
     help.lines()
diff --git a/src/app/parser.rs b/src/app/parser.rs
index 75c6fa5a..1d5bc63e 100644
--- a/src/app/parser.rs
+++ b/src/app/parser.rs
@@ -8,8 +8,8 @@ use std::iter::Peekable;
 use std::mem;
 use std::cell::Cell;
 
-// Third party
-use map::{self, VecMap};
+// Third party facade
+use map::VecMap;
 
 // Internal
 use INTERNAL_ERROR_MSG;
@@ -926,6 +926,51 @@ where
         Ok(())
     }
 
+    fn use_long_help(&self) -> bool {
+        debugln!("Parser::use_long_help;");
+        // In this case, both must be checked. This allows the retention of 
+        // original formatting, but also ensures that the actual -h or --help
+        // specified by the user is sent through. If HiddenShortHelp is not included,
+        // then items specified with hidden_short_help will also be hidden.
+        let should_long = |v: &Arg| {
+            v.long_help.is_some() || 
+            v.is_set(ArgSettings::HiddenLongHelp) || 
+            v.is_set(ArgSettings::HiddenShortHelp) 
+        };
+
+        self.app.long_about.is_some()
+            || args!(self.app).any(|f| should_long(&f))
+            || subcommands!(self.app).any(|s| s.long_about.is_some())
+    }
+    
+//    fn _help(&self, mut use_long: bool) -> ClapError {
+//        debugln!("Parser::_help: use_long={:?}", use_long && self.use_long_help());
+//        use_long = use_long && self.use_long_help();
+//        let mut buf = vec![];
+//        match Help::write_parser_help(&mut buf, self, use_long) {
+//            Err(e) => e,
+//            _ => ClapError {
+//                message: String::from_utf8(buf).unwrap_or_default(),
+//                kind: ErrorKind::HelpDisplayed,
+//                info: None,
+//            },
+//        }
+//    }
+//
+//    fn _version(&self, use_long: bool) -> ClapError {
+//        debugln!("Parser::_version: ");
+//        let out = io::stdout();
+//        let mut buf_w = BufWriter::new(out.lock());
+//        match self.print_version(&mut buf_w, use_long) {
+//            Err(e) => e,
+//            _ => ClapError {
+//                message: String::new(),
+//                kind: ErrorKind::VersionDisplayed,
+//                info: None,
+//            },
+//        }
+//    }
+
     fn parse_long_arg(
         &mut self,
         matcher: &mut ArgMatcher<'a>,
@@ -1464,8 +1509,8 @@ where
     }
 
     fn help_err(&self, mut use_long: bool) -> ClapError {
-        debugln!("Parser::_help: use_long={:?}", use_long);
-        use_long = use_long && self.app.use_long_help();
+        debugln!("Parser::help_err: use_long={:?}", use_long && self.use_long_help());
+        use_long = use_long && self.use_long_help();
         let mut buf = vec![];
         match Help::write_parser_help(&mut buf, self, use_long) {
             Err(e) => e,
@@ -1478,7 +1523,7 @@ where
     }
 
     fn version_err(&self, use_long: bool) -> ClapError {
-        debugln!("Parser::_version: ");
+        debugln!("Parser::version_err: ");
         let out = io::stdout();
         let mut buf_w = BufWriter::new(out.lock());
         match self.print_version(&mut buf_w, use_long) {
diff --git a/src/args/arg.rs b/src/args/arg.rs
index bc145130..57e6c7bc 100644
--- a/src/args/arg.rs
+++ b/src/args/arg.rs
@@ -10,6 +10,7 @@ use osstringext::OsStrExt3;
 use std::os::unix::ffi::OsStrExt;
 use std::env;
 use std::cmp::{Ord, Ordering};
+use std::str;
 
 #[cfg(feature = "yaml")]
 use yaml_rust::Yaml;
@@ -3769,6 +3770,160 @@ impl<'a, 'b> Arg<'a, 'b> {
         self.multiple(raw).allow_hyphen_values(raw).last(raw)
     }
 
+    /// Hides an argument from short help message output.
+    ///
+    /// **NOTE:** This does **not** hide the argument from usage strings on error
+    ///
+    /// **NOTE:** Setting this option will cause next-line-help output style to be used
+    /// when long help (`--help`) is called.
+    /// 
+    /// # Examples
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// Arg::with_name("debug")
+    ///     .hidden_short_help(true)
+    /// # ;
+    /// ```
+    /// Setting `hidden_short_help(true)` will hide the argument when displaying short help text
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// let m = App::new("prog")
+    ///     .arg(Arg::with_name("cfg")
+    ///         .long("config")
+    ///         .hidden_short_help(true)
+    ///         .help("Some help text describing the --config arg"))
+    ///     .get_matches_from(vec![
+    ///         "prog", "-h"
+    ///     ]);
+    /// ```
+    ///
+    /// The above example displays
+    ///
+    /// ```notrust
+    /// helptest
+    ///
+    /// USAGE:
+    ///    helptest [FLAGS]
+    ///
+    /// FLAGS:
+    /// -h, --help       Prints help information
+    /// -V, --version    Prints version information
+    /// ```
+    /// 
+    /// However, when --help is called
+    /// 
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// let m = App::new("prog")
+    ///     .arg(Arg::with_name("cfg")
+    ///         .long("config")
+    ///         .hidden_short_help(true)
+    ///         .help("Some help text describing the --config arg"))
+    ///     .get_matches_from(vec![
+    ///         "prog", "--help"
+    ///     ]);
+    /// ```
+    /// 
+    /// Then the following would be displayed
+    /// 
+    /// ```notrust
+    /// helptest
+    /// 
+    /// USAGE:
+    ///    helptest [FLAGS]
+    /// 
+    /// FLAGS:
+    ///     --config     Some help text describing the --config arg
+    /// -h, --help       Prints help information
+    /// -V, --version    Prints version information
+    /// ```
+    pub fn hidden_short_help(self, hide: bool) -> Self {
+        if hide { 
+            self.set(ArgSettings::HiddenShortHelp)
+        } else { 
+            self.unset(ArgSettings::HiddenShortHelp)
+        }
+    }
+
+    /// Hides an argument from long help message output.
+    ///
+    /// **NOTE:** This does **not** hide the argument from usage strings on error
+    ///
+    /// **NOTE:** Setting this option will cause next-line-help output style to be used
+    /// when long help (`--help`) is called.
+    /// 
+    /// # Examples
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// Arg::with_name("debug")
+    ///     .hidden_long_help(true)
+    /// # ;
+    /// ```
+    /// Setting `hidden_long_help(true)` will hide the argument when displaying long help text
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// let m = App::new("prog")
+    ///     .arg(Arg::with_name("cfg")
+    ///         .long("config")
+    ///         .hidden_long_help(true)
+    ///         .help("Some help text describing the --config arg"))
+    ///     .get_matches_from(vec![
+    ///         "prog", "--help"
+    ///     ]);
+    /// ```
+    ///
+    /// The above example displays
+    ///
+    /// ```notrust
+    /// helptest
+    ///
+    /// USAGE:
+    ///    helptest [FLAGS]
+    ///
+    /// FLAGS:
+    /// -h, --help       Prints help information
+    /// -V, --version    Prints version information
+    /// ```
+    /// 
+    /// However, when -h is called
+    /// 
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// let m = App::new("prog")
+    ///     .arg(Arg::with_name("cfg")
+    ///         .long("config")
+    ///         .hidden_long_help(true)
+    ///         .help("Some help text describing the --config arg"))
+    ///     .get_matches_from(vec![
+    ///         "prog", "-h"
+    ///     ]);
+    /// ```
+    /// 
+    /// Then the following would be displayed
+    /// 
+    /// ```notrust
+    /// helptest
+    /// 
+    /// USAGE:
+    ///    helptest [FLAGS]
+    /// 
+    /// FLAGS:
+    ///     --config     Some help text describing the --config arg
+    /// -h, --help       Prints help information
+    /// -V, --version    Prints version information
+    /// ```
+    pub fn hidden_long_help(self, hide: bool) -> Self {
+        if hide {
+            self.set(ArgSettings::HiddenLongHelp)
+        } else {
+            self.unset(ArgSettings::HiddenLongHelp)
+        }
+    }
+
     // @TODO @docs @v3-beta: write better docs as ArgSettings is now critical
     /// Checks if one of the [`ArgSettings`] is set for the argument
     /// [`ArgSettings`]: ./enum.ArgSettings.html
@@ -4096,7 +4251,7 @@ mod test {
         f.long = Some("flag");
         f.aliases = Some(vec![("als", true)]);
 
-        assert_eq!(&*format!("{}", f), "--flag");
+        assert_eq!(&*format!("{}", f), "--flag")
     }
 
     #[test]
diff --git a/src/args/settings.rs b/src/args/settings.rs
index f2b11456..8c04b2a9 100644
--- a/src/args/settings.rs
+++ b/src/args/settings.rs
@@ -23,7 +23,9 @@ bitflags! {
         const HIDE_DEFAULT_VAL = 1 << 15 | Self::TAKES_VAL.bits;
         const CASE_INSENSITIVE = 1 << 16;
         const HIDE_ENV_VALS    = 1 << 17;
-        const MULTIPLE_VALS    = 1 << 18 | Self::TAKES_VAL.bits;
+        const HIDDEN_SHORT_H   = 1 << 18;
+        const HIDDEN_LONG_H    = 1 << 19;
+        const MULTIPLE_VALS    = 1 << 20 | Self::TAKES_VAL.bits;
     }
 }
 
@@ -54,7 +56,9 @@ impl ArgFlags {
         Last => Flags::LAST,
         IgnoreCase => Flags::CASE_INSENSITIVE,
         HideEnvValues => Flags::HIDE_ENV_VALS,
-        HideDefaultValue => Flags::HIDE_DEFAULT_VAL
+        HideDefaultValue => Flags::HIDE_DEFAULT_VAL,
+        HiddenShortHelp => Flags::HIDDEN_SHORT_H,
+        HiddenLongHelp => Flags::HIDDEN_LONG_H
     }
 }
 
@@ -111,6 +115,10 @@ pub enum ArgSettings {
     /// Hides any values currently assigned to ENV variables in the help message (good for sensitive
     /// information)
     HideEnvValues,
+    /// The argument should **not** be shown in short help text
+    HiddenShortHelp,
+    /// The argument should **not** be shown in long help text
+    HiddenLongHelp,
     #[doc(hidden)] RequiredUnlessAll,
     #[doc(hidden)] ValueDelimiterNotSet,
 }
@@ -136,6 +144,8 @@ impl FromStr for ArgSettings {
             "hidedefaultvalue" => Ok(ArgSettings::HideDefaultValue),
             "ignorecase" => Ok(ArgSettings::IgnoreCase),
             "hideenvvalues" => Ok(ArgSettings::HideEnvValues),
+            "hiddenshorthelp" => Ok(ArgSettings::HiddenShortHelp),
+            "hiddenlonghelp" => Ok(ArgSettings::HiddenLongHelp),
             _ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
         }
     }
@@ -212,6 +222,14 @@ mod test {
             "hideenvvalues".parse::<ArgSettings>().unwrap(),
             ArgSettings::HideEnvValues
         );
+        assert_eq!(
+            "hiddenshorthelp".parse::<ArgSettings>().unwrap(),
+            ArgSettings::HiddenShortHelp
+        );
+        assert_eq!(
+            "hiddenlonghelp".parse::<ArgSettings>().unwrap(),
+            ArgSettings::HiddenLongHelp
+        );
         assert!("hahahaha".parse::<ArgSettings>().is_err());
     }
 }
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)* }
     };


Test patch:
diff --git a/tests/hidden_args.rs b/tests/hidden_args.rs
index a57eccd2..88dd0cd0 100644
--- a/tests/hidden_args.rs
+++ b/tests/hidden_args.rs
@@ -30,7 +30,151 @@ fn hidden_args() {
             Arg::from_usage("-f, --flag 'some flag'").hidden(true),
             Arg::from_usage("-F, --flag2 'some other flag'"),
             Arg::from_usage("--option [opt] 'some option'"),
-            Arg::with_name("DUMMY").required(false).hidden(true),
+            Arg::with_name("DUMMY").hidden(true),
         ]);
     assert!(test::compare_output(app, "test --help", HIDDEN_ARGS, false));
 }
+
+static HIDDEN_SHORT_ARGS: &'static str = "test 2.31.2
+Steve P.
+hides short args
+
+USAGE:
+    test [FLAGS]
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+    -v, --visible    This text should be visible";
+
+static HIDDEN_SHORT_ARGS_LONG_HELP: &'static str = "test 2.31.2
+Steve P.
+hides short args
+
+USAGE:
+    test [FLAGS]
+
+FLAGS:
+    -c, --config     
+            Some help text describing the --config arg
+
+    -h, --help       
+            Prints help information
+
+    -V, --version    
+            Prints version information
+
+    -v, --visible    
+            This text should be visible";
+
+/// Ensure hidden with short option
+#[test]
+fn hidden_short_args() {
+    let app = App::new("test")
+        .about("hides short args")
+        .author("Steve P.")
+        .version("2.31.2")
+        .args(&[
+            Arg::with_name("cfg")
+                .short("c")
+                .long("config")
+                .hidden_short_help(true)
+                .help("Some help text describing the --config arg"),                
+            Arg::with_name("visible")
+                .short("v")
+                .long("visible")
+                .help("This text should be visible")]);
+
+    assert!(test::compare_output(app, "test -h", HIDDEN_SHORT_ARGS, false));        
+}
+
+/// Ensure visible with opposite option
+#[test]
+fn hidden_short_args_long_help() {
+    let app = App::new("test")
+        .about("hides short args")
+        .author("Steve P.")
+        .version("2.31.2")
+        .args(&[
+            Arg::with_name("cfg")
+                .short("c")
+                .long("config")
+                .hidden_short_help(true)
+                .help("Some help text describing the --config arg"),                
+            Arg::with_name("visible")
+                .short("v")
+                .long("visible")
+                .help("This text should be visible")]);
+
+    assert!(test::compare_output(app, "test --help", HIDDEN_SHORT_ARGS_LONG_HELP, false));        
+}
+
+static HIDDEN_LONG_ARGS: &'static str = "test 2.31.2
+Steve P.
+hides long args
+
+USAGE:
+    test [FLAGS]
+
+FLAGS:
+    -h, --help       
+            Prints help information
+
+    -V, --version    
+            Prints version information
+
+    -v, --visible    
+            This text should be visible";
+
+#[test]
+fn hidden_long_args() {
+    let app = App::new("test")
+        .about("hides long args")
+        .author("Steve P.")
+        .version("2.31.2")
+        .args(&[
+            Arg::with_name("cfg")
+                .short("c")
+                .long("config")
+                .hidden_long_help(true)
+                .help("Some help text describing the --config arg"),                
+            Arg::with_name("visible")
+                .short("v")
+                .long("visible")
+                .help("This text should be visible")]);
+
+    assert!(test::compare_output(app, "test --help", HIDDEN_LONG_ARGS, false));        
+}
+
+static HIDDEN_LONG_ARGS_SHORT_HELP: &'static str = "test 2.31.2
+Steve P.
+hides long args
+
+USAGE:
+    test [FLAGS]
+
+FLAGS:
+    -c, --config     Some help text describing the --config arg
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+    -v, --visible    This text should be visible";
+
+#[test]
+fn hidden_long_args_short_help() {
+    let app = App::new("test")
+        .about("hides long args")
+        .author("Steve P.")
+        .version("2.31.2")
+        .args(&[
+            Arg::with_name("cfg")
+                .short("c")
+                .long("config")
+                .hidden_long_help(true)
+                .help("Some help text describing the --config arg"),                
+            Arg::with_name("visible")
+                .short("v")
+                .long("visible")
+                .help("This text should be visible")]);
+
+    assert!(test::compare_output(app, "test -h", HIDDEN_LONG_ARGS_SHORT_HELP, false));        
+}
\ No newline at end of file
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. <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!{