• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

clap-rs / clap / 947307948

pending completion
947307948

Pull #2543

github

GitHub
Merge b5c4f2fde into a531d3935
Pull Request #2543: Add a new arg option for the max_occurrences

60 of 60 new or added lines in 4 files covered. (100.0%)

11345 of 13137 relevant lines covered (86.36%)

12.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.1
/src/output/usage.rs
1
// std
2
use std::collections::BTreeMap;
3

4
// Internal
5
use crate::{
6
    build::AppSettings as AS,
7
    build::{Arg, ArgSettings},
8
    parse::{ArgMatcher, Parser},
9
    util::Id,
10
    INTERNAL_ERROR_MSG,
11
};
12

13
pub(crate) struct Usage<'help, 'app, 'parser> {
14
    p: &'parser Parser<'help, 'app>,
15
}
16

17
impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
18
    pub(crate) fn new(p: &'parser Parser<'help, 'app>) -> Self {
66✔
19
        Usage { p }
20
    }
21

22
    // Creates a usage string for display. This happens just after all arguments were parsed, but before
23
    // any subcommands have been parsed (so as to give subcommands their own usage recursively)
24
    pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> String {
34✔
25
        debug!("Usage::create_usage_with_title");
×
26
        let mut usage = String::with_capacity(75);
34✔
27
        usage.push_str("USAGE:\n    ");
34✔
28
        usage.push_str(&*self.create_usage_no_title(used));
35✔
29
        usage
×
30
    }
31

32
    // Creates a usage string (*without title*) if one was not provided by the user manually.
33
    pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> String {
58✔
34
        debug!("Usage::create_usage_no_title");
×
35
        if let Some(u) = self.p.app.usage_str {
59✔
36
            String::from(&*u)
1✔
37
        } else if used.is_empty() {
116✔
38
            self.create_help_usage(true)
54✔
39
        } else {
40
            self.create_smart_usage(used)
13✔
41
        }
42
    }
43

44
    // Creates a usage string for display in help messages (i.e. not for errors)
45
    pub(crate) fn create_help_usage(&self, incl_reqs: bool) -> String {
55✔
46
        debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs);
×
47
        let mut usage = String::with_capacity(75);
55✔
48
        let name = self
111✔
49
            .p
×
50
            .app
×
51
            .usage
×
52
            .as_ref()
53
            .unwrap_or_else(|| self.p.app.bin_name.as_ref().unwrap_or(&self.p.app.name));
162✔
54
        usage.push_str(&*name);
55✔
55
        let req_string = if incl_reqs {
55✔
56
            self.get_required_usage_from(&[], None, false)
174✔
57
                .iter()
58
                .fold(String::new(), |a, s| a + &format!(" {}", s)[..])
105✔
59
        } else {
60
            String::new()
4✔
61
        };
62

63
        let flags = self.needs_flags_tag();
59✔
64
        if flags && !self.p.is_set(AS::UnifiedHelpMessage) {
86✔
65
            usage.push_str(" [FLAGS]");
27✔
66
        } else if flags {
93✔
67
            usage.push_str(" [OPTIONS]");
2✔
68
        }
69
        if !self.p.is_set(AS::UnifiedHelpMessage)
228✔
70
            && self
120✔
71
                .p
×
72
                .app
×
73
                .get_opts_with_no_heading()
×
74
                .any(|o| !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden))
60✔
75
        {
76
            usage.push_str(" [OPTIONS]");
26✔
77
        }
78

79
        usage.push_str(&req_string[..]);
62✔
80

81
        let has_last = self
62✔
82
            .p
×
83
            .app
×
84
            .get_positionals()
85
            .any(|p| p.is_set(ArgSettings::Last));
52✔
86
        // places a '--' in the usage string if there are args and options
87
        // supporting multiple values
88
        if self
267✔
89
            .p
×
90
            .app
×
91
            .get_opts_with_no_heading()
