Repository: google/argh (rust)

Issue:
Allow positional arguments to be omitted from the help text
I have been using argh to develop a new cargo command. Cargo passes the actual command being executed as the first command line argument. In order to capture (and ignore) that argument, I have an extra positional argument in the structure, followed by the *real* positional arguments. However, this first argument shows up in the command line help and is quite confusing as users should not actually enter it. It would be good to have a flag that omits a positional argument (and possibly others) from the generated help text.

Pull request description:
Add support for an `hidden_help` attribute 
This PR allows hiding the help for some of the options.

It is based on #75, which is updated to match the latest changes.

Closes #73
Closes #75



Patch:
diff --git a/argh/src/lib.rs b/argh/src/lib.rs
index c507be0..9e955da 100644
--- a/argh/src/lib.rs
+++ b/argh/src/lib.rs
@@ -290,6 +290,29 @@
 //!     }
 //! }
 //! ```
+//!
+//! Programs that are run from an environment such as cargo may find it
+//! useful to have positional arguments present in the structure but
+//! omitted from the usage output. This can be accomplished by adding
+//! the `hidden_help` attribute to that argument:
+//!
+//! ```rust
+//! # use argh::FromArgs;
+//!
+//! #[derive(FromArgs)]
+//! /// Cargo arguments
+//! struct CargoArgs {
+//!     // Cargo puts the command name invoked into the first argument,
+//!     // so we don't want this argument to show up in the usage text.
+//!     #[argh(positional, hidden_help)]
+//!     command: String,
+//!     /// an option used for internal debugging
+//!     #[argh(option, hidden_help)]
+//!     internal_debugging: String,
+//!     #[argh(positional)]
+//!     real_first_arg: String,
+//! }
+//! ```
 
 #![deny(missing_docs)]
 
diff --git a/argh_derive/src/help.rs b/argh_derive/src/help.rs
index c9daad5..9f8bbe7 100644
--- a/argh_derive/src/help.rs
+++ b/argh_derive/src/help.rs
@@ -30,8 +30,9 @@ pub(crate) fn help(
     #![allow(clippy::format_push_string)]
     let mut format_lit = "Usage: {command_name}".to_string();
 
-    let positional =
-        fields.iter().filter(|f| f.kind == FieldKind::Positional && f.attrs.greedy.is_none());
+    let positional = fields.iter().filter(|f| {
+        f.kind == FieldKind::Positional && f.attrs.greedy.is_none() && !f.attrs.hidden_help
+    });
     let mut has_positional = false;
     for arg in positional.clone() {
         has_positional = true;
@@ -39,14 +40,15 @@ pub(crate) fn help(
         positional_usage(&mut format_lit, arg);
     }
 
-    let options = fields.iter().filter(|f| f.long_name.is_some());
+    let options = fields.iter().filter(|f| f.long_name.is_some() && !f.attrs.hidden_help);
     for option in options.clone() {
         format_lit.push(' ');
         option_usage(&mut format_lit, option);
     }
 
-    let remain =
-        fields.iter().filter(|f| f.kind == FieldKind::Positional && f.attrs.greedy.is_some());
+    let remain = fields.iter().filter(|f| {
+        f.kind == FieldKind::Positional && f.attrs.greedy.is_some() && !f.attrs.hidden_help
+    });
     for arg in remain {
         format_lit.push(' ');
         positional_usage(&mut format_lit, arg);
diff --git a/argh_derive/src/parse_attrs.rs b/argh_derive/src/parse_attrs.rs
index f5fa841..0d1cca3 100644
--- a/argh_derive/src/parse_attrs.rs
+++ b/argh_derive/src/parse_attrs.rs
@@ -19,6 +19,7 @@ pub struct FieldAttrs {
     pub short: Option<syn::LitChar>,
     pub arg_name: Option<syn::LitStr>,
     pub greedy: Option<syn::Path>,
+    pub hidden_help: bool,
 }
 
 /// The purpose of a particular field on a `#![derive(FromArgs)]` struct.
@@ -123,13 +124,15 @@ impl FieldAttrs {
                     );
                 } else if name.is_ident("greedy") {
                     this.greedy = Some(name.clone());
+                } else if name.is_ident("hidden_help") {
+                    this.hidden_help = true;
                 } else {
                     errors.err(
                         &meta,
                         concat!(
                             "Invalid field-level `argh` attribute\n",
                             "Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ",
-                            "`long`, `option`, `short`, `subcommand`, `switch`",
+                            "`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`",
                         ),
                     );
                 }


Test patch:
diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs
index 584897c..522a513 100644
--- a/argh/tests/lib.rs
+++ b/argh/tests/lib.rs
@@ -1168,6 +1168,36 @@ Destroy the contents of <file>.
 Positional Arguments:
   name
 
+Options:
+  --help            display usage information
+"###,
+        );
+    }
+
+    #[test]
+    fn hidden_help_attribute() {
+        #[derive(FromArgs)]
+        /// Short description
+        struct Cmd {
+            /// this one should be hidden
+            #[argh(positional, hidden_help)]
+            _one: String,
+            #[argh(positional)]
+            /// this one is real
+            _two: String,
+            /// this one should be hidden
+            #[argh(option, hidden_help)]
+            _three: String,
+        }
+
+        assert_help_string::<Cmd>(
+            r###"Usage: test_arg_0 <_two>
+
+Short description
+
+Positional Arguments:
+  _two              this one is real
+
 Options:
   --help            display usage information
 "###,