[][src]Macro argwerk::define

macro_rules! define {
    (
        $(#[doc = $doc:literal])*
        $(#[usage = $usage:literal])*
        $vis:vis struct $name:ident { $($body:tt)* }
        $($config:tt)*
    ) => { ... };
}

Parse command-line 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:

argwerk::define! {
    /// A simple test command.
    #[usage = "command [-h]"]
    struct Args {
        help: bool,
        limit: usize = 10,
    }
    /// Print this help.
    ["-h" | "--help"] => {
        help = true;
    }
}

let args = Args::args()?;

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>.

argwerk::define! {
    /// A simple test command.
    #[usage = "command [-h]"]
    struct Args {
        help: bool,
        positional: Option<(String, String, String)>,
    }
    [a, b, c] => {
        positional = Some((a, b, c));
    }
}

let args = Args::parse(vec!["foo", "bar", "baz"])?;

assert_eq!(args.positional, Some((String::from("foo"), String::from("bar"), String::from("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.

argwerk::define! {
    /// A simple test command.
    #[usage = "command [-h]"]
    struct Args {
        help: bool,
        limit: usize = 10,
    }
    /// Print this help.
    ["-h" | "--help"] => {
        help = true;
    }
    /// Specify a limit (default: 10).
    ["--limit", n] => {
        limit = str::parse(&n)?;
    }
}

let args = Args::parse(["--limit", "20"].iter().copied())?;

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.

argwerk::define! {
    #[usage = "command [-h]"]
    struct Args {
        help: bool
    }
    ["-h" | "--help"] => {
        help = true;
    }
}

let args = Args::parse(vec!["-h"])?;

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.

argwerk::define! {
    #[usage = "command [-h]"]
    struct Args {
        positional: Option<(String, String)>,
    }
    [foo, bar] if positional.is_none() => {
        positional = Some((foo, bar));
    }
}

let args = Args::parse(["a", "b"].iter().copied())?;

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 is also available as the HELP static variable inside of match branches. Help formatting can be further customized using Help::format.

argwerk::define! {
    /// A simple test command.
    #[usage = "command [-h]"]
    struct Args {
        help2: 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"] => {
        println!("{}", HELP.format().width(120));
    }
    ["--help2"] => {
        help2 = true;
    }
}

let args = Args::args()?;

// Another way to access and format help documentation.
if args.help2 {
    println!("{}", Args::help().format().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.

Required arguments using #[required]

You can specify required arguments using the #[required] attribute in the field specification. Fields which are marked as #[required] have the type Option<T>. If the field is left as uninitialized (None) once all arguments have been parsed will cause an error to be raised. See ErrorKind::MissingRequired.

A reason that the argument is required can be optionally provided by doing #[required = "--name is required"].

Examples

argwerk::define! {
    struct Args {
        #[required = "--name must be used"]
        name: String,
    }
    ["--name", n] => {
        name = Some(n);
    }
}

let args = Args::parse(vec!["--name", "John"])?;
assert_eq!(args.name, "John");

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:

argwerk::define! {
    /// A simple test command.
    #[usage = "command [-h]"]
    struct Args {
        rest: Vec<String>,
    }
    [#[rest] args] => {
        rest = args;
    }
}

let args = Args::parse(["foo", "bar", "baz"].iter().copied())?;

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

And the following through a switch:

argwerk::define! {
    #[usage = "command [-h]"]
    struct Args {
        rest: Vec<String>,
    }
    ["--test", #[rest] args] => {
        rest = args;
    }
}

let args = Args::parse(["--test", "foo", "bar", "baz"].iter().copied())?;

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 -).
argwerk::define! {
    /// A simple test command.
    #[usage = "command [-h]"]
    struct Args {
        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 = Args::parse(["--foo", "--bar"].iter().copied())?;
assert_eq!(args.foo.as_deref(), None);
assert!(args.bar);

// Argument does not exist.
let args = Args::parse(["--foo"].iter().copied())?;
assert_eq!(args.foo.as_deref(), None);
assert!(!args.bar);

let args = Args::parse(["--foo", "bar"].iter().copied())?;
assert_eq!(args.foo.as_deref(), Some("bar"));
assert!(!args.bar);