×
92
            .any(|o| o.is_set(ArgSettings::MultipleValues))
58✔
93
            && self
22✔
94
                .p
×
95
                .app
×
96
                .get_positionals()
×
97
                .any(|p| !p.is_set(ArgSettings::Required))
12✔
98
            && !(self.p.app.has_visible_subcommands()
22✔
99
                || self.p.is_set(AS::AllowExternalSubcommands))
3✔
100
            && !has_last
3✔
101
        {
102
            usage.push_str(" [--]");
3✔
103
        }
104
        let not_req_or_hidden = |p: &Arg| {
27✔
105
            (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last))
59✔
106
                && !p.is_set(ArgSettings::Hidden)
32✔
107
        };
108
        if self.p.app.get_positionals().any(not_req_or_hidden) {
121✔
109
            if let Some(args_tag) = self.get_args_tag(incl_reqs) {
54✔
110
                usage.push_str(&*args_tag);
15✔
111
            } else {
112
                usage.push_str(" [ARGS]");
5✔
113
            }
114
            if has_last && incl_reqs {
52✔
115
                let pos = self
4✔
116
                    .p
×
117
                    .app
×
118
                    .get_positionals()
119
                    .find(|p| p.is_set(ArgSettings::Last))
4✔
120
                    .expect(INTERNAL_ERROR_MSG);
×
121
                debug!("Usage::create_help_usage: '{}' has .last(true)", pos.name);
×
122
                let req = pos.is_set(ArgSettings::Required);
2✔
123
                if req
6✔
124
                    && self
2✔
125
                        .p
×
126
                        .app
×
127
                        .get_positionals()
×
128
                        .any(|p| !p.is_set(ArgSettings::Required))
2✔
129
                {
130
                    usage.push_str(" -- <");
1✔
131
                } else if req {
3✔
132
                    usage.push_str(" [--] <");
1✔
133
                } else {
134
                    usage.push_str(" [-- <");
2✔
135
                }
136
                usage.push_str(&*pos.name_no_brackets());
2✔
137
                usage.push('>');
2✔
138
                usage.push_str(pos.multiple_str());
2✔
139
                if !req {
3✔
140
                    usage.push(']');
2✔
141
                }
142
            }
143
        }
144

145
        // incl_reqs is only false when this function is called recursively
146
        if self.p.app.has_visible_subcommands() && incl_reqs
185✔
147
            || self.p.is_set(AS::AllowExternalSubcommands)
58✔
148
        {
149
            let placeholder = self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND");
30✔
150
            if self.p.is_set(AS::SubcommandsNegateReqs) || self.p.is_set(AS::ArgsNegateSubcommands)
17✔
151
            {
152
                usage.push_str("\n    ");
2✔
153
                if !self.p.is_set(AS::ArgsNegateSubcommands) {
5✔
154
                    usage.push_str(&*self.create_help_usage(false));
4✔
155
                } else {
156
                    usage.push_str(&*name);
2✔
157
                }
158
                usage.push_str(" <");
2✔
159
                usage.push_str(placeholder);
2✔
160
                usage.push('>');
2✔
161
            } else if self.p.is_set(AS::SubcommandRequired)
47✔
162
                || self.p.is_set(AS::SubcommandRequiredElseHelp)
15✔
163
            {
164
                usage.push_str(" <");
5✔
165
                usage.push_str(placeholder);
5✔
166
                usage.push('>');
5✔
167
            } else {
168
                usage.push_str(" [");
11✔
169
                usage.push_str(placeholder);
11✔
170
                usage.push(']');
11✔
171
            }
172
        }
173
        usage.shrink_to_fit();
63✔
174
        debug!("Usage::create_help_usage: usage={}", usage);
×
175
        usage
63✔
176
    }
177

178
    // Creates a context aware usage string, or "smart usage" from currently used
179
    // args, and requirements
