Repository: clap-rs/clap
Pull request #5665 (2024-08-11 01:43:21)

fix(complete)!: Put CompleteCommand behind unstable-command 
<!--
Thanks for helping out!

Please link the appropriate issue from your PR.

If you don't have an issue, we'd recommend starting with one first so the PR can focus on the
implementation (unless its an obvious bug or documentation fix that will have
little conversation).
-->



Patch:
diff --git a/clap_complete/Cargo.toml b/clap_complete/Cargo.toml
index f20969ec..9461771e 100644
--- a/clap_complete/Cargo.toml
+++ b/clap_complete/Cargo.toml
@@ -52,12 +52,13 @@ automod = "1.0.14"
 
 [[example]]
 name = "dynamic"
-required-features = ["unstable-dynamic"]
+required-features = ["unstable-dynamic", "unstable-command"]
 
 [features]
 default = []
-unstable-doc = ["unstable-dynamic"] # for docs.rs
-unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:unicode-xid", "clap/derive", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
+unstable-doc = ["unstable-dynamic", "unstable-command"] # for docs.rs
+unstable-dynamic = ["dep:clap_lex", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
+unstable-command = ["unstable-dynamic", "dep:shlex", "dep:unicode-xid", "clap/derive", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
 debug = ["clap/debug"]
 
 [lints]
diff --git a/clap_complete/examples/exhaustive.rs b/clap_complete/examples/exhaustive.rs
index 7a79c108..ce7d313f 100644
--- a/clap_complete/examples/exhaustive.rs
+++ b/clap_complete/examples/exhaustive.rs
@@ -1,5 +1,5 @@
 use clap::builder::PossibleValue;
-#[cfg(feature = "unstable-dynamic")]
+#[cfg(feature = "unstable-command")]
 use clap::{FromArgMatches, Subcommand};
 use clap_complete::{generate, Generator, Shell};
 
@@ -12,7 +12,7 @@ fn main() {
         return;
     }
 
-    #[cfg(feature = "unstable-dynamic")]
+    #[cfg(feature = "unstable-command")]
     if let Ok(completions) = clap_complete::dynamic::CompleteCommand::from_arg_matches(&matches) {
         completions.complete(&mut cli());
         return;
@@ -195,7 +195,7 @@ fn cli() -> clap::Command {
                     .value_hint(clap::ValueHint::EmailAddress),
             ]),
         ]);
-    #[cfg(feature = "unstable-dynamic")]
+    #[cfg(feature = "unstable-command")]
     let cli = clap_complete::dynamic::CompleteCommand::augment_subcommands(cli);
     cli
 }
diff --git a/clap_complete/src/dynamic/candidate.rs b/clap_complete/src/dynamic/candidate.rs
index 8add845d..517e2879 100644
--- a/clap_complete/src/dynamic/candidate.rs
+++ b/clap_complete/src/dynamic/candidate.rs
@@ -5,7 +5,7 @@ use clap::builder::StyledStr;
 
 /// A shell-agnostic completion candidate
 ///
-/// [`ShellCompleter`][crate::dynamic::shells::ShellCompleter] will adapt what it can to the
+/// [`Shell`][crate::dynamic::shells::Shell] will adapt what it can to the
 /// current shell.
 #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct CompletionCandidate {
diff --git a/clap_complete/src/dynamic/complete.rs b/clap_complete/src/dynamic/complete.rs
index c80e71b4..214e3d65 100644
--- a/clap_complete/src/dynamic/complete.rs
+++ b/clap_complete/src/dynamic/complete.rs
@@ -10,7 +10,7 @@ use super::CompletionCandidate;
 ///
 /// For integration with clap and shells, see [`CompleteCommand`][crate::dynamic::shells::CompleteCommand].
 ///
