Repository: clap-rs/clap
Pull request #4289 (2022-09-29 16:44:10)

Generate subcommand alias
This should fix https://github.com/clap-rs/clap/issues/4273, https://github.com/clap-rs/clap/issues/4280, and the Bash part of https://github.com/clap-rs/clap/issues/4265. I haven't started looking at the Fish side or other shells yet.

This is a new attempt after checks for #4284 got into a weird state.


Patch:
diff --git a/clap_complete/examples/completion-derive.rs b/clap_complete/examples/completion-derive.rs
index bbaaf25f..baf142fb 100644
--- a/clap_complete/examples/completion-derive.rs
+++ b/clap_complete/examples/completion-derive.rs
@@ -12,18 +12,30 @@
 //! . ./value_hints_derive.fish
 //! ./target/debug/examples/value_hints_derive --<TAB>
 //! ```
-use clap::{Command, CommandFactory, Parser, ValueHint};
+use clap::{Args, Command, CommandFactory, Parser, Subcommand, ValueHint};
 use clap_complete::{generate, Generator, Shell};
 use std::ffi::OsString;
 use std::io;
 use std::path::PathBuf;
 
 #[derive(Parser, Debug, PartialEq)]
-#[command(name = "value_hints_derive")]
+#[command(name = "completion-derive")]
 struct Opt {
-    /// If provided, outputs the completion file for given shell
+    // If provided, outputs the completion file for given shell
     #[arg(long = "generate", value_enum)]
     generator: Option<Shell>,
+    #[clap(subcommand)]
+    command: Option<Commands>,
+}
+
+#[derive(Subcommand, Debug, PartialEq)]
+enum Commands {
+    #[clap(visible_alias = "hint")]
+    ValueHint(ValueHintOpt),
+}
+
+#[derive(Args, Debug, PartialEq)]
+struct ValueHintOpt {
     // Showcasing all possible ValueHints:
     #[arg(long, value_hint = ValueHint::Unknown)]
     unknown: Option<String>,
diff --git a/clap_complete/examples/completion.rs b/clap_complete/examples/completion.rs
index cf3d315a..0455d4f9 100644
--- a/clap_complete/examples/completion.rs
+++ b/clap_complete/examples/completion.rs
@@ -17,12 +17,8 @@ use clap_complete::{generate, Generator, Shell};
 use std::io;
 
 fn build_cli() -> Command {
-    Command::new("value_hints")
-        .arg(
-            Arg::new("generator")
-                .long("generate")
-                .value_parser(value_parser!(Shell)),
-        )
+    let value_hint_command = Command::new("value-hint")
+        .visible_alias("hint")
         .arg(
             Arg::new("unknown")
                 .long("unknown")
@@ -87,7 +83,15 @@ fn build_cli() -> Command {
             Arg::new("email")
                 .long("email")
                 .value_hint(ValueHint::EmailAddress),
+        );
+
+    Command::new("completion")
+        .arg(
+            Arg::new("generator")
+                .long("generate")
+                .value_parser(value_parser!(Shell)),
         )
+        .subcommand(value_hint_command)
 }
 
 fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
diff --git a/clap_complete/src/generator/utils.rs b/clap_complete/src/generator/utils.rs
index 0a2c5620..ca76d189 100644
--- a/clap_complete/src/generator/utils.rs
+++ b/clap_complete/src/generator/utils.rs
@@ -39,10 +39,6 @@ pub fn subcommands(p: &Command) -> Vec<(String, String)> {
 
     let mut subcmds = vec![];
 
-    if !p.has_subcommands() {
-        return subcmds;
-    }
-
     for sc in p.get_subcommands() {
         let sc_bin_name = sc.get_bin_name().unwrap();
 
diff --git a/clap_complete/src/shells/bash.rs b/clap_complete/src/shells/bash.rs
index e1429c77..e110537e 100644
--- a/clap_complete/src/shells/bash.rs
+++ b/clap_complete/src/shells/bash.rs
@@ -31,8 +31,8 @@ impl Generator for Bash {
 
     for i in ${{COMP_WORDS[@]}}
     do
-        case \"${{i}}\" in
-            \"$1\")
+        case \"${{cmd}},${{i}}\" in
+            \",$1\")
                 cmd=\"{cmd}\"
                 ;;{subcmds}
             *)
@@ -75,26 +75,52 @@ complete -F _{name} -o bashdefault -o default {name}
 fn all_subcommands(cmd: &Command) -> String {
     debug!("all_subcommands");
 
-    let mut subcmds = vec![String::new()];
-    let mut scs = utils::all_subcommands(cmd)
-        .iter()
-        .map(|x| x.0.clone())
-        .collect::<Vec<_>>();
-
-    scs.sort();
-    scs.dedup();
+    fn add_command(
+        parent_fn_name: &str,
+        cmd: &Command,
+        subcmds: &mut Vec<(String, String, String)>,
+    ) {
+        let fn_name = format!(
+            "{parent_fn_name}__{cmd_name}",
+            parent_fn_name = parent_fn_name,
+            cmd_name = cmd.get_name().to_string().replace('-', "__")
+        );
+        subcmds.push((
+            parent_fn_name.to_string(),
+            cmd.get_name().to_string(),
+            fn_name.clone(),
+        ));
+        for alias in cmd.get_visible_aliases() {
+            subcmds.push((
+                parent_fn_name.to_string(),
+                alias.to_string(),
+                fn_name.clone(),
+            ));
+        }
+        for subcmd in cmd.get_subcommands() {
+            add_command(&fn_name, subcmd, subcmds);
+        }
+    }
+    let mut subcmds = vec![];
+    let fn_name = cmd.get_name().replace('-', "__");
+    for subcmd in cmd.get_subcommands() {
+        add_command(&fn_name, subcmd, &mut subcmds);
+    }
+    subcmds.sort();
 
-    subcmds.extend(scs.iter().map(|sc| {
-        format!(
-            "{name})
-                cmd+=\"__{fn_name}\"
+    let mut cases = vec![String::new()];
+    for (parent_fn_name, name, fn_name) in subcmds {
+        cases.push(format!(
+            "{parent_fn_name},{name})
+                cmd=\"{fn_name}\"
                 ;;",
-            name = sc,
-            fn_name = sc.replace('-', "__")
-        )
-    }));
+            parent_fn_name = parent_fn_name,
+            name = name,
+            fn_name = fn_name,
+        ));
+    }
 
-    subcmds.join("\n            ")
+    cases.join("\n            ")
 }
 
 fn subcommand_details(cmd: &Command) -> String {
diff --git a/clap_complete/src/shells/zsh.rs b/clap_complete/src/shells/zsh.rs
index d4fc63ba..9730229a 100644
--- a/clap_complete/src/shells/zsh.rs
+++ b/clap_complete/src/shells/zsh.rs
@@ -156,9 +156,7 @@ fn subcommands_of(p: &Command) -> String {
             help = escape_help(&subcommand.get_about().unwrap_or_default().to_string())
         );
 
-        if !text.is_empty() {
-            ret.push(text);
-        }
+        ret.push(text);
     }
 
     // The subcommands


Test patch:
diff --git a/clap_complete/tests/common.rs b/clap_complete/tests/common.rs
index 1e029923..ab52fe93 100644
--- a/clap_complete/tests/common.rs
+++ b/clap_complete/tests/common.rs
@@ -151,6 +151,7 @@ pub fn sub_subcommands_command(name: &'static str) -> clap::Command {
     feature_sample_command(name).subcommand(
         clap::Command::new("some_cmd")
             .about("top level subcommand")
+            .visible_alias("some_cmd_alias")
             .subcommand(
                 clap::Command::new("sub_cmd").about("sub-subcommand").arg(
                     clap::Arg::new("config")
diff --git a/clap_complete/tests/snapshots/aliases.bash b/clap_complete/tests/snapshots/aliases.bash
index a9949102..0ca8e493 100644
--- a/clap_complete/tests/snapshots/aliases.bash
+++ b/clap_complete/tests/snapshots/aliases.bash
@@ -8,8 +8,8 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
             *)
diff --git a/clap_complete/tests/snapshots/basic.bash b/clap_complete/tests/snapshots/basic.bash
index 08b27e0b..70946e79 100644
--- a/clap_complete/tests/snapshots/basic.bash
+++ b/clap_complete/tests/snapshots/basic.bash
@@ -8,15 +8,21 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
-            help)
-                cmd+="__help"
+            my__app,help)
+                cmd="my__app__help"
                 ;;
-            test)
-                cmd+="__test"
+            my__app,test)
+                cmd="my__app__test"
+                ;;
+            my__app__help,help)
+                cmd="my__app__help__help"
+                ;;
+            my__app__help,test)
+                cmd="my__app__help__test"
                 ;;
             *)
                 ;;
