clap_builder/output/
usage.rs

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";
17const USAGE_SEP: &str = "\n       ";
18
19pub(crate) struct Usage<'cmd> {
20    cmd: &'cmd Command,
21    styles: &'cmd Styles,
22    required: Option<&'cmd ChildGraph<Id>>,
23}
24
25impl<'cmd> Usage<'cmd> {
26    pub(crate) fn new(cmd: &'cmd Command) -> Self {
27        Usage {
28            cmd,
29            styles: cmd.get_styles(),
30            required: None,
31        }
32    }
33
34    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)
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.
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.
75    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")]
100impl Usage<'_> {
101    // Creates a usage string for display in help messages (i.e. not for errors)
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
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!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
148        }
149    }
150
151    fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) {
152        debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}");
153        use std::fmt::Write as _;
154        let literal = &self.styles.get_literal();
155        let placeholder = &self.styles.get_placeholder();
156
157        let bin_name = self.cmd.get_usage_name_fallback();
158        if !bin_name.is_empty() {
159            // the trim won't properly remove a leading space due to the formatting
160            let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
161        }
162
163        if used.is_empty() && self.needs_options_tag() {
164            let _ = write!(styled, "{placeholder}[OPTIONS]{placeholder:#} ",);
165        }
166
167        self.write_args(styled, used, !incl_reqs);
168    }
169
170    fn write_subcommand_usage(&self, styled: &mut StyledStr) {
171        debug!("Usage::write_subcommand_usage");
172        use std::fmt::Write as _;
173
174        // incl_reqs is only false when this function is called recursively
175        if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() {
176            let literal = &self.styles.get_literal();
177            let placeholder = &self.styles.get_placeholder();
178            let value_name = self
179                .cmd
180                .get_subcommand_value_name()
181                .unwrap_or(DEFAULT_SUB_VALUE_NAME);
182            if self.cmd.is_subcommand_negates_reqs_set()
183                || self.cmd.is_args_conflicts_with_subcommands_set()
184            {
185                styled.trim_end();
186                let _ = write!(styled, "{USAGE_SEP}");
187                if self.cmd.is_args_conflicts_with_subcommands_set() {
188                    let bin_name = self.cmd.get_usage_name_fallback();
189                    // Short-circuit full usage creation since no args will be relevant
190                    let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
191                } else {
192                    self.write_arg_usage(styled, &[], false);
193                }
194                let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
195            } else if self.cmd.is_subcommand_required_set() {
196                let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
197            } else {
198                let _ = write!(styled, "{placeholder}[{value_name}]{placeholder:#}",);
199            }
200        }
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, styled: &mut StyledStr, incls: &[Id], force_optional: bool) {
255        debug!("Usage::write_args: incls={incls:?}",);
256        use std::fmt::Write as _;
257        let literal = &self.styles.get_literal();
258
259        let required_owned;
260        let required = if let Some(required) = self.required {
261            required
262        } else {
263            required_owned = self.cmd.required_graph();
264            &required_owned
265        };
266
267        let mut unrolled_reqs = Vec::new();
268        for a in required.iter() {
269            let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
270                let required = match val {
271                    ArgPredicate::Equals(_) => false,
272                    ArgPredicate::IsPresent => true,
273                };
274                required.then(|| req_arg.clone())
275            };
276
277            for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
278                // if we don't check for duplicates here this causes duplicate error messages
279                // see https://github.com/clap-rs/clap/issues/2770
280                unrolled_reqs.push(aa);
281            }
282            // always include the required arg itself. it will not be enumerated
283            // by unroll_requirements_for_arg.
284            unrolled_reqs.push(a.clone());
285        }
286        debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}");
287
288        let mut required_groups_members = FlatSet::new();
289        let mut required_groups = FlatSet::new();
290        for req in unrolled_reqs.iter().chain(incls.iter()) {
291            if self.cmd.find_group(req).is_some() {
292                let group_members = self.cmd.unroll_args_in_group(req);
293                let elem = self.cmd.format_group(req);
294                required_groups.insert(elem);
295                required_groups_members.extend(group_members);
296            } else {
297                debug_assert!(self.cmd.find(req).is_some());
298            }
299        }
300
301        let mut required_opts = FlatSet::new();
302        let mut required_positionals = Vec::new();
303        for req in unrolled_reqs.iter().chain(incls.iter()) {
304            if let Some(arg) = self.cmd.find(req) {
305                if required_groups_members.contains(arg.get_id()) {
306                    continue;
307                }
308
309                let stylized = arg.stylized(self.styles, Some(!force_optional));
310                if let Some(index) = arg.get_index() {
311                    let new_len = index + 1;
312                    if required_positionals.len() < new_len {
313                        required_positionals.resize(new_len, None);
314                    }
315                    required_positionals[index] = Some(stylized);
316                } else {
317                    required_opts.insert(stylized);
318                }
319            } else {
320                debug_assert!(self.cmd.find_group(req).is_some());
321            }
322        }
323
324        for pos in self.cmd.get_positionals() {
325            if pos.is_hide_set() {
326                continue;
327            }
328            if required_groups_members.contains(pos.get_id()) {
329                continue;
330            }
331
332            let index = pos.get_index().unwrap();
333            let new_len = index + 1;
334            if required_positionals.len() < new_len {
335                required_positionals.resize(new_len, None);
336            }
337            if required_positionals[index].is_some() {
338                if pos.is_last_set() {
339                    let styled = required_positionals[index].take().unwrap();
340                    let mut new = StyledStr::new();
341                    let _ = write!(new, "{literal}--{literal:#} ");
342                    new.push_styled(&styled);
343                    required_positionals[index] = Some(new);
344                }
345            } else {
346                let mut styled;
347                if pos.is_last_set() {
348                    styled = StyledStr::new();
349                    let _ = write!(styled, "{literal}[--{literal:#} ");
350                    styled.push_styled(&pos.stylized(self.styles, Some(true)));
351                    let _ = write!(styled, "{literal}]{literal:#}");
352                } else {
353                    styled = pos.stylized(self.styles, Some(false));
354                }
355                required_positionals[index] = Some(styled);
356            }
357            if pos.is_last_set() && force_optional {
358                required_positionals[index] = None;
359            }
360        }
361
362        if !force_optional {
363            for arg in required_opts {
364                styled.push_styled(&arg);
365                styled.push_str(" ");
366            }
367            for arg in required_groups {
368                styled.push_styled(&arg);
369                styled.push_str(" ");
370            }
371        }
372        for arg in required_positionals.into_iter().flatten() {
373            styled.push_styled(&arg);
374            styled.push_str(" ");
375        }
376    }
377
378    pub(crate) fn get_required_usage_from(
379        &self,
380        incls: &[Id],
381        matcher: Option<&ArgMatcher>,
382        incl_last: bool,
383    ) -> Vec<StyledStr> {
384        debug!(
385            "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
386            incls,
387            matcher.is_some(),
388            incl_last
389        );
390
391        let required_owned;
392        let required = if let Some(required) = self.required {
393            required
394        } else {
395            required_owned = self.cmd.required_graph();
396            &required_owned
397        };
398
399        let mut unrolled_reqs = Vec::new();
400        for a in required.iter() {
401            let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
402                let required = match val {
403                    ArgPredicate::Equals(_) => {
404                        if let Some(matcher) = matcher {
405                            matcher.check_explicit(a, val)
406                        } else {
407                            false
408                        }
409                    }
410                    ArgPredicate::IsPresent => true,
411                };
412                required.then(|| req_arg.clone())
413            };
414
415            for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
416                // if we don't check for duplicates here this causes duplicate error messages
417                // see https://github.com/clap-rs/clap/issues/2770
418                unrolled_reqs.push(aa);
419            }
420            // always include the required arg itself. it will not be enumerated
421            // by unroll_requirements_for_arg.
422            unrolled_reqs.push(a.clone());
423        }
424        debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}");
425
426        let mut required_groups_members = FlatSet::new();
427        let mut required_groups = FlatSet::new();
428        for req in unrolled_reqs.iter().chain(incls.iter()) {
429            if self.cmd.find_group(req).is_some() {
430                let group_members = self.cmd.unroll_args_in_group(req);
431                let is_present = matcher
432                    .map(|m| {
433                        group_members
434                            .iter()
435                            .any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent))
436                    })
437                    .unwrap_or(false);
438                debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}");
439                if is_present {
440                    continue;
441                }
442
443                let elem = self.cmd.format_group(req);
444                required_groups.insert(elem);
445                required_groups_members.extend(group_members);
446            } else {
447                debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist");
448            }
449        }
450
451        let mut required_opts = FlatSet::new();
452        let mut required_positionals = Vec::new();
453        for req in unrolled_reqs.iter().chain(incls.iter()) {
454            if let Some(arg) = self.cmd.find(req) {
455                if required_groups_members.contains(arg.get_id()) {
456                    continue;
457                }
458
459                let is_present = matcher
460                    .map(|m| m.check_explicit(req, &ArgPredicate::IsPresent))
461                    .unwrap_or(false);
462                debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}");
463                if is_present {
464                    continue;
465                }
466
467                let stylized = arg.stylized(self.styles, Some(true));
468                if let Some(index) = arg.get_index() {
469                    if !arg.is_last_set() || incl_last {
470                        let new_len = index + 1;
471                        if required_positionals.len() < new_len {
472                            required_positionals.resize(new_len, None);
473                        }
474                        required_positionals[index] = Some(stylized);
475                    }
476                } else {
477                    required_opts.insert(stylized);
478                }
479            } else {
480                debug_assert!(self.cmd.find_group(req).is_some());
481            }
482        }
483
484        let mut ret_val = Vec::new();
485        ret_val.extend(required_opts);
486        ret_val.extend(required_groups);
487        for pos in required_positionals.into_iter().flatten() {
488            ret_val.push(pos);
489        }
490
491        debug!("Usage::get_required_usage_from: ret_val={ret_val:?}");
492        ret_val
493    }
494}