Crates.io | bpaf |
lib.rs | bpaf |
version | 0.9.12 |
source | src |
created_at | 2022-03-10 11:11:16.190788 |
updated_at | 2024-04-29 12:09:45.482951 |
description | A simple Command Line Argument Parser with parser combinators |
homepage | |
repository | https://github.com/pacak/bpaf |
max_upload_size | |
id | 547572 |
size | 870,659 |
Lightweight and flexible command line argument parser with derive and combinatoric style API
bpaf
supports both combinatoric and derive APIs and it’s possible to mix and match both APIs
at once. Both APIs provide access to mostly the same features, some things are more convenient
to do with derive (usually less typing), some - with combinatoric (usually maximum flexibility
and reducing boilerplate structs). In most cases using just one would suffice. Whenever
possible APIs share the same keywords and overall structure. Documentation is shared and
contains examples for both combinatoric and derive style.
bpaf
supports dynamic shell completion for bash
, zsh
, fish
and elvish
.
Add bpaf
under [dependencies]
in your Cargo.toml
[dependencies]
bpaf = { version = "0.9", features = ["derive"] }
Define a structure containing command line attributes and run generated function
use bpaf::Bpaf;
#[derive(Clone, Debug, Bpaf)]
#[bpaf(options, version)]
/// Accept speed and distance, print them
struct SpeedAndDistance {
/// Speed in KPH
speed: f64,
/// Distance in miles
distance: f64,
}
fn main() {
// #[derive(Bpaf)] generates `speed_and_distance` function
let opts = speed_and_distance().run();
println!("Options: {:?}", opts);
}
Try to run the app
% very_basic --help
Accept speed and distance, print them
Usage: --speed=ARG --distance=ARG
Available options:
--speed=ARG Speed in KPH
--distance=ARG Distance in miles
-h, --help Prints help information
-V, --version Prints version information
% very_basic --speed 100
Expected --distance ARG, pass --help for usage information
% very_basic --speed 100 --distance 500
Options: SpeedAndDistance { speed: 100.0, distance: 500.0 }
% very_basic --version
Version: 0.9.0 (taken from Cargo.toml by default)
Add bpaf
under [dependencies]
in your Cargo.toml
[dependencies]
bpaf = "0.9"
Declare parsers for components, combine them and run it
use bpaf::{construct, long, Parser};
#[derive(Clone, Debug)]
struct SpeedAndDistance {
/// Speed in KPH
speed: f64,
/// Distance in miles
distance: f64,
}
fn main() {
// primitive parsers
let speed = long("speed")
.help("Speed in KPG")
.argument::<f64>("SPEED");
let distance = long("distance")
.help("Distance in miles")
.argument::<f64>("DIST");
// parser containing information about both speed and distance
let parser = construct!(SpeedAndDistance { speed, distance });
// option parser with metainformation attached
let speed_and_distance
= parser
.to_options()
.descr("Accept speed and distance, print them");
let opts = speed_and_distance.run();
println!("Options: {:?}", opts);
}
Try to run the app
% very_basic --help
Accept speed and distance, print them
Usage: --speed=ARG --distance=ARG
Available options:
--speed=ARG Speed in KPH
--distance=ARG Distance in miles
-h, --help Prints help information
-V, --version Prints version information
% very_basic --speed 100
Expected --distance ARG, pass --help for usage information
% very_basic --speed 100 --distance 500
Options: SpeedAndDistance { speed: 100.0, distance: 500.0 }
% very_basic --version
Version: 0.5.0 (taken from Cargo.toml by default)
Library allows to consume command line arguments by building up parsers for individual arguments and combining those primitive parsers using mostly regular Rust code plus one macro. For example, it’s possible to take a parser that requires a single floating point number and transform it to a parser that takes several of them or takes it optionally so different subcommands or binaries can share a lot of the code:
// a regular function that doesn't depend on any context, you can export it
// and share across subcommands and binaries
fn speed() -> impl Parser<f64> {
long("speed")
.help("Speed in KPH")
.argument::<f64>("SPEED")
}
// this parser accepts multiple `--speed` flags from a command line when used,
// collecting results into a vector
fn multiple_args() -> impl Parser<Vec<f64>> {
speed().many()
}
// this parser checks if `--speed` is present and uses value of 42.0 if it's not
fn with_fallback() -> impl Parser<f64> {
speed().fallback(42.0)
}
At any point you can apply additional validation or fallback values in terms of current parsed state of each sub-parser and you can have several stages as well:
#[derive(Clone, Debug)]
struct Speed(f64);
fn speed() -> impl Parser<Speed> {
long("speed")
.help("Speed in KPH")
.argument::<f64>("SPEED")
// You can perform additional validation with `parse` and `guard` functions
// in as many steps as required.
// Before and after next two applications the type is still `impl Parser<f64>`
.guard(|&speed| speed >= 0.0, "You need to buy a DLC to move backwards")
.guard(|&speed| speed <= 100.0, "You need to buy a DLC to break the speed limits")
// You can transform contained values, next line gives `impl Parser<Speed>` as a result
.map(|speed| Speed(speed))
}
The library follows the parse, don’t validate approach when possible. Usually you parse your values just once, and then get the results as a Rust struct/enum with strict types in both combinatoric and derive APIs.
The main restriction that the library sets is that you can’t use parsed values (but not the fact that parser succeeded or failed) to decide how to parse subsequent values. In other words the parsers don’t have the monadic strength, only the applicative one.
To give an example, you can implement this description:
Program takes one of
--stdout
or--file
flag to specify the output target, when it’s--file
program also requires-f
attribute with the filename
But not this one:
Program takes an
-o
attribute with possible values of'stdout'
and'file'
, when it’s'file'
program also requires-f
attribute with the filename
This set of restrictions allows bpaf
to extract information about the structure of the
computations to generate help, dynamic completion and overall results in less confusing endures
experience
bpaf
performs no parameter names validation, in fact having multiple parameters with the same
name is fine, and you can combine them as alternatives and performs no fallback other than
fallback
. You need to pay attention to the order of the alternatives inside the
macro: parser that consumes the left most available argument on a command line wins, if this is
the same - left most parser wins. So to parse a parameter --test
that can be both
switch
and argument
you should put the argument one first.
You must place positional
items at the end of a structure in derive API or
consume them as last arguments in derive API.
bpaf
implements shell completion to allow to automatically fill in not only flag and command
names, but also argument and positional item values.
Enable autocomplete
feature:
bpaf = { version = "0.9", features = ["autocomplete"] }
Decorate argument
and positional
parsers with
complete
to autocomplete argument values
Depending on your shell, it generates the appropriate completion file and place it to wherever your shell is going to look for it. The name of the file should correspond in some way to name of your program. Consult the manual for your shell for the location and named conventions:
bash
$ your_program --bpaf-complete-style-bash >> ~/.bash_completion
zsh: note _
at the beginning of the filename
$ your_program --bpaf-complete-style-zsh > ~/.zsh/_your_program
fish
$ your_program --bpaf-complete-style-fish > ~/.config/fish/completions/your_program.fish
elvish
$ your_program --bpaf-complete-style-elvish >> ~/.config/elvish/rc.elv
Restart your shell - you need to do it only once or optionally after bpaf
major version
upgrade: generated completion files contain only instructions how to ask your program for
possible completions and don’t change even if options are different.
Generated scripts rely on your program being accessible in $PATH
You can find a more examples here: https://github.com/pacak/bpaf/tree/master/examples
They’re usually documented or at least contain an explanation to important bits and you can see how they work by cloning the repo and running
$ cargo run --example example_name
You can test your own parsers to maintain compatibility or simply checking expected output with run_inner
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
pub user: String
}
#[test]
fn test_my_options() {
let help = options()
.run_inner(&["--help"])
.unwrap_err()
.unwrap_stdout();
let expected_help = "\
Usage --user=ARG
<skip>
";
assert_eq!(help, expected_help);
}
derive
: adds a dependency on bpaf_derive
crate and reexport Bpaf
derive macro. You
need to enable it to use derive API. Disabled by default.
extradocs
: used internally to include tutorials to https://docs.rs/bpaf, no reason to
enable it for local development unless you want to build your own copy of the documentation
(https://github.com/rust-lang/cargo/issues/8905). Disabled by default.
batteries
: helpers implemented with public bpaf
API. Disabled by default.
autocomplete
: enables support for shell autocompletion. Disabled by default.
bright-color
, dull-color
: use more colors when printing --help
and such. Enabling
either color feature adds some extra dependencies and might raise MRSV. If you are planning
to use this feature in a published app - it’s best to expose them as feature flags:
[features]
bright-color = ["bpaf/bright-color"]
dull-color = ["bpaf/dull-color"]
Disabled by default.
docgen
: generate documentation from help declaration, see OptionParser::render_markdown
. Disabled by default.