7 releases

0.1.6 Jun 26, 2021
0.1.5 Jun 26, 2021
0.1.4 Apr 18, 2021

#512 in Command-line interface

MIT license

79KB
1.5K SLoC

Parsnip

A small Rust Argparser

// ./prog --arg param1 param2 -a
fn main() {
    let args = args! {
        // define all your args here
        args: vec![arg! {
            name: "arg",
            short: Some("a"),
        }, arg! {
            name: "arg2",
            long: Some("arg2"),
            num_values: NumValues::Between(1, 3),
        }],
        // subcommands (e.g git add)
        subcommands: vec![args! {}],
        // optional, by default parsing will return a "Results" object
        handler: |results| { return results.args.get("arg2"); }
    };
    let results = args.parse(std::env::args());
}

Setup

Add the lib to your Cargo.toml.

[dependencies]
argparsnip = "0.1.5"

Features

  • Autogenerated help/version commands
    • can be overwritten
    • [subcommand]* help & [subcommand]* version commands are also generated
    • name/version/about parameters default to using Cargo.toml CARGO_PKG_NAME/CARGO_PKG_VERSION/CARGO_PKG_DESCRIPTION variables
  • Arguments
    • supports short -h and --help syntax
    • unicode support by default
    • supports returning (as a vec) or failing on unknown arguments
    • Flags
      • supports combinations (e.g -rli is the same as -r -l -i)
      • supports repeats, e.g -vvv -v will count as the same flag v appearing 4 times
    • With Values
      • supports constraints on the number of values for an arg, e.g having exactly one value, having between 2 & 4 values, having any number of values. E.g -v foo bar
      • supports restricting values to specific primitive types (any, bool, i32, i64, f32, f64, String) via the TryInto trait
      • supports additional custom validation (so you can e.g write your own sum type restrictions)
      • supports default values
    • Combinations
      • Parsing can be configured to fail if any required argument is missing
      • supports requiring at least one of a set of arguments (e.g A || B || C)
      • supports requiring all arguments in a set (e.g A && B && C)
      • supports inverting sets (e.g !(A && B))
      • supports requiring any or all of multiple sets (e.g (A && B) || (A && C)). This can also be negated
    • Positional
      • supports unix --, i.e foo -- -a -b -c will recieve positional arguments ["-a", "-b", "-c"]
      • treats all args that don't start with - or -- as positional.
  • Subcommands
    • e.g cargo run vs cargo test (run/test are subcommands)
    • subcommands can have their own subcommands and arguments
    • help/version commands are generated separately for each subcommand
  • Optional Callback support
    • Instead of returning a results object with the argparsing results, A handler fn(Results) -> T can be provided for each command/subcommand (not supported when using serde).
  • no_std support
    • disable default features to enable no_std
    • parsnip = { version = "x", default-features = false }
  • serde support
    • parsnip = { version = "x", features = ["derive"] }
    • write your args schema in any format with a serde parser (serde_json, toml etc.), see derive-test for an example
  • Other opt-in features
    • debug - enables logging info about arg parsing
    • macros - enabled by default, we provide some utility macros to avoid writing ..Default::default() everywhere

Usage

Here are some quick common cases. For more examples please look at the tests in lib.rs

Documentation

https://docs.rs/argparsnip/0.1.5/argparsnip/

Examples

Minimal Flag Example

// ./prog --arg
fn main() {
    let args = args! {
        args: vec![arg! {
            name: "arg",
            short: Some("a"),
        }],
    };
    let results = args.parse(std::env::args());
    assert_eq!(1, results.flags("arg"));
}

Check if a flag was given once

// ./prog --arg
fn main() {
    let args = args! {
        args: vec![arg! {
            name: "arg",
            short: Some("a"),
            about: "a flag",
            long: Some("arg"),
            required: true,
        }],
    };
    let results = args.parse(std::env::args());
    assert_eq!(1, results.flags("arg"));
}

Get the value of an arg

// ./prog -a 1
fn main() {
    let args = args! {
        args: vec![arg! {
            name: "arg",
            short: Some("a"),
            default: Some(|| { Value::From(2) }),
            value_type: Type::Int,
            num_values: NumValues::Fixed(1),
        }],
    };
    let results = args.parse(std::env::args());
    assert_eq!(1, results.params.get("arg")?.try_into());
}

Validate an argument

// ./prog -a 1 2
fn main() {
    let args = args! {
        args: vec![arg! {
            name: "arg",
            short: Some("a"),
            value_type: Type::Int,
            num_values: NumValues::AtLeast(1),
            validation: |val| {
                let val: &i32 = v.try_into().unwrap();
                if 2 >= *val {
                    Ok(())
                } else {
                    Err("failed validation")
                }
            }
        }],
    };
    let results = args.parse(std::env::args());
    assert_eq!(vec![1, 2], results.params.get("arg")?.try_into());
}

Using Subcommand

// ./prog sub --arg 
fn main() {
    let args = args! {
        args: vec![arg! {
            name: "arg",
            long: Some("arg"),
            num_values: NumValues::None,
        }],
        subcommands: vec![args! {
            name: "sub",
            path: Some("main/sub"),
            args: vec![arg! {
                name: "arg",
                long: Some("arg"),
                num_values: NumValues::None,
            }],
        }],
    };
    let results = args.parse(std::env::args());
    // this is the unique identifier for the subcommand
    assert_eq!("main/sub", results.path);
    assert_eq!(1, results.flags["arg"]);
}

Filters

// only supports combinations (--arg && --arg2) or (--arg && --arg3)
// will fail if --arg or --arg2 or --arg3 are passed on their own
fn main() {
    let args = args! {
        args: vec![arg! {
            name: "arg",
            long: Some("arg"),
            num_values: NumValues::None,
        }, arg! {
            name: "arg2",
            long: Some("arg2"),
            num_values: NumValues::None,
        }, arg! {
            name: "arg3",
            long: Some("arg3"),
            num_values: NumValues::None,
        }],
        filters: Filters {
            filters: vec![Filter {
                filter_type: FilterType::All,
                inverse: false,
                args: vec!["arg", "arg2"],
            }, Filter {
                filter_type: FilterType::All,
                inverse: false,
                args: vec!["arg", "arg3"],
            }],
            ..Default::default()
        },
        // this flag means we will fail if we see the same value multiple times
        disable_overrides: true,
    };
    let results = args.parse(std::env::args());
}

Development

TODO

  • Benchmarks
  • More tests
  • Features
    • Bash/Zsh completion
    • Support disabling positional args
    • Support updating repeats, e.g --arg Foo --arg Bar should give {"arg": ["Foo", "Bar"]}
  • Pretty sure my design isn't iterator friendly, try using a counter instead

Dependencies

~315–720KB
~11K SLoC