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