-/// This is generally called by a [`ShellCompleter`][crate::dynamic::shells::ShellCompleter] which
+/// This is generally called by a [`Shell`][crate::dynamic::shells::Shell] which
 /// handles the shell-specific logic.
 pub fn complete(
     cmd: &mut clap::Command,
diff --git a/clap_complete/src/dynamic/mod.rs b/clap_complete/src/dynamic/mod.rs
index ef396d8d..3ccb879e 100644
--- a/clap_complete/src/dynamic/mod.rs
+++ b/clap_complete/src/dynamic/mod.rs
@@ -19,5 +19,7 @@ pub use custom::ArgValueCompleter;
 pub use custom::CustomCompleter;
 
 // These live in `shells` because they are tightly coupled with the `ShellCompleter`s
+#[cfg(feature = "unstable-command")]
 pub use shells::CompleteArgs;
+#[cfg(feature = "unstable-command")]
 pub use shells::CompleteCommand;
diff --git a/clap_complete/src/dynamic/shells/command.rs b/clap_complete/src/dynamic/shells/command.rs
index 4aa1d0d3..4ed25c9c 100644
--- a/clap_complete/src/dynamic/shells/command.rs
+++ b/clap_complete/src/dynamic/shells/command.rs
@@ -76,6 +76,7 @@ use super::Shell;
 #[allow(missing_docs)]
 #[derive(Clone, Debug)]
 #[command(about = None, long_about = None)]
+#[cfg(feature = "unstable-command")]
 pub enum CompleteCommand {
     /// Register shell completions for this program
     #[command(hide = true)]
@@ -173,6 +174,7 @@ impl CompleteCommand {
 /// ```
 #[derive(clap::Args, Clone, Debug)]
 #[command(about = None, long_about = None)]
+#[cfg(feature = "unstable-command")]
 pub struct CompleteArgs {
     /// Specify shell to complete for
     #[arg(value_name = "NAME")]
@@ -229,15 +231,16 @@ impl CompleteArgs {
 /// This will generally be called by [`CompleteCommand`] or [`CompleteArgs`].
 ///
 /// This handles adapting between the shell and [`completer`][crate::dynamic::complete()].
-/// A `ShellCompleter` can choose how much of that lives within the registration script and or
-/// lives in [`ShellCompleter::write_complete`].
-pub trait ShellCompleter {
+/// A `CommandCompleter` can choose how much of that lives within the registration script and or
+/// lives in [`CommandCompleter::write_complete`].
+#[cfg(feature = "unstable-command")]
+pub trait CommandCompleter {
     /// The recommended file name for the registration code
     fn file_name(&self, name: &str) -> String;
     /// Register for completions
     ///
     /// Write the `buf` the logic needed for calling into `<cmd> complete`, passing needed
-    /// arguments to [`ShellCompleter::write_complete`] through the environment.
+    /// arguments to [`CommandCompleter::write_complete`] through the environment.
     fn write_registration(
         &self,
         name: &str,
@@ -247,7 +250,7 @@ pub trait ShellCompleter {
     ) -> Result<(), std::io::Error>;
     /// Complete the given command
     ///
-    /// Adapt information from arguments and [`ShellCompleter::write_registration`]-defined env
+    /// Adapt information from arguments and [`CommandCompleter::write_registration`]-defined env
     /// variables to what is needed for [`completer`][crate::dynamic::complete()].
     ///
     /// Write out the [`CompletionCandidate`][crate::dynamic::CompletionCandidate]s in a way the shell will understand.
@@ -260,7 +263,41 @@ pub trait ShellCompleter {
     ) -> Result<(), std::io::Error>;
 }
 
-impl ShellCompleter for super::Bash {
+impl CommandCompleter for Shell {
+    fn file_name(&self, name: &str) -> String {
+        shell_completer(self).file_name(name)
+    }
+    fn write_registration(
+        &self,
+        name: &str,
+        bin: &str,
+        completer: &str,
+        buf: &mut dyn std::io::Write,
+    ) -> Result<(), std::io::Error> {
+        shell_completer(self).write_registration(name, bin, completer, buf)
+    }
+    fn write_complete(
+        &self,
+        cmd: &mut clap::Command,
+        args: Vec<OsString>,
+        current_dir: Option<&std::path::Path>,
+        buf: &mut dyn std::io::Write,
+    ) -> Result<(), std::io::Error> {
+        shell_completer(self).write_complete(cmd, args, current_dir, buf)
+    }
+}
+
+fn shell_completer(shell: &Shell) -> &dyn CommandCompleter {
+    match shell {
+        Shell::Bash => &super::Bash,
+        Shell::Elvish => &super::Elvish,
+        Shell::Fish => &super::Fish,
+        Shell::Powershell => &super::Powershell,
+        Shell::Zsh => &super::Zsh,
+    }
+}
+
+impl CommandCompleter for super::Bash {
     fn file_name(&self, name: &str) -> String {
         format!("{name}.bash")
     }
@@ -380,7 +417,7 @@ impl Default for CompType {
         Self::Normal
     }
 }
-impl ShellCompleter for super::Elvish {
+impl CommandCompleter for super::Elvish {
     fn file_name(&self, name: &str) -> String {
         format!("{name}.elv")
     }
@@ -436,7 +473,7 @@ set edit:completion:arg-completer[BIN] = { |@words|
     }
 }
 
-impl ShellCompleter for super::Fish {
+impl CommandCompleter for super::Fish {
     fn file_name(&self, name: &str) -> String {
         format!("{name}.fish")
     }
@@ -481,7 +518,7 @@ impl ShellCompleter for super::Fish {
     }
 }
 
-impl ShellCompleter for super::Powershell {
+impl CommandCompleter for super::Powershell {
     fn file_name(&self, name: &str) -> String {
         format!("{name}.ps1")
     }
@@ -547,7 +584,7 @@ Register-ArgumentCompleter -Native -CommandName {bin} -ScriptBlock {{
     }
 }
 
-impl ShellCompleter for super::Zsh {
+impl CommandCompleter for super::Zsh {
     fn file_name(&self, name: &str) -> String {
         format!("{name}.zsh")
     }
diff --git a/clap_complete/src/dynamic/shells/mod.rs b/clap_complete/src/dynamic/shells/mod.rs
index 74c259fe..147d95ae 100644
--- a/clap_complete/src/dynamic/shells/mod.rs
+++ b/clap_complete/src/dynamic/shells/mod.rs
@@ -1,7 +1,9 @@
 //! Shell completion support, see [`CompleteCommand`] for more details
 
+#[cfg(feature = "unstable-command")]
 mod command;
 
+#[cfg(feature = "unstable-command")]
 pub use command::*;
 
 use std::fmt::Display;
@@ -132,59 +134,22 @@ impl ValueEnum for Shell {
     }
 }
 
-impl Shell {
-    fn completer(&self) -> &dyn ShellCompleter {
-        match self {
-            Self::Bash => &Bash,
-            Self::Elvish => &Elvish,
-            Self::Fish => &Fish,
-            Self::Powershell => &Powershell,
-            Self::Zsh => &Zsh,
-        }
-    }
-}
-
-impl ShellCompleter for Shell {
-    fn file_name(&self, name: &str) -> String {
-        self.completer().file_name(name)
-    }
-    fn write_registration(
-        &self,
-        name: &str,
-        bin: &str,
-        completer: &str,
-        buf: &mut dyn std::io::Write,
-    ) -> Result<(), std::io::Error> {
-        self.completer()
-            .write_registration(name, bin, completer, buf)
-    }
-    fn write_complete(
-        &self,
-        cmd: &mut clap::Command,
-        args: Vec<std::ffi::OsString>,
-        current_dir: Option<&std::path::Path>,
-        buf: &mut dyn std::io::Write,
-    ) -> Result<(), std::io::Error> {
-        self.completer().write_complete(cmd, args, current_dir, buf)
-    }
-}
-
-/// A [`ShellCompleter`] for Bash
+/// Bash completion adapter
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct Bash;
 
-/// A [`ShellCompleter`] for Elvish
+/// Elvish completion adapter
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct Elvish;
 
-/// A [`ShellCompleter`] for Fish
+/// Fish completion adapter
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct Fish;
 
-/// A [`ShellCompleter`] for Powershell
+/// Powershell completion adapter
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct Powershell;
 
-/// A [`ShellCompleter`] for zsh
+/// Zsh completion adapter
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub struct Zsh;


Test patch:
diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs
index cc19243c..c5d9267f 100644
--- a/clap_complete/tests/testsuite/bash.rs
+++ b/clap_complete/tests/testsuite/bash.rs
@@ -111,10 +111,10 @@ fn value_terminator() {
     );
 }
 
-#[cfg(feature = "unstable-dynamic")]
+#[cfg(feature = "unstable-command")]
 #[test]
 fn register_minimal() {
-    use clap_complete::dynamic::shells::ShellCompleter as _;
+    use clap_complete::dynamic::shells::CommandCompleter as _;
 
     let name = "my-app";
     let bin = name;
diff --git a/clap_complete/tests/testsuite/common.rs b/clap_complete/tests/testsuite/common.rs
index 29d0f6df..7ebfea68 100644
--- a/clap_complete/tests/testsuite/common.rs
+++ b/clap_complete/tests/testsuite/common.rs
@@ -321,6 +321,7 @@ pub(crate) fn register_example<R: completest::RuntimeBuilder>(context: &str, nam
             // Unconditionally include to avoid completion file tests failing based on the how
             // `cargo test` is invoked
             "--features=unstable-dynamic",
+            "--features=unstable-command",
         ],
     )
     .unwrap();
@@ -379,6 +380,7 @@ where
             // Unconditionally include to avoid completion file tests failing based on the how
             // `cargo test` is invoked
             "--features=unstable-dynamic",
+            "--features=unstable-command",
         ],
     )
     .unwrap();
diff --git a/clap_complete/tests/testsuite/elvish.rs b/clap_complete/tests/testsuite/elvish.rs
index b0130773..9feeb0b4 100644
--- a/clap_complete/tests/testsuite/elvish.rs
+++ b/clap_complete/tests/testsuite/elvish.rs
@@ -175,14 +175,14 @@ value          value
     assert_data_eq!(actual, expected);
 }
 
-#[cfg(all(unix, feature = "unstable-dynamic"))]
+#[cfg(all(unix, feature = "unstable-command"))]
 #[test]
 fn register_dynamic() {
     common::register_example::<completest_pty::ElvishRuntimeBuilder>("dynamic", "exhaustive");
 }
 
 #[test]
-#[cfg(all(unix, feature = "unstable-dynamic"))]
+#[cfg(all(unix, feature = "unstable-command"))]
 fn complete_dynamic() {
     if !common::has_command("elvish") {
         return;
diff --git a/clap_complete/tests/testsuite/fish.rs b/clap_complete/tests/testsuite/fish.rs
index b35458fd..f27db021 100644
--- a/clap_complete/tests/testsuite/fish.rs
+++ b/clap_complete/tests/testsuite/fish.rs
@@ -165,14 +165,14 @@ bash  (bash (shell))  fish  (fish shell)  zsh  (zsh shell)"#;
     assert_data_eq!(actual, expected);
 }
 
-#[cfg(all(unix, feature = "unstable-dynamic"))]
+#[cfg(all(unix, feature = "unstable-command"))]
 #[test]
 fn register_dynamic() {
     common::register_example::<completest_pty::FishRuntimeBuilder>("dynamic", "exhaustive");
 }
 
 #[test]
-#[cfg(all(unix, feature = "unstable-dynamic"))]
+#[cfg(all(unix, feature = "unstable-command"))]
 fn complete_dynamic() {
     if !common::has_command("fish") {
         return;
diff --git a/clap_complete/tests/testsuite/zsh.rs b/clap_complete/tests/testsuite/zsh.rs
index fbfd1ac7..b5abb971 100644
--- a/clap_complete/tests/testsuite/zsh.rs
+++ b/clap_complete/tests/testsuite/zsh.rs
@@ -163,14 +163,14 @@ pacman    action  alias  value  quote  hint  last  --
     assert_data_eq!(actual, expected);
 }
 
-#[cfg(all(unix, feature = "unstable-dynamic"))]
+#[cfg(all(unix, feature = "unstable-command"))]
 #[test]
 fn register_dynamic() {
     common::register_example::<completest_pty::ZshRuntimeBuilder>("dynamic", "exhaustive");
 }
 
 #[test]
-#[cfg(all(unix, feature = "unstable-dynamic"))]
+#[cfg(all(unix, feature = "unstable-command"))]
 fn complete_dynamic() {
     if !common::has_command("zsh") {
         return;