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