# 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
- <program> [subcommand]* help & <program> [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