]>
Commit | Line | Data |
---|---|---|
04454e1e FG |
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")`. | |
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`. | |
f25598a0 | 22 | pub fn find_subcommand_with_path<'cmd>(p: &'cmd Command, path: Vec<&str>) -> &'cmd Command { |
04454e1e FG |
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")`. | |
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 | ||
04454e1e FG |
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`]. | |
f25598a0 | 58 | /// Includes `h` and `V` depending on the [`clap::Command`] settings. |
04454e1e FG |
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`]. | |
f25598a0 | 83 | /// Includes `help` and `version` depending on the [`clap::Command`] settings. |
04454e1e FG |
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). | |
f25598a0 FG |
113 | /// Includes `help` and `version` depending on the [`clap::Command`] settings. |
114 | pub fn flags(p: &Command) -> Vec<Arg> { | |
04454e1e FG |
115 | debug!("flags: name={}", p.get_name()); |
116 | p.get_arguments() | |
f25598a0 | 117 | .filter(|a| !a.get_num_args().expect("built").takes_values() && !a.is_positional()) |
04454e1e FG |
118 | .cloned() |
119 | .collect() | |
120 | } | |
121 | ||
f25598a0 FG |
122 | /// Get the possible values for completion |
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 | ||
04454e1e FG |
133 | #[cfg(test)] |
134 | mod tests { | |
135 | use super::*; | |
136 | use clap::Arg; | |
f25598a0 | 137 | use clap::ArgAction; |
04454e1e | 138 | |
f25598a0 | 139 | fn common_app() -> Command { |
04454e1e FG |
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") | |
f25598a0 | 148 | .action(ArgAction::SetTrue) |
04454e1e FG |
149 | .visible_alias("path"), |
150 | ), | |
151 | ) | |
152 | .subcommand(Command::new("hello")) | |
153 | .bin_name("my-cmd") | |
154 | } | |
155 | ||
f25598a0 | 156 | fn built() -> Command { |
04454e1e FG |
157 | let mut cmd = common_app(); |
158 | ||
f25598a0 | 159 | cmd.build(); |
04454e1e FG |
160 | cmd |
161 | } | |
162 | ||
f25598a0 | 163 | fn built_with_version() -> Command { |
04454e1e FG |
164 | let mut cmd = common_app().version("3.0"); |
165 | ||
f25598a0 | 166 | cmd.build(); |
04454e1e FG |
167 | cmd |
168 | } | |
169 | ||
170 | #[test] | |
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] | |
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()), | |
f25598a0 FG |
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()), | |
04454e1e FG |
202 | ] |
203 | ); | |
204 | } | |
205 | ||
206 | #[test] | |
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] | |
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] | |
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] | |
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] | |
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 | } |