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