Repository: clap-rs/clap (rust)

Issue:
Allow changing SUBCOMMAND placeholder in usage
<!-- Issuehunt Badges -->
[<img alt="Issuehunt badges" src="https://img.shields.io/badge/IssueHunt-%245%20Funded-%2300A156.svg" />](https://issuehunt.io/r/clap-rs/clap/issues/1597)
<!-- /Issuehunt Badges -->


(Note: I'm using v2 right now, but I believe that v3 doesn't change this.)

Basically, the usage will use the placeholder `<SUBCOMMAND>` or `[SUBCOMMAND]` depending on whether the subcommand is used, and it won't let you change this. Sometimes, it may actually be more beneficial to change this, e.g.:

```
USAGE:
    util list <THING>
```

as opposed to:

```
USAGE:
    util list <SUBCOMMAND>
```


<!-- Issuehunt content -->

---

<details>
<summary>
<b>IssueHunt Summary</b>
</summary>


### Backers (Total: $5.00)

- [<img src='https://avatars0.githubusercontent.com/u/174703?v=4' alt='pksunkara' width=24 height=24> pksunkara](https://issuehunt.io/u/pksunkara) ($5.00)


#### [Become a backer now!](https://issuehunt.io/r/clap-rs/clap/issues/1597)
#### [Or submit a pull request to get the deposits!](https://issuehunt.io/r/clap-rs/clap/issues/1597)
### Tips

- Checkout the [Issuehunt explorer](https://issuehunt.io/r/clap-rs/clap/) to discover more funded issues.
- Need some help from other developers? [Add your repositories](https://issuehunt.io/r/new) on IssueHunt to raise funds.
</details>
<!-- /Issuehunt content-->

Pull request:
Custom subcommand placeholders
<!--
If your PR closes some issues, please write `Closes #XXXX`
where `XXXX` is the number of the issue you want to fix.
Each issue goes on it's own line.
-->

This PR lets you change the "SUBCOMMAND" placeholder in usage text to text of your choosing.

Closes #1597.

There are a few questions I have about how this should be implemented:
 - In the full help text, there is a section labeled "SUBCOMMANDS".
   - Should this also change?
   - Should there be a separate option for that?
 - How's the naming? "subcommand_placeholder" is long but descriptive -- to me, at least.
 - The current implementation does repeat some logic a bit, I could factor that into a private helper on App if you like.
 - I suspect the documentation I wrote could use some work.
 - ... I just read the contribution guidelines, I can redo the commit messages before this gets merged.

Patch:
diff --git a/README.md b/README.md
index b3b897b3..fa0bdc8c 100644
--- a/README.md
+++ b/README.md
@@ -410,7 +410,7 @@ To try out the pre-built [examples][examples], use the following steps:
 
 To test out `clap`'s default auto-generated help/version follow these steps:
 * Create a new cargo project `$ cargo new fake --bin && cd fake`
-* Write your program as described in the quick example section.
+* Writer your program as described in the quick example section.
 * Build your program `$ cargo build --release`
 * Run with help or version `$ ./target/release/fake --help` or `$ ./target/release/fake --version`
 
diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs
index 372152d4..22f2f852 100644
--- a/src/build/app/mod.rs
+++ b/src/build/app/mod.rs
@@ -95,6 +95,8 @@ pub struct App<'b> {
     pub(crate) replacers: HashMap<&'b str, &'b [&'b str]>,
     pub(crate) groups: Vec<ArgGroup<'b>>,
     pub(crate) current_help_heading: Option<&'b str>,
+    pub(crate) subcommand_placeholder: Option<&'b str>,
+    pub(crate) subcommand_header: Option<&'b str>,
 }
 
 impl<'b> App<'b> {
@@ -1446,6 +1448,73 @@ impl<'b> App<'b> {
 
         self._do_parse(&mut it)
     }
+
+    /// Sets the placeholder text used for subcommands when printing usage and help.
+    /// By default, this is "SUBCOMMAND" with a header of "SUBCOMMANDS"
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// # use clap::{App, Arg};
+    /// App::new("myprog")
+    ///     .subcommand(App::new("sub1"))
+    ///     .print_help()
+    /// # ;
+    /// ```
+    ///
+    /// will produce
+    ///
+    /// ```text
+    /// myprog
+    ///
+    /// USAGE:
+    ///     myprog [SUBCOMMAND]
+    ///
+    /// FLAGS:
+    ///     -h, --help       Prints help information
+    ///     -V, --version    Prints version information
+    ///
+    /// SUBCOMMANDS:
+    ///     help    Prints this message or the help of the given subcommand(s)
+    ///     sub1    
+    /// ```
+    ///
+    /// but usage of `subcommand_placeholder`
+    ///
+    /// ```no_run
+    /// # use clap::{App, Arg};
+    /// App::new("myprog")
+    ///     .subcommand(App::new("sub1"))
+    ///     .subcommand_placeholder("THING", "THINGS")
+    ///     .print_help()
+    /// # ;
+    /// ```
+    ///
+    /// will produce
+    ///
+    /// ```text
+    /// myprog
+    ///
+    /// USAGE:
+    ///     myprog [THING]
+    ///
+    /// FLAGS:
+    ///     -h, --help       Prints help information
+    ///     -V, --version    Prints version information
+    ///
+    /// THINGS:
+    ///     help    Prints this message or the help of the given subcommand(s)
+    ///     sub1    
+    /// ```
+    pub fn subcommand_placeholder<S, T>(mut self, placeholder: S, header: T) -> Self
+    where
+        S: Into<&'b str>,
+        T: Into<&'b str>,
+    {
+        self.subcommand_placeholder = Some(placeholder.into());
+        self.subcommand_header = Some(header.into());
+        self
+    }
 }
 
 // Internally used only
diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs
index 25303145..e0cf9ecc 100644
--- a/src/build/arg/mod.rs
+++ b/src/build/arg/mod.rs
@@ -3005,8 +3005,8 @@ impl<'help> Arg<'help> {
     ///
     /// **WARNING**: When building your CLIs, consider the effects of allowing leading hyphens and
     /// the user passing in a value that matches a valid short. For example `prog -opt -F` where
-    /// `-F` is supposed to be a value, yet `-F` is *also* a valid short for another arg.
-    /// Care should be taken when designing these args. This is compounded by the ability to "stack"
+    /// `-F` is supposed to be a value, yet `-F` is *also* a valid short for another arg. Care should
+    /// should be taken when designing these args. This is compounded by the ability to "stack"
     /// short args. I.e. if `-val` is supposed to be a value, but `-v`, `-a`, and `-l` are all valid
     /// shorts.
     ///
diff --git a/src/output/help.rs b/src/output/help.rs
index dbbddd9e..b6bf1796 100644
--- a/src/output/help.rs
+++ b/src/output/help.rs
@@ -750,7 +750,10 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
             if !first {
                 self.none("\n\n")?;
             }
-            self.warning("SUBCOMMANDS:\n")?;
+
+            self.warning(self.parser.app.subcommand_header.unwrap_or("SUBCOMMANDS"))?;
+            self.warning(":\n")?;
+
             self.write_subcommands(&self.parser.app)?;
         }
 
diff --git a/src/output/usage.rs b/src/output/usage.rs
index 3bd0e82d..0294d1a1 100644
--- a/src/output/usage.rs
+++ b/src/output/usage.rs
@@ -131,18 +131,28 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
                 if !self.p.is_set(AS::ArgsNegateSubcommands) {
                     usage.push_str("\n    ");
                     usage.push_str(&*self.create_help_usage(false));
-                    usage.push_str(" <SUBCOMMAND>");
+
+                    usage.push_str(" <");
+                    usage.push_str(self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND"));
+                    usage.push_str(">");
                 } else {
                     usage.push_str("\n    ");
                     usage.push_str(&*name);
-                    usage.push_str(" <SUBCOMMAND>");
+
+                    usage.push_str(" <");
+                    usage.push_str(self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND"));
+                    usage.push_str(">");
                 }
             } else if self.p.is_set(AS::SubcommandRequired)
                 || self.p.is_set(AS::SubcommandRequiredElseHelp)
             {
-                usage.push_str(" <SUBCOMMAND>");
+                usage.push_str(" <");
+                usage.push_str(self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND"));
+                usage.push_str(">");
             } else {
-                usage.push_str(" [SUBCOMMAND]");
+                usage.push_str(" [");
+                usage.push_str(self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND"));
+                usage.push_str("]");
             }
         }
         usage.shrink_to_fit();
@@ -171,7 +181,9 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
         );
         usage.push_str(&*r_string);
         if self.p.is_set(AS::SubcommandRequired) {
-            usage.push_str(" <SUBCOMMAND>");
+            usage.push_str(" <");
+            usage.push_str(self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND"));
+            usage.push_str(">");
         }
         usage.shrink_to_fit();
         usage


Test patch:
diff --git a/tests/subcommands.rs b/tests/subcommands.rs
index f6a06d6f..4148295e 100644
--- a/tests/subcommands.rs
+++ b/tests/subcommands.rs
@@ -362,3 +362,23 @@ fn issue_1722_not_emit_error_when_arg_follows_similar_to_a_subcommand() {
         .try_get_matches_from(vec!["myprog", "--", "subcommand"]);
     assert_eq!(m.unwrap().value_of("argument"), Some("subcommand"));
 }
+
+#[test]
+fn subcommand_placeholder_test() {
+    let mut app = App::new("myprog")
+        .subcommand(App::new("subcommand"))
+        .subcommand_placeholder("TEST_PLACEHOLDER", "TEST_HEADER");
+
+    assert_eq!(
+        &app.generate_usage(),
+        "USAGE:\n    myprog [TEST_PLACEHOLDER]"
+    );
+
+    let mut help_text = Vec::new();
+    app.write_help(&mut help_text)
+        .expect("Failed to write to internal buffer");
+
+    assert!(String::from_utf8(help_text)
+        .unwrap()
+        .contains("TEST_HEADER:"));
+}