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