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 | /// ``` |
37 | fn file_name(&self, name: &str) -> String; |
38 | |
39 | /// Generates output out of [`clap::Command`](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`](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 | /// ``` |
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 | /// ``` |
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 = PathBuf::from(out_dir.into()); |
205 | let file_name: String = gen.file_name(cmd.get_bin_name().unwrap()); |
206 | |
207 | let path: PathBuf = out_dir.join(path:file_name); |
208 | let mut file: 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 | /// ``` |
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 | |
258 | fn _generate<G: Generator>(gen: G, cmd: &mut Command, buf: &mut dyn Write) { |
259 | cmd.build(); |
260 | gen.generate(cmd, buf) |
261 | } |
262 | |