#parser

typeparam

A typesafe parameter parsing library

1 unstable release

Uses old Rust 2015

0.0.1 Oct 12, 2017

#4250 in #parser

MIT license

37KB
599 lines

TypeParam allows to write argument parsing in typesafe manner.

TypeParam is a macro taking an annotated structure and generating a structure and implementation for parsing. Internally it uses clap.

Please note that it is in very early stage of development and not all features are implemented. The interface and defaults are subject to change.

Example

#[macro_use]
extern crate typeparam;
extern crate clap;
typeparam!{
    struct Params [@app test ::std::string::ParseError] {
        quiet: bool [QUIET: -q],
        verbose: bool [VERBOSE: -v (value: flag)],
        cfg: String [CFG: -c (value: default String::from("---"))],
        path: String [PATH: -p (value: required)],
        foo: Option<String> [FOO: --foo (value: optional)],
        n: Option<u32> [N: -n (value: option map (|_v: Option<&str>| Some(3)))],
        x: u32 [X: -x (value: map (|_| 4))],
        command: @SUBCOMMANDS<Commands> [list => List(List),
                                         get => Get2(Get),
                                         foo => Foo: (default = Default)]
    }
    struct List [@subcommand ::std::string::ParseError];
    struct Get [@subcommand ::std::string::ParseError];
}

#
fn main() {
    use typeparam::Command;
    let params = Params::parse(["simple", "-v", "--foo", "bar", "-p", "path", "-x", "X"].iter()).unwrap();
    assert!(!params.quiet);
    assert!(params.verbose);
    assert_eq!(params.cfg, "---");
    assert_eq!(params.path, "path");
    assert_eq!(params.foo, Some(String::from("bar")));
    assert_eq!(params.n, Some(3));
    assert_eq!(params.x, 4);
    match params.command {
        Commands::Default => {},
        Commands::List(_) | Commands::Get2(_) | Commands::Foo => {
            panic!("params.commands != Commands::Default")
        }
    }
}

In following example it created an parsing structure for application named test. Application takes two required arguments - -p and -x - and three optional ones - --cfg, --foo and -n. In addition it accepts two flags - -q anf -v.

It accepts optionally one of three commands - list, get or foo.

After successful parsing it returns a structure containing parsed commands.

Structures and Enums

Currently two types of structures are accepted - apps and subcommands. Both have the same syntax with exception of the square brackets after type name.

Apps need to have @app as first token appearing in square bracket followed by app name and type of error parsing may return.

Subcommands need to have @subcommand as first token followed by type of error parsing may return.

Parameters and Options

Parameters and options are specified as normal fields in struct followed by square brackets containing their name. Optionally after the name there can be colon followed by any number of arguments. Arguments can be specified in any order.

To denote a short option a dash with a letter should be specified as argument (-l) while long double dash with an identifier (--foo). They are not mutually exclusive.

#[macro_use]
extern crate typeparam;
extern crate clap;
typeparam!{
    struct Params [@app test ::std::string::ParseError] {
        foo: bool [FOO: --foo],
        bar: bool [BAR: -b],
        foobar: bool [FOOBAR: -f --foobar]
    }
}

fn main() {
    use typeparam::Command;
    let params = Params::parse(["simple", "-f", "--foo"].iter()).unwrap();
    assert_eq!(params.foo, true);
    assert_eq!(params.bar, false);
    assert_eq!(params.foobar, true);
}

If either option is omitted the field denote a positional argument.

#[macro_use]
extern crate typeparam;
extern crate clap;
typeparam!{
    struct Params [@app test ::std::string::ParseError] {
        foo: String [FOO: (value: required)],
        bar: String [BAR: (value: required)],
        foobar: bool [FOOBAR: -f]
    }
}

fn main() {
    use typeparam::Command;
    let params = Params::parse(["simple", "foo", "-f", "bar"].iter()).unwrap();
    assert_eq!(params.foo.as_str(), "foo");
    assert_eq!(params.bar.as_str(), "bar");
    assert_eq!(params.foobar, true);
}

Each option can also take a value setting. Currently there are 5 valid settings:

  • value: flag is default and denotes a single flag - in other words no argument. Value returned is bool.
  • value: required denotes an required argument. If user does not passes it, an error is returned. Otherwise fromStr is called on value passed by user.
  • value: optional denotes an optional argument. If user does not passes it None is returned. Otherwise fromStr is called on value and wrapped by Some.
  • value: map callback denotes an required argument, just as value: required, however it allows to supply arbitrary function. Both functions returning value directly as well as Result are accepted.
  • value: option map callback denotes an optional argument, just as value: optional. However it allows to supply an arbitrary function. Both functions returning value directly as well as Result are accepted.

Subcommands

Subcommands functions as nested apps inside the command. This style has been popularized by (git)https://git-scm.com/.

For application (or recursivly subcommand) to have subcommands it is necessary to add field of type @SUBCOMMANDS<NameOfEnum>. There can be at most one such field in struct.

#[macro_use]
extern crate typeparam;
extern crate clap;
typeparam!{
    struct IllegalParams [@app illegal ::std::string::ParseError] {
        illegal1: @SUBCOMMANDS<Illegal1> [foo => Foo],
        illegal2: @SUBCOMMANDS<Illegal2> [foo => Bar]
    }
}

Afterwards the subcommands are specified in square brackets. There are two forms of subcommand specification - including type (subcommand => Identifier(Type)) and not (subcommand => Identifier). In the first form a type must be a subcommand and the description of fields is taken from there. The second form makes a subcommand not to get any additional fields. In both cases the Identifier is added to the enum specified after @SUBCOMMAND

  • with single argument or without any arguments respectivly.
#[macro_use]
extern crate typeparam;
extern crate clap;
typeparam!{
    struct Params [@app test ::std::string::ParseError] {
        command: @SUBCOMMANDS<Commands> [list => List, find => Find(Find)],
        debug: bool [DEBUG: -d (value: flag)]
    }
}
typeparam!{
    struct Find [@subcommand ::std::string::ParseError] {
        name: String [NAME: (value: required)],
        case_insensitive: bool [CASE_INSENSITIVE: -i (value: flag)]
    }
}

#
fn main() {
   use typeparam::Command;
   let list = Params::parse(["simple", "-d", "list"].iter()).unwrap();
   assert_eq!(list.debug, true);
   match list.command {
       Commands::List => {},
       _ => panic!("Expected Commands::List")
   }
   let find = Params::parse(["simple", "-d", "find", "-i", "something"].iter()).unwrap();
   assert_eq!(find.debug, true);
   match find.command {
       Commands::Find(find_cmd) => {
           assert_eq!(find_cmd.case_insensitive, true);
           assert_eq!(find_cmd.name.as_str(), "something");
       },
       _ => panic!("Expected Commands::Find(_)")
   }
}

Optionally they can be followed by colon and any number of arguments passed. Currently there is only one argument supported - (default = Value). If it is specified Value becomes a value of enum when no command is given. Otherwise passing a command is required.

#[macro_use]
extern crate typeparam;
extern crate clap;
typeparam!{
    struct Params [@app test ::std::string::ParseError] {
        command: @SUBCOMMANDS<Commands> [list => List, find => Find(Find)],
        debug: bool [DEBUG: -d (value: flag)]
    }
    struct Find [@subcommand ::std::string::ParseError] {
        name: String [NAME: (value: required)],
        case_insensitive: bool [CASE_INSENSITIVE: -i (value: flag)]
    }
}

#
fn main() {
   use typeparam::Command;
   assert!(Params::parse(["simple"].iter()).is_err());
}

Dependencies

~1.5MB
~24K SLoC