clap/app/
usage.rs

1// std
2use std::collections::{BTreeMap, VecDeque};
3
4// Internal
5use crate::{
6    app::{parser::Parser, settings::AppSettings as AS},
7    args::{settings::ArgSettings, AnyArg, ArgMatcher, PosBuilder},
8    INTERNAL_ERROR_MSG,
9};
10
11// Creates a usage string for display. This happens just after all arguments were parsed, but before
12// any subcommands have been parsed (so as to give subcommands their own usage recursively)
13pub fn create_usage_with_title(p: &Parser, used: &[&str]) -> String {
14    debugln!("usage::create_usage_with_title;");
15    let mut usage = String::with_capacity(75);
16    usage.push_str("USAGE:\n    ");
17    usage.push_str(&*create_usage_no_title(p, used));
18    usage
19}
20
21// Creates a usage string to be used in error message (i.e. one with currently used args)
22pub fn create_error_usage<'a, 'b>(
23    p: &Parser<'a, 'b>,
24    matcher: &'b ArgMatcher<'a>,
25    extra: Option<&str>,
26) -> String {
27    let mut args: Vec<_> = matcher
28        .arg_names()
29        .iter()
30        .filter(|n| {
31            if let Some(o) = find_by_name!(p, **n, opts, iter) {
32                !o.b.is_set(ArgSettings::Required) && !o.b.is_set(ArgSettings::Hidden)
33            } else if let Some(p) = find_by_name!(p, **n, positionals, values) {
34                !p.b.is_set(ArgSettings::Required) && p.b.is_set(ArgSettings::Hidden)
35            } else {
36                true // flags can't be required, so they're always true
37            }
38        })
39        .copied()
40        .collect();
41    if let Some(r) = extra {
42        args.push(r);
43    }
44    create_usage_with_title(p, &*args)
45}
46
47// Creates a usage string (*without title*) if one was not provided by the user manually.
48pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String {
49    debugln!("usage::create_usage_no_title;");
50    if let Some(u) = p.meta.usage_str {
51        String::from(&*u)
52    } else if used.is_empty() {
53        create_help_usage(p, true)
54    } else {
55        create_smart_usage(p, used)
56    }
57}
58
59// Creates a usage string for display in help messages (i.e. not for errors)
60pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String {
61    let mut usage = String::with_capacity(75);
62    let name = p
63        .meta
64        .usage
65        .as_ref()
66        .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name));
67    usage.push_str(&*name);
68    let req_string = if incl_reqs {
69        let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect();
70        reqs.sort_unstable();
71        reqs.dedup();
72        get_required_usage_from(p, &reqs, None, None, false)
73            .iter()
74            .fold(String::new(), |a, s| a + &format!(" {}", s)[..])
75    } else {
76        String::new()
77    };
78
79    let flags = needs_flags_tag(p);
80    if flags && !p.is_set(AS::UnifiedHelpMessage) {
81        usage.push_str(" [FLAGS]");
82    } else if flags {
83        usage.push_str(" [OPTIONS]");
84    }
85    if !p.is_set(AS::UnifiedHelpMessage)
86        && p.opts
87            .iter()
88            .any(|o| !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden))
89    {
90        usage.push_str(" [OPTIONS]");
91    }
92
93    usage.push_str(&req_string[..]);
94
95    let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last));
96    // places a '--' in the usage string if there are args and options
97    // supporting multiple values
98    if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple))
99        && p.positionals
100            .values()
101            .any(|p| !p.is_set(ArgSettings::Required))
102        && !(p.has_visible_subcommands() || p.is_set(AS::AllowExternalSubcommands))
103        && !has_last
104    {
105        usage.push_str(" [--]");
106    }
107    let not_req_or_hidden = |p: &PosBuilder| {
108        (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last))
109            && !p.is_set(ArgSettings::Hidden)
110    };
111    if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) {
112        if let Some(args_tag) = get_args_tag(p, incl_reqs) {
113            usage.push_str(&*args_tag);
114        } else {
115            usage.push_str(" [ARGS]");
116        }
117        if has_last && incl_reqs {
118            let pos = p
119                .positionals
120                .values()
121                .find(|p| p.b.is_set(ArgSettings::Last))
122                .expect(INTERNAL_ERROR_MSG);
123            debugln!("usage::create_help_usage: '{}' has .last(true)", pos.name());
124            let req = pos.is_set(ArgSettings::Required);
125            if req
126                && p.positionals
127                    .values()
128                    .any(|p| !p.is_set(ArgSettings::Required))
129            {
130                usage.push_str(" -- <");
131            } else if req {
132                usage.push_str(" [--] <");
133            } else {
134                usage.push_str(" [-- <");
135            }
136            usage.push_str(&*pos.name_no_brackets());
137            usage.push('>');
138            usage.push_str(pos.multiple_str());
139            if !req {
140                usage.push(']');
141            }
142        }
143    }
144
145    // incl_reqs is only false when this function is called recursively
146    if p.has_visible_subcommands() && incl_reqs || p.is_set(AS::AllowExternalSubcommands) {
147        if p.is_set(AS::SubcommandsNegateReqs) || p.is_set(AS::ArgsNegateSubcommands) {
148            usage.push_str("\n    ");
149            if !p.is_set(AS::ArgsNegateSubcommands) {
150                usage.push_str(&*create_help_usage(p, false));
151            } else {
152                usage.push_str(&*name);
153            }
154            usage.push_str(" <SUBCOMMAND>");
155        } else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) {
156            usage.push_str(" <SUBCOMMAND>");
157        } else {
158            usage.push_str(" [SUBCOMMAND]");
159        }
160    }
161    usage.shrink_to_fit();
162    debugln!("usage::create_help_usage: usage={}", usage);
163    usage
164}
165
166// Creates a context aware usage string, or "smart usage" from currently used
167// args, and requirements
168fn create_smart_usage(p: &Parser, used: &[&str]) -> String {
169    debugln!("usage::smart_usage;");
170    let mut usage = String::with_capacity(75);
171    let mut hs: Vec<&str> = p.required().map(|s| &**s).collect();
172    hs.extend_from_slice(used);
173
174    let r_string = get_required_usage_from(p, &hs, None, None, false)
175        .iter()
176        .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);
177
178    usage.push_str(
179        &p.meta
180            .usage
181            .as_ref()
182            .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name))[..],
183    );
184    usage.push_str(&*r_string);
185    if p.is_set(AS::SubcommandRequired) {
186        usage.push_str(" <SUBCOMMAND>");
187    }
188    usage.shrink_to_fit();
189    usage
190}
191
192// Gets the `[ARGS]` tag for the usage string
193fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option<String> {
194    debugln!("usage::get_args_tag;");
195    let mut count = 0;
196    'outer: for pos in p
197        .positionals
198        .values()
199        .filter(|pos| !pos.is_set(ArgSettings::Required))
200        .filter(|pos| !pos.is_set(ArgSettings::Hidden))
201        .filter(|pos| !pos.is_set(ArgSettings::Last))
202    {
203        debugln!("usage::get_args_tag:iter:{}:", pos.b.name);
204        if let Some(g_vec) = p.groups_for_arg(pos.b.name) {
205            for grp_s in &g_vec {
206                debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s);
207                // if it's part of a required group we don't want to count it
208                if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) {
209                    continue 'outer;
210                }
211            }
212        }
213        count += 1;
214        debugln!(
215            "usage::get_args_tag:iter: {} Args not required or hidden",
216            count
217        );
218    }
219    if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
220        debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]");
221        return None; // [ARGS]
222    } else if count == 1 && incl_reqs {
223        let pos = p
224            .positionals
225            .values()
226            .find(|pos| {
227                !pos.is_set(ArgSettings::Required)
228                    && !pos.is_set(ArgSettings::Hidden)
229                    && !pos.is_set(ArgSettings::Last)
230            })
231            .expect(INTERNAL_ERROR_MSG);
232        debugln!(
233            "usage::get_args_tag:iter: Exactly one, returning '{}'",
234            pos.name()
235        );
236        return Some(format!(
237            " [{}]{}",
238            pos.name_no_brackets(),
239            pos.multiple_str()
240        ));
241    } else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs {
242        debugln!("usage::get_args_tag:iter: Don't collapse returning all");
243        return Some(
244            p.positionals
245                .values()
246                .filter(|pos| !pos.is_set(ArgSettings::Required))
247                .filter(|pos| !pos.is_set(ArgSettings::Hidden))
248                .filter(|pos| !pos.is_set(ArgSettings::Last))
249                .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
250                .collect::<Vec<_>>()
251                .join(""),
252        );
253    } else if !incl_reqs {
254        debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
255        let highest_req_pos = p
256            .positionals
257            .iter()
258            .filter_map(|(idx, pos)| {
259                if pos.b.is_set(ArgSettings::Required) && !pos.b.is_set(ArgSettings::Last) {
260                    Some(idx)
261                } else {
262                    None
263                }
264            })
265            .max()
266            .unwrap_or_else(|| p.positionals.len());
267        return Some(
268            p.positionals
269                .iter()
270                .filter_map(|(idx, pos)| {
271                    if idx <= highest_req_pos {
272                        Some(pos)
273                    } else {
274                        None
275                    }
276                })
277                .filter(|pos| !pos.is_set(ArgSettings::Required))
278                .filter(|pos| !pos.is_set(ArgSettings::Hidden))
279                .filter(|pos| !pos.is_set(ArgSettings::Last))
280                .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
281                .collect::<Vec<_>>()
282                .join(""),
283        );
284    }
285    Some("".into())
286}
287
288// Determines if we need the `[FLAGS]` tag in the usage string
289fn needs_flags_tag(p: &Parser) -> bool {
290    debugln!("usage::needs_flags_tag;");
291    'outer: for f in &p.flags {
292        debugln!("usage::needs_flags_tag:iter: f={};", f.b.name);
293        if let Some(l) = f.s.long {
294            if l == "help" || l == "version" {
295                // Don't print `[FLAGS]` just for help or version
296                continue;
297            }
298        }
299        if let Some(g_vec) = p.groups_for_arg(f.b.name) {
300            for grp_s in &g_vec {
301                debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s);
302                if p.groups.iter().any(|g| &g.name == grp_s && g.required) {
303                    debugln!("usage::needs_flags_tag:iter:iter: Group is required");
304                    continue 'outer;
305                }
306            }
307        }
308        if f.is_set(ArgSettings::Hidden) {
309            continue;
310        }
311        debugln!("usage::needs_flags_tag:iter: [FLAGS] required");
312        return true;
313    }
314
315    debugln!("usage::needs_flags_tag: [FLAGS] not required");
316    false
317}
318
319// Returns the required args in usage string form by fully unrolling all groups
320pub fn get_required_usage_from<'a, 'b>(
321    p: &Parser<'a, 'b>,
322    reqs: &[&'a str],
323    matcher: Option<&ArgMatcher<'a>>,
324    extra: Option<&str>,
325    incl_last: bool,
326) -> VecDeque<String> {
327    debugln!(
328        "usage::get_required_usage_from: reqs={:?}, extra={:?}",
329        reqs,
330        extra
331    );
332    let mut desc_reqs: Vec<&str> = vec![];
333    desc_reqs.extend(extra);
334    let mut new_reqs: Vec<&str> = vec![];
335    macro_rules! get_requires {
336        (@group $a: ident, $v:ident, $p:ident) => {{
337            if let Some(rl) = p
338                .groups
339                .iter()
340                .filter(|g| g.requires.is_some())
341                .find(|g| &g.name == $a)
342                .map(|g| g.requires.as_ref().unwrap())
343            {
344                for r in rl {
345                    if !$p.contains(&r) {
346                        debugln!(
347                            "usage::get_required_usage_from:iter:{}: adding group req={:?}",
348                            $a,
349                            r
350                        );
351                        $v.push(r);
352                    }
353                }
354            }
355        }};
356        ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{
357            if let Some(rl) = p
358                .$what
359                .$how()
360                .filter(|a| a.b.requires.is_some())
361                .find(|arg| &arg.b.name == $a)
362                .map(|a| a.b.requires.as_ref().unwrap())
363            {
364                for &(_, r) in rl.iter() {
365                    if !$p.contains(&r) {
366                        debugln!(
367                            "usage::get_required_usage_from:iter:{}: adding arg req={:?}",
368                            $a,
369                            r
370                        );
371                        $v.push(r);
372                    }
373                }
374            }
375        }};
376    }
377    // initialize new_reqs
378    for a in reqs {
379        get_requires!(a, flags, iter, new_reqs, reqs);
380        get_requires!(a, opts, iter, new_reqs, reqs);
381        get_requires!(a, positionals, values, new_reqs, reqs);
382        get_requires!(@group a, new_reqs, reqs);
383    }
384    desc_reqs.extend_from_slice(&*new_reqs);
385    debugln!(
386        "usage::get_required_usage_from: after init desc_reqs={:?}",
387        desc_reqs
388    );
389    loop {
390        let mut tmp = vec![];
391        for a in &new_reqs {
392            get_requires!(a, flags, iter, tmp, desc_reqs);
393            get_requires!(a, opts, iter, tmp, desc_reqs);
394            get_requires!(a, positionals, values, tmp, desc_reqs);
395            get_requires!(@group a, tmp, desc_reqs);
396        }
397        if tmp.is_empty() {
398            debugln!("usage::get_required_usage_from: no more children");
399            break;
400        } else {
401            debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp);
402            debugln!(
403                "usage::get_required_usage_from: after iter new_reqs={:?}",
404                new_reqs
405            );
406            desc_reqs.extend_from_slice(&*new_reqs);
407            new_reqs.clear();
408            new_reqs.extend_from_slice(&*tmp);
409            debugln!(
410                "usage::get_required_usage_from: after iter desc_reqs={:?}",
411                desc_reqs
412            );
413        }
414    }
415    desc_reqs.extend_from_slice(reqs);
416    desc_reqs.sort_unstable();
417    desc_reqs.dedup();
418    debugln!(
419        "usage::get_required_usage_from: final desc_reqs={:?}",
420        desc_reqs
421    );
422    let mut ret_val = VecDeque::new();
423    let args_in_groups = p
424        .groups
425        .iter()
426        .filter(|gn| desc_reqs.contains(&gn.name))
427        .flat_map(|g| p.arg_names_in_group(g.name))
428        .collect::<Vec<_>>();
429
430    let pmap = if let Some(m) = matcher {
431        desc_reqs
432            .iter()
433            .filter(|a| p.positionals.values().any(|p| &&p.b.name == a))
434            .filter(|&pos| !m.contains(pos))
435            .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
436            .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
437            .filter(|pos| !args_in_groups.contains(&pos.b.name))
438            .map(|pos| (pos.index, pos))
439            .collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
440    } else {
441        desc_reqs
442            .iter()
443            .filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a))
444            .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
445            .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
446            .filter(|pos| !args_in_groups.contains(&pos.b.name))
447            .map(|pos| (pos.index, pos))
448            .collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
449    };
450    debugln!(
451        "usage::get_required_usage_from: args_in_groups={:?}",
452        args_in_groups
453    );
454    for &p in pmap.values() {
455        let s = p.to_string();
456        if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) {
457            ret_val.push_back(s);
458        }
459    }
460    for a in desc_reqs
461        .iter()
462        .filter(|name| !p.positionals.values().any(|p| &&p.b.name == name))
463        .filter(|name| !p.groups.iter().any(|g| &&g.name == name))
464        .filter(|name| !args_in_groups.contains(name))
465        .filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)))
466    {
467        debugln!("usage::get_required_usage_from:iter:{}:", a);
468        let arg = find_by_name!(p, *a, flags, iter)
469            .map(|f| f.to_string())
470            .unwrap_or_else(|| {
471                find_by_name!(p, *a, opts, iter)
472                    .map(|o| o.to_string())
473                    .expect(INTERNAL_ERROR_MSG)
474            });
475        ret_val.push_back(arg);
476    }
477    let mut g_vec: Vec<String> = vec![];
478    for g in desc_reqs
479        .iter()
480        .filter(|n| p.groups.iter().any(|g| &&g.name == n))
481    {
482        let g_string = p.args_in_group(g).join("|");
483        let elem = format!("<{}>", &g_string[..g_string.len()]);
484        if !g_vec.contains(&elem) {
485            g_vec.push(elem);
486        }
487    }
488    for g in g_vec {
489        ret_val.push_back(g);
490    }
491
492    ret_val
493}