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}