diff --git a/clap_complete/tests/snapshots/feature_sample.bash b/clap_complete/tests/snapshots/feature_sample.bash
index 2d1ec386..8ea49145 100644
--- a/clap_complete/tests/snapshots/feature_sample.bash
+++ b/clap_complete/tests/snapshots/feature_sample.bash
@@ -8,15 +8,21 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
-            help)
-                cmd+="__help"
+            my__app,help)
+                cmd="my__app__help"
                 ;;
-            test)
-                cmd+="__test"
+            my__app,test)
+                cmd="my__app__test"
+                ;;
+            my__app__help,help)
+                cmd="my__app__help__help"
+                ;;
+            my__app__help,test)
+                cmd="my__app__help__test"
                 ;;
             *)
                 ;;
diff --git a/clap_complete/tests/snapshots/quoting.bash b/clap_complete/tests/snapshots/quoting.bash
index b831012b..e387c0bc 100644
--- a/clap_complete/tests/snapshots/quoting.bash
+++ b/clap_complete/tests/snapshots/quoting.bash
@@ -8,30 +8,51 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
-            cmd-backslash)
-                cmd+="__cmd__backslash"
+            my__app,cmd-backslash)
+                cmd="my__app__cmd__backslash"
                 ;;
-            cmd-backticks)
-                cmd+="__cmd__backticks"
+            my__app,cmd-backticks)
+                cmd="my__app__cmd__backticks"
                 ;;
