• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use indexmap::IndexSet;
2 
3 // Internal
4 use crate::builder::AppSettings as AS;
5 use crate::builder::{Arg, ArgPredicate, Command};
6 use crate::parser::ArgMatcher;
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> {
new(cmd: &'cmd Command<'help>) -> Self17     pub(crate) fn new(cmd: &'cmd Command<'help>) -> Self {
18         Usage {
19             cmd,
20             required: None,
21         }
22     }
23 
required(mut self, required: &'cmd ChildGraph<Id>) -> Self24     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)
create_usage_with_title(&self, used: &[Id]) -> String31     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.
create_usage_no_title(&self, used: &[Id]) -> String40     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)
create_help_usage(&self, incl_reqs: bool) -> String52     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         }
157         let usage = usage.trim().to_owned();
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
create_smart_usage(&self, used: &[Id]) -> String164     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
get_args_tag(&self, incl_reqs: bool) -> Option<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
needs_options_tag(&self) -> bool292     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.
get_required_usage_from( &self, incls: &[Id], matcher: Option<&ArgMatcher>, incl_last: bool, ) -> IndexSet<String>331     pub(crate) fn get_required_usage_from(
332         &self,
333         incls: &[Id],
334         matcher: Option<&ArgMatcher>,
335         incl_last: bool,
336     ) -> IndexSet<String> {
337         debug!(
338             "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
339             incls,
340             matcher.is_some(),
341             incl_last
342         );
343         let mut ret_val = IndexSet::new();
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 mut required_groups_members = IndexSet::new();
386         let mut required_opts = IndexSet::new();
387         let mut required_groups = IndexSet::new();
388         let mut required_positionals = Vec::new();
389         for req in unrolled_reqs.iter().chain(incls.iter()) {
390             if let Some(arg) = self.cmd.find(req) {
391                 let is_present = matcher
392                     .map(|m| m.check_explicit(req, ArgPredicate::IsPresent))
393                     .unwrap_or(false);
394                 debug!(
395                     "Usage::get_required_usage_from:iter:{:?} arg is_present={}",
396                     req, is_present
397                 );
398                 if !is_present {
399                     if arg.is_positional() {
400                         if incl_last || !arg.is_last_set() {
401                             required_positionals.push((arg.index.unwrap(), arg.to_string()));
402                         }
403                     } else {
404                         required_opts.insert(arg.to_string());
405                     }
406                 }
407             } else {
408                 debug_assert!(self.cmd.find_group(req).is_some());
409                 let group_members = self.cmd.unroll_args_in_group(req);
410                 let is_present = matcher
411                     .map(|m| {
412                         group_members
413                             .iter()
414                             .any(|arg| m.check_explicit(arg, ArgPredicate::IsPresent))
415                     })
416                     .unwrap_or(false);
417                 debug!(
418                     "Usage::get_required_usage_from:iter:{:?} group is_present={}",
419                     req, is_present
420                 );
421                 if !is_present {
422                     let elem = self.cmd.format_group(req);
423                     required_groups.insert(elem);
424                     required_groups_members.extend(
425                         group_members
426                             .iter()
427                             .flat_map(|id| self.cmd.find(id))
428                             .map(|arg| arg.to_string()),
429                     );
430                 }
431             }
432         }
433 
434         required_opts.retain(|arg| !required_groups_members.contains(arg));
435         ret_val.extend(required_opts);
436 
437         ret_val.extend(required_groups);
438 
439         required_positionals.sort_by_key(|(ind, _)| *ind); // sort by index
440         for (_, p) in required_positionals {
441             if !required_groups_members.contains(&p) {
442                 ret_val.insert(p);
443             }
444         }
445 
446         debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val);
447         ret_val
448     }
449 }
450