xref: /third_party/rust/crates/clap/src/output/usage.rs (revision 19625d8c)
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::StyledStr;
8use crate::builder::{ArgPredicate, Command};
9use crate::parser::ArgMatcher;
10use crate::util::ChildGraph;
11use crate::util::FlatSet;
12use crate::util::Id;
13
14static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND";
15
16pub(crate) struct Usage<'cmd> {
17    cmd: &'cmd Command,
18    required: Option<&'cmd ChildGraph<Id>>,
19}
20
21impl<'cmd> Usage<'cmd> {
22    pub(crate) fn new(cmd: &'cmd Command) -> Self {
23        Usage {
24            cmd,
25            required: None,
26        }
27    }
28
29    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)
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.
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")]
71impl<'cmd> Usage<'cmd> {
72    // Creates a usage string for display in help messages (i.e. not for errors)
73    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
128    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
154    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
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
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
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