180
    fn create_smart_usage(&self, used: &[Id]) -> String {
13✔
181
        debug!("Usage::create_smart_usage");
×
182
        let mut usage = String::with_capacity(75);
13✔
183

184
        let r_string = self
39✔
185
            .get_required_usage_from(used, None, true)
13✔
186
            .iter()
187
            .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);
52✔
188

189
        usage.push_str(
13✔
190
            &self
26✔
191
                .p
×
192
                .app
×
193
                .usage
×
194
                .as_ref()
×
195
                .unwrap_or_else(|| self.p.app.bin_name.as_ref().unwrap_or(&self.p.app.name))[..],
37✔
196
        );
197
        usage.push_str(&*r_string);
13✔
198
        if self.p.is_set(AS::SubcommandRequired) {
26✔
199
            usage.push_str(" <");
×
200
            usage.push_str(self.p.app.subcommand_placeholder.unwrap_or("SUBCOMMAND"));
×
201
            usage.push('>');
×
202
        }
203
        usage.shrink_to_fit();
13✔
204
        usage
13✔
205
    }
206

207
    // Gets the `[ARGS]` tag for the usage string
208
    fn get_args_tag(&self, incl_reqs: bool) -> Option<String> {
16✔
209
        debug!("Usage::get_args_tag; incl_reqs = {:?}", incl_reqs);
×
210
        let mut count = 0;
16✔
211
        for pos in self
32✔
212
            .p
×
213
            .app
×
214
            .get_positionals()
×
215
            .filter(|pos| !pos.is_set(ArgSettings::Required))
32✔
216
            .filter(|pos| !pos.is_set(ArgSettings::Hidden))
32✔
217
            .filter(|pos| !pos.is_set(ArgSettings::Last))
32✔
218
        {
219
            debug!("Usage::get_args_tag:iter:{}", pos.name);
×
220
            let required = self.p.app.groups_for_arg(&pos.id).any(|grp_s| {
18✔
221
                debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s);
×
222
                // if it's part of a required group we don't want to count it
223
                self.p
4✔
224
                    .app
×
225
                    .groups
×
226
                    .iter()
×
227
                    .any(|g| g.required && (g.id == grp_s))
7✔
228
            });
229
            if !required {
19✔
230
                count += 1;
30✔
231
                debug!(
×
232
                    "Usage::get_args_tag:iter: {} Args not required or hidden",
233
                    count
×
234
                );
235
            }
236
        }
237

238
        if !self.p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
24✔
239
            debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]");
×
240

241
            // [ARGS]
242
            None
5✔
243
        } else if count == 1 && incl_reqs {
28✔
244
            let pos = self
26✔
245
                .p
×
246
                .app
×
247
                .get_positionals()
248
                .find(|pos| {
26✔
249
                    !pos.is_set(ArgSettings::Required)
84✔
250
                        && !pos.is_set(ArgSettings::Hidden)
26✔
251
                        && !pos.is_set(ArgSettings::Last)
26✔
252
                        && !self.p.app.groups_for_arg(&pos.id).any(|grp_s| {
29✔
253
                            debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s);
×
254
                            // if it's part of a required group we don't want to count it
255
                            self.p
2✔
256
                                .app
×
257
                                .groups
×
258
                                .iter()
×
259
                                .any(|g| g.required && (g.id == grp_s))
3✔
260
                        })
261
                })
262
                .expect(INTERNAL_ERROR_MSG);
×
263

264
            debug!(
×
265
                "Usage::get_args_tag:iter: Exactly one, returning '{}'",
266
                pos.name
×
267
            );
268

269
            Some(format!(
14✔
270
                " [{}]{}",
×
271
                pos.name_no_brackets(),
14✔
272
                pos.multiple_str()
13✔
273
            ))
274
        } else if self.p.is_set(AS::DontCollapseArgsInUsage)
8✔
275
            && self.p.app.has_positionals()
2✔
276
            && incl_reqs
