rags_rs/lib.rs
1//! # Introduction
2//!
3//! `rags` is an easy to use argument parsing library that provides pretty help-printing.
4//!
5//! The library allows defining arguments in the same tree-like manner that users
6//! and developers expect. This leads to efficient parsing as we can efficiently
7//! eliminate work based on the state of the parsing. Once an argument has been
8//! matched it will never be inspected again.
9//!
10//! `rags` also makes liberal use of the `From<String>` trait so that arguments
11//! can be parsed into any complex type. This means, for example, that an argument
12//! naming a file can be constructed directly into a struct wrapping `std::fs::File`.
13//! This leads to re-usable code between subcommands and developers can spend less time
14//! and effort inspecting args.
15//!
16//! Arguments in the same level (it's tree-like) are parsed in the order in which
17//! they are defined. This means that global args are easy and provides both
18//! argument and semantic isolation between subcommands. Once a branch in the parse
19//! tree is taken (subcommand), the parser will not consider arguments defined "after"
20//! that branch in a higher scope. Because nested subcommands always lead to a lower
21//! scope, all arguments along that parse path are considered. This leads to 2 basic
22//! rules of usage:
23//!
24//! 1. global arguments should always be declared first
25//! 2. positional arguments should be defined within a subcommand scope even if shared
26//! betwwen subcommands
27//!
28//!
29//!
30//! # Example Usage
31//!
32//! Below is an example of usage that tries to capture most features an concepts.
33//! While this had to be edited to match Rust's doctest requirements, the examples
34//! directory contains examples which follow best practices in a "real" application
35//! such as defining descriptions as static, not returning errors from `main`, etc.
36//!
37//! ```rust
38//! extern crate rags_rs as rags;
39//!
40//! #[derive(Debug)]
41//! pub struct Options {
42//! debug: bool,
43//! verbosity: usize,
44//!
45//! subcmds: Vec<String>,
46//!
47//! build_release: bool,
48//! build_link: Vec<String>,
49//! package: String,
50//!
51//! dry_run: bool,
52//!
53//! initial_file: String,
54//! additional_files: Vec<String>,
55//! }
56//! impl Default for Options {
57//! fn default() -> Options {
58//! Options {
59//! debug: false,
60//! verbosity: 0,
61//!
62//! subcmds: vec!(),
63//!
64//! build_release: false,
65//! build_link: vec!(),
66//! package: "main".to_string(),
67//!
68//! dry_run: false,
69//!
70//! initial_file: "".to_string(),
71//! additional_files: vec!(),
72//! }
73//! }
74//! }
75//!
76//! fn main() -> Result<(), rags::Error> {
77//! let long_desc: &'static str =
78//! "This example aims to show beginner to intermediate options on the parser
79//! as well as good practices.
80//!
81//! As such, the usefulness of the binary is minimal but it will show you how
82//! an application should be structured, options passed, errors handled, and
83//! using parser state to control execution flow (print_help+exit, subcommands, etc).";
84//!
85//!
86//! let mut opts = Options::default();
87//! let mut parser = rags::Parser::from_args();
88//! parser
89//! .app_desc("example using most rags features")
90//! .app_long_desc(long_desc)
91//! .group("logging", "adjust logging output")?
92//! .flag('D', "debug", "enter debug mode", &mut opts.debug, false)?
93//! .count('v', "verbose", "increase vebosity (can be given multiple times)",
94//! &mut opts.verbosity, 1)?
95//! .done()?
96//! .subcommand("build", "build a target", &mut opts.subcmds, None)?
97//! .arg('p', "package", "rename the package", &mut opts.package, Some("PKG"), true)?
98//! .list('l', "lib", "libraries to link", &mut opts.build_link, Some("LIB"), false)?
99//! .long_flag("release", "do a release build", &mut opts.build_release, false)?
100//! .positional("file", "file to build", &mut opts.initial_file, true)?
101//! .positional_list("files", "additional files to build",
102//! &mut opts.additional_files, false)?
103//! .done()?
104//! .subcommand("clean", "clean all build artifacts", &mut opts.subcmds, None)?
105//! .flag('p', "print-only", "print what files would be cleaned, but do not clean",
106//! &mut opts.dry_run, false)?
107//! .done()?
108//! ;
109//!
110//! if parser.wants_help() {
111//! parser.print_help();
112//! } else {
113//! println!("final config: {:?}", opts);
114//! }
115//!
116//! Ok(())
117//! }
118//! ```
119//!
120//!
121//!
122//! # Example Help Dialog
123//!
124//! The above example prints the following under various help requests:
125//!
126//! ### Root Command Help
127//!
128//! ```ignore
129//! $ rags --help
130//! rags - 0.1.0 - example using most rags features
131//!
132//! usage: rags {subcommand} [-Dv]
133//!
134//! This example aims to show beginner to intermediate options on the parser
135//! as well as good practices.
136//!
137//! As such, the usefulness of the binary is minimal but it will show you how
138//! an application should be structured, options passed, errors handled, and
139//! using parser state to control execution flow (print_help+exit, subcommands, etc).
140//!
141//! subcommands:
142//! build build a target
143//! clean clean all build artifacts
144//!
145//! logging: adjust logging output
146//! -D, --debug enter debug mode [default: false]
147//! -v, --verbose increase vebosity (can be given multiple times) [default: 0]
148//!
149//! ```
150//!
151//!
152//!
153//! ### Subcommand Help
154//!
155//! Notice that in the subcommand help we still see the global arguments.
156//!
157//! ```ignore
158//! $ rags build --help
159//! rags build - 0.1.0 - build a target
160//!
161//! usage: rags build [-Dv -l LIB --release] -p PKG file [files...]
162//!
163//! logging: adjust logging output
164//! -D, --debug enter debug mode [default: false]
165//! -v, --verbose increase vebosity (can be given multiple times) [default: 0]
166//!
167//! options:
168//! -p, --package PKG rename the package [required, default: main]
169//! -l, --lib LIB libraries to link
170//! --release do a release build [default: false]
171//!
172//! positionals:
173//! file file to build [required]
174//! files... additional files to build
175//!
176//! ```
177
178use std::env;
179use std::str::FromStr;
180use std::string::ToString;
181use std::collections::BTreeMap;
182
183extern crate bit_set;
184
185pub mod errors;
186pub use errors::*;
187
188mod printer;
189use printer::arg_string;
190
191type MatchResult = Result<Option<FoundMatch>, Error>;
192
193#[cfg(test)] mod test_args;
194#[cfg(test)] mod test_flags;
195#[cfg(test)] mod test_count;
196#[cfg(test)] mod test_lists;
197#[cfg(test)] mod test_positionals;
198#[cfg(test)] mod test_subcmds;
199#[cfg(test)] mod test_unused;
200
201/// Helper macro to populate the application name, version, and description
202/// from the Cargo manifest. Metadata setter functions can be called multiple
203/// times if only some of this information is specified in the manifest.
204///
205/// If called with no arguments, this is the same as
206/// [Parser::from_args](struct.Parser.html#method.from_args).
207/// Providing one or more args passes the args to
208/// [Parser::from_strings](struct.Parser.html#method.from_strings).
209#[macro_export]
210macro_rules! argparse {
211 () => {{
212 let mut p = $crate::Parser::from_args();
213 argparse!(p, true)
214 }};
215 ($args:ident) => {{
216 let mut p = $crate::Parser::from_strings($args);
217 argparse!(p, true)
218 }};
219 ($args:expr) => {{
220 let mut p = $crate::Parser::from_strings($args);
221 argparse!(p, true)
222 }};
223 ($p:ident, true) => {{
224 $p.app_name(env!("CARGO_PKG_NAME"))
225 .app_version(env!("CARGO_PKG_VERSION"))
226 .app_desc(env!("CARGO_PKG_DESCRIPTION"));
227 $p
228 }}
229}
230
231
232enum ItemType {
233 Argument,
234 Subcommand,
235 Group,
236}
237
238/// Defines where the value (if any) associated with a given argument is located.
239#[derive(Debug)]
240enum ValueLocation {
241 Unknown,
242 HasEqual(usize),
243 TakesNext,
244}
245
246/// FoundMatch is emitted when we match an argument. This carries all necessary
247/// metadata about the argument to be parsed.
248struct FoundMatch {
249 index: usize,
250 run_count: usize,
251 value: ValueLocation,
252}
253impl FoundMatch {
254 pub fn new(idx: usize, runs: usize, loc: ValueLocation) -> FoundMatch {
255 FoundMatch {
256 index: idx,
257 run_count: runs,
258 value: loc,
259 }
260 }
261}
262
263
264/// Defines the types of arguments we can handle, and when matched, our best
265/// guess as to what kind of arg that is until we can verify with more context.
266#[derive(PartialEq)]
267pub enum LooksLike {
268 ShortArg,
269 LongArg,
270 Positional,
271}
272impl std::fmt::Display for LooksLike {
273 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
274 match self {
275 LooksLike::ShortArg => {
276 write!(f, "short-arg")
277 }
278 LooksLike::LongArg => {
279 write!(f, "long-arg")
280 }
281 LooksLike::Positional => {
282 write!(f, "positional")
283 }
284 }
285 }
286}
287
288/// Unused carries information about arguments which go unmatched.
289/// Used both in delineating short-code runs as well as passing back
290/// all unmatched arguments to the user (when requested via
291/// [Parser::unused](struct.Parser.html#method.unused)).
292pub struct Unused {
293 pub arg: String,
294 pub looks_like: LooksLike,
295}
296impl Unused {
297 pub fn new(value: String) -> Unused {
298 let arg_0 = value.chars().nth(0).or(Some('\0')).unwrap();
299 let arg_1 = value.chars().nth(1).or(Some('\0')).unwrap();
300
301 let looks_like = if (arg_0 == '-') && (arg_1 == '-') {
302 LooksLike::LongArg
303 } else if (arg_0 == '-') && (arg_1 != '-') {
304 LooksLike::ShortArg
305 } else {
306 LooksLike::Positional
307 };
308
309 Unused {
310 arg: value,
311 looks_like: looks_like,
312 }
313 }
314}
315impl std::fmt::Display for Unused {
316 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
317 match self.looks_like {
318 LooksLike::ShortArg | LooksLike::LongArg => {
319 write!(f, "unused or unknown argument: {}", self.arg)
320 }
321 LooksLike::Positional => {
322 write!(f, "unused positional or arg-value: {}", self.arg)
323 }
324 }
325 }
326}
327
328/// Parser holds the state required for parsing. The methods provided here
329/// define how arguments should be treated as well as where they are constructed.
330///
331/// Arguments used for help (-h/--help) are already registered, and the boolean
332/// for this can be accessed via [Parser::wants_help](#method.wants_help).
333///
334/// Memory usage of this structure is bounded by the arguments passed as well
335/// as a bitset mapping which args have been matched. Rust does not provide
336/// an `O(1)` access to the args iterator, thus we store it. This also keeps
337/// implementation consistent when using
338/// [Parser::from_strings](#method.from_strings)
339///
340/// This structure can be dropped after handling of args/help are complete.
341pub struct Parser {
342 args: Vec<String>,
343 mask: bit_set::BitSet,
344 run_masks: BTreeMap<usize, bit_set::BitSet>,
345
346 walk_depth: usize,
347 commit_depth: usize,
348 max_depth: usize,
349 parse_done: bool,
350 curr_group: Option<&'static str>,
351
352 help: bool,
353 has_variadic: bool,
354 argstop: Option<usize>,
355 printer: printer::Printer,
356}
357impl Parser {
358 /// Creates a new parser for the arg strings given.
359 pub fn from_strings(input: Vec<String>) -> Parser {
360 let argstop = match input.iter().enumerate().find(|(_, a)| a.as_str() == "--") {
361 Some((i, _)) => { Some(i) }
362 None => { None }
363 };
364 let count = argstop.unwrap_or(input.len());
365
366 let mut bits = bit_set::BitSet::with_capacity(count);
367 // TODO: PR with BitSet::set_all() -- or an inverse iter that iterates all unset
368 for i in 1..count {
369 bits.insert(i);
370 }
371
372 let mut p = Parser{
373 args: input,
374 mask: bits,
375 run_masks: BTreeMap::new(),
376 walk_depth: 0,
377 commit_depth: 0,
378 max_depth: 0,
379 parse_done: false,
380 curr_group: None,
381
382 help: false,
383 has_variadic: false,
384 argstop,
385 printer: printer::Printer::new(printer::App::empty()),
386 };
387
388 let mut wants_help = false;
389 p.flag('h', "help", "print this help dialog", &mut wants_help, false)
390 .expect("could not handle help flag");
391 p.help = wants_help;
392
393 p
394 }
395
396 /// Collects the arguments given on the command line and defers to
397 /// [Parser::from_strings](#method.from_strings).
398 pub fn from_args() -> Parser {
399 let args = env::args().collect::<Vec<String>>();
400 Parser::from_strings(args)
401 }
402
403 /// Unused returns all unmatched args. The [Unused](struct.Unused.html) struct
404 /// contains the necessary information to call out unrecognized args or typos in
405 /// passed arguments.
406 ///
407 /// If there is an unused character in a run of shortcodes (e.g. `-abcd`, with `b` unused)
408 /// the argument within the [Unused](struct.Unused.html) struct will be prefixed with a dash.
409 pub fn unused(&self) -> Vec<Unused> {
410 let mut result = vec!();
411 for i in self.mask.iter() {
412 match self.run_masks.get(&i) {
413 None => {}
414 Some(mask) => {
415 for m in mask.iter() {
416 let s = format!("-{}", self.args[i].chars().nth(m+1).unwrap());
417 result.push(Unused{
418 arg: s,
419 looks_like: LooksLike::ShortArg,
420 });
421 }
422 continue;
423 }
424 }
425
426 result.push(Unused::new(self.args[i].clone()));
427 }
428
429 result
430 }
431
432
433 //----------------------------------------------------------------
434 // help setup
435 //----------------------------------------------------------------
436
437 /// Sets the name of the application to be printed in the help dialog.
438 /// Printed on the first line of the dialog.
439 pub fn app_name<'a>(&'a mut self, name: &'static str) -> &'a mut Parser {
440 self.printer.set_name(name);
441 self
442 }
443
444 /// Sets the description of the application to be printed in the help dialog.
445 /// Printed on the first line of the dialog.
446 pub fn app_desc<'a>(&'a mut self, desc: &'static str) -> &'a mut Parser {
447 self.printer.set_short_desc(desc);
448 self
449 }
450
451 /// Sets the long-form description of the application to be printed in the
452 /// help dialog. Printed after the base application info and usage lines.
453 pub fn app_long_desc<'a>(&'a mut self, desc: &'static str) -> &'a mut Parser {
454 self.printer.set_long_desc(desc);
455 self
456 }
457
458 /// Sets the version of the application to be printed in the help dialog.
459 /// Printed on the first line of the dialog.
460 pub fn app_version<'a>(&'a mut self, vers: &'static str) -> &'a mut Parser {
461 self.printer.set_version(vers);
462 self
463 }
464
465 /// Returns whether the help argument was given and help should be printed.
466 /// The help dialog can be printed using [Parser::print_help](#method.print_help).
467 pub fn wants_help(&self) -> bool {
468 self.help
469 }
470
471 /// Prints the help information. If subcommands are provided, the help for
472 /// the leaf subcommand is printed.
473 pub fn print_help(&self) {
474 self.printer.print();
475 }
476
477
478 //----------------------------------------------------------------
479 // parse helpers
480 //----------------------------------------------------------------
481
482 /// Closes a context opened by calling [Parser::group](#method.group) or
483 /// [Parser::subcommand](#method.subcommand).
484 pub fn done(&mut self) -> Result<&mut Parser, Error> {
485 if self.curr_group.is_some() {
486 self.curr_group = None;
487 return Ok(self);
488 }
489
490 if self.walk_depth == 0 {
491 return Err(Error::InvalidState("call to done() at top-level"));
492 }
493
494 if (self.walk_depth == self.commit_depth) && ( self.commit_depth == self.max_depth) {
495 self.parse_done = true;
496 }
497 self.walk_depth -= 1;
498
499 Ok(self)
500 }
501
502 fn should_ignore(&self, item: ItemType) -> bool {
503 if self.parse_done {
504 return true;
505 }
506 match item {
507 ItemType::Argument => {
508 self.walk_depth != self.max_depth
509 }
510 ItemType::Subcommand => {
511 self.walk_depth != (self.commit_depth + 1)
512 }
513 ItemType::Group => {
514 // never ignore a group as there is no side-effect, and needs to be registered for the ensuing done()
515 false
516 }
517 }
518
519 }
520
521 fn commit_next_level(&mut self) {
522 self.commit_depth += 1;
523 self.max_depth = std::cmp::max(self.commit_depth, self.max_depth);
524 }
525
526 fn walk_next_level(&mut self) {
527 self.walk_depth += 1;
528 }
529
530
531 fn handle_run(&mut self, idx: usize, short: char, expect_value: bool) -> MatchResult {
532 let arg = &self.args[idx];
533 if expect_value && !arg.ends_with(short) {
534 return Err(Error::ValuedArgInRun(short, arg.clone()));
535 }
536
537 let matches = arg.match_indices(short).map(|(i,_)| i).collect::<Vec<usize>>();
538 if matches.is_empty() {
539 // no matches here
540 return Ok(None);
541 }
542
543 // fetch the current mask for this run, or insert a new one
544 let runmask = match self.run_masks.get_mut(&idx) {
545 Some(mutref) => {
546 mutref
547 }
548 None => {
549 let mut bits = bit_set::BitSet::with_capacity(arg.len());
550 for i in 1..arg.len() { // skip 0, because we want to skip the leading '-'
551 bits.insert(i);
552 }
553 self.run_masks.insert(idx, bits);
554 self.run_masks.get_mut(&idx).expect("failed to insert run mask")
555 }
556 };
557 if runmask.is_empty() {
558 return Ok(None);
559 }
560
561 let mut count: usize = 0;
562 for i in matches.iter() {
563 if runmask.contains(*i) == false { continue; }
564
565 runmask.remove(*i);
566 count += 1;
567 }
568 if count == 0 {
569 return Ok(None);
570 }
571
572 // when we empty a runmask, we set the "parent" index to be fully used
573 if runmask.is_empty() {
574 self.mask.remove(idx);
575 }
576
577 Ok(Some(FoundMatch::new(idx, count,
578 if expect_value {
579 ValueLocation::TakesNext
580 } else {
581 ValueLocation::Unknown
582 }
583 )))
584 }
585
586 fn matches_short(&mut self, idx: usize, short: char, expect_value: bool) -> MatchResult {
587 if short == '\0' { return Ok(None); } // no match
588
589 let arg = &self.args[idx];
590 if arg.len() < 2 {
591 return Ok(None);
592 }
593
594 if self.run_masks.contains_key(&idx) {
595 return self.handle_run(idx, short, expect_value);
596 }
597
598
599 let mut chars = arg.chars();
600 let arg_0 = chars.next().or(Some('\0')).unwrap();
601 let arg_1 = chars.next().or(Some('\0')).unwrap();
602 let arg_2 = chars.next().or(Some('\0')).unwrap();
603
604 // expect arg[0] to be '-' -- otherwise, looks like a positional
605 // also expect arg[1] NOT to be '-' -- otherwise, looks like a long
606 if arg_0 != '-' {
607 return Ok(None);
608 } else if arg_1 == '-' {
609 return Ok(None);
610 }
611
612 // expect arg[1] to be the character we are looking for (so not a long)
613 if arg_1 != short {
614 // if it is not, but we have something that looks like a run, try that
615 if arg.len() > 2 && arg_1 != '-' && arg_2 != '=' {
616 return self.handle_run(idx, short, expect_value);
617 }
618 return Ok(None)
619 }
620
621 // if we got here, and the length is 2, we have the base case so just return
622 if arg.len() == 2 {
623 let has_next = self.mask.contains(idx + 1);
624 return if expect_value && has_next {
625 Ok(Some(FoundMatch::new(idx, 0, ValueLocation::TakesNext)))
626 } else {
627 Ok(Some(FoundMatch::new(idx, 0, ValueLocation::Unknown)))
628 };
629 }
630
631 // if the arg has >2 characters, and arg[2] == '=', then we match and
632 // return the '=' offset
633 if arg_2 == '=' {
634 // return HasEqual regardless of expect_value because errors should be handled there
635 // rather than this lower context
636 return Ok(Some(FoundMatch::new(idx, 0, ValueLocation::HasEqual(2))));
637 }
638
639 // we know the arg has len>=3, arg[2] != '=', so it must be a run
640 self.handle_run(idx, short, expect_value)
641 }
642
643 fn matches_long(&self, idx: usize, long: &'static str, expect_value: bool) -> MatchResult {
644 if long.is_empty() { return Ok(None); }
645
646 let arg = self.args[idx].as_str();
647 let end_of_arg = 2 + long.len();
648
649 // not enough string to match
650 if arg.len() < end_of_arg {
651 return Ok(None);
652 }
653
654 // not a long arg
655 if &arg[..2] != "--" {
656 return Ok(None);
657 }
658
659 if &arg[2..end_of_arg] != long {
660 return Ok(None);
661 }
662
663 // we got exactly what we were looking for, so return
664 if arg.len() == end_of_arg {
665 let has_next = self.mask.contains(idx + 1);
666 return Ok(Some(FoundMatch::new(
667 idx, 0,
668 if expect_value && has_next {
669 ValueLocation::TakesNext
670 } else {
671 ValueLocation::Unknown
672 }
673 )));
674 }
675
676 // we got here, so the string is longer than we expect
677 // so check for a '=' trailing and return as such
678 if let Some(c) = arg.chars().nth(end_of_arg) {
679 if c == '=' {
680 // return HasEqual regardless of expect_value because errors should be handled
681 // there rather than this lower context
682 return Ok(Some(FoundMatch::new(idx, 0, ValueLocation::HasEqual(end_of_arg))));
683 }
684 }
685
686 // otherwise, no match
687 Ok(None)
688 }
689
690 fn find_match(&mut self, short: char, long: &'static str, expect_value: bool)
691 -> MatchResult
692 {
693 let mask_view = self.mask.iter().collect::<Vec<usize>>();
694 for i in mask_view.iter() {
695 match self.matches_short(*i, short, expect_value) {
696 Ok(Some(mat)) => {
697 return Ok(Some(mat));
698 }
699 Ok(None) => {} // no match, so ignore
700 Err(e) => { return Err(e); }
701 }
702
703 match self.matches_long(*i, long, expect_value) {
704 Ok(Some(mat)) => {
705 return Ok(Some(mat));
706 }
707 Ok(None) => {} // no match, so ignore
708 Err(e) => { return Err(e); }
709 }
710 }
711 Ok(None)
712 }
713
714 fn find_subcommand(&self, name: &'static str) -> Option<FoundMatch> {
715 for i in self.mask.iter() {
716 let arg = &self.args[i];
717 if arg == name {
718 return Some(FoundMatch::new(i, 0, ValueLocation::Unknown));
719 }
720 }
721 None
722 }
723
724 // takes index of the arg that matched, not the value to be constructed
725 fn construct_arg<T: FromStr>(&mut self,
726 info: &FoundMatch,
727 short: char, long: &'static str,
728 into: &mut T
729 ) -> Result<(), Error>
730 where <T as FromStr>::Err: std::fmt::Display
731 {
732 match info.value {
733 ValueLocation::Unknown => {
734 Err(Error::MissingArgValue(short, long))
735 }
736 ValueLocation::TakesNext => {
737 if self.mask.contains(info.index + 1) == false {
738 return Err(Error::MissingArgValue(short, long));
739 }
740 self.mask.remove(info.index + 1); // mark the argument index as having been used/claimed
741 let val = &self.args[info.index + 1];
742 *into = T::from_str(val.as_str())
743 .map_err(|e| Error::ConstructionError(short, long, format!("{}", e)))?;
744 Ok(())
745 }
746 ValueLocation::HasEqual(off) => {
747 let val = &self.args[info.index][(off+1)..];
748 // TODO: val.len() > 1 check + error
749 *into = T::from_str(val)
750 .map_err(|e| Error::ConstructionError(short, long, format!("{}", e)))?;
751 Ok(())
752 }
753 }
754 }
755
756
757 //----------------------------------------------------------------
758 // arg(s)
759 //----------------------------------------------------------------
760
761 /// Registers a long and short code which are expected to be followed by a value.
762 /// The associated value can be separated by either a space or an equal sign
763 /// (e.g. `--foo=7` or `--foo 7`).
764 ///
765 /// The type you wish to be parse the arg value into must implement `From<String>`
766 /// for construction as well as `ToString` for printing defaults in the help dialog.
767 ///
768 /// You may provide a label to display next to the argument in the help dialog
769 /// (e.g. `-f, --file FILE` where the label here is `FILE`).
770 ///
771 /// Arguments may additionally be marked as required. If the argument is not provided
772 /// when marked as required, this method will return an error which will propogate up
773 /// the call stack without parsing further args (fail fast).
774 pub fn arg<'a, T: FromStr+ToString>(&'a mut self,
775 short: char, long: &'static str, desc: &'static str,
776 into: &mut T, label: Option<&'static str>, required: bool
777 ) -> Result<&'a mut Parser, Error>
778 where <T as FromStr>::Err: std::fmt::Display
779 {
780
781 if self.should_ignore(ItemType::Argument) { return Ok(self); }
782
783 // only add help if it is wanted
784 if self.wants_help() {
785 self.printer.add_arg(
786 printer::Argument::new(
787 short, long, desc,
788 label, Some(into.to_string()), required
789 ),
790 self.curr_group
791 )?;
792 return Ok(self);
793 }
794
795 let found_opt = self.find_match(short, long, true)?;
796 if found_opt.is_none() {
797 // only required if !help
798 if required && !self.wants_help() {
799 return Err(Error::MissingArgument(arg_string(short, long, false)));
800 }
801 return Ok(self);
802 }
803
804 let found = found_opt.unwrap();
805 self.mask.remove(found.index);
806 self.construct_arg(&found, short, long, into)?;
807
808 Ok(self)
809 }
810
811 /// Convenience method for declaring a [Parser::arg](#method.arg) without a long code.
812 pub fn short_arg<'a, T: FromStr+ToString>(&'a mut self,
813 short: char, desc: &'static str, into: &mut T, label: Option<&'static str>,
814 required: bool
815 ) -> Result<&'a mut Parser, Error>
816 where <T as FromStr>::Err: std::fmt::Display
817 {
818 self.arg(short, "", desc, into, label, required)
819 }
820
821 /// Convenience method for declaring a [Parser::arg](#method.arg) without a short code.
822 pub fn long_arg<'a, T: FromStr+ToString>(&'a mut self,
823 long: &'static str, desc: &'static str, into: &mut T, label: Option<&'static str>,
824 required: bool
825 ) -> Result<&'a mut Parser, Error>
826 where <T as FromStr>::Err: std::fmt::Display
827 {
828 self.arg('\0', long, desc, into, label, required)
829 }
830
831
832
833 //----------------------------------------------------------------
834 // flag(s)
835 //----------------------------------------------------------------
836
837 // TODO: the help flags should be stored on `self` which is why this is
838 // a member function. once the flag(s) are configurable we will store them
839 // on the parser for this case
840 fn is_help_flags(&self, short: char, long: &'static str) -> bool {
841 (short == 'h') || (long == "help")
842 }
843
844 /// Flag defines an argument that takes no value, but instead sets a boolean.
845 /// Typically when a flag is given the backing bool is set to `true`, however,
846 /// the `invert` argument here allows "negative-flags" which instead turn an
847 /// option off.
848 pub fn flag<'a>(&'a mut self,
849 short: char, long: &'static str, desc: &'static str,
850 into: &mut bool, invert: bool
851 ) -> Result<&'a mut Parser, Error>
852 {
853
854 if self.should_ignore(ItemType::Argument) { return Ok(self); }
855
856 if self.wants_help() {
857 self.printer.add_arg(
858 printer::Argument::new(short, long, desc, None, Some(into.to_string()), false),
859 self.curr_group
860 )?;
861
862 if !self.is_help_flags(short, long) {
863 return Ok(self);
864 }
865 }
866
867 let found_opt = self.find_match(short, long, false)?;
868 if found_opt.is_none() {
869 return Ok(self);
870 }
871
872 let found = found_opt.unwrap();
873 self.mask.remove(found.index);
874
875 match found.value {
876 ValueLocation::Unknown => {
877 *into = !invert;
878 }
879 ValueLocation::TakesNext => {
880 return Err(Error::InvalidInput(short, long, "flag should not have a value"));
881 }
882 ValueLocation::HasEqual(_) => {
883 return Err(Error::InvalidInput(short, long, "flag should not have a value"));
884 }
885 }
886
887 Ok(self)
888 }
889
890 /// Convenience method for declaring a [Parser::flag](#method.flag) without a long code.
891 pub fn short_flag<'a>(&'a mut self,
892 short: char, desc: &'static str,
893 into: &mut bool, invert: bool
894 ) -> Result<&'a mut Parser, Error>
895 {
896 self.flag(short, "", desc, into, invert)
897 }
898
899 /// Convenience method for declaring a [Parser::flag](#method.flag) without a short code.
900 pub fn long_flag<'a>(&'a mut self,
901 long: &'static str, desc: &'static str,
902 into: &'a mut bool, invert: bool
903 ) -> Result<&'a mut Parser, Error>
904 {
905 self.flag('\0', long, desc, into, invert)
906 }
907
908
909 //----------------------------------------------------------------
910 // count(s)
911 //----------------------------------------------------------------
912
913 /// Count (inc|dec)rements a backing numeric type every time the argument is provided.
914 /// The classic case is increasing verbosity (e.g. `-v` is 1, `-vvvv` is 4).
915 ///
916 /// The `step` argument to this method defines what should be added to the target value
917 /// every time the arg is seen. You may provide negative numbers to decrement.
918 ///
919 /// Floating point numeric types are supported, but are atypical.
920 pub fn count<'a, T: std::ops::AddAssign + ToString + Clone>(&'a mut self,
921 short: char, long: &'static str, desc: &'static str,
922 into: &mut T, step: T
923 ) -> Result<&'a mut Parser, Error>
924 {
925
926 if self.should_ignore(ItemType::Argument) { return Ok(self); }
927
928 if self.wants_help() {
929 self.printer.add_arg(
930 printer::Argument::new(short, long, desc, None, Some(into.to_string()), false),
931 self.curr_group
932 )?;
933 return Ok(self);
934 }
935
936 loop { // loop until we get no results back
937 let found_opt = self.find_match(short, long, false)?;
938 if found_opt.is_none() {
939 return Ok(self);
940 }
941
942 let found = found_opt.unwrap();
943 if found.run_count == 0 { // was not part of a run, remove eniter index
944 self.mask.remove(found.index);
945 }
946
947 match found.value {
948 ValueLocation::Unknown => {
949 if found.run_count == 0 {
950 into.add_assign(step.clone());
951 } else {
952 for _ in 0..found.run_count {
953 into.add_assign(step.clone());
954 }
955 }
956 }
957 ValueLocation::TakesNext => {
958 return Err(Error::InvalidInput(short, long, "count should not have a value"));
959 }
960 ValueLocation::HasEqual(_) => {
961 return Err(Error::InvalidInput(short, long, "count should not have a value"));
962 }
963 }
964 }
965 }
966
967 /// Convenience method for declaring a [Parser::count](#method.count) without a long code.
968 pub fn short_count<'a, T: std::ops::AddAssign + ToString + Clone>(&'a mut self,
969 short: char, desc: &'static str,
970 into: &mut T, step: T
971 ) -> Result<&'a mut Parser, Error>
972 {
973 self.count(short, "", desc, into, step)
974 }
975
976 /// Convenience method for declaring a [Parser::count](#method.count) without a short code.
977 pub fn long_count<'a, T: std::ops::AddAssign + ToString + Clone>(&'a mut self,
978 long: &'static str, desc: &'static str,
979 into: &mut T, step: T
980 ) -> Result<&'a mut Parser, Error>
981 {
982 self.count('\0', long, desc, into, step)
983 }
984
985
986 //----------------------------------------------------------------
987 // list(s)
988 //----------------------------------------------------------------
989
990 /// List collects values from args and appends them to a vector of the target type.
991 ///
992 /// Follows the same parsing semantics as [Parser::arg](#method.arg), but appends to
993 /// a collection rather a single value. Just as with an arg, the target type must
994 /// implement `From<String>` as well as `ToString`. Likewise, the `label` and `required`
995 /// arguments to this method work the same.
996 pub fn list<'a, T: FromStr + ToString>(&'a mut self,
997 short: char, long: &'static str, desc: &'static str,
998 into: &mut Vec<T>, label: Option<&'static str>, required: bool
999 ) -> Result<&'a mut Parser, Error>
1000 where <T as FromStr>::Err: std::fmt::Display
1001 {
1002
1003 if self.should_ignore(ItemType::Argument) { return Ok(self); }
1004
1005 if self.wants_help() {
1006 self.printer.add_arg(
1007 printer::Argument::new(short, long, desc, label, None, required),
1008 self.curr_group
1009 )?;
1010 return Ok(self);
1011 }
1012
1013 let mut found_count = 0;
1014 loop { // loop until we get no results back
1015 let found_opt = self.find_match(short, long, true)?;
1016 if found_opt.is_none() { // TODO: required count -- does this make sense?
1017 // only requried when !help
1018 if required && (found_count == 0) && !self.wants_help() {
1019 return Err(Error::MissingArgument(arg_string(short, long, false)));
1020 }
1021 return Ok(self);
1022 }
1023 found_count += 1;
1024
1025 let found = found_opt.unwrap();
1026 self.mask.remove(found.index);
1027
1028 let ctor_result = match found.value {
1029 ValueLocation::Unknown => {
1030 return Err(Error::MissingArgValue(short, long));
1031 }
1032 ValueLocation::TakesNext => {
1033 self.mask.remove(found.index + 1);
1034 let str_val = &self.args[found.index + 1];
1035 T::from_str(str_val)
1036 }
1037 ValueLocation::HasEqual(eq_idx) => {
1038 // index already removed
1039 let str_val = &self.args[found.index][eq_idx + 1..];
1040 T::from_str(str_val)
1041 }
1042 };
1043
1044 into.push(
1045 ctor_result
1046 .map_err(|e| Error::ConstructionError(short, long, format!("{}", e)))?
1047 );
1048 }
1049 }
1050
1051 /// Convenience method for declaring a [Parser::list](#method.list) without a long code.
1052 pub fn short_list<'a, T: FromStr + ToString>(&'a mut self,
1053 short: char, desc: &'static str,
1054 into: &mut Vec<T>, label: Option<&'static str>, required: bool
1055 ) -> Result<&'a mut Parser, Error>
1056 where <T as FromStr>::Err: std::fmt::Display
1057 {
1058 self.list(short, "", desc, into, label, required)
1059 }
1060
1061 /// Convenience method for declaring a [Parser::list](#method.list) without a short code.
1062 pub fn long_list<'a, T: FromStr + ToString>(&'a mut self,
1063 long: &'static str, desc: &'static str,
1064 into: &mut Vec<T>, label: Option<&'static str>, required: bool
1065 ) -> Result<&'a mut Parser, Error>
1066 where <T as FromStr>::Err: std::fmt::Display
1067 {
1068 self.list('\0', long, desc, into, label, required)
1069 }
1070
1071
1072
1073 //----------------------------------------------------------------
1074 // subcommand(s)
1075 //----------------------------------------------------------------
1076
1077 /// Subcommands provide information about what the application should do as well
1078 /// as giving scope to arguments. This method creates a new context (zero cost)
1079 /// for which arguments can be defined. By creating a new context we allow for
1080 /// subcommands to share argument codes with differing meanings. You must close
1081 /// this context/scope using [Parser::done](#method.done).
1082 ///
1083 /// When a subcommand is matched it is appended to a vector. The application is
1084 /// expected to iterate that vector to determine the correct internal function(s)
1085 /// to call.
1086 ///
1087 /// An optional long description specific to this command can be provided.
1088 /// The application's long description is not printed in the help dialog when a
1089 /// subcommand is matched.
1090 ///
1091 /// Because subcommands are indistinguishable from positional arguments, all
1092 /// definitions for positional arguments should be done after defining all subcommands.
1093 pub fn subcommand<'a, T: FromStr + ToString>(&'a mut self,
1094 name: &'static str, desc: &'static str, into: &mut Vec<T>,
1095 long_desc: Option<&'static str>
1096 ) -> Result<&'a mut Parser, Error>
1097 where <T as FromStr>::Err: std::fmt::Display
1098 {
1099 // even if we do not match this subcommand, all parsing until the
1100 // associated ::done() call happens within the next level so we
1101 // must move into it unconditionally
1102 self.walk_next_level();
1103
1104 if self.should_ignore(ItemType::Subcommand) {
1105 return Ok(self);
1106 }
1107
1108 if self.wants_help() {
1109 self.printer.add_subcommand(printer::Subcommand::new(name, desc));
1110 // do not return, subcommands need to continue parsing to set levels
1111 // and help appropriately
1112 }
1113
1114 if name.is_empty() {
1115 return Err(Error::InvalidState("subcommand(...) given empty name"));
1116 }
1117
1118 if let Some(info) = self.find_subcommand(name) {
1119 self.mask.remove(info.index);
1120 let arg = &self.args[info.index];
1121 into.push(
1122 T::from_str(arg)
1123 .map_err(|e| Error::SubConstructionError(name, format!("{}", e)))?
1124 );
1125
1126 self.commit_next_level();
1127 self.printer.new_level(
1128 name, desc,
1129 if let Some(d) = long_desc { d } else { "" }
1130 );
1131 }
1132
1133 Ok(self)
1134 }
1135
1136 //----------------------------------------------------------------
1137 // group(s)
1138 //----------------------------------------------------------------
1139
1140 /// Group can be used to group various arguments into a named section within
1141 /// the help dialog. When a help argument is not provided, this is a no-op.
1142 ///
1143 /// This method opens a new scope/context which must be closed using
1144 /// [Parser::done](#method.done). However, no masking of arguments occurs in
1145 /// this created scope. The only effect a group has is on the printing of args.
1146 pub fn group<'a>(&'a mut self, name: &'static str, desc: &'static str)
1147 -> Result<&'a mut Parser, Error>
1148 {
1149 if let Some(orig) = self.curr_group {
1150 return Err(Error::NestedGroup(orig, name));
1151 }
1152
1153 if self.should_ignore(ItemType::Group) { return Ok(self); }
1154
1155 self.curr_group = Some(name);
1156 if self.wants_help() {
1157 self.printer.add_group(name, desc)?;
1158 }
1159 Ok(self)
1160 }
1161
1162
1163 //----------------------------------------------------------------
1164 // positional(s)
1165 //----------------------------------------------------------------
1166
1167 /// Creates a named positional argument. Positionals are taken on an in-order basis
1168 /// meaning when multiple positionals are defined, the values are constructed in the
1169 /// order they are provided by the user. This method does not parse anything after
1170 /// the arg-stop setinel (`--`); see [Parser::positional_list](#method.positional_list).
1171 ///
1172 /// You may define as many named positionals as required, but if you simply wish to
1173 /// capture all positionals, see [Parser::positional_list](#method.positional_list).
1174 ///
1175 /// Because positionals are indistinguishable from subcommands, all positionals should
1176 /// be defined after all subcommands. You can, however, safely define positionals within
1177 /// a leaf subcommand scope.
1178 ///
1179 /// Just as in the base [Parser::arg](#method.arg) case, the target type must implement
1180 /// both `From<String>` and `ToString`.
1181 pub fn positional<'a, T: ToString + FromStr>(&'a mut self,
1182 name: &'static str, desc: &'static str,
1183 into: &mut T, required: bool
1184 ) -> Result<&'a mut Parser, Error>
1185 where <T as FromStr>::Err: std::fmt::Display
1186 {
1187 if self.should_ignore(ItemType::Argument) { return Ok(self); }
1188
1189 if self.has_variadic {
1190 return Err(Error::UnorderedPositionals(name));
1191 }
1192
1193 if self.wants_help() {
1194 let def = into.to_string();
1195 self.printer.add_positional(printer::Positional::new(
1196 name, desc, if def.is_empty() { None } else { Some(def) },
1197 required, false
1198 ))?;
1199 return Ok(self);
1200 }
1201
1202 let idx = match self.mask.iter().next() {
1203 Some(i) => { i }
1204 None => {
1205 if required {
1206 return Err(Error::MissingPositional(name.to_string()));
1207 } else {
1208 return Ok(self);
1209 }
1210 }
1211 };
1212 let val = &self.args[idx];
1213 *into = T::from_str(val)
1214 .map_err(|e| Error::PositionalConstructionError(name, format!("{}", e)))?;
1215
1216 self.mask.remove(idx);
1217
1218 Ok(self)
1219 }
1220
1221 /// Gathers all unused arguments which are assumed to be positionals. Unused here
1222 /// does not include short code runs. Unrecognized arguments will also be returned
1223 /// here as there is mass complexity in determining the difference. For instance,
1224 /// `-9` is a valid short code flag but also has meaning as a positional.
1225 ///
1226 /// All arguments provided after the arg-stop setinel (`--`) will be gathered here.
1227 /// For example, in `my_app list --foo=7 -- list --help` the trailing `list --help`
1228 /// will not be parsed as arguments by this parser but instead will be considered
1229 /// positionals.
1230 ///
1231 /// Just as [Parser::list](#method.list) is a vector of [Parser::arg](#method.arg),
1232 /// this method is a vector of [Parser::positional](#method.positional) sharing a
1233 /// single name for the set.
1234 ///
1235 /// This method may only be called once, or an error will be returned.
1236 pub fn positional_list<'a, T: ToString + FromStr>(&'a mut self,
1237 name: &'static str, desc: &'static str,
1238 into: &mut Vec<T>, required: bool
1239 ) -> Result<&'a mut Parser, Error>
1240 where <T as FromStr>::Err: std::fmt::Display
1241 {
1242 if self.should_ignore(ItemType::Argument) { return Ok(self); }
1243
1244 if self.has_variadic {
1245 return Err(Error::MultipleVariadic(name));
1246 } else {
1247 self.has_variadic = true;
1248 }
1249
1250 // TODO: should we print defaults of lists?
1251 if self.wants_help() {
1252 self.printer.add_positional(printer::Positional::new(
1253 name, desc, None, required, true
1254 ))?;
1255 return Ok(self);
1256 }
1257
1258 let mut found_count: usize = 0;
1259 // TODO: I hate this, but self.mask.iter() is immut and mask mod is mut....
1260 let mut found_idxs: Vec<usize> = vec!();
1261 for i in self.mask.iter() {
1262 let val = &self.args[i];
1263 into.push(
1264 T::from_str(val).map_err(|e|
1265 Error::PositionalConstructionError(name, format!("{}", e))
1266 )?
1267 );
1268
1269 found_count += 1;
1270 found_idxs.push(i);
1271 }
1272 for i in found_idxs.iter() {
1273 self.mask.remove(*i);
1274 }
1275
1276 if let Some(stop) = self.argstop {
1277 for i in (stop+1)..self.args.len() {
1278 let val = &self.args[i];
1279 into.push(
1280 T::from_str(val).map_err(|e|
1281 Error::PositionalConstructionError(name, format!("{}", e))
1282 )?
1283 );
1284
1285 found_count += 1;
1286 found_idxs.push(i);
1287 }
1288 }
1289
1290 if required && (found_count == 0) {
1291 Err(Error::MissingPositional(format!("{}...", name)))
1292 } else {
1293 Ok(self)
1294 }
1295 }
1296}
1297
1298#[cfg(test)]
1299#[macro_use]
1300pub mod test_helpers {
1301 #[macro_export]
1302 macro_rules! string_vec {
1303 ( $($x:expr),* ) => {
1304 vec!( $(($x.to_string()),)* )
1305 }
1306 }
1307}
1308
1309#[cfg(test)]
1310mod handle_args {
1311 use super::*;
1312
1313 #[test]
1314 fn as_string_vec() {
1315 let mut verbosity = 0;
1316 let test_args = string_vec!("a", "b", "c");
1317 assert!(test_args.len() == 3);
1318 Parser::from_strings(test_args)
1319 .arg('v', "verbose", "increase verbosity with each given", &mut verbosity, None, false)
1320 .expect("failed to handle verbose argument(s)")
1321 ;
1322 }
1323
1324 #[test]
1325 fn as_args_iter() {
1326 let mut verbosity: u64 = 0;
1327 Parser::from_args()
1328 .arg('v', "verbose", "increase verbosity with each given", &mut verbosity, None, false)
1329 .expect("failed to handle verbose argument(s)")
1330 ;
1331 }
1332}