]>
Commit | Line | Data |
---|---|---|
04454e1e FG |
1 | use indexmap::IndexSet; |
2 | ||
3 | // Internal | |
923072b8 FG |
4 | use crate::builder::AppSettings as AS; |
5 | use crate::builder::{Arg, ArgPredicate, Command}; | |
6 | use crate::parser::ArgMatcher; | |
04454e1e FG |
7 | use crate::util::ChildGraph; |
8 | use crate::util::Id; | |
9 | use crate::INTERNAL_ERROR_MSG; | |
10 | ||
11 | pub(crate) struct Usage<'help, 'cmd> { | |
12 | cmd: &'cmd Command<'help>, | |
13 | required: Option<&'cmd ChildGraph<Id>>, | |
14 | } | |
15 | ||
16 | impl<'help, 'cmd> Usage<'help, 'cmd> { | |
17 | pub(crate) fn new(cmd: &'cmd Command<'help>) -> Self { | |
18 | Usage { | |
19 | cmd, | |
20 | required: None, | |
21 | } | |
22 | } | |
23 | ||
24 | pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self { | |
25 | self.required = Some(required); | |
26 | self | |
27 | } | |
28 | ||
29 | // Creates a usage string for display. This happens just after all arguments were parsed, but before | |
30 | // any subcommands have been parsed (so as to give subcommands their own usage recursively) | |
31 | pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> String { | |
32 | debug!("Usage::create_usage_with_title"); | |
33 | let mut usage = String::with_capacity(75); | |
34 | usage.push_str("USAGE:\n "); | |
35 | usage.push_str(&*self.create_usage_no_title(used)); | |
36 | usage | |
37 | } | |
38 | ||
39 | // Creates a usage string (*without title*) if one was not provided by the user manually. | |
40 | pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> String { | |
41 | debug!("Usage::create_usage_no_title"); | |
42 | if let Some(u) = self.cmd.get_override_usage() { | |
43 | String::from(&*u) | |
44 | } else if used.is_empty() { | |
45 | self.create_help_usage(true) | |
46 | } else { | |
47 | self.create_smart_usage(used) | |
48 | } | |
49 | } | |
50 | ||
51 | // Creates a usage string for display in help messages (i.e. not for errors) | |
52 | fn create_help_usage(&self, incl_reqs: bool) -> String { | |
53 | debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs); | |
54 | let mut usage = String::with_capacity(75); | |
55 | let name = self | |
56 | .cmd | |
57 | .get_usage_name() | |
58 | .or_else(|| self.cmd.get_bin_name()) | |
59 | .unwrap_or_else(|| self.cmd.get_name()); | |
60 | usage.push_str(name); | |
61 | let req_string = if incl_reqs { | |
62 | self.get_required_usage_from(&[], None, false) | |
63 | .iter() | |
64 | .fold(String::new(), |a, s| a + " " + s) | |
65 | } else { | |
66 | String::new() | |
67 | }; | |
68 | ||
69 | if self.needs_options_tag() { | |
70 | usage.push_str(" [OPTIONS]"); | |
71 | } | |
72 | ||
73 | let allow_missing_positional = self.cmd.is_allow_missing_positional_set(); | |
74 | if !allow_missing_positional { | |
75 | usage.push_str(&req_string); | |
76 | } | |
77 | ||
78 | let has_last = self.cmd.get_positionals().any(|p| p.is_last_set()); | |
79 | // places a '--' in the usage string if there are args and options | |
80 | // supporting multiple values | |
81 | if self | |
82 | .cmd | |
83 | .get_non_positionals() | |
84 | .any(|o| o.is_multiple_values_set()) | |
85 | && self.cmd.get_positionals().any(|p| !p.is_required_set()) | |
86 | && !(self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set()) | |
87 | && !has_last | |
88 | { | |
89 | usage.push_str(" [--]"); | |
90 | } | |
91 | let not_req_or_hidden = | |
92 | |p: &Arg| (!p.is_required_set() || p.is_last_set()) && !p.is_hide_set(); | |
93 | if self.cmd.get_positionals().any(not_req_or_hidden) { | |
94 | if let Some(args_tag) = self.get_args_tag(incl_reqs) { | |
95 | usage.push_str(&*args_tag); | |
96 | } else { | |
97 | usage.push_str(" [ARGS]"); | |
98 | } | |
99 | if has_last && incl_reqs { | |
100 | let pos = self | |
101 | .cmd | |
102 | .get_positionals() | |
103 | .find(|p| p.is_last_set()) | |
104 | .expect(INTERNAL_ERROR_MSG); | |
105 | debug!("Usage::create_help_usage: '{}' has .last(true)", pos.name); | |
106 | let req = pos.is_required_set(); | |
107 | if req && self.cmd.get_positionals().any(|p| !p.is_required_set()) { | |
108 | usage.push_str(" -- <"); | |
109 | } else if req { | |
110 | usage.push_str(" [--] <"); | |
111 | } else { | |
112 | usage.push_str(" [-- <"); | |
113 | } | |
114 | usage.push_str(&*pos.name_no_brackets()); | |
115 | usage.push('>'); | |
116 | usage.push_str(pos.multiple_str()); | |
117 | if !req { | |
118 | usage.push(']'); | |
119 | } | |
120 | } | |
121 | } | |
122 | ||
123 | if allow_missing_positional { | |
124 | usage.push_str(&req_string); | |
125 | } | |
126 | ||
127 | // incl_reqs is only false when this function is called recursively | |
128 | if self.cmd.has_visible_subcommands() && incl_reqs | |
129 | || self.cmd.is_allow_external_subcommands_set() | |
130 | { | |
131 | let placeholder = self.cmd.get_subcommand_value_name().unwrap_or("SUBCOMMAND"); | |
132 | #[allow(deprecated)] | |
133 | if self.cmd.is_subcommand_negates_reqs_set() | |
134 | || self.cmd.is_args_conflicts_with_subcommands_set() | |
135 | { | |
136 | usage.push_str("\n "); | |
137 | if !self.cmd.is_args_conflicts_with_subcommands_set() { | |
138 | usage.push_str(&*self.create_help_usage(false)); | |
139 | } else { | |
140 | usage.push_str(&*name); | |
141 | } | |
142 | usage.push_str(" <"); | |
143 | usage.push_str(placeholder); | |
144 | usage.push('>'); | |
145 | } else if self.cmd.is_subcommand_required_set() | |
146 | || self.cmd.is_set(AS::SubcommandRequiredElseHelp) | |
147 | { | |
148 | usage.push_str(" <"); | |
149 | usage.push_str(placeholder); | |
150 | usage.push('>'); | |
151 | } else { | |
152 | usage.push_str(" ["); | |
153 | usage.push_str(placeholder); | |
154 | usage.push(']'); | |
155 | } | |
156 | } | |
923072b8 | 157 | let usage = usage.trim().to_owned(); |
04454e1e FG |
158 | debug!("Usage::create_help_usage: usage={}", usage); |
159 | usage | |
160 | } | |
161 | ||
162 | // Creates a context aware usage string, or "smart usage" from currently used | |
163 | // args, and requirements | |
164 | fn create_smart_usage(&self, used: &[Id]) -> String { | |
165 | debug!("Usage::create_smart_usage"); | |
166 | let mut usage = String::with_capacity(75); | |
167 | ||
168 | let r_string = self | |
169 | .get_required_usage_from(used, None, true) | |
170 | .iter() | |
171 | .fold(String::new(), |acc, s| acc + " " + s); | |
172 | ||
173 | usage.push_str( | |
174 | self.cmd | |
175 | .get_usage_name() | |
176 | .or_else(|| self.cmd.get_bin_name()) | |
177 | .unwrap_or_else(|| self.cmd.get_name()), | |
178 | ); | |
179 | usage.push_str(&*r_string); | |
180 | if self.cmd.is_subcommand_required_set() { | |
181 | usage.push_str(" <"); | |
182 | usage.push_str(self.cmd.get_subcommand_value_name().unwrap_or("SUBCOMMAND")); | |
183 | usage.push('>'); | |
184 | } | |
185 | usage.shrink_to_fit(); | |
186 | usage | |
187 | } | |
188 | ||
189 | // Gets the `[ARGS]` tag for the usage string | |
190 | fn get_args_tag(&self, incl_reqs: bool) -> Option<String> { | |
191 | debug!("Usage::get_args_tag; incl_reqs = {:?}", incl_reqs); | |
192 | let mut count = 0; | |
193 | for pos in self | |
194 | .cmd | |
195 | .get_positionals() | |
196 | .filter(|pos| !pos.is_required_set()) | |
197 | .filter(|pos| !pos.is_hide_set()) | |
198 | .filter(|pos| !pos.is_last_set()) | |
199 | { | |
200 | debug!("Usage::get_args_tag:iter:{}", pos.name); | |
201 | let required = self.cmd.groups_for_arg(&pos.id).any(|grp_s| { | |
202 | debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); | |
203 | // if it's part of a required group we don't want to count it | |
204 | self.cmd.get_groups().any(|g| g.required && (g.id == grp_s)) | |
205 | }); | |
206 | if !required { | |
207 | count += 1; | |
208 | debug!( | |
209 | "Usage::get_args_tag:iter: {} Args not required or hidden", | |
210 | count | |
211 | ); | |
212 | } | |
213 | } | |
214 | ||
215 | if !self.cmd.is_dont_collapse_args_in_usage_set() && count > 1 { | |
216 | debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]"); | |
217 | ||
218 | // [ARGS] | |
219 | None | |
220 | } else if count == 1 && incl_reqs { | |
221 | let pos = self | |
222 | .cmd | |
223 | .get_positionals() | |
224 | .find(|pos| { | |
225 | !pos.is_required_set() | |
226 | && !pos.is_hide_set() | |
227 | && !pos.is_last_set() | |
228 | && !self.cmd.groups_for_arg(&pos.id).any(|grp_s| { | |
229 | debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); | |
230 | // if it's part of a required group we don't want to count it | |
231 | self.cmd.get_groups().any(|g| g.required && (g.id == grp_s)) | |
232 | }) | |
233 | }) | |
234 | .expect(INTERNAL_ERROR_MSG); | |
235 | ||
236 | debug!( | |
237 | "Usage::get_args_tag:iter: Exactly one, returning '{}'", | |
238 | pos.name | |
239 | ); | |
240 | ||
241 | Some(format!( | |
242 | " [{}]{}", | |
243 | pos.name_no_brackets(), | |
244 | pos.multiple_str() | |
245 | )) | |
246 | } else if self.cmd.is_dont_collapse_args_in_usage_set() | |
247 | && self.cmd.has_positionals() | |
248 | && incl_reqs | |
249 | { | |
250 | debug!("Usage::get_args_tag:iter: Don't collapse returning all"); | |
251 | Some( | |
252 | self.cmd | |
253 | .get_positionals() | |
254 | .filter(|pos| !pos.is_required_set()) | |
255 | .filter(|pos| !pos.is_hide_set()) | |
256 | .filter(|pos| !pos.is_last_set()) | |
257 | .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())) | |
258 | .collect::<Vec<_>>() | |
259 | .join(""), | |
260 | ) | |
261 | } else if !incl_reqs { | |
262 | debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); | |
263 | let highest_req_pos = self | |
264 | .cmd | |
265 | .get_positionals() | |
266 | .filter_map(|pos| { | |
267 | if pos.is_required_set() && !pos.is_last_set() { | |
268 | Some(pos.index) | |
269 | } else { | |
270 | None | |
271 | } | |
272 | }) | |
273 | .max() | |
274 | .unwrap_or_else(|| Some(self.cmd.get_positionals().count())); | |
275 | Some( | |
276 | self.cmd | |
277 | .get_positionals() | |
278 | .filter(|pos| pos.index <= highest_req_pos) | |
279 | .filter(|pos| !pos.is_required_set()) | |
280 | .filter(|pos| !pos.is_hide_set()) | |
281 | .filter(|pos| !pos.is_last_set()) | |
282 | .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())) | |
283 | .collect::<Vec<_>>() | |
284 | .join(""), | |
285 | ) | |
286 | } else { | |
287 | Some("".into()) | |
288 | } | |
289 | } | |
290 | ||
291 | // Determines if we need the `[OPTIONS]` tag in the usage string | |
292 | fn needs_options_tag(&self) -> bool { | |
293 | debug!("Usage::needs_options_tag"); | |
294 | 'outer: for f in self.cmd.get_non_positionals() { | |
295 | debug!("Usage::needs_options_tag:iter: f={}", f.name); | |
296 | ||
297 | // Don't print `[OPTIONS]` just for help or version | |
298 | if f.long == Some("help") || f.long == Some("version") { | |
299 | debug!("Usage::needs_options_tag:iter Option is built-in"); | |
300 | continue; | |
301 | } | |
302 | ||
303 | if f.is_hide_set() { | |
304 | debug!("Usage::needs_options_tag:iter Option is hidden"); | |
305 | continue; | |
306 | } | |
307 | if f.is_required_set() { | |
308 | debug!("Usage::needs_options_tag:iter Option is required"); | |
309 | continue; | |
310 | } | |
311 | for grp_s in self.cmd.groups_for_arg(&f.id) { | |
312 | debug!("Usage::needs_options_tag:iter:iter: grp_s={:?}", grp_s); | |
313 | if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) { | |
314 | debug!("Usage::needs_options_tag:iter:iter: Group is required"); | |
315 | continue 'outer; | |
316 | } | |
317 | } | |
318 | ||
319 | debug!("Usage::needs_options_tag:iter: [OPTIONS] required"); | |
320 | return true; | |
321 | } | |
322 | ||
323 | debug!("Usage::needs_options_tag: [OPTIONS] not required"); | |
324 | false | |
325 | } | |
326 | ||
327 | // Returns the required args in usage string form by fully unrolling all groups | |
328 | // `incl_last`: should we include args that are Arg::Last? (i.e. `prog [foo] -- [last]). We | |
329 | // can't do that for required usages being built for subcommands because it would look like: | |
330 | // `prog [foo] -- [last] <subcommand>` which is totally wrong. | |
331 | pub(crate) fn get_required_usage_from( | |
332 | &self, | |
333 | incls: &[Id], | |
334 | matcher: Option<&ArgMatcher>, | |
335 | incl_last: bool, | |
923072b8 | 336 | ) -> IndexSet<String> { |
04454e1e FG |
337 | debug!( |
338 | "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}", | |
339 | incls, | |
340 | matcher.is_some(), | |
341 | incl_last | |
342 | ); | |
923072b8 | 343 | let mut ret_val = IndexSet::new(); |
04454e1e FG |
344 | |
345 | let mut unrolled_reqs = IndexSet::new(); | |
346 | ||
347 | let required_owned; | |
348 | let required = if let Some(required) = self.required { | |
349 | required | |
350 | } else { | |
351 | required_owned = self.cmd.required_graph(); | |
352 | &required_owned | |
353 | }; | |
354 | ||
355 | for a in required.iter() { | |
356 | let is_relevant = |(val, req_arg): &(ArgPredicate<'_>, Id)| -> Option<Id> { | |
357 | let required = match val { | |
358 | ArgPredicate::Equals(_) => { | |
359 | if let Some(matcher) = matcher { | |
360 | matcher.check_explicit(a, *val) | |
361 | } else { | |
362 | false | |
363 | } | |
364 | } | |
365 | ArgPredicate::IsPresent => true, | |
366 | }; | |
367 | required.then(|| req_arg.clone()) | |
368 | }; | |
369 | ||
370 | for aa in self.cmd.unroll_arg_requires(is_relevant, a) { | |
371 | // if we don't check for duplicates here this causes duplicate error messages | |
372 | // see https://github.com/clap-rs/clap/issues/2770 | |
373 | unrolled_reqs.insert(aa); | |
374 | } | |
375 | // always include the required arg itself. it will not be enumerated | |
376 | // by unroll_requirements_for_arg. | |
377 | unrolled_reqs.insert(a.clone()); | |
378 | } | |
379 | ||
380 | debug!( | |
381 | "Usage::get_required_usage_from: unrolled_reqs={:?}", | |
382 | unrolled_reqs | |
383 | ); | |
384 | ||
385 | let args_in_groups = self | |
386 | .cmd | |
387 | .get_groups() | |
388 | .filter(|gn| required.contains(&gn.id)) | |
389 | .flat_map(|g| self.cmd.unroll_args_in_group(&g.id)) | |
390 | .collect::<Vec<_>>(); | |
391 | ||
392 | for a in unrolled_reqs | |
393 | .iter() | |
394 | .chain(incls.iter()) | |
395 | .filter(|name| !self.cmd.get_positionals().any(|p| &&p.id == name)) | |
396 | .filter(|name| !self.cmd.get_groups().any(|g| &&g.id == name)) | |
397 | .filter(|name| !args_in_groups.contains(name)) | |
923072b8 FG |
398 | .filter(|name| { |
399 | !(matcher.is_some() | |
400 | && matcher | |
401 | .as_ref() | |
402 | .unwrap() | |
403 | .check_explicit(name, ArgPredicate::IsPresent)) | |
404 | }) | |
04454e1e FG |
405 | { |
406 | debug!("Usage::get_required_usage_from:iter:{:?}", a); | |
407 | let arg = self.cmd.find(a).expect(INTERNAL_ERROR_MSG).to_string(); | |
923072b8 | 408 | ret_val.insert(arg); |
04454e1e FG |
409 | } |
410 | let mut g_vec: Vec<String> = vec![]; | |
411 | for g in unrolled_reqs | |
412 | .iter() | |
413 | .filter(|n| self.cmd.get_groups().any(|g| g.id == **n)) | |
414 | { | |
415 | // don't print requirement for required groups that have an arg. | |
416 | if let Some(m) = matcher { | |
417 | let have_group_entry = self | |
418 | .cmd | |
419 | .unroll_args_in_group(g) | |
420 | .iter() | |
923072b8 | 421 | .any(|arg| m.check_explicit(arg, ArgPredicate::IsPresent)); |
04454e1e FG |
422 | if have_group_entry { |
423 | continue; | |
424 | } | |
425 | } | |
426 | ||
427 | let elem = self.cmd.format_group(g); | |
428 | if !g_vec.contains(&elem) { | |
429 | g_vec.push(elem); | |
430 | } | |
431 | } | |
923072b8 | 432 | ret_val.extend(g_vec); |
04454e1e FG |
433 | |
434 | let mut pvec = unrolled_reqs | |
435 | .iter() | |
436 | .chain(incls.iter()) | |
437 | .filter(|a| self.cmd.get_positionals().any(|p| &&p.id == a)) | |
923072b8 FG |
438 | .filter(|&pos| { |
439 | matcher.map_or(true, |m| !m.check_explicit(pos, ArgPredicate::IsPresent)) | |
440 | }) | |
04454e1e FG |
441 | .filter_map(|pos| self.cmd.find(pos)) |
442 | .filter(|&pos| incl_last || !pos.is_last_set()) | |
443 | .filter(|pos| !args_in_groups.contains(&pos.id)) | |
444 | .map(|pos| (pos.index.unwrap(), pos)) | |
445 | .collect::<Vec<(usize, &Arg)>>(); | |
446 | pvec.sort_by_key(|(ind, _)| *ind); // sort by index | |
447 | ||
448 | for (_, p) in pvec { | |
923072b8 | 449 | debug!("Usage::get_required_usage_from:push:{:?}", p.id); |
04454e1e | 450 | if !args_in_groups.contains(&p.id) { |
923072b8 | 451 | ret_val.insert(p.to_string()); |
04454e1e FG |
452 | } |
453 | } | |
454 | ||
455 | debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val); | |
456 | ret_val | |
457 | } | |
458 | } |