Expand description
Tools to define primitive parsers
§Ways to consume data
§Flag
flag
- a string that consists of two dashes (--flag
) and a name and a single dash and a single character (-f
) created withlong
andshort
respectively. Depending if this name is present or absent on the command line primitive flag parser produces one of two values. User can combine several short flags in a single invocation:-a -b -c
is the same as-abc
.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
decision: Decision,
}
#[derive(Debug, Clone)]
pub enum Decision {
Yes,
No,
}
fn parse_decision() -> impl Parser<Decision> {
long("decision")
.help("Positive decision")
.flag(Decision::Yes, Decision::No)
}
pub fn options() -> OptionParser<Options> {
let decision = parse_decision();
construct!(Options { decision }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Positive decision
#[bpaf(flag(Decision::Yes, Decision::No))]
decision: Decision,
}
#[derive(Debug, Clone)]
pub enum Decision {
Yes,
No,
}
fn main() {
println!("{:?}", options().run())
}
Output
In --help
output bpaf
shows flags with no meta variable attached
Usage: app [--decision]
- --decision
- Positive decision
- -h, --help
- Prints help information
Presense of a long name is decoded into Yes
Options { decision: Yes }
Absense is No
Options { decision: No }
§Required flag
Similar to flag
, but instead of falling back to the second value required flag parser would
fail. Mostly useful in combination with other parsers, created with NamedArg::req_flag
.
Combinatoric example
#[derive(Debug, Clone)]
pub enum Style {
Intel,
Att,
Llvm,
}
#[derive(Debug, Clone)]
pub enum Report {
/// Include defailed report
Detailed,
/// Include minimal report
Minimal,
/// No preferences
Undecided,
}
#[derive(Debug, Clone)]
pub struct Options {
agree: (),
style: Style,
report: Report,
}
pub fn options() -> OptionParser<Options> {
let agree = long("agree")
.help("You must agree to perform the action")
.req_flag(());
let intel = long("intel")
.help("Show assembly using Intel style")
.req_flag(Style::Intel);
let att = long("att")
.help("Show assembly using AT&T style")
.req_flag(Style::Att);
let llvm = long("llvm").help("Show llvm-ir").req_flag(Style::Llvm);
let style = construct!([intel, att, llvm]);
let detailed = long("detailed")
.help("Include detailed report")
.req_flag(Report::Detailed);
let minimal = long("minimal")
.help("Include minimal report")
.req_flag(Report::Minimal);
let report = construct!([detailed, minimal]).fallback(Report::Undecided);
construct!(Options {
agree,
style,
report
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
pub enum Style {
/// Show assembly using Intel style
Intel,
/// Show assembly using AT&T style
Att,
/// Show llvm-ir
Llvm,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(fallback(Report::Undecided))]
pub enum Report {
/// Include detailed report
Detailed,
/// Include minimal report
Minimal,
#[bpaf(skip)]
/// No preferences
Undecided,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// You must agree to perform the action
agree: (),
// external here uses explicit reference to function `style`
// generated above
#[bpaf(external(style))]
style: Style,
// here reference is implicit and derived from field name: `report`
#[bpaf(external)]
report: Report,
}
fn main() {
println!("{:?}", options().run())
}
Output
In --help
message req_flag
look similarly to switch
and
flag
Usage: app --agree (--intel | --att | --llvm) [--detailed | --minimal]
- --agree
- You must agree to perform the action
- --intel
- Show assembly using Intel style
- --att
- Show assembly using AT&T style
- --llvm
- Show llvm-ir
- --detailed
- Include detailed report
- --minimal
- Include minimal report
- -h, --help
- Prints help information
Example contains two parsers that fails without any input: agree
requires passing --agree
Error: expected --agree, pass --help for usage information
While style
takes one of several possible values
Error: expected --intel, --att, or more, pass --help for usage information
It is possible to alter the behavior using fallback
or
hide
.
Options { agree: (), style: Intel, report: Undecided }
While parser for style
takes any posted output - it won’t take multiple of them at once
(unless other combinators such as many
permit it) or last
.
Error: --llvm cannot be used at the same time as --att
§Switch
A special case of a flag that gets decoded into a bool
, mostly serves as a convenient
shortcut to .flag(true, false)
. Created with NamedArg::switch
.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
verbose: bool,
release: bool,
default_features: bool,
}
pub fn options() -> OptionParser<Options> {
let verbose = short('v')
.long("verbose")
.help("Produce verbose output")
.switch();
let release = long("release")
.help("Build artifacts in release mode")
.flag(true, false);
let default_features = long("no-default-features")
.help("Do not activate default features")
// default_features uses opposite values,
// producing `true` when value is absent
.flag(false, true);
construct!(Options {
verbose,
release,
default_features,
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Produce verbose output
// bpaf uses `switch` for `bool` fields in named
// structs unless consumer attribute is present.
// But it is also possible to give it explicit
// consumer annotation to serve as a reminder:
// #[bpaf(short, long, switch)]
#[bpaf(short, long)]
verbose: bool,
#[bpaf(flag(true, false))]
/// Build artifacts in release mode
release: bool,
/// Do not activate default features
// default_features uses opposite values,
// producing `true` when value is absent
#[bpaf(long("no-default-features"), flag(false, true))]
default_features: bool,
}
fn main() {
println!("{:?}", options().run())
}
Output
In --help
output bpaf
shows switches as usual flags with no meta variable attached
Usage: app [-v] [--release] [--no-default-features]
- -v, --verbose
- Produce verbose output
- --release
- Build artifacts in release mode
- --no-default-features
- Do not activate default features
- -h, --help
- Prints help information
Both switch
and flag
succeed if value is not present, switch
returns true, flag
returns
second value.
Options { verbose: false, release: false, default_features: true }
When value is present - switch
returns true
, flag
returns first value.
Error: --detailed is not expected in this context
Like with most parsrs unless specified switch
and flag
consume at most one item from the
command line:
Error: argument --no-default-features cannot be used multiple times in this context
§Argument
A short or long flag
followed by either a space or =
and
then by a string literal. -f foo
, --flag bar
or -o=-
are all valid argument examples. Note, string
literal can’t start with -
unless separated from the flag with =
. For short flags value
can follow immediately: -fbar
.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
name: String,
age: usize,
}
pub fn options() -> OptionParser<Options> {
let name = short('n')
.long("name")
.help("Specify user name")
// you can specify exact type argument should produce
// for as long as it implements `FromStr`
.argument::<String>("NAME");
let age = long("age")
.help("Specify user age")
// but often rust can figure it out from the context,
// here age is going to be `usize`
.argument("AGE")
.fallback(18)
.display_fallback();
construct!(Options { name, age }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
// you can specify exact type argument should produce
// for as long as it implements `FromStr`
#[bpaf(short, long, argument::<String>("NAME"))]
/// Specify user name
name: String,
// but often rust can figure it out from the context,
// here age is going to be `usize`
#[bpaf(argument("AGE"), fallback(18), display_fallback)]
/// Specify user age
age: usize,
}
fn main() {
println!("{:?}", options().run())
}
Output
Usage: app -n=NAME [--age=AGE]
- -n, --name=NAME
- Specify user name
- --age=AGE
- Specify user age
- [default: 18]
- -h, --help
- Prints help information
--help
shows arguments as a short name with attached metavariable
Value can be separated from flag by space, =
sign
Options { name: "Bob", age: 12 }
Options { name: "Bob", age: 12 }
Options { name: "Bob", age: 18 }
Options { name: "Bob", age: 18 }
Or in case of short name - be directly adjacent to it
Options { name: "Bob", age: 18 }
For long names - this doesn’t work since parser can’t tell where name stops and argument begins:
Error: no such flag: --age12, did you mean --age?
Either way - value is required, passing just the argument name results in parse failure
Error: --name requires an argument NAME
§Positional
A positional argument with no additonal name, for example in vim main.rs
main.rs
is a positional argument. Can’t start with -
, created with positional
.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
verbose: bool,
crate_name: String,
feature_name: Option<String>,
}
pub fn options() -> OptionParser<Options> {
let verbose = short('v')
.long("verbose")
.help("Display detailed information")
.switch();
let crate_name = positional("CRATE").help("Crate name to use");
let feature_name = positional("FEATURE")
.help("Display information about this feature")
.optional();
construct!(Options {
verbose,
// You must place positional items and commands after
// all other parsers
crate_name,
feature_name
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Display detailed information
#[bpaf(short, long)]
verbose: bool,
// You must place positional items and commands after
// all other parsers
#[bpaf(positional("CRATE"))]
/// Crate name to use
crate_name: String,
#[bpaf(positional("FEATURE"))]
/// Display information about this feature
feature_name: Option<String>,
}
fn main() {
println!("{:?}", options().run())
}
Output
Positional items show up in a separate group of arguments if they contain a help message, otherwise they will show up only in Usage part.
Usage: app [-v] CRATE [FEATURE]
- CRATE
- Crate name to use
- FEATURE
- Display information about this feature
- -v, --verbose
- Display detailed information
- -h, --help
- Prints help information
You can mix positional items with regular items
Options { verbose: true, crate_name: "bpaf", feature_name: None }
And since bpaf
API expects to have non positional items consumed before positional ones - you
can use them in a different order. In this example bpaf
corresponds to a crate_name
field and
--verbose
– to verbose
.
Options { verbose: true, crate_name: "bpaf", feature_name: None }
In previous examples optional field feature
was missing, this one contains it.
Options { verbose: false, crate_name: "bpaf", feature_name: Some("autocomplete") }
Users can use --
to tell bpaf
to treat remaining items as positionals - this might be
required to handle unusual items.
Options { verbose: false, crate_name: "bpaf", feature_name: Some("--verbose") }
Options { verbose: false, crate_name: "bpaf", feature_name: Some("--verbose") }
Without using --
bpaf
would only accept items that don’t start with -
as positional.
Error: expected CRATE, got --detailed. Pass --help for usage information
Error: expected CRATE, pass --help for usage information
You can use any
to work around this restriction.
§Any
Also a positional argument with no additional name, but unlike positional
itself, any
isn’t restricted to positional looking structure and would consume any items as they appear on
a command line. Can be useful to collect anything unused to pass to other applications.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
turbo: bool,
rest: Vec<OsString>,
}
pub fn options() -> OptionParser<Options> {
let turbo = short('t')
.long("turbo")
.help("Engage the turbo mode")
.switch();
let rest = any::<OsString, _, _>("REST", |x| (x != "--help").then_some(x))
.help("app will pass anything unused to a child process")
.many();
construct!(Options { turbo, rest }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(short, long)]
/// Engage the turbo mode
turbo: bool,
#[bpaf(any("REST", not_help), many)]
/// app will pass anything unused to a child process
rest: Vec<OsString>,
}
fn not_help(s: OsString) -> Option<OsString> {
if s == "--help" {
None
} else {
Some(s)
}
}
fn main() {
println!("{:?}", options().run())
}
Output
--help
keeps working for as long as any
captures only intended values - that is it ignores
--help
flag specifically
Usage: app [-t] [REST]...
- REST
- app will pass anything unused to a child process
- -t, --turbo
- Engage the turbo mode
- -h, --help
- Prints help information
You can mix any
with regular options, here switch
turbo
works because it goes
before rest
in the parser declaration
Options { turbo: true, rest: ["git", "commit", "-m", "hello world"] }
“before” in the previous line means in the parser definition, not on the user input, here
--turbo
gets consumed by turbo
parser even the argument goes
Options { turbo: true, rest: ["git", "commit", "-m=hello world"] }
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
block_size: usize,
count: usize,
output_file: String,
turbo: bool,
}
/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
fn tag<T>(name: &'static str, meta: &str, help: impl Into<Doc>) -> impl Parser<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
// closure inside checks if command line argument starts with a given name
// and if it is - it accepts it, otherwise it behaves like it never saw it
// it is possible to parse OsString here and strip the prefix with
// `os_str_bytes` or a similar crate
any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
// this defines custom metavar for the help message
// so it looks like something it designed to parse
.metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
.help(help)
// this makes it so tag parser tries to read all (unconsumed by earlier parsers)
// item on a command line instead of trying and failing on the first one
.anywhere()
// At this point parser produces `String` while consumer might expect some other
// type. [`parse`](Parser::parse) handles that
.parse(|s| s.parse())
}
pub fn options() -> OptionParser<Options> {
let block_size = tag("bs=", "BLOCK", "How many bytes to read at once")
.fallback(1024)
.display_fallback();
let count = tag("count=", "NUM", "How many blocks to read").fallback(1);
let output_file = tag("of=", "FILE", "Save results into this file");
// this consumes literal value of "+turbo" locate and produces `bool`
let turbo = literal("+turbo")
.help("Engage turbo mode!")
.anywhere()
.map(|_| true)
.fallback(false);
construct!(Options {
block_size,
count,
output_file,
turbo
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
// This example is still technically derive API, but derive is limited to gluing
// things together and keeping macro complexity under control.
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
// `external` here and below derives name from the field name, looking for
// functions called `block_size`, `count`, etc that produce parsers of
// the right type.
// A different way would be to write down the name explicitly:
// #[bpaf(external(block_size), fallback(1024), display_fallback)]
#[bpaf(external, fallback(1024), display_fallback)]
block_size: usize,
#[bpaf(external, fallback(1))]
count: usize,
#[bpaf(external)]
output_file: String,
#[bpaf(external)]
turbo: bool,
}
fn block_size() -> impl Parser<usize> {
tag("bs=", "BLOCK", "How many bytes to read at once")
}
fn count() -> impl Parser<usize> {
tag("count=", "NUM", "How many blocks to read")
}
fn output_file() -> impl Parser<String> {
tag("of=", "FILE", "Save results into this file")
}
fn turbo() -> impl Parser<bool> {
literal("+turbo")
.help("Engage turbo mode!")
.anywhere()
.map(|_| true)
.fallback(false)
}
/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
fn tag<T>(name: &'static str, meta: &str, help: impl Into<Doc>) -> impl Parser<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
// closure inside checks if command line argument starts with a given name
// and if it is - it accepts it, otherwise it behaves like it never saw it
// it is possible to parse OsString here and strip the prefix with
// `os_str_bytes` or a similar crate
any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
// this defines custom metavar for the help message
// so it looks like something it designed to parse
.metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
.help(help)
// this makes it so tag parser tries to read all (unconsumed by earlier parsers)
// item on a command line instead of trying and failing on the first one
.anywhere()
// At this point parser produces `String` while consumer might expect some other
// type. [`parse`](Parser::parse) handles that
.parse(|s| s.parse())
}
fn main() {
println!("{:?}", options().run())
}
Output
Instead of usual metavariable any
parsers take something that can represent any value
Usage: app [bs=BLOCK] [count=NUM] of=FILE [+turbo]
- bs=BLOCK
- How many bytes to read at once
- [default: 1024]
- count=NUM
- How many blocks to read
- of=FILE
- Save results into this file
- +turbo
- Engage turbo mode!
- -h, --help
- Prints help information
Output file is required in this parser, other values are optional
Error: expected of=FILE, pass --help for usage information
Options { block_size: 1024, count: 1, output_file: "simple.txt", turbo: false }
Since options are defined with anywhere
- order doesn’t matter
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
Options { block_size: 65536, count: 12, output_file: "hello_world.rs", turbo: false }
§Command
A command defines a starting point for an independent subparser. Name must be a valid utf8
string. For example cargo build
invokes command "build"
and after "build"
cargo
starts accepting values it won’t accept otherwise
Combinatoric example
#[derive(Debug, Clone)]
pub struct Cmd {
flag: bool,
arg: usize,
}
#[derive(Debug, Clone)]
pub struct Options {
flag: bool,
cmd: Cmd,
}
fn cmd() -> impl Parser<Cmd> {
let flag = long("flag")
.help("This flag is specific to command")
.switch();
let arg = long("arg").argument::<usize>("ARG");
construct!(Cmd { flag, arg })
.to_options()
.descr("Command to do something")
.command("cmd")
// you can chain add extra short and long names
.short('c')
}
pub fn options() -> OptionParser<Options> {
let flag = long("flag")
.help("This flag is specific to the outer layer")
.switch();
construct!(Options { flag, cmd() }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
// `command` annotation with no name gets the name from the object it is attached to,
// but you can override it using something like #[bpaf(command("my_command"))]
// you can chain more short and long names here to serve as aliases
#[bpaf(command("cmd"), short('c'))]
/// Command to do something
pub struct Cmd {
/// This flag is specific to command
flag: bool,
arg: usize,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// This flag is specific to the outer layer
flag: bool,
#[bpaf(external)]
cmd: Cmd,
}
fn main() {
println!("{:?}", options().run())
}
Output
Commands show up on both outer level help
Usage: app [--flag] COMMAND ...
- --flag
- This flag is specific to the outer layer
- -h, --help
- Prints help information
- cmd, c
- Command to do something
As well as showing their own help
Command to do something
Usage: app cmd [--flag] --arg=ARG
- --flag
- This flag is specific to command
- --arg=ARG
- -h, --help
- Prints help information
In this example there’s only one command and it is required, so is the argument inside of it
Options { flag: false, cmd: Cmd { flag: false, arg: 42 } }
If you don’t specify this command - parsing will fail
You can have the same flag names inside and outside of the command, but it might be confusing for the end user. This example enables the outer flag
Options { flag: true, cmd: Cmd { flag: false, arg: 42 } }
And this one - both inside and outside
Options { flag: true, cmd: Cmd { flag: true, arg: 42 } }
And that’s the confusing part - unless you add context restrictions with
adjacent
and parse command first - outer flag wins.
So it’s best not to mix names on different levels
Options { flag: true, cmd: Cmd { flag: false, arg: 42 } }
Structs§
- Parser for a named argument, created with
argument
. - Builder structure for the [
command
] - Parser for a named switch, created with
NamedArg::flag
orNamedArg::switch
- Parse a positional item, created with
positional