1 //! Shell completion machinery
2 
3 pub mod utils;
4 
5 use std::ffi::OsString;
6 use std::fs::File;
7 use std::io::Error;
8 use std::io::Write;
9 use std::path::PathBuf;
10 
11 use clap::Command;
12 
13 /// Generator trait which can be used to write generators
14 pub trait Generator {
15     /// Returns the file name that is created when this generator is called during compile time.
16     ///
17     /// # Panics
18     ///
19     /// May panic when called outside of the context of [`generate`] or [`generate_to`]
20     ///
21     /// # Examples
22     ///
23     /// ```
24     /// # use std::io::Write;
25     /// # use clap::Command;
26     /// use clap_complete::Generator;
27     ///
28     /// pub struct Fish;
29     ///
30     /// impl Generator for Fish {
31     ///     fn file_name(&self, name: &str) -> String {
32     ///         format!("{name}.fish")
33     ///     }
34     /// #   fn generate(&self, cmd: &Command, buf: &mut dyn Write) {}
35     /// }
36     /// ```
file_name(&self, name: &str) -> String37     fn file_name(&self, name: &str) -> String;
38 
39     /// Generates output out of [`clap::Command`].
40     ///
41     /// # Panics
42     ///
43     /// May panic when called outside of the context of [`generate`] or [`generate_to`]
44     ///
45     /// # Examples
46     ///
47     /// The following example generator displays the [`clap::Command`]
48     /// as if it is printed using [`std::println`].
49     ///
50     /// ```
51     /// use std::{io::Write, fmt::write};
52     /// use clap::Command;
53     /// use clap_complete::Generator;
54     ///
55     /// pub struct ClapDebug;
56     ///
57     /// impl Generator for ClapDebug {
58     /// #   fn file_name(&self, name: &str) -> String {
59     /// #       name.into()
60     /// #   }
61     ///     fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
62     ///         write!(buf, "{cmd}").unwrap();
63     ///     }
64     /// }
65     /// ```
generate(&self, cmd: &Command, buf: &mut dyn Write)66     fn generate(&self, cmd: &Command, buf: &mut dyn Write);
67 }
68 
69 /// Generate a completions file for a specified shell at compile-time.
70 ///
71 /// **NOTE:** to generate the file at compile time you must use a `build.rs` "Build Script" or a
72 /// [`cargo-xtask`](https://github.com/matklad/cargo-xtask)
73 ///
74 /// # Examples
75 ///
76 /// The following example generates a bash completion script via a `build.rs` script. In this
77 /// simple example, we'll demo a very small application with only a single subcommand and two
78 /// args. Real applications could be many multiple levels deep in subcommands, and have tens or
79 /// potentially hundreds of arguments.
80 ///
81 /// First, it helps if we separate out our `Command` definition into a separate file. Whether you
82 /// do this as a function, or bare Command definition is a matter of personal preference.
83 ///
84 /// ```
85 /// // src/cli.rs
86 /// # use clap::{Command, Arg, ArgAction};
87 /// pub fn build_cli() -> Command {
88 ///     Command::new("compl")
89 ///         .about("Tests completions")
90 ///         .arg(Arg::new("file")
91 ///             .help("some input file"))
92 ///         .subcommand(Command::new("test")
93 ///             .about("tests things")
94 ///             .arg(Arg::new("case")
95 ///                 .long("case")
96 ///                 .action(ArgAction::Set)
97 ///                 .help("the case to test")))
98 /// }
99 /// ```
100 ///
101 /// In our regular code, we can simply call this `build_cli()` function, then call
102 /// `get_matches()`, or any of the other normal methods directly after. For example:
103 ///
104 /// ```ignore
105 /// // src/main.rs
106 ///
107 /// mod cli;
108 ///
109 /// fn main() {
110 ///     let _m = cli::build_cli().get_matches();
111 ///
112 ///     // normal logic continues...
113 /// }
114 /// ```
115 ///
116 /// Next, we set up our `Cargo.toml` to use a `build.rs` build script.
117 ///
118 /// ```toml
119 /// # Cargo.toml
120 /// build = "build.rs"
121 ///
122 /// [dependencies]
123 /// clap = "*"
124 ///
125 /// [build-dependencies]
126 /// clap = "*"
127 /// clap_complete = "*"
128 /// ```
129 ///
130 /// Next, we place a `build.rs` in our project root.
131 ///
132 /// ```ignore
133 /// use clap_complete::{generate_to, shells::Bash};
134 /// use std::env;
135 /// use std::io::Error;
136 ///
137 /// include!("src/cli.rs");
138 ///
139 /// fn main() -> Result<(), Error> {
140 ///     let outdir = match env::var_os("OUT_DIR") {
141 ///         None => return Ok(()),
142 ///         Some(outdir) => outdir,
143 ///     };
144 ///
145 ///     let mut cmd = build_cli();
146 ///     let path = generate_to(
147 ///         Bash,
148 ///         &mut cmd, // We need to specify what generator to use
149 ///         "myapp",  // We need to specify the bin name manually
150 ///         outdir,   // We need to specify where to write to
151 ///     )?;
152 ///
153 ///     println!("cargo:warning=completion file is generated: {path:?}");
154 ///
155 ///     Ok(())
156 /// }
157 /// ```
158 ///
159 /// Now, once we compile there will be a `{bin_name}.bash` file in the directory.
160 /// Assuming we compiled with debug mode, it would be somewhere similar to
161 /// `<project>/target/debug/build/myapp-<hash>/out/myapp.bash`.
162 ///
163 /// **NOTE:** Please look at the individual [shells][crate::shells]
164 /// to see the name of the files generated.
165 ///
166 /// Using [`ValueEnum::value_variants()`][clap::ValueEnum::value_variants] you can easily loop over
167 /// all the supported shell variants to generate all the completions at once too.
168 ///
169 /// ```ignore
170 /// use clap::ValueEnum;
171 /// use clap_complete::{generate_to, Shell};
172 /// use std::env;
173 /// use std::io::Error;
174 ///
175 /// include!("src/cli.rs");
176 ///
177 /// fn main() -> Result<(), Error> {
178 ///     let outdir = match env::var_os("OUT_DIR") {
179 ///         None => return Ok(()),
180 ///         Some(outdir) => outdir,
181 ///     };
182 ///
183 ///     let mut cmd = build_cli();
184 ///     for &shell in Shell::value_variants() {
185 ///         generate_to(shell, &mut cmd, "myapp", outdir)?;
186 ///     }
187 ///
188 ///     Ok(())
189 /// }
190 /// ```
generate_to<G, S, T>( gen: G, cmd: &mut Command, bin_name: S, out_dir: T, ) -> Result<PathBuf, Error> where G: Generator, S: Into<String>, T: Into<OsString>,191 pub fn generate_to<G, S, T>(
192     gen: G,
193     cmd: &mut Command,
194     bin_name: S,
195     out_dir: T,
196 ) -> Result<PathBuf, Error>
197 where
198     G: Generator,
199     S: Into<String>,
200     T: Into<OsString>,
201 {
202     cmd.set_bin_name(bin_name);
203 
204     let out_dir = PathBuf::from(out_dir.into());
205     let file_name = gen.file_name(cmd.get_bin_name().unwrap());
206 
207     let path = out_dir.join(file_name);
208     let mut file = File::create(&path)?;
209 
210     _generate::<G>(gen, cmd, &mut file);
211     Ok(path)
212 }
213 
214 /// Generate a completions file for a specified shell at runtime.
215 ///
216 /// Until `cargo install` can install extra files like a completion script, this may be
217 /// used e.g. in a command that outputs the contents of the completion script, to be
218 /// redirected into a file by the user.
219 ///
220 /// # Examples
221 ///
222 /// Assuming a separate `cli.rs` like the [`generate_to` example](generate_to()),
223 /// we can let users generate a completion script using a command:
224 ///
225 /// ```ignore
226 /// // src/main.rs
227 ///
228 /// mod cli;
229 /// use std::io;
230 /// use clap_complete::{generate, shells::Bash};
231 ///
232 /// fn main() {
233 ///     let matches = cli::build_cli().get_matches();
234 ///
235 ///     if matches.is_present("generate-bash-completions") {
236 ///         generate(Bash, &mut cli::build_cli(), "myapp", &mut io::stdout());
237 ///     }
238 ///
239 ///     // normal logic continues...
240 /// }
241 ///
242 /// ```
243 ///
244 /// Usage:
245 ///
246 /// ```console
247 /// $ myapp generate-bash-completions > /usr/share/bash-completion/completions/myapp.bash
248 /// ```
generate<G, S>(gen: G, cmd: &mut Command, bin_name: S, buf: &mut dyn Write) where G: Generator, S: Into<String>,249 pub fn generate<G, S>(gen: G, cmd: &mut Command, bin_name: S, buf: &mut dyn Write)
250 where
251     G: Generator,
252     S: Into<String>,
253 {
254     cmd.set_bin_name(bin_name);
255     _generate::<G>(gen, cmd, buf);
256 }
257 
_generate<G: Generator>(gen: G, cmd: &mut Command, buf: &mut dyn Write)258 fn _generate<G: Generator>(gen: G, cmd: &mut Command, buf: &mut dyn Write) {
259     cmd.build();
260     gen.generate(cmd, buf);
261 }
262