clap_complete/generator/
utils.rs

1//! Helpers for writing generators
2
3use clap::{Arg, Command};
4
5/// Gets all subcommands including child subcommands in the form of `("name", "bin_name")`.
6///
7/// Subcommand `rustup toolchain install` would be converted to
8/// `("install", "rustup toolchain install")`.
9pub fn all_subcommands(cmd: &Command) -> Vec<(String, String)> {
10    let mut subcmds: Vec<_> = subcommands(cmd);
11
12    for sc_v in cmd.get_subcommands().map(all_subcommands) {
13        subcmds.extend(sc_v);
14    }
15
16    subcmds
17}
18
19/// Finds the subcommand [`clap::Command`] from the given [`clap::Command`] with the given path.
20///
21/// **NOTE:** `path` should not contain the root `bin_name`.
22pub fn find_subcommand_with_path<'cmd>(p: &'cmd Command, path: Vec<&str>) -> &'cmd Command {
23    let mut cmd = p;
24
25    for sc in path {
26        cmd = cmd.find_subcommand(sc).unwrap();
27    }
28
29    cmd
30}
31
32/// Gets subcommands of [`clap::Command`] in the form of `("name", "bin_name")`.
33///
34/// Subcommand `rustup toolchain install` would be converted to
35/// `("install", "rustup toolchain install")`.
36pub fn subcommands(p: &Command) -> Vec<(String, String)> {
37    debug!("subcommands: name={}", p.get_name());
38    debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
39
40    let mut subcmds = vec![];
41
42    for sc in p.get_subcommands() {
43        let sc_bin_name = sc.get_bin_name().unwrap();
44
45        debug!(
46            "subcommands:iter: name={}, bin_name={}",
47            sc.get_name(),
48            sc_bin_name
49        );
50        subcmds.push((sc.get_name().to_string(), sc_bin_name.to_string()));
51
52        for alias in sc.get_visible_aliases() {
53            debug!(
54                "subcommands:iter: alias={}, bin_name={}",
55                alias, sc_bin_name
56            );
57            subcmds.push((alias.to_string(), sc_bin_name.to_string()));
58        }
59    }
60
61    subcmds
62}
63
64/// Gets all the short options, their visible aliases and flags of a [`clap::Command`].
65/// Includes `h` and `V` depending on the [`clap::Command`] settings.
66pub fn shorts_and_visible_aliases(p: &Command) -> Vec<char> {
67    debug!("shorts: name={}", p.get_name());
68
69    p.get_arguments()
70        .filter_map(|a| {
71            if !a.is_positional() {
72                if a.get_visible_short_aliases().is_some() && a.get_short().is_some() {
73                    let mut shorts_and_visible_aliases = a.get_visible_short_aliases().unwrap();
74                    shorts_and_visible_aliases.push(a.get_short().unwrap());
75                    Some(shorts_and_visible_aliases)
76                } else if a.get_visible_short_aliases().is_none() && a.get_short().is_some() {
77                    Some(vec![a.get_short().unwrap()])
78                } else {
79                    None
80                }
81            } else {
82                None
83            }
84        })
85        .flatten()
86        .collect()
87}
88
89/// Gets all the long options, their visible aliases and flags of a [`clap::Command`].
90/// Includes `help` and `version` depending on the [`clap::Command`] settings.
91pub fn longs_and_visible_aliases(p: &Command) -> Vec<String> {
92    debug!("longs: name={}", p.get_name());
93
94    p.get_arguments()
95        .filter_map(|a| {
96            if !a.is_positional() {
97                if a.get_visible_aliases().is_some() && a.get_long().is_some() {
98                    let mut visible_aliases: Vec<_> = a
99                        .get_visible_aliases()
100                        .unwrap()
101                        .into_iter()
102                        .map(|s| s.to_string())
103                        .collect();
104                    visible_aliases.push(a.get_long().unwrap().to_string());
105                    Some(visible_aliases)
106                } else if a.get_visible_aliases().is_none() && a.get_long().is_some() {
107                    Some(vec![a.get_long().unwrap().to_string()])
108                } else {
109                    None
110                }
111            } else {
112                None
113            }
114        })
115        .flatten()
116        .collect()
117}
118
119/// Gets all the flags of a [`clap::Command`].
120/// Includes `help` and `version` depending on the [`clap::Command`] settings.
121pub fn flags(p: &Command) -> Vec<Arg> {
122    debug!("flags: name={}", p.get_name());
123    p.get_arguments()
124        .filter(|a| !a.get_num_args().expect("built").takes_values() && !a.is_positional())
125        .cloned()
126        .collect()
127}
128
129/// Get the possible values for completion
130pub fn possible_values(a: &Arg) -> Option<Vec<clap::builder::PossibleValue>> {
131    if !a.get_num_args().expect("built").takes_values() {
132        None
133    } else {
134        a.get_value_parser()
135            .possible_values()
136            .map(|pvs| pvs.collect())
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use clap::Arg;
144    use clap::ArgAction;
145
146    fn common_app() -> Command {
147        Command::new("myapp")
148            .subcommand(
149                Command::new("test").subcommand(Command::new("config")).arg(
150                    Arg::new("file")
151                        .short('f')
152                        .short_alias('c')
153                        .visible_short_alias('p')
154                        .long("file")
155                        .action(ArgAction::SetTrue)
156                        .visible_alias("path"),
157                ),
158            )
159            .subcommand(Command::new("hello"))
160            .bin_name("my-cmd")
161    }
162
163    fn built() -> Command {
164        let mut cmd = common_app();
165
166        cmd.build();
167        cmd
168    }
169
170    fn built_with_version() -> Command {
171        let mut cmd = common_app().version("3.0");
172
173        cmd.build();
174        cmd
175    }
176
177    #[test]
178    fn test_subcommands() {
179        let cmd = built_with_version();
180
181        assert_eq!(
182            subcommands(&cmd),
183            vec![
184                ("test".to_string(), "my-cmd test".to_string()),
185                ("hello".to_string(), "my-cmd hello".to_string()),
186                ("help".to_string(), "my-cmd help".to_string()),
187            ]
188        );
189    }
190
191    #[test]
192    fn test_all_subcommands() {
193        let cmd = built_with_version();
194
195        assert_eq!(
196            all_subcommands(&cmd),
197            vec![
198                ("test".to_string(), "my-cmd test".to_string()),
199                ("hello".to_string(), "my-cmd hello".to_string()),
200                ("help".to_string(), "my-cmd help".to_string()),
201                ("config".to_string(), "my-cmd test config".to_string()),
202                ("help".to_string(), "my-cmd test help".to_string()),
203                ("config".to_string(), "my-cmd test help config".to_string()),
204                ("help".to_string(), "my-cmd test help help".to_string()),
205                ("test".to_string(), "my-cmd help test".to_string()),
206                ("hello".to_string(), "my-cmd help hello".to_string()),
207                ("help".to_string(), "my-cmd help help".to_string()),
208                ("config".to_string(), "my-cmd help test config".to_string()),
209            ]
210        );
211    }
212
213    #[test]
214    fn test_find_subcommand_with_path() {
215        let cmd = built_with_version();
216        let sc_app = find_subcommand_with_path(&cmd, "test config".split(' ').collect());
217
218        assert_eq!(sc_app.get_name(), "config");
219    }
220
221    #[test]
222    fn test_flags() {
223        let cmd = built_with_version();
224        let actual_flags = flags(&cmd);
225
226        assert_eq!(actual_flags.len(), 2);
227        assert_eq!(actual_flags[0].get_long(), Some("help"));
228        assert_eq!(actual_flags[1].get_long(), Some("version"));
229
230        let sc_flags = flags(find_subcommand_with_path(&cmd, vec!["test"]));
231
232        assert_eq!(sc_flags.len(), 2);
233        assert_eq!(sc_flags[0].get_long(), Some("file"));
234        assert_eq!(sc_flags[1].get_long(), Some("help"));
235    }
236
237    #[test]
238    fn test_flag_subcommand() {
239        let cmd = built();
240        let actual_flags = flags(&cmd);
241
242        assert_eq!(actual_flags.len(), 1);
243        assert_eq!(actual_flags[0].get_long(), Some("help"));
244
245        let sc_flags = flags(find_subcommand_with_path(&cmd, vec!["test"]));
246
247        assert_eq!(sc_flags.len(), 2);
248        assert_eq!(sc_flags[0].get_long(), Some("file"));
249        assert_eq!(sc_flags[1].get_long(), Some("help"));
250    }
251
252    #[test]
253    fn test_shorts() {
254        let cmd = built_with_version();
255        let shorts = shorts_and_visible_aliases(&cmd);
256
257        assert_eq!(shorts.len(), 2);
258        assert_eq!(shorts[0], 'h');
259        assert_eq!(shorts[1], 'V');
260
261        let sc_shorts = shorts_and_visible_aliases(find_subcommand_with_path(&cmd, vec!["test"]));
262
263        assert_eq!(sc_shorts.len(), 3);
264        assert_eq!(sc_shorts[0], 'p');
265        assert_eq!(sc_shorts[1], 'f');
266        assert_eq!(sc_shorts[2], 'h');
267    }
268
269    #[test]
270    fn test_longs() {
271        let cmd = built_with_version();
272        let longs = longs_and_visible_aliases(&cmd);
273
274        assert_eq!(longs.len(), 2);
275        assert_eq!(longs[0], "help");
276        assert_eq!(longs[1], "version");
277
278        let sc_longs = longs_and_visible_aliases(find_subcommand_with_path(&cmd, vec!["test"]));
279
280        assert_eq!(sc_longs.len(), 3);
281        assert_eq!(sc_longs[0], "path");
282        assert_eq!(sc_longs[1], "file");
283        assert_eq!(sc_longs[2], "help");
284    }
285}