×
277
        {
278
            debug!("Usage::get_args_tag:iter: Don't collapse returning all");
×
279
            Some(
280
                self.p
4✔
281
                    .app
×
282
                    .get_positionals()
×
283
                    .filter(|pos| !pos.is_set(ArgSettings::Required))
4✔
284
                    .filter(|pos| !pos.is_set(ArgSettings::Hidden))
2✔
285
                    .filter(|pos| !pos.is_set(ArgSettings::Last))
2✔
286
                    .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
2✔
287
                    .collect::<Vec<_>>()
×
288
                    .join(""),
×
289
            )
290
        } else if !incl_reqs {
4✔
291
            debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
×
292
            let highest_req_pos = self
3✔
293
                .p
×
294
                .app
×
295
                .get_positionals()
296
                .filter_map(|pos| {
1✔
297
                    if pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Last) {
2✔
298
                        Some(pos.index)
1✔
299
                    } else {
300
                        None
1✔
301
                    }
302
                })
303
                .max()
304
                .unwrap_or_else(|| Some(self.p.app.get_positionals().count()));
3✔
305
            Some(
306
                self.p
2✔
307
                    .app
×
308
                    .get_positionals()
×
309
                    .filter(|pos| pos.index <= highest_req_pos)
3✔
310
                    .filter(|pos| !pos.is_set(ArgSettings::Required))
2✔
311
                    .filter(|pos| !pos.is_set(ArgSettings::Hidden))
2✔
312
                    .filter(|pos| !pos.is_set(ArgSettings::Last))
2✔
313
                    .map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
2✔
314
                    .collect::<Vec<_>>()
×
315
                    .join(""),
×
316
            )
317
        } else {
318
            Some("".into())
4✔
319
        }
320
    }
321

322
    // Determines if we need the `[FLAGS]` tag in the usage string
323
    fn needs_flags_tag(&self) -> bool {
59✔
324
        debug!("Usage::needs_flags_tag");
×
325
        'outer: for f in self.p.app.get_flags_with_no_heading() {
59✔
326
            debug!("Usage::needs_flags_tag:iter: f={}", f.name);
×
327

328
            // Don't print `[FLAGS]` just for help or version
329
            if f.long == Some("help") || f.long == Some("version") {
61✔
330
                continue;
×
331
            }
332

333
            if f.is_set(ArgSettings::Hidden) {
28✔
334
                continue;
×
335
            }
336
            for grp_s in self.p.app.groups_for_arg(&f.id) {
30✔
337
                debug!("Usage::needs_flags_tag:iter:iter: grp_s={:?}", grp_s);
×
338
                if self
8✔
339
                    .p
×
340
                    .app
×
341
                    .groups
×
342
                    .iter()
343
                    .any(|g| g.id == grp_s && g.required)
12✔
344
                {
345
                    debug!("Usage::needs_flags_tag:iter:iter: Group is required");
×
346
                    continue 'outer;
×
347
                }
348
            }
349

350
            debug!("Usage::needs_flags_tag:iter: [FLAGS] required");
×
351
            return true;
27✔
352
        }
353

354
        debug!("Usage::needs_flags_tag: [FLAGS] not required");
×
355
        false
45✔
356
    }
357

358
    // Returns the required args in usage string form by fully unrolling all groups
359
    // `incl_last`: should we include args that are Arg::Last? (i.e. `prog [foo] -- [last]). We
360
    // can't do that for required usages being built for subcommands because it would look like:
361
    // `prog [foo] -- [last] <subcommand>` which is totally wrong.
362
    // TODO: remove the allow clippy when we update the compiler version.
363
    #[allow(clippy::needless_collect)]
