parkour
A fast, extensible, command-line arguments parser.
📚
Introduction The most popular argument parser, clap
, allows you list all the possible arguments and their constraints, and then gives you a dynamic, stringly-typed object containing all the values. Usually these values are then manually extracted into structs and enums to access the values more conveniently and get the advantages of a static type system (example).
Parkour uses a different approach: Instead of parsing the arguments into an intermediate, stringly-typed object, it parses them directly into the types you want, so there's no cumbersome conversion. For types outside the standard library, you need to implement a trait, but in most cases this can be done with a simple derive macro.
This has several advantages:
- It is very flexible: Every aspect of argument parsing can be tailored to your needs.
- It is strongly typed: Many errors can be caught at compile time, so you waste less time debugging.
- It is zero-cost: If you don't need a feature, you don't have to use it. Parkour should also be pretty fast, but don't take my word for it, benchmark it
😉
Status
Parkour started as an experiment and is very new (about 1 week old at the time of writing). Expect frequent breaking changes. If you like what you see, consider supporting this work by
- Reading the docs
- Trying it out
- Giving feedback in this issue
- Opening issues or sending PRs
Right now, parkour lacks some important features, which I intend to implement:
- Auto-generated help messages
- A DSL to write (sub)commands more ergonomically
- More powerful derive macros
- Error messages with ANSI colors
Example
use parkour::prelude::*;
#[derive(FromInputValue)]
enum ColorMode {
Always,
Auto,
Never,
}
struct Command {
color_mode: ColorMode,
file: String,
}
impl FromInput<'static> for Command {
type Context = ();
fn from_input<P: Parse>(input: &mut P, _: &Self::Context)
-> Result<Self, parkour::Error> {
// discard the first argument
input.bump_argument().unwrap();
let mut file = None;
let mut color_mode = None;
while !input.is_empty() {
if input.parse_long_flag("help") || input.parse_short_flag("h") {
println!("Usage: run [-h,--help] [--color,-c auto|always|never] FILE");
return Err(parkour::Error::early_exit());
}
if SetOnce(&mut color_mode)
.apply(input, &Flag::LongShort("color", "c").into())? {
continue;
}
if SetPositional(&mut file).apply(input, &"FILE")? {
continue;
}
input.expect_empty()?;
}
Ok(Command {
color_mode: color_mode.unwrap_or(ColorMode::Auto),
file: file.ok_or_else(|| parkour::Error::missing_argument("FILE"))?,
})
}
}
In the future, I'd like to support a syntax like this:
use parkour::prelude::*;
#[derive(FromInput)]
#[parkour(main, help)]
#[arg(long = "version", short = "V", function = print_help)]
struct Command {
#[parkour(default = ColorMode::Auto)]
#[arg(long = "color", short = "c")]
color_mode: ColorMode,
#[arg(positional)]
subcommand: Option<Subcommand>,
}
#[derive(FromInputValue)]
enum ColorMode {
Always,
Auto,
Never,
}
#[derive(FromInput)]
enum Subcommand {
Foo(Foo),
Bar(Bar),
}
#[derive(FromInput)]
#[parkour(subcommand)]
struct Foo {
#[parkour(default = 0, max = 3)]
#[arg(long, short, action = Inc)]
verbose: u8,
#[arg(positional)]
values: Vec<String>,
}
#[derive(FromInput)]
#[parkour(subcommand)]
struct Bar {
#[parkour(default = false)]
#[arg(long, short)]
dry_run: bool,
}
fn print_version(_: &mut impl Parse) -> parkour::Result<()> {
println!("command {}", env!("CARGO_PKG_VERSION"));
Err(parkour::Error::early_exit())
}
🤝
Code of Conduct Please be friendly and respectful to others. This should be a place where everyone can feel safe, therefore I intend to enforce the Rust code of conduct.
License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.