-            cmd-brackets)
-                cmd+="__cmd__brackets"
+            my__app,cmd-brackets)
+                cmd="my__app__cmd__brackets"
                 ;;
-            cmd-double-quotes)
-                cmd+="__cmd__double__quotes"
+            my__app,cmd-double-quotes)
+                cmd="my__app__cmd__double__quotes"
                 ;;
-            cmd-expansions)
-                cmd+="__cmd__expansions"
+            my__app,cmd-expansions)
+                cmd="my__app__cmd__expansions"
                 ;;
-            cmd-single-quotes)
-                cmd+="__cmd__single__quotes"
+            my__app,cmd-single-quotes)
+                cmd="my__app__cmd__single__quotes"
                 ;;
-            help)
-                cmd+="__help"
+            my__app,help)
+                cmd="my__app__help"
+                ;;
+            my__app__help,cmd-backslash)
+                cmd="my__app__help__cmd__backslash"
+                ;;
+            my__app__help,cmd-backticks)
+                cmd="my__app__help__cmd__backticks"
+                ;;
+            my__app__help,cmd-brackets)
+                cmd="my__app__help__cmd__brackets"
+                ;;
+            my__app__help,cmd-double-quotes)
+                cmd="my__app__help__cmd__double__quotes"
+                ;;
+            my__app__help,cmd-expansions)
+                cmd="my__app__help__cmd__expansions"
+                ;;
+            my__app__help,cmd-single-quotes)
+                cmd="my__app__help__cmd__single__quotes"
+                ;;
+            my__app__help,help)
+                cmd="my__app__help__help"
                 ;;
             *)
                 ;;
diff --git a/clap_complete/tests/snapshots/special_commands.bash b/clap_complete/tests/snapshots/special_commands.bash
index 11f8c186..31b5354a 100644
--- a/clap_complete/tests/snapshots/special_commands.bash
+++ b/clap_complete/tests/snapshots/special_commands.bash
@@ -8,24 +8,39 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
-            help)
-                cmd+="__help"
+            my__app,help)
+                cmd="my__app__help"
                 ;;