364
    pub(crate) fn get_required_usage_from(
69✔
365
        &self,
366
        incls: &[Id],
367
        matcher: Option<&ArgMatcher>,
368
        incl_last: bool,
369
    ) -> Vec<String> {
370
        debug!(
×
371
            "Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
372
            incls,
×
373
            matcher.is_some(),
×
374
            incl_last
×
375
        );
376
        let mut ret_val = Vec::new();
69✔
377

378
        let mut unrolled_reqs = vec![];
70✔
379

380
        for a in self.p.required.iter() {
97✔
381
            if let Some(m) = matcher {
26✔
382
                for aa in self.p.app.unroll_requirements_for_arg(a, m) {
14✔
383
                    unrolled_reqs.push(aa);
1✔
384
                }
385
            }
386
            // always include the required arg itself. it will not be enumerated
387
            // by unroll_requirements_for_arg.
388
            unrolled_reqs.push(a.clone());
26✔
389
        }
390

391
        debug!(
×
392
            "Usage::get_required_usage_from: unrolled_reqs={:?}",
393
            unrolled_reqs
×
394
        );
395

396
        let args_in_groups = self
288✔
397
            .p
×
398
            .app
×
399
            .groups
×
400
            .iter()
401
            .filter(|gn| self.p.required.contains(&gn.id))
92✔
402
            .flat_map(|g| self.p.app.unroll_args_in_group(&g.id))
88✔
403
            .collect::<Vec<_>>();
404

405
        for a in unrolled_reqs
456✔
406
            .iter()
×
407
            .chain(incls.iter())
72✔
408
            .filter(|name| !self.p.app.get_positionals().any(|p| &&p.id == name))
180✔
409
            .filter(|name| !self.p.app.groups.iter().any(|g| &&g.id == name))
130✔
410
            .filter(|name| !args_in_groups.contains(name))
112✔
411
            .filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)))
110✔
412
        {
413
            debug!("Usage::get_required_usage_from:iter:{:?}", a);
×
414
            let arg = self.p.app.find(a).expect(INTERNAL_ERROR_MSG).to_string();
18✔
415
            ret_val.push(arg);
18✔
416
        }
417
        let mut g_vec: Vec<String> = vec![];
73✔
418
        for g in unrolled_reqs
157✔
419
            .iter()
×
420
            .filter(|n| self.p.app.groups.iter().any(|g| g.id == **n))
147✔
421
        {
422
            // don't print requirement for required groups that have an arg.
423
            if let Some(m) = matcher {
12✔
424
                let have_group_entry = self
8✔
425
                    .p
×
426
                    .app
×
427
                    .unroll_args_in_group(g)
×
428
                    .iter()
429
                    .any(|arg| m.contains(arg));
16✔
430
                if have_group_entry {
4✔
431
                    continue;
×
432
                }
433
            }
434

435
            let elem = self.p.app.format_group(g);
8✔
436
            if !g_vec.contains(&elem) {
8✔
437
                g_vec.push(elem);
8✔
438
            }
439
        }
440
        ret_val.extend_from_slice(&g_vec);
148✔
441

442
        let pmap = unrolled_reqs
525✔
443
            .iter()
444
            .chain(incls.iter())
75✔
445
            .filter(|a| self.p.app.get_positionals().any(|p| &&p.id == a))
185✔
446
            .filter(|&pos| matcher.map_or(true, |m| !m.contains(pos)))
127✔
447
            .filter_map(|pos| self.p.app.find(pos))
115✔
448
            .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
115✔
449
            .filter(|pos| !args_in_groups.contains(&pos.id))
115✔
450
            .map(|pos| (pos.index.unwrap(), pos))
40✔
451
            .collect::<BTreeMap<usize, &Arg>>(); // sort by index
452

453
        for p in pmap.values() {
94✔
454
            debug!("Usage::get_required_usage_from:iter:{:?}", p.id);
×
455
            if !args_in_groups.contains(&p.id) {
40✔
456
                ret_val.push(p.to_string());
40✔
457
            }
458
        }
459

460
        debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val);
×
461
        ret_val
74✔
462
    }
463
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2024 Coveralls, Inc