Repository: clap-rs/clap
Pull request #5648 (2024-08-09 21:18:37)

feat(complete): Make registration args optional
I think `comp_words` is always required, so in theory this should work


Patch:
diff --git a/clap_complete/src/dynamic/shells/mod.rs b/clap_complete/src/dynamic/shells/mod.rs
index 713f144d..fa23f203 100644
--- a/clap_complete/src/dynamic/shells/mod.rs
+++ b/clap_complete/src/dynamic/shells/mod.rs
@@ -161,45 +161,46 @@ impl CompleteCommand {
 ///
 /// Bash
 /// ```bash
-/// echo "source <(your_program complete --shell bash --register -)" >> ~/.bashrc
+/// echo "source <(your_program complete --shell bash)" >> ~/.bashrc
 /// ```
 ///
 /// Elvish
 /// ```elvish
-/// echo "eval (your_program complete --shell elvish --register -)" >> ~/.elvish/rc.elv
+/// echo "eval (your_program complete --shell elvish)" >> ~/.elvish/rc.elv
 /// ```
 ///
 /// Fish
 /// ```fish
-/// echo "source (your_program complete --shell fish --register - | psub)" >> ~/.config/fish/config.fish
+/// echo "source (your_program complete --shell fish | psub)" >> ~/.config/fish/config.fish
 /// ```
 ///
 /// Powershell
 /// ```powershell
-/// echo "your_program complete --shell powershell --register - | Invoke-Expression" >> $PROFILE
+/// echo "your_program complete --shell powershell | Invoke-Expression" >> $PROFILE
 /// ```
 ///
 /// Zsh
 /// ```zsh
-/// echo "source <(your_program complete --shell zsh --register -)" >> ~/.zshrc
+/// echo "source <(your_program complete --shell zsh)" >> ~/.zshrc
 /// ```
-#[derive(clap::Args)]
-#[command(arg_required_else_help = true)]
-#[command(group = clap::ArgGroup::new("complete").multiple(true).conflicts_with("register"))]
-#[allow(missing_docs)]
-#[derive(Clone, Debug)]
+#[derive(clap::Args, Clone, Debug)]
 #[command(about = None, long_about = None)]
 pub struct CompleteArgs {
-    /// Specify shell to complete for
-    #[arg(long)]
-    shell: Shell,
-
     /// Path to write completion-registration to
-    #[arg(long, required = true)]
+    #[arg(long, value_name = "PATH")]
     register: Option<std::path::PathBuf>,
 
-    #[arg(raw = true, hide_short_help = true, group = "complete")]
-    comp_words: Vec<OsString>,
+    #[arg(
+        raw = true,
+        value_name = "ARG",
+        hide = true,
+        conflicts_with = "register"
+    )]
+    comp_words: Option<Vec<OsString>>,
+
+    /// Specify shell to complete for
+    #[arg(long, value_name = "NAME")]
+    shell: Option<Shell>,
 }
 
 impl CompleteArgs {
@@ -217,30 +218,36 @@ impl CompleteArgs {
     /// **Warning:** `stdout` should not be written to before or after this has run.
     pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> {
         debug!("CompleteCommand::try_complete: {self:?}");
-        if let Some(out_path) = self.register.as_deref() {
+
+        let shell = self
+            .shell
+            .or_else(|| Shell::from_env())
+            .unwrap_or(Shell::Bash);
+
+        if let Some(comp_words) = self.comp_words.as_ref() {
+            let current_dir = std::env::current_dir().ok();
+
             let mut buf = Vec::new();
+            shell.write_complete(cmd, comp_words.clone(), current_dir.as_deref(), &mut buf)?;
+            std::io::stdout().write_all(&buf)?;
+        } else {
+            let out_path = self
+                .register
+                .as_deref()
+                .unwrap_or(std::path::Path::new("-"));
             let name = cmd.get_name();
             let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name());
-            self.shell.write_registration(name, bin, bin, &mut buf)?;
+
+            let mut buf = Vec::new();
+            shell.write_registration(name, bin, bin, &mut buf)?;
             if out_path == std::path::Path::new("-") {
                 std::io::stdout().write_all(&buf)?;
             } else if out_path.is_dir() {
-                let out_path = out_path.join(self.shell.file_name(name));
+                let out_path = out_path.join(shell.file_name(name));
                 std::fs::write(out_path, buf)?;
             } else {
                 std::fs::write(out_path, buf)?;
             }
-        } else {
-            let current_dir = std::env::current_dir().ok();
-
-            let mut buf = Vec::new();
-            self.shell.write_complete(
-                cmd,
-                self.comp_words.clone(),
-                current_dir.as_deref(),
-                &mut buf,
-            )?;
-            std::io::stdout().write_all(&buf)?;
         }
 
         Ok(())


Test patch:
diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc
index 49c9960b..f99506a4 100644
--- a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc
+++ b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc
@@ -238,20 +238,20 @@ _exhaustive() {
             return 0
             ;;
         exhaustive__complete)
-            opts="-h -V --shell --register --global --help --version [COMP_WORDS]..."
+            opts="-h -V --register --shell --global --help --version [ARG]..."
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
             fi
             case "${prev}" in
-                --shell)
-                    COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
-                    return 0
-                    ;;
                 --register)
                     COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
+                --shell)
+                    COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
+                    return 0
+                    ;;
                 *)
                     COMPREPLY=()
                     ;;
diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/elvish/elvish/rc.elv b/clap_complete/tests/snapshots/home/static/exhaustive/elvish/elvish/rc.elv
index c8253260..1b63da9f 100644
--- a/clap_complete/tests/snapshots/home/static/exhaustive/elvish/elvish/rc.elv
+++ b/clap_complete/tests/snapshots/home/static/exhaustive/elvish/elvish/rc.elv
@@ -239,11 +239,11 @@ set edit:completion:arg-completer[exhaustive] = {|@words|
             cand --version 'Print version'
         }
         &'exhaustive;complete'= {
-            cand --shell 'Specify shell to complete for'
             cand --register 'Path to write completion-registration to'
+            cand --shell 'Specify shell to complete for'
             cand --global 'everywhere'
-            cand -h 'Print help (see more with ''--help'')'
-            cand --help 'Print help (see more with ''--help'')'
+            cand -h 'Print help'
+            cand --help 'Print help'
             cand -V 'Print version'
             cand --version 'Print version'
         }
diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish b/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish
index 7b28a7cd..b66c67ef 100644
--- a/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish
+++ b/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish
@@ -136,10 +136,10 @@ complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l email -r
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l global -d 'everywhere'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -s h -l help -d 'Print help'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -s V -l version -d 'Print version'
-complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l shell -d 'Specify shell to complete for' -r -f -a "{bash\t'',elvish\t'',fish\t'',powershell\t'',zsh\t''}"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l register -d 'Path to write completion-registration to' -r -F
+complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l shell -d 'Specify shell to complete for' -r -f -a "{bash\t'',elvish\t'',fish\t'',powershell\t'',zsh\t''}"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -l global -d 'everywhere'
-complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -s h -l help -d 'Print help (see more with \'--help\')'
+complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -s h -l help -d 'Print help'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand complete" -s V -l version -d 'Print version'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand help; and not __fish_seen_subcommand_from action quote value pacman last alias hint complete help" -f -a "action"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand help; and not __fish_seen_subcommand_from action quote value pacman last alias hint complete help" -f -a "quote"
diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive b/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive
index d596f71e..88571e04 100644
--- a/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive
+++ b/clap_complete/tests/snapshots/home/static/exhaustive/zsh/zsh/_exhaustive
@@ -325,11 +325,11 @@ _arguments "${_arguments_options[@]}" : \
 ;;
 (complete)
 _arguments "${_arguments_options[@]}" : \
-'--shell=[Specify shell to complete for]:SHELL:(bash elvish fish powershell zsh)' \
-'--register=[Path to write completion-registration to]:REGISTER:_files' \
+'--register=[Path to write completion-registration to]:PATH:_files' \
+'--shell=[Specify shell to complete for]:NAME:(bash elvish fish powershell zsh)' \
 '--global[everywhere]' \
-'-h[Print help (see more with '\''--help'\'')]' \
-'--help[Print help (see more with '\''--help'\'')]' \
+'-h[Print help]' \
+'--help[Print help]' \
 '-V[Print version]' \
 '--version[Print version]' \
 '*::comp_words:' \