-            some-cmd-with-hyphens)
-                cmd+="__some__cmd__with__hyphens"
+            my__app,some-cmd-with-hyphens)
+                cmd="my__app__some__cmd__with__hyphens"
                 ;;
-            some-hidden-cmd)
-                cmd+="__some__hidden__cmd"
+            my__app,some-hidden-cmd)
+                cmd="my__app__some__hidden__cmd"
                 ;;
-            some_cmd)
-                cmd+="__some_cmd"
+            my__app,some_cmd)
+                cmd="my__app__some_cmd"
                 ;;
-            test)
-                cmd+="__test"
+            my__app,test)
+                cmd="my__app__test"
+                ;;
+            my__app__help,help)
+                cmd="my__app__help__help"
+                ;;
+            my__app__help,some-cmd-with-hyphens)
+                cmd="my__app__help__some__cmd__with__hyphens"
+                ;;
+            my__app__help,some-hidden-cmd)
+                cmd="my__app__help__some__hidden__cmd"
+                ;;
+            my__app__help,some_cmd)
+                cmd="my__app__help__some_cmd"
+                ;;
+            my__app__help,test)
+                cmd="my__app__help__test"
                 ;;
             *)
                 ;;
diff --git a/clap_complete/tests/snapshots/sub_subcommands.bash b/clap_complete/tests/snapshots/sub_subcommands.bash
index 69793078..4c2573a7 100644
--- a/clap_complete/tests/snapshots/sub_subcommands.bash
+++ b/clap_complete/tests/snapshots/sub_subcommands.bash
@@ -8,21 +8,45 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
-            help)
-                cmd+="__help"
+            my__app,help)
+                cmd="my__app__help"
                 ;;
-            some_cmd)
-                cmd+="__some_cmd"
+            my__app,some_cmd)
+                cmd="my__app__some_cmd"
                 ;;
-            sub_cmd)
-                cmd+="__sub_cmd"
+            my__app,some_cmd_alias)
+                cmd="my__app__some_cmd"
                 ;;
-            test)
-                cmd+="__test"
+            my__app,test)
+                cmd="my__app__test"
+                ;;
+            my__app__help,help)
+                cmd="my__app__help__help"
+                ;;
+            my__app__help,some_cmd)
+                cmd="my__app__help__some_cmd"
+                ;;
+            my__app__help,test)
+                cmd="my__app__help__test"
+                ;;
+            my__app__help__some_cmd,sub_cmd)
+                cmd="my__app__help__some_cmd__sub_cmd"
+                ;;
+            my__app__some_cmd,help)
+                cmd="my__app__some_cmd__help"
+                ;;
+            my__app__some_cmd,sub_cmd)
+                cmd="my__app__some_cmd__sub_cmd"
+                ;;
+            my__app__some_cmd__help,help)
+                cmd="my__app__some_cmd__help__help"
+                ;;
+            my__app__some_cmd__help,sub_cmd)
+                cmd="my__app__some_cmd__help__sub_cmd"
                 ;;
             *)
                 ;;
diff --git a/clap_complete/tests/snapshots/sub_subcommands.zsh b/clap_complete/tests/snapshots/sub_subcommands.zsh
index 922613ba..7d853d5d 100644
--- a/clap_complete/tests/snapshots/sub_subcommands.zsh
+++ b/clap_complete/tests/snapshots/sub_subcommands.zsh
@@ -151,6 +151,7 @@ _my-app_commands() {
     local commands; commands=(
 'test:tests things' \
 'some_cmd:top level subcommand' \
+'some_cmd_alias:top level subcommand' \
 'help:Print this message or the help of the given subcommand(s)' \
     )
     _describe -t commands 'my-app commands' commands "$@"
diff --git a/clap_complete/tests/snapshots/value_hint.bash b/clap_complete/tests/snapshots/value_hint.bash
index 2d3d88ba..11fb3962 100644
--- a/clap_complete/tests/snapshots/value_hint.bash
+++ b/clap_complete/tests/snapshots/value_hint.bash
@@ -8,8 +8,8 @@ _my-app() {
 
     for i in ${COMP_WORDS[@]}
     do
-        case "${i}" in
-            "$1")
+        case "${cmd},${i}" in
+            ",$1")
                 cmd="my__app"
                 ;;
             *)