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