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::ArgAction;
8 use crate::builder::StyledStr;
9 use crate::builder::Styles;
10 use crate::builder::{ArgPredicate, Command};
11 use crate::parser::ArgMatcher;
12 use crate::util::ChildGraph;
13 use crate::util::FlatSet;
14 use crate::util::Id;
15 
16 static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND";
17 const USAGE_SEP: &str = "\n       ";
18 
19 pub(crate) struct Usage<'cmd> {
20     cmd: &'cmd Command,
21     styles: &'cmd Styles,
22     required: Option<&'cmd ChildGraph<Id>>,
23 }
24 
25 impl<'cmd> Usage<'cmd> {
new(cmd: &'cmd Command) -> Self26     pub(crate) fn new(cmd: &'cmd Command) -> Self {
27         Usage {
28             cmd,
29             styles: cmd.get_styles(),
30             required: None,
31         }
32     }
33 
required(mut self, required: &'cmd ChildGraph<Id>) -> Self34     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)
create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr>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.
create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr>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.
write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool75     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")]
100 impl<'cmd> Usage<'cmd> {
101     // Creates a usage string for display in help messages (i.e. not for errors)
write_help_usage(&self, styled: &mut StyledStr)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
write_smart_usage(&self, styled: &mut StyledStr, used: &[Id])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 
write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool)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 
write_subcommand_usage(&self, styled: &mut StyledStr)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
needs_options_tag(&self) -> bool239     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
write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool)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 
get_required_usage_from( &self, incls: &[Id], matcher: Option<&ArgMatcher>, incl_last: bool, ) -> Vec<StyledStr>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