clap_complete_command/
lib.rs

1//! # Examples
2//!
3//! ## Derive
4//!
5//! ```no_run
6//! use clap::{CommandFactory, Parser, Subcommand};
7//!
8//! #[derive(Parser)]
9//! struct Cli {
10//!     #[command(subcommand)]
11//!     command: Commands,
12//! }
13//!
14//! #[derive(Subcommand)]
15//! enum Commands {
16//!     /// Generate shell completions
17//!     Completions {
18//!         /// The shell to generate the completions for
19//!         #[arg(value_enum)]
20//!         shell: clap_complete_command::Shell,
21//!     },
22//! }
23//!
24//! let cli = Cli::parse();
25//!
26//! match cli.command {
27//!     // e.g. `$ cli completions bash`
28//!     Commands::Completions { shell } => {
29//!         shell.generate(&mut Cli::command(), &mut std::io::stdout());
30//!     }
31//! }
32//! ```
33//!
34//! ## Builder
35//!
36//! ```no_run
37//! use clap::{Arg, Command};
38//!
39//! fn build_cli() -> Command {
40//!     Command::new(env!("CARGO_PKG_NAME"))
41//!         .subcommand_required(true)
42//!         .subcommand(
43//!             Command::new("completions")
44//!                 .about("Generate shell completions")
45//!                 .arg(
46//!                     Arg::new("shell")
47//!                         .value_name("SHELL")
48//!                         .help("The shell to generate the completions for")
49//!                         .required(true)
50//!                         .value_parser(
51//!                             clap::builder::EnumValueParser::<clap_complete_command::Shell>::new(),
52//!                         ),
53//!                 ),
54//!         )
55//! }
56//!
57//! let matches = build_cli().get_matches();
58//!
59//! match matches.subcommand() {
60//!     Some(("completions", sub_matches)) => {
61//!         // e.g. `$ cli completions bash`
62//!         if let Some(shell) = sub_matches.get_one::<clap_complete_command::Shell>("shell") {
63//!             let mut command = build_cli();
64//!             shell.generate(&mut command, &mut std::io::stdout());
65//!         }
66//!     }
67//!     _ => {
68//!         unreachable!("Exhausted list of subcommands and `subcommand_required` prevents `None`")
69//!     }
70//! }
71//! ```
72
73#![cfg_attr(docsrs, feature(doc_auto_cfg))]
74
75#![warn(clippy::cast_lossless)]
76#![warn(clippy::cast_possible_wrap)]
77#![warn(clippy::default_trait_access)]
78#![warn(clippy::else_if_without_else)]
79#![warn(clippy::empty_enum)]
80#![warn(clippy::empty_line_after_outer_attr)]
81#![warn(clippy::enum_glob_use)]
82#![warn(clippy::equatable_if_let)]
83#![warn(clippy::float_cmp)]
84#![warn(clippy::fn_params_excessive_bools)]
85#![warn(clippy::get_unwrap)]
86#![warn(clippy::inefficient_to_string)]
87#![warn(clippy::integer_division)]
88#![warn(clippy::let_unit_value)]
89#![warn(clippy::linkedlist)]
90#![warn(clippy::lossy_float_literal)]
91#![warn(clippy::macro_use_imports)]
92#![warn(clippy::manual_assert)]
93#![warn(clippy::manual_ok_or)]
94#![warn(clippy::many_single_char_names)]
95#![warn(clippy::map_unwrap_or)]
96#![warn(clippy::match_bool)]
97#![warn(clippy::match_on_vec_items)]
98#![warn(clippy::match_same_arms)]
99#![warn(clippy::match_wild_err_arm)]
100#![warn(clippy::match_wildcard_for_single_variants)]
101#![warn(clippy::mem_forget)]
102#![warn(clippy::missing_const_for_fn)]
103#![warn(clippy::must_use_candidate)]
104#![warn(clippy::mut_mut)]
105#![warn(clippy::negative_feature_names)]
106#![warn(non_ascii_idents)]
107#![warn(clippy::option_option)]
108#![warn(clippy::redundant_feature_names)]
109#![warn(clippy::redundant_pub_crate)]
110#![warn(clippy::single_match_else)]
111#![warn(clippy::str_to_string)]
112#![warn(clippy::string_to_string)]
113#![warn(clippy::trait_duplication_in_bounds)]
114#![warn(clippy::unused_async)]
115#![warn(clippy::unused_self)]
116#![warn(clippy::use_self)]
117#![warn(clippy::wildcard_dependencies)]
118#![warn(clippy::wildcard_imports)]
119#![warn(clippy::zero_sized_map_values)]
120
121use std::{ffi::OsString, path::PathBuf};
122
123use clap::ValueEnum;
124
125/// A [`clap::ValueEnum`] for available shell completions.
126///
127/// # Examples
128///
129/// ## Derive
130///
131/// ```no_run
132/// use clap::{Parser, Subcommand};
133///
134/// #[derive(Parser)]
135/// struct Cli {
136///     #[command(subcommand)]
137///     command: Commands,
138/// }
139///
140/// #[derive(Subcommand)]
141/// enum Commands {
142///     Completions {
143///         #[arg(value_enum)]
144///         shell: clap_complete_command::Shell,
145///     },
146/// }
147/// ```
148///
149/// ## Builder
150///
151/// ```no_run
152/// use clap::{Arg, Command};
153///
154/// fn build_cli() -> Command {
155///     Command::new(env!("CARGO_PKG_NAME"))
156///         .subcommand_required(true)
157///         .subcommand(
158///             Command::new("completions")
159///                 .about("Generate shell completions")
160///                 .arg(
161///                     Arg::new("shell")
162///                         .value_name("SHELL")
163///                         .help("The shell to generate the completions for")
164///                         .required(true)
165///                         .value_parser(
166///                             clap::builder::EnumValueParser::<clap_complete_command::Shell>::new(),
167///                         ),
168///                 ),
169///         )
170/// }
171///
172/// let matches = build_cli().get_matches();
173///
174/// match matches.subcommand() {
175///     Some(("completions", sub_matches)) => {
176///         if let Some(shell) = sub_matches.get_one::<clap_complete_command::Shell>("shell") {
177///             // ...
178///         }
179///     }
180///     _ => {
181///         unreachable!("Exhausted list of subcommands and `subcommand_required` prevents `None`")
182///     }
183/// }
184/// ```
185#[derive(Clone, Copy, Debug)]
186#[non_exhaustive]
187pub enum Shell {
188    /// Bourne Again SHell (bash)
189    Bash,
190    /// Carapace spec
191    #[cfg(feature = "carapace")]
192    Carapace,
193    /// Elvish shell
194    Elvish,
195    /// Fig
196    #[cfg(feature = "fig")]
197    Fig,
198    /// Friendly Interactive SHell (fish)
199    Fish,
200    /// NUshell (nu)
201    #[cfg(feature = "nushell")]
202    Nu,
203    /// PowerShell
204    PowerShell,
205    /// Z SHell (zsh)
206    Zsh,
207}
208
209impl clap_complete::Generator for Shell {
210    fn file_name(&self, name: &str) -> String {
211        match self {
212            Self::Bash => clap_complete::Shell::Bash.file_name(name),
213            Self::Elvish => clap_complete::Shell::Elvish.file_name(name),
214            Self::Fish => clap_complete::Shell::Fish.file_name(name),
215            Self::PowerShell => clap_complete::Shell::PowerShell.file_name(name),
216            Self::Zsh => clap_complete::Shell::Zsh.file_name(name),
217
218            #[cfg(feature = "carapace")]
219            Self::Carapace => carapace_spec_clap::Spec.file_name(name),
220            #[cfg(feature = "fig")]
221            Self::Fig => clap_complete_fig::Fig.file_name(name),
222            #[cfg(feature = "nushell")]
223            Self::Nu => clap_complete_nushell::Nushell.file_name(name),
224        }
225    }
226
227    fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
228        match self {
229            Self::Bash => clap_complete::Shell::Bash.generate(cmd, buf),
230            Self::Elvish => clap_complete::Shell::Elvish.generate(cmd, buf),
231            Self::Fish => clap_complete::Shell::Fish.generate(cmd, buf),
232            Self::PowerShell => clap_complete::Shell::PowerShell.generate(cmd, buf),
233            Self::Zsh => clap_complete::Shell::Zsh.generate(cmd, buf),
234
235            #[cfg(feature = "carapace")]
236            Self::Carapace => carapace_spec_clap::Spec.generate(cmd, buf),
237            #[cfg(feature = "fig")]
238            Self::Fig => clap_complete_fig::Fig.generate(cmd, buf),
239            #[cfg(feature = "nushell")]
240            Self::Nu => clap_complete_nushell::Nushell.generate(cmd, buf),
241        }
242    }
243}
244
245impl Shell {
246    /// See [`clap_complete::generate()`].
247    ///
248    /// The `command`'s bin name is used as the completion's bin name.
249    /// If the `command`'s bin name is not set, it will be set to the `command`'s name.
250    pub fn generate(self, command: &mut clap::Command, buffer: &mut dyn std::io::Write) {
251        let bin_name = command
252            .get_bin_name()
253            .unwrap_or_else(|| command.get_name())
254            .to_owned();
255        clap_complete::generate(self, command, bin_name, buffer)
256    }
257
258    /// See [`clap_complete::generate_to()`].
259    ///
260    /// The `command`'s bin name is used as the completion's bin name.
261    /// If the `command`'s bin name is not set, it will be set to the `command`'s name.
262    pub fn generate_to<S>(
263        self,
264        command: &mut clap::Command,
265        out_dir: S,
266    ) -> Result<PathBuf, std::io::Error>
267    where
268        S: Into<OsString>,
269    {
270        let bin_name = command
271            .get_bin_name()
272            .unwrap_or_else(|| command.get_name())
273            .to_owned();
274        clap_complete::generate_to(self, command, bin_name, out_dir)
275    }
276}
277
278// Hand-rolled to avoid depending on Clap's `derive` feature
279impl ValueEnum for Shell {
280    fn value_variants<'a>() -> &'a [Self] {
281        &[
282            Self::Bash,
283            #[cfg(feature = "carapace")]
284            Self::Carapace,
285            Self::Elvish,
286            #[cfg(feature = "fig")]
287            Self::Fig,
288            Self::Fish,
289            #[cfg(feature = "nushell")]
290            Self::Nu,
291            Self::PowerShell,
292            Self::Zsh,
293        ]
294    }
295
296    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
297        Some(match self {
298            Self::Bash => clap::builder::PossibleValue::new("bash"),
299            #[cfg(feature = "carapace")]
300            Self::Carapace => clap::builder::PossibleValue::new("carapace"),
301            Self::Elvish => clap::builder::PossibleValue::new("elvish"),
302            #[cfg(feature = "fig")]
303            Self::Fig => clap::builder::PossibleValue::new("fig"),
304            Self::Fish => clap::builder::PossibleValue::new("fish"),
305            #[cfg(feature = "nushell")]
306            Self::Nu => clap::builder::PossibleValue::new("nushell"),
307            Self::PowerShell => clap::builder::PossibleValue::new("powershell"),
308            Self::Zsh => clap::builder::PossibleValue::new("zsh"),
309        })
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use clap::ValueEnum;
317
318    macro_rules! check_shell_value_test {
319        ($test_name:ident, $shell:expr, $value:expr) => {
320            #[test]
321            fn $test_name() {
322                assert_eq!($shell.to_possible_value().unwrap().get_name(), $value);
323            }
324        };
325    }
326
327    check_shell_value_test!(test_shell_value_bash, Shell::Bash, "bash");
328    #[cfg(feature = "carapace")]
329    check_shell_value_test!(test_shell_value_carapace, Shell::Carapace, "carapace");
330    check_shell_value_test!(test_shell_value_elvish, Shell::Elvish, "elvish");
331    #[cfg(feature = "fig")]
332    check_shell_value_test!(test_shell_value_fig, Shell::Fig, "fig");
333    check_shell_value_test!(test_shell_value_fish, Shell::Fish, "fish");
334    #[cfg(feature = "nushell")]
335    check_shell_value_test!(test_shell_value_nushell, Shell::Nu, "nushell");
336    check_shell_value_test!(test_shell_value_powershell, Shell::PowerShell, "powershell");
337    check_shell_value_test!(test_shell_value_zsh, Shell::Zsh, "zsh");
338
339    #[test]
340    fn check_order() {
341        let names = Shell::value_variants()
342            .iter()
343            .map(|shell| shell.to_possible_value().unwrap().get_name().to_owned())
344            .collect::<Vec<_>>();
345
346        let mut sorted = names.clone();
347        sorted.sort_unstable();
348
349        assert_eq!(names, sorted);
350
351        let correct_order = [
352            ("bash", true),
353            ("carapace", cfg!(feature = "carapace")),
354            ("elvish", true),
355            ("fig", cfg!(feature = "fig")),
356            ("fish", true),
357            ("nushell", cfg!(feature = "nushell")),
358            ("powershell", true),
359            ("zsh", true),
360        ]
361        .iter()
362        .filter(|(_, enabled)| *enabled)
363        .map(|(shell, _)| *shell)
364        .collect::<Vec<_>>();
365
366        assert_eq!(names, correct_order);
367    }
368}