Repository: kbknapp/clap-rs
Pull request #1235 (2018-03-28 19:59:28)

Add option to hide help based on long/short
Relates to #1064 

I've added the enum variants and related setting methods to provide the functionality. However, the usage of [Parser::use_long_help()](https://github.com/kbknapp/clap-rs/blob/master/src/app/parser.rs#L1501) is causing issues. 

It seems that the linked method will not allow `use_long` to be `true` unless there is actually a long help value defined for the `Arg`. However, I noticed in [help.rs](https://github.com/kbknapp/clap-rs/blob/master/src/app/help.rs#L445) that we would choose to use the desired long or short help based on what was specified during the run and the extra logic does not seem like it is really needed. However when I bypassed the code in `parser.rs`, there were some failing tests.

@kbknapp do you have any suggestions?

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



Patch:
diff --git a/src/app/help.rs b/src/app/help.rs
index 3ec2165a..78b3d44f 100644
--- a/src/app/help.rs
+++ b/src/app/help.rs
@@ -202,8 +202,9 @@ impl<'a> Help<'a> {
         // 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()));
@@ -231,12 +232,13 @@ impl<'a> Help<'a> {
         // 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, but we don't care to compute how long because it may be
             // NextLineHelp on purpose *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);
@@ -565,6 +567,15 @@ impl<'a> Help<'a> {
     }
 }
 
+fn should_show_arg(use_long: bool, arg: &ArgWithOrder) -> bool {
+    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)
+}
 
 // Methods to write Parser help.
 impl<'a> Help<'a> {
diff --git a/src/app/parser.rs b/src/app/parser.rs
index 55758611..903d0e67 100644
--- a/src/app/parser.rs
+++ b/src/app/parser.rs
@@ -1499,14 +1499,25 @@ where
     }
 
     fn use_long_help(&self) -> bool {
-        self.meta.long_about.is_some() || self.flags.iter().any(|f| f.b.long_help.is_some())
-            || self.opts.iter().any(|o| o.b.long_help.is_some())
-            || self.positionals.values().any(|p| p.b.long_help.is_some())
+        // 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: &Base| { 
+            v.long_help.is_some() || 
+            v.is_set(ArgSettings::HiddenLongHelp) || 
+            v.is_set(ArgSettings::HiddenShortHelp) 
+        };
+
+        self.meta.long_about.is_some() 
+            || self.flags.iter().any(|f| should_long(&f.b))
+            || self.opts.iter().any(|o| should_long(&o.b))
+            || self.positionals.values().any(|p| should_long(&p.b))
             || self.subcommands
                 .iter()
                 .any(|s| s.p.meta.long_about.is_some())
     }
-
+    
     fn _help(&self, mut use_long: bool) -> Error {
         debugln!("Parser::_help: use_long={:?}", use_long);
         use_long = use_long && self.use_long_help();
diff --git a/src/args/arg.rs b/src/args/arg.rs
index 7829f29c..7c020bf1 100644
--- a/src/args/arg.rs
+++ b/src/args/arg.rs
@@ -2299,6 +2299,9 @@ impl<'a, 'b> Arg<'a, 'b> {
 
     /// Hides an argument from help message output.
     ///
+    /// **NOTE:** Implicitly sets [`Arg::hidden_short_help(true)`] and [`Arg::hidden_long_help(true)`]
+    /// when set to true
+    /// 
     /// **NOTE:** This does **not** hide the argument from usage strings on error
     ///
     /// # Examples
@@ -2335,6 +2338,8 @@ impl<'a, 'b> Arg<'a, 'b> {
     /// -h, --help       Prints help information
     /// -V, --version    Prints version information
     /// ```
+    /// [`Arg::hidden_short_help(true)`]: ./struct.Arg.html#method.hidden_short_help
+    /// [`Arg::hidden_long_help(true)`]: ./struct.Arg.html#method.hidden_long_help
     pub fn hidden(self, h: bool) -> Self {
         if h {
             self.set(ArgSettings::Hidden)
@@ -3735,6 +3740,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)
+        }
+    }
+
     /// Checks if one of the [`ArgSettings`] settings is set for the argument
     /// [`ArgSettings`]: ./enum.ArgSettings.html
     pub fn is_set(&self, s: ArgSettings) -> bool {
diff --git a/src/args/settings.rs b/src/args/settings.rs
index 72f91d3f..f5e3e376 100644
--- a/src/args/settings.rs
+++ b/src/args/settings.rs
@@ -23,6 +23,8 @@ bitflags! {
         const HIDE_DEFAULT_VAL = 1 << 15;
         const CASE_INSENSITIVE = 1 << 16;
         const HIDE_ENV_VALS    = 1 << 17;
+        const HIDDEN_SHORT_H   = 1 << 18;
+        const HIDDEN_LONG_H    = 1 << 19;
     }
 }
 
@@ -51,7 +53,9 @@ impl ArgFlags {
         Last => Flags::LAST,
         CaseInsensitive => 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
     }
 }
 
@@ -101,6 +105,10 @@ pub enum ArgSettings {
     CaseInsensitive,
     /// Hides ENV values in the help message
     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,
 }
@@ -127,6 +135,8 @@ impl FromStr for ArgSettings {
             "hidedefaultvalue" => Ok(ArgSettings::HideDefaultValue),
             "caseinsensitive" => Ok(ArgSettings::CaseInsensitive),
             "hideenvvalues" => Ok(ArgSettings::HideEnvValues),
+            "hiddenshorthelp" => Ok(ArgSettings::HiddenShortHelp),
+            "hiddenlonghelp" => Ok(ArgSettings::HiddenLongHelp),
             _ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
         }
     }
@@ -207,6 +217,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());
     }
 }


Test patch:
diff --git a/tests/hidden_args.rs b/tests/hidden_args.rs
index cbc9225b..bf6a2234 100644
--- a/tests/hidden_args.rs
+++ b/tests/hidden_args.rs
@@ -32,3 +32,147 @@ fn hidden_args() {
                     Arg::with_name("DUMMY").required(false).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