Repository: clap-rs/clap
Pull request #2175 (2020-10-17 17:10:26)

Add option to hide env keys from help text
This PR adds the option to hide the automatic env key (and/or value) section from the help text. This would be useful if the key is explained already and more explicitly in the defined help text.

This arose for the above reasons when adding env var parsing to bat, in https://github.com/sharkdp/bat/pull/1281.


Patch:
diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs
index 1c555444..1432aa33 100644
--- a/src/build/arg/mod.rs
+++ b/src/build/arg/mod.rs
@@ -3565,6 +3565,42 @@ impl<'help> Arg<'help> {
         }
     }
 
+    /// Specifies that environment variable arguments should not be displayed in the help text.
+    ///
+    /// This is useful when the variable option is explained elsewhere in the help text.
+    ///
+    /// **NOTE:** Setting this implies [`ArgSettings::TakesValue`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg, ArgSettings};
+    /// Arg::new("config")
+    ///     .setting(ArgSettings::HideEnv)
+    /// # ;
+    /// ```
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg, ArgSettings};
+    /// let m = App::new("prog")
+    ///     .arg(Arg::new("mode")
+    ///         .long("mode")
+    ///         .env("MODE")
+    ///         .setting(ArgSettings::HideEnv));
+    ///
+    /// ```
+    ///
+    /// If we were to run the above program with `--help` the `[env: MODE]` portion of the help
+    /// text would be omitted.
+    #[inline]
+    pub fn hide_env(self, hide: bool) -> Self {
+        if hide {
+            self.setting(ArgSettings::HideEnv)
+        } else {
+            self.unset_setting(ArgSettings::HideEnv)
+        }
+    }
+
     /// Specifies that any values inside the associated ENV variables of an argument should not be
     /// displayed in the help text.
     ///
diff --git a/src/build/arg/settings.rs b/src/build/arg/settings.rs
index 4fce6926..6b722b88 100644
--- a/src/build/arg/settings.rs
+++ b/src/build/arg/settings.rs
@@ -27,6 +27,7 @@ bitflags! {
         const HIDDEN_SHORT_H   = 1 << 18;
         const HIDDEN_LONG_H    = 1 << 19;
         const MULTIPLE_VALS    = 1 << 20 | Self::TAKES_VAL.bits;
+        const HIDE_ENV         = 1 << 21 | Self::TAKES_VAL.bits;
     }
 }
 
@@ -50,6 +51,7 @@ impl_settings! { ArgSettings, ArgFlags,
     RequireEquals("requireequals") => Flags::REQUIRE_EQUALS,
     Last("last") => Flags::LAST,
     IgnoreCase("ignorecase") => Flags::CASE_INSENSITIVE,
+    HideEnv("hideenv") => Flags::HIDE_ENV,
     HideEnvValues("hideenvvalues") => Flags::HIDE_ENV_VALS,
     HideDefaultValue("hidedefaultvalue") => Flags::HIDE_DEFAULT_VAL,
     HiddenShortHelp("hiddenshorthelp") => Flags::HIDDEN_SHORT_H,
@@ -104,6 +106,8 @@ pub enum ArgSettings {
     HideDefaultValue,
     /// Possible values become case insensitive
     IgnoreCase,
+    /// Hides environment variable arguments from the help message
+    HideEnv,
     /// Hides any values currently assigned to ENV variables in the help message (good for sensitive
     /// information)
     HideEnvValues,
@@ -174,6 +178,10 @@ mod test {
             "ignorecase".parse::<ArgSettings>().unwrap(),
             ArgSettings::IgnoreCase
         );
+        assert_eq!(
+            "hideenv".parse::<ArgSettings>().unwrap(),
+            ArgSettings::HideEnv
+        );
         assert_eq!(
             "hideenvvalues".parse::<ArgSettings>().unwrap(),
             ArgSettings::HideEnvValues
diff --git a/src/output/help.rs b/src/output/help.rs
index 8378f1dd..fc065e5c 100644
--- a/src/output/help.rs
+++ b/src/output/help.rs
@@ -492,22 +492,24 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
         debug!("Help::spec_vals: a={}", a);
         let mut spec_vals = vec![];
         if let Some(ref env) = a.env {
-            debug!(
-                "Help::spec_vals: Found environment variable...[{:?}:{:?}]",
-                env.0, env.1
-            );
-            let env_val = if !a.is_set(ArgSettings::HideEnvValues) {
-                format!(
-                    "={}",
-                    env.1
-                        .as_ref()
-                        .map_or(Cow::Borrowed(""), |val| val.to_string_lossy())
-                )
-            } else {
-                String::new()
-            };
-            let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
-            spec_vals.push(env_info);
+            if !a.is_set(ArgSettings::HideEnv) {
+                debug!(
+                    "Help::spec_vals: Found environment variable...[{:?}:{:?}]",
+                    env.0, env.1
+                );
+                let env_val = if !a.is_set(ArgSettings::HideEnvValues) {
+                    format!(
+                        "={}",
+                        env.1
+                            .as_ref()
+                            .map_or(Cow::Borrowed(""), |val| val.to_string_lossy())
+                    )
+                } else {
+                    String::new()
+                };
+                let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
+                spec_vals.push(env_info);
+            }
         }
         if !a.is_set(ArgSettings::HideDefaultValue) && !a.default_vals.is_empty() {
             debug!(


Test patch:
diff --git a/tests/help.rs b/tests/help.rs
index e18c1a15..f93ad45e 100644
--- a/tests/help.rs
+++ b/tests/help.rs
@@ -545,6 +545,30 @@ FLAGS:
     -V, --version
             Prints version information";
 
+static HIDE_ENV: &str = "ctest 0.1
+
+USAGE:
+    ctest [OPTIONS]
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+OPTIONS:
+    -c, --cafe <FILE>    A coffeehouse, coffee shop, or café.";
+
+static SHOW_ENV: &str = "ctest 0.1
+
+USAGE:
+    ctest [OPTIONS]
+
+FLAGS:
+    -h, --help       Prints help information
+    -V, --version    Prints version information
+
+OPTIONS:
+    -c, --cafe <FILE>    A coffeehouse, coffee shop, or café. [env: ENVVAR=MYVAL]";
+
 static HIDE_ENV_VALS: &str = "ctest 0.1
 
 USAGE:
@@ -1492,6 +1516,42 @@ fn issue_1052_require_delim_help() {
     ));
 }
 
+#[test]
+fn hide_env() {
+    use std::env;
+
+    env::set_var("ENVVAR", "MYVAL");
+    let app = App::new("ctest").version("0.1").arg(
+        Arg::new("cafe")
+            .short('c')
+            .long("cafe")
+            .value_name("FILE")
+            .hide_env(true)
+            .env("ENVVAR")
+            .about("A coffeehouse, coffee shop, or café.")
+            .takes_value(true),
+    );
+    assert!(utils::compare_output(app, "ctest --help", HIDE_ENV, false));
+}
+
+#[test]
+fn show_env() {
+    use std::env;
+
+    env::set_var("ENVVAR", "MYVAL");
+    let app = App::new("ctest").version("0.1").arg(
+        Arg::new("cafe")
+            .short('c')
+            .long("cafe")
+            .value_name("FILE")
+            .hide_env(false)
+            .env("ENVVAR")
+            .about("A coffeehouse, coffee shop, or café.")
+            .takes_value(true),
+    );
+    assert!(utils::compare_output(app, "ctest --help", SHOW_ENV, false));
+}
+
 #[test]
 fn hide_env_vals() {
     use std::env;