]>
Commit | Line | Data |
---|---|---|
add651ee FG |
1 | #![cfg_attr(not(feature = "usage"), allow(unused_imports))] |
2 | #![cfg_attr(not(feature = "usage"), allow(unused_variables))] | |
3 | #![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))] | |
4 | #![cfg_attr(not(feature = "usage"), allow(dead_code))] | |
5 | ||
6 | // Internal | |
7 | use crate::builder::StyledStr; | |
8 | use crate::builder::Styles; | |
9 | use crate::builder::{ArgPredicate, Command}; | |
10 | use crate::parser::ArgMatcher; | |
11 | use crate::util::ChildGraph; | |
12 | use crate::util::FlatSet; | |
13 | use crate::util::Id; | |
14 | ||
15 | static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND"; | |
16 | ||
17 | pub(crate) struct Usage<'cmd> { | |
18 | cmd: &'cmd Command, | |
19 | styles: &'cmd Styles, | |
20 | required: Option<&'cmd ChildGraph<Id>>, | |
21 | } | |
22 | ||
23 | impl<'cmd> Usage<'cmd> { | |
24 | pub(crate) fn new(cmd: &'cmd Command) -> Self { | |
25 | Usage { | |
26 | cmd, | |
27 | styles: cmd.get_styles(), | |
28 | required: None, | |
29 | } | |
30 | } | |
31 | ||
32 | pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self { | |
33 | self.required = Some(required); | |
34 | self | |
35 | } | |
36 | ||
37 | // Creates a usage string for display. This happens just after all arguments were parsed, but before | |
38 | // any subcommands have been parsed (so as to give subcommands their own usage recursively) | |
39 | pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> { | |
40 | debug!("Usage::create_usage_with_title"); | |
41 | let usage = some!(self.create_usage_no_title(used)); | |
42 | ||
43 | use std::fmt::Write as _; | |
44 | let mut styled = StyledStr::new(); | |
45 | let _ = write!( | |
46 | styled, | |
47 | "{}Usage:{} ", | |
48 | self.styles.get_usage().render(), | |
49 | self.styles.get_usage().render_reset() | |
50 | ); | |
51 | styled.push_styled(&usage); | |
52 | Some(styled) | |
53 | } | |
54 | ||
55 | // Creates a usage string (*without title*) if one was not provided by the user manually. | |
56 | pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> { | |
57 | debug!("Usage::create_usage_no_title"); | |
58 | if let Some(u) = self.cmd.get_override_usage() { | |
59 | Some(u.clone()) | |
60 | } else { | |
61 | #[cfg(feature = "usage")] | |
62 | { | |
63 | if used.is_empty() { | |
64 | Some(self.create_help_usage(true)) | |
65 | } else { | |
66 | Some(self.create_smart_usage(used)) | |
67 | } | |
68 | } | |
69 | ||
70 | #[cfg(not(feature = "usage"))] | |
71 | { | |
72 | None | |
73 | } | |
74 | } | |
75 | } | |
76 | } | |
77 | ||
78 | #[cfg(feature = "usage")] | |
79 | impl<'cmd> Usage<'cmd> { | |
80 | // Creates a usage string for display in help messages (i.e. not for errors) | |
81 | fn create_help_usage(&self, incl_reqs: bool) -> StyledStr { | |
82 | debug!("Usage::create_help_usage; incl_reqs={incl_reqs:?}"); | |
83 | use std::fmt::Write as _; | |
84 | let literal = &self.styles.get_literal(); | |
85 | let placeholder = &self.styles.get_placeholder(); | |
86 | let mut styled = StyledStr::new(); | |
87 | ||
88 | let name = self | |
89 | .cmd | |
90 | .get_usage_name() | |
91 | .or_else(|| self.cmd.get_bin_name()) | |
92 | .unwrap_or_else(|| self.cmd.get_name()); | |
93 | if !name.is_empty() { | |
94 | // the trim won't properly remove a leading space due to the formatting | |
95 | let _ = write!( | |
96 | styled, | |
97 | "{}{name}{}", | |
98 | literal.render(), | |
99 | literal.render_reset() | |
100 | ); | |
101 | } | |
102 | ||
103 | if self.needs_options_tag() { | |
104 | let _ = write!( | |
105 | styled, | |
106 | "{} [OPTIONS]{}", | |
107 | placeholder.render(), | |
108 | placeholder.render_reset() | |
109 | ); | |
110 | } | |
111 | ||
112 | self.write_args(&[], !incl_reqs, &mut styled); | |
113 | ||
114 | // incl_reqs is only false when this function is called recursively | |
115 | if self.cmd.has_visible_subcommands() && incl_reqs | |
116 | || self.cmd.is_allow_external_subcommands_set() | |
117 | { | |
118 | let value_name = self | |
119 | .cmd | |
120 | .get_subcommand_value_name() | |
121 | .unwrap_or(DEFAULT_SUB_VALUE_NAME); | |
122 | if self.cmd.is_subcommand_negates_reqs_set() | |
123 | || self.cmd.is_args_conflicts_with_subcommands_set() | |
124 | { | |
125 | let _ = write!(styled, "\n "); | |
126 | if self.cmd.is_args_conflicts_with_subcommands_set() { | |
127 | // Short-circuit full usage creation since no args will be relevant | |
128 | let _ = write!( | |
129 | styled, | |
130 | "{}{name}{}", | |
131 | literal.render(), | |
132 | literal.render_reset() | |
133 | ); | |
134 | } else { | |
135 | styled.push_styled(&self.create_help_usage(false)); | |
136 | } | |
137 | let _ = write!( | |
138 | styled, | |
139 | " {}<{value_name}>{}", | |
140 | placeholder.render(), | |
141 | placeholder.render_reset() | |
142 | ); | |
143 | } else if self.cmd.is_subcommand_required_set() { | |
144 | let _ = write!( | |
145 | styled, | |
146 | " {}<{value_name}>{}", | |
147 | placeholder.render(), | |
148 | placeholder.render_reset() | |
149 | ); | |
150 | } else { | |
151 | let _ = write!( | |
152 | styled, | |
153 | " {}[{value_name}]{}", | |
154 | placeholder.render(), | |
155 | placeholder.render_reset() | |
156 | ); | |
157 | } | |
158 | } | |
159 | styled.trim(); | |
160 | debug!("Usage::create_help_usage: usage={styled}"); | |
161 | styled | |
162 | } | |
163 | ||
164 | // Creates a context aware usage string, or "smart usage" from currently used | |
165 | // args, and requirements | |
166 | fn create_smart_usage(&self, used: &[Id]) -> StyledStr { | |
167 | debug!("Usage::create_smart_usage"); | |
168 | use std::fmt::Write; | |
169 | let literal = &self.styles.get_literal(); | |
170 | let placeholder = &self.styles.get_placeholder(); | |
171 | let mut styled = StyledStr::new(); | |
172 | ||
173 | let bin_name = self | |
174 | .cmd | |
175 | .get_usage_name() | |
176 | .or_else(|| self.cmd.get_bin_name()) | |
177 | .unwrap_or_else(|| self.cmd.get_name()); | |
178 | let _ = write!( | |
179 | styled, | |
180 | "{}{bin_name}{}", | |
181 | literal.render(), | |
182 | literal.render_reset() | |
183 | ); | |
184 | ||
185 | self.write_args(used, false, &mut styled); | |
186 | ||
187 | if self.cmd.is_subcommand_required_set() { | |
188 | let value_name = self | |
189 | .cmd | |
190 | .get_subcommand_value_name() | |
191 | .unwrap_or(DEFAULT_SUB_VALUE_NAME); | |
192 | let _ = write!( | |
193 | styled, | |
194 | " {}<{value_name}>{}", | |
195 | placeholder.render(), | |
196 | placeholder.render_reset() | |
197 | ); | |
198 | } | |
199 | styled | |
200 | } | |
201 | ||
202 | // Determines if we need the `[OPTIONS]` tag in the usage string | |
203 | fn needs_options_tag(&self) -> bool { | |
204 | debug!("Usage::needs_options_tag"); | |
205 | 'outer: for f in self.cmd.get_non_positionals() { | |
206 | debug!("Usage::needs_options_tag:iter: f={}", f.get_id()); | |
207 | ||
208 | // Don't print `[OPTIONS]` just for help or version | |
209 | if f.get_long() == Some("help") || f.get_long() == Some("version") { | |
210 | debug!("Usage::needs_options_tag:iter Option is built-in"); | |
211 | continue; | |
212 | } | |
213 | ||
214 | if f.is_hide_set() { | |
215 | debug!("Usage::needs_options_tag:iter Option is hidden"); | |
216 | continue; | |
217 | } | |
218 | if f.is_required_set() { | |
219 | debug!("Usage::needs_options_tag:iter Option is required"); | |
220 | continue; | |
221 | } | |
222 | for grp_s in self.cmd.groups_for_arg(f.get_id()) { | |
223 | debug!("Usage::needs_options_tag:iter:iter: grp_s={grp_s:?}"); | |
224 | if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) { | |
225 | debug!("Usage::needs_options_tag:iter:iter: Group is required"); | |
226 | continue 'outer; | |
227 | } | |
228 | } | |
229 | ||
230 | debug!("Usage::needs_options_tag:iter: [OPTIONS] required"); | |
231 | return true; | |
232 | } | |
233 | ||
234 | debug!("Usage::needs_options_tag: [OPTIONS] not required"); | |
235 | false | |
236 | } | |
237 | ||
238 | // Returns the required args in usage string form by fully unrolling all groups | |
239 | pub(crate) fn write_args(&self, incls: &[Id], force_optional: bool, styled: &mut StyledStr) { | |
240 | for required in self.get_args(incls, force_optional) { | |
241 | styled.push_str(" "); | |
242 | styled.push_styled(&required); | |
243 | } | |
244 | } | |
245 | ||
246 | pub(crate) fn get_args(&self, incls: &[Id], force_optional: bool) -> Vec<StyledStr> { | |
247 | debug!("Usage::get_args: incls={incls:?}",); | |
248 | use std::fmt::Write as _; | |
249 | let literal = &self.styles.get_literal(); | |
250 | ||
251 | let required_owned; | |
252 | let required = if let Some(required) = self.required { | |
253 | required | |
254 | } else { | |
255 | required_owned = self.cmd.required_graph(); | |
256 | &required_owned | |
257 | }; | |
258 | ||
259 | let mut unrolled_reqs = Vec::new(); | |
260 | for a in required.iter() { | |
261 | let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { | |
262 | let required = match val { | |
263 | ArgPredicate::Equals(_) => false, | |
264 | ArgPredicate::IsPresent => true, | |
265 | }; | |
266 | required.then(|| req_arg.clone()) | |
267 | }; | |
268 | ||
269 | for aa in self.cmd.unroll_arg_requires(is_relevant, a) { | |
270 | // if we don't check for duplicates here this causes duplicate error messages | |
271 | // see https://github.com/clap-rs/clap/issues/2770 | |
272 | unrolled_reqs.push(aa); | |
273 | } | |
274 | // always include the required arg itself. it will not be enumerated | |
275 | // by unroll_requirements_for_arg. | |
276 | unrolled_reqs.push(a.clone()); | |
277 | } | |
278 | debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}"); | |
279 | ||
280 | let mut required_groups_members = FlatSet::new(); | |
281 | let mut required_groups = FlatSet::new(); | |
282 | for req in unrolled_reqs.iter().chain(incls.iter()) { | |
283 | if self.cmd.find_group(req).is_some() { | |
284 | let group_members = self.cmd.unroll_args_in_group(req); | |
285 | let elem = self.cmd.format_group(req); | |
286 | required_groups.insert(elem); | |
287 | required_groups_members.extend(group_members); | |
288 | } else { | |
289 | debug_assert!(self.cmd.find(req).is_some()); | |
290 | } | |
291 | } | |
292 | ||
293 | let mut required_opts = FlatSet::new(); | |
294 | let mut required_positionals = Vec::new(); | |
295 | for req in unrolled_reqs.iter().chain(incls.iter()) { | |
296 | if let Some(arg) = self.cmd.find(req) { | |
297 | if required_groups_members.contains(arg.get_id()) { | |
298 | continue; | |
299 | } | |
300 | ||
301 | let stylized = arg.stylized(self.styles, Some(!force_optional)); | |
302 | if let Some(index) = arg.get_index() { | |
303 | let new_len = index + 1; | |
304 | if required_positionals.len() < new_len { | |
305 | required_positionals.resize(new_len, None); | |
306 | } | |
307 | required_positionals[index] = Some(stylized); | |
308 | } else { | |
309 | required_opts.insert(stylized); | |
310 | } | |
311 | } else { | |
312 | debug_assert!(self.cmd.find_group(req).is_some()); | |
313 | } | |
314 | } | |
315 | ||
316 | for pos in self.cmd.get_positionals() { | |
317 | if pos.is_hide_set() { | |
318 | continue; | |
319 | } | |
320 | if required_groups_members.contains(pos.get_id()) { | |
321 | continue; | |
322 | } | |
323 | ||
324 | let index = pos.get_index().unwrap(); | |
325 | let new_len = index + 1; | |
326 | if required_positionals.len() < new_len { | |
327 | required_positionals.resize(new_len, None); | |
328 | } | |
329 | if required_positionals[index].is_some() { | |
330 | if pos.is_last_set() { | |
331 | let styled = required_positionals[index].take().unwrap(); | |
332 | let mut new = StyledStr::new(); | |
333 | let _ = write!(new, "{}--{} ", literal.render(), literal.render_reset()); | |
334 | new.push_styled(&styled); | |
335 | required_positionals[index] = Some(new); | |
336 | } | |
337 | } else { | |
338 | let mut styled; | |
339 | if pos.is_last_set() { | |
340 | styled = StyledStr::new(); | |
341 | let _ = write!(styled, "{}[--{} ", literal.render(), literal.render_reset()); | |
342 | styled.push_styled(&pos.stylized(self.styles, Some(true))); | |
343 | let _ = write!(styled, "{}]{}", literal.render(), literal.render_reset()); | |
344 | } else { | |
345 | styled = pos.stylized(self.styles, Some(false)); | |
346 | } | |
347 | required_positionals[index] = Some(styled); | |
348 | } | |
349 | if pos.is_last_set() && force_optional { | |
350 | required_positionals[index] = None; | |
351 | } | |
352 | } | |
353 | ||
354 | let mut ret_val = Vec::new(); | |
355 | if !force_optional { | |
356 | ret_val.extend(required_opts); | |
357 | ret_val.extend(required_groups); | |
358 | } | |
359 | for pos in required_positionals.into_iter().flatten() { | |
360 | ret_val.push(pos); | |
361 | } | |
362 | ||
363 | debug!("Usage::get_args: ret_val={ret_val:?}"); | |
364 | ret_val | |
365 | } | |
366 | ||
367 | pub(crate) fn get_required_usage_from( | |
368 | &self, | |
369 | incls: &[Id], | |
370 | matcher: Option<&ArgMatcher>, | |
371 | incl_last: bool, | |
372 | ) -> Vec<StyledStr> { | |
373 | debug!( | |
374 | "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}", | |
375 | incls, | |
376 | matcher.is_some(), | |
377 | incl_last | |
378 | ); | |
379 | ||
380 | let required_owned; | |
381 | let required = if let Some(required) = self.required { | |
382 | required | |
383 | } else { | |
384 | required_owned = self.cmd.required_graph(); | |
385 | &required_owned | |
386 | }; | |
387 | ||
388 | let mut unrolled_reqs = Vec::new(); | |
389 | for a in required.iter() { | |
390 | let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> { | |
391 | let required = match val { | |
392 | ArgPredicate::Equals(_) => { | |
393 | if let Some(matcher) = matcher { | |
394 | matcher.check_explicit(a, val) | |
395 | } else { | |
396 | false | |
397 | } | |
398 | } | |
399 | ArgPredicate::IsPresent => true, | |
400 | }; | |
401 | required.then(|| req_arg.clone()) | |
402 | }; | |
403 | ||
404 | for aa in self.cmd.unroll_arg_requires(is_relevant, a) { | |
405 | // if we don't check for duplicates here this causes duplicate error messages | |
406 | // see https://github.com/clap-rs/clap/issues/2770 | |
407 | unrolled_reqs.push(aa); | |
408 | } | |
409 | // always include the required arg itself. it will not be enumerated | |
410 | // by unroll_requirements_for_arg. | |
411 | unrolled_reqs.push(a.clone()); | |
412 | } | |
413 | debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}"); | |
414 | ||
415 | let mut required_groups_members = FlatSet::new(); | |
416 | let mut required_groups = FlatSet::new(); | |
417 | for req in unrolled_reqs.iter().chain(incls.iter()) { | |
418 | if self.cmd.find_group(req).is_some() { | |
419 | let group_members = self.cmd.unroll_args_in_group(req); | |
420 | let is_present = matcher | |
421 | .map(|m| { | |
422 | group_members | |
423 | .iter() | |
424 | .any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent)) | |
425 | }) | |
426 | .unwrap_or(false); | |
427 | debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}"); | |
428 | if is_present { | |
429 | continue; | |
430 | } | |
431 | ||
432 | let elem = self.cmd.format_group(req); | |
433 | required_groups.insert(elem); | |
434 | required_groups_members.extend(group_members); | |
435 | } else { | |
436 | debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist"); | |
437 | } | |
438 | } | |
439 | ||
440 | let mut required_opts = FlatSet::new(); | |
441 | let mut required_positionals = Vec::new(); | |
442 | for req in unrolled_reqs.iter().chain(incls.iter()) { | |
443 | if let Some(arg) = self.cmd.find(req) { | |
444 | if required_groups_members.contains(arg.get_id()) { | |
445 | continue; | |
446 | } | |
447 | ||
448 | let is_present = matcher | |
449 | .map(|m| m.check_explicit(req, &ArgPredicate::IsPresent)) | |
450 | .unwrap_or(false); | |
451 | debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}"); | |
452 | if is_present { | |
453 | continue; | |
454 | } | |
455 | ||
456 | let stylized = arg.stylized(self.styles, Some(true)); | |
457 | if let Some(index) = arg.get_index() { | |
458 | if !arg.is_last_set() || incl_last { | |
459 | let new_len = index + 1; | |
460 | if required_positionals.len() < new_len { | |
461 | required_positionals.resize(new_len, None); | |
462 | } | |
463 | required_positionals[index] = Some(stylized); | |
464 | } | |
465 | } else { | |
466 | required_opts.insert(stylized); | |
467 | } | |
468 | } else { | |
469 | debug_assert!(self.cmd.find_group(req).is_some()); | |
470 | } | |
471 | } | |
472 | ||
473 | let mut ret_val = Vec::new(); | |
474 | ret_val.extend(required_opts); | |
475 | ret_val.extend(required_groups); | |
476 | for pos in required_positionals.into_iter().flatten() { | |
477 | ret_val.push(pos); | |
478 | } | |
479 | ||
480 | debug!("Usage::get_required_usage_from: ret_val={ret_val:?}"); | |
481 | ret_val | |
482 | } | |
483 | } |