[][src]Macro argwerk::parse

macro_rules! parse {
    (
        $it:expr => $($config:tt)*
    ) => { ... };
    (
        $($config:tt)*
    ) => { ... };
}

Parse commandline arguments.

This will generate an anonymous structure containing the arguments defined which is returned by the macro.

Each branch is executed when an incoming argument matches and must return a Result, like Ok(()). Error raised in the branch will cause a ErrorKind::Error error to be raised associated with that argument with the relevant error attached.

The parse macro can be invoked in two ways.

Using std::env::args() to get arguments from the environment:

let args = argwerk::parse! {
    /// A simple test command.
    "command [-h]" {
        help: bool,
        limit: usize = 10,
    }
    /// Print this help.
    ["-h" | "--help"] => {
        help = true;
    }
}?;

if args.help {
    println!("{}", args.help());
}

Or explicitly specifying an iterator to use with <into_iter> => <config>. This works with anything that can be converted into an iterator using IntoIterator where its items implements AsRef<str>.

let args = argwerk::parse! {
    vec!["foo", "bar", "baz"] =>
    /// A simple test command.
    "command [-h]" {
        help: bool,
        positional: Option<(&'static str, &'static str, &'static str)>,
    }
    [a, b, c] => {
        positional = Some((a, b, c));
    }
}?;

assert_eq!(args.positional, Some(("foo", "bar", "baz")));

Args structure

The first part of the parse macro defines the state available to the parser. These are field-like declarations which can specify a default value. Fields which do not specify an initialization value will be initialized through Default::default. This is the only required component of the macro.

The macro returns an anonymous Args struct with fields matching this declaration. This can be used to conveniently group and access data populated during argument parsing.

let args = argwerk::parse! {
    ["--limit", "20"].iter().copied() =>
    /// A simple test command.
    "command [-h]" {
        help: bool,
        limit: usize = 10,
    }
    /// Print this help.
    ["-h" | "--help"] => {
        help = true;
    }
    /// Specify a limit (default: 10).
    ["--limit", n] => {
        limit = str::parse(&n)?;
    }
}?;

if args.help {
    println!("{}", args.help());
}

assert_eq!(args.help, false);
assert_eq!(args.limit, 20);

Parsing switches

The basic form of an argument branch parsing a switch is one which matches on a string literal. The string literal (e.g. "--help") will then be treated as the switch for the branch. You can specify multiple matches for each branch by separating them with a pipe (|).

Note: it's not necessary that switches start with -, but this is assumed for convenience.

let args = argwerk::parse! {
    vec!["-h"] =>
    "command [-h]" { help: bool }
    ["-h" | "--help"] => {
        help = true;
    }
}?;

if args.help {
    println!("{}", args.help());
}

assert_eq!(args.help, true);

Parsing positional arguments

Positional arguments are parsed by specifying a vector of bindings in a branch. Like [foo, bar, baz].

The following is a basic example. Both foo and bar are required if the branch matches.

let args = argwerk::parse! {
    ["a", "b"].iter().copied().map(String::from) =>
    "command [-h]" { positional: Option<(String, String)>, }
    [foo, bar] if positional.is_none() => {
        positional = Some((foo, bar));
    }
}?;

assert_eq!(args.positional, Some((String::from("a"), String::from("b"))));

Help documentation

You specify documentation for switches and arguments using doc comments (e.g. /// Hello World). These are automatically wrapped to 80 characters.

Documentation can be formatted with the help associated function, which returns a static instance of Help. This can be further configured using functions such as Help::with_width.

let args = argwerk::parse! {
    /// A simple test command.
    "command [-h]" {
        help: bool,
    }
    /// Prints the help.
    ///
    /// This includes:
    ///    * All the available switches.
    ///    * All the available positional arguments.
    ///    * Whatever else the developer decided to put in here! We even support wrapping comments which are overly long.
    ["-h" | "--help"] => {
        help = true;
    }
}?;

if args.help {
    println!("{}", args.help().with_width(120));
}

Invoking this with -h would print:

Usage: command [-h]
A simple test command.

This is nice!

Options:
  -h, --help  Prints the help.

              This includes:
                 * All the available switches.
                 * All the available positional arguments.
                 * Whatever else the developer decided to put in here! We even
                   support wrapping comments which are overly long.

We determine the initial indentation level from the first doc comment. Looking at the code above, this would be the line containing Prints the help.. We then wrap additional lines relative to this level of indentation.

We also determine the individual indentation level of a line by looking at all the non-alphanumerical character that prefixes that line. That's why the "overly long" markdown list bullet above wraps correctly. Instead of wrapping at the *, it wraps to the first alphanumeric character after it.

Capture all available arguments using #[rest]

You can write a branch that receives all available arguments using the #[rest] attribute. This can be done both with arguments to switches, and positional arguments.

The following showcases capturing using a positional argument:

let args = argwerk::parse! {
    vec!["foo", "bar", "baz"].into_iter().map(String::from) =>
    /// A simple test command.
    "command [-h]" {
        rest: Vec<String>,
    }
    [#[rest] args] => {
        rest = args;
    }
}?;

assert_eq!(args.rest, &["foo", "bar", "baz"]);

And the following through a switch:

let args = argwerk::parse! {
    vec!["--test", "foo", "bar", "baz"].into_iter().map(String::from) =>
    "command [-h]" { rest: Vec<String>, }
    ["--test", #[rest] args] => {
        rest = args;
    }
}?;

assert_eq!(args.rest, &["foo", "bar", "baz"]);

Parsing optional arguments with #[option]

Switches and positional arguments can be marked with the #[option] attribute. This will cause the argument to take a value of type Option<I::Item> where I represents the iterator that is being parsed.

An optional argument parses to None if:

  • There are no more arguments to parse.
  • The argument is a switch (starts with -).
let parser = |iter: &[&str]| argwerk::parse! {
    iter.iter().copied().map(String::from) =>
    /// A simple test command.
    "command [-h]" {
        foo: Option<String>,
        bar: bool,
    }
    /// A switch taking an optional argument.
    ["--foo", #[option] arg] => {
        foo = arg;
    }
    ["--bar"] => {
        bar = true;
    }
};

// Argument exists, but looks like a switch.
let args = parser(&["--foo", "--bar"])?;
assert_eq!(args.foo.as_deref(), None);
assert!(args.bar);

// Argument does not exist.
let args = parser(&["--foo"])?;
assert_eq!(args.foo.as_deref(), None);
assert!(!args.bar);

let args = parser(&["--foo", "bar"])?;
assert_eq!(args.foo.as_deref(), Some("bar"));
assert!(!args.bar);