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