pub trait Parser<T> {
Show 21 methods
// Provided methods
fn many(self) -> ParseMany<Self>
where Self: Sized { ... }
fn collect<C>(self) -> ParseCollect<Self, C, T>
where C: FromIterator<T>,
Self: Sized { ... }
fn some(self, message: &'static str) -> ParseSome<Self>
where Self: Sized + Parser<T> { ... }
fn optional(self) -> ParseOptional<Self>
where Self: Sized + Parser<T> { ... }
fn count(self) -> ParseCount<Self, T>
where Self: Sized + Parser<T> { ... }
fn last(self) -> ParseLast<Self>
where Self: Sized + Parser<T> { ... }
fn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
where Self: Sized + Parser<T>,
F: Fn(T) -> Result<R, E>,
E: ToString { ... }
fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
where Self: Sized + Parser<T>,
F: Fn(T) -> R + 'static { ... }
fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
where Self: Sized + Parser<T>,
F: Fn(&T) -> bool { ... }
fn fallback(self, value: T) -> ParseFallback<Self, T>
where Self: Sized + Parser<T> { ... }
fn fallback_with<F, E>(
self,
fallback: F,
) -> ParseFallbackWith<T, Self, F, E>
where Self: Sized + Parser<T>,
F: Fn() -> Result<T, E>,
E: ToString { ... }
fn hide(self) -> ParseHide<Self>
where Self: Sized + Parser<T> { ... }
fn hide_usage(self) -> ParseUsage<Self>
where Self: Sized + Parser<T> { ... }
fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
where M: Into<Doc>,
Self: Sized + Parser<T> { ... }
fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
where Self: Sized + Parser<T> { ... }
fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
where Self: Sized + Parser<T>,
F: Fn(MetaInfo<'_>) -> Doc { ... }
fn complete<M, F>(self, op: F) -> ParseComp<Self, F>
where M: Into<String>,
F: Fn(&T) -> Vec<(M, Option<M>)>,
Self: Sized + Parser<T> { ... }
fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
where Self: Sized + Parser<T> { ... }
fn to_options(self) -> OptionParser<T>
where Self: Sized + Parser<T> + 'static { ... }
fn run(self) -> T
where Self: Sized + Parser<T> + 'static { ... }
fn boxed(self) -> Box<dyn Parser<T>>
where Self: Sized + Parser<T> + 'static { ... }
}
Expand description
Simple or composed argument parser
§Overview
It’s best to think of an object implementing Parser
trait as a container with a value
inside that is composable with other Parser
containers using construct!
and the only
way to extract this value is by transforming it to OptionParser
with
to_options
and running it with run
. At which
point you either get your value out or bpaf
would generate a message describing a problem
(missing argument, validation failure, user requested help, etc) and the program would
exit.
Values inside can be of any type for as long as they implement Debug
, Clone
and
there are no lifetimes other than static.
When consuming the values you can jump straight to a value that implements
FromStr
trait and then transform it into something that your program would use. Alternatively,
you can consume either String
or OsString
and parse that by hand. It’s better to perform
as much parsing and validation inside the Parser
as possible so the program itself gets
strictly typed and correct value while the user gets immediate feedback on what’s wrong with the
arguments they pass.
Order of operations matters, each subsequent parser gets the output of the earlier one. Both
parsers a
and b
would consume multiple numeric values, each less than 10, but a
validates a single value and then consumes multiple of them already validated, while b
first
consumes and then performs validation. The former approach is usually more readable.
let a = short('a').argument::<usize>("N")
.guard(|&a| a < 10, "`a` must be below 10")
.many();
let b = short('b').argument::<usize>("N")
.many()
.guard(|bs| bs.iter().all(|&b| b < 10), "`b` must be below 10");
The same logic applies to derive API - the current type depends on the order of annotations:
#[derive(Bpaf, Debug, Clone)]
struct Simple {
#[bpaf(argument("N"), guard(less_than_10, "`a` must be below 10"), many)]
a: Vec<usize>,
#[bpaf(argument("N"), many, guard(all_less_than_10, "`b` must be below 10"))]
b: Vec<usize>,
}
For example suppose your program needs the user to specify dimensions of a rectangle, with sides being 1..20 units long and the total area must not exceed 200 units square. A parser that consumes it might look like this:
#[derive(Debug, Copy, Clone)]
struct Rectangle {
width: u32,
height: u32,
}
fn rectangle() -> impl Parser<Rectangle> {
let invalid_size = "Sides of a rectangle must be 1..20 units long";
let invalid_area = "Area of a rectangle must not exceed 200 units square";
let width = long("width")
.help("Width of the rectangle")
.argument::<u32>("PX")
.guard(|&x| 1 <= x && x <= 10, invalid_size);
let height = long("height")
.help("Height of the rectangle")
.argument::<u32>("PX")
.guard(|&x| 1 <= x && x <= 10, invalid_size);
construct!(Rectangle { width, height })
.guard(|&r| r.width * r.height <= 400, invalid_area)
}
§Derive specific considerations
Every method defined on this trait belongs to the postprocessing
section of the field
annotation. bpaf
would try to figure out what chain to use for as long as there are no
options changing the type: you can use fallback
,
fallback_with
, guard
, hide
and
group_help
but not the rest of them.
#[derive(Debug, Clone, Bpaf)]
struct Options {
// no annotation at all - `bpaf` inserts implicit `argument` and gets the right type
number_1: u32,
// fallback isn't changing the type so `bpaf` still handles it
#[bpaf(fallback(42))]
number_2: u32,
// `bpaf` inserts implicit `argument`, `optional` and the right type
number_3: Option<u32>,
// fails to compile: you need to specify `argument`
// #[bpaf(optional)]
// number_4: Option<u32>,
#[bpaf(argument("N"), optional)]
number_5: Option<u32>,
// explicit consumer and a full postprocessing chain
#[bpaf(argument::<u32>("N"), optional)]
number_6: Option<u32>,
}
Provided Methods§
sourcefn many(self) -> ParseMany<Self>where
Self: Sized,
fn many(self) -> ParseMany<Self>where
Self: Sized,
Consume zero or more items from a command line and collect them into a Vec
many
preserves any parsing failures and propagates them outwards, with an extra
catch
statement you can instead stop at the first value
that failed to parse and ignore it and all the subsequent ones.
many
will collect at most one result that does not consume anything from the argument
list allowing using it in combination with any parsers with a fallback. After the first
one, it will keep collecting the results as long as they consume something.
For derive usage bpaf
would insert implicit many
when the resulting type is a
vector.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
argument: Vec<u32>,
switches: Vec<bool>,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.many();
let switches = long("switch").help("some switch").switch().many();
construct!(Options { argument, switches }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// important argument
argument: Vec<u32>,
/// some switch
#[bpaf(long("switch"), switch)]
switches: Vec<bool>,
}
fn main() {
println!("{:?}", options().run())
}
Output
In usage lines many
items are indicated with ...
Usage: app [--argument=ARG]... [--switch]...
- --argument=ARG
- important argument
- --switch
- some switch
- -h, --help
- Prints help information
Run inner parser as many times as possible collecting all the new results
First false
is collected from a switch even if it is not consuming anything
Options { argument: [10, 20], switches: [false] }
If there’s no matching parameters - it would produce an empty vector. Note, in case of
switch
parser or other parsers that can succeed without consuming anything
it would capture that value so many
captures the first one of those.
You can use req_flag
to avoid that.
Options { argument: [], switches: [false] }
For parsers that can succeed without consuming anything such as flag
or switch
- many
only collects values as long as they produce something
Options { argument: [], switches: [true, true] }
§See also
some
also collects results to a vector but requires at least one
element to succeed, collect
collects results into a FromIterator
structure
Examples found in repository?
More examples
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
fn verbose() -> impl Parser<usize> {
short('v')
.long("verbose")
.help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
.req_flag(())
.many()
.map(|xs| xs.len())
.guard(|&x| x <= 3, "It doesn't get any more verbose than this")
}
// an argument, parsed and with default value
fn speed() -> impl Parser<f64> {
short('s')
.long("speed")
.help("Set speed")
.argument::<f64>("SPEED")
.fallback(42.0)
}
fn output() -> impl Parser<PathBuf> {
short('o')
.long("output")
.help("output file")
.argument::<PathBuf>("OUTPUT")
}
// no magical name transmogrifications.
fn nb_cars() -> impl Parser<u32> {
short('n').long("nb-cars").argument::<u32>("N")
}
fn files_to_process() -> impl Parser<Vec<PathBuf>> {
short('f')
.long("file")
.help("File to process")
.argument::<PathBuf>("FILE")
.many()
}
6 7 8 9 10 11 12 13 14 15 16 17
fn args() -> impl Parser<Vec<u16>> {
long("ports")
.help("Comma separated list of ports")
.argument::<String>("PORTS")
.parse(|s| {
s.split(',')
.map(u16::from_str)
.collect::<Result<Vec<_>, _>>()
})
.many()
.map(|nested| nested.into_iter().flatten().collect())
}
sourcefn collect<C>(self) -> ParseCollect<Self, C, T>where
C: FromIterator<T>,
Self: Sized,
fn collect<C>(self) -> ParseCollect<Self, C, T>where
C: FromIterator<T>,
Self: Sized,
Transform parser into a collection parser
A generic variant of many
, instead of collecting into a vector
it collects into any collection that implements FromIterator
trait
collect
preserves any parsing failures and propagates them outwards, with extra
catch
statement you can instead stop at the first value
that failed to parse and ignore it and all the subsequent ones.
Combinatoric example
use std::collections::BTreeSet;
#[derive(Debug, Clone)]
pub struct Options {
argument: BTreeSet<u32>,
switches: BTreeSet<bool>,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.collect();
let switches = long("switch").help("some switch").switch().collect();
construct!(Options { argument, switches }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
use std::collections::BTreeSet;
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// important argument
#[bpaf(argument::<u32>("ARG"), collect)]
argument: BTreeSet<u32>,
/// some switch
#[bpaf(long("switch"), switch, collect)]
switches: BTreeSet<bool>,
}
fn main() {
println!("{:?}", options().run())
}
Output
In usage lines collect
items are indicated with ...
Usage: app --argument=ARG... [--switch]...
- --argument=ARG
- important argument
- --switch
- some switch
- -h, --help
- Prints help information
Run inner parser as many times as possible collecting all the new results
First false
is collected from a switch even if it is not consuming anything
Options { argument: {10, 20}, switches: {false} }
If there’s no matching parameters - it would produce an empty set. Note, in case of
switch
parser or other parsers that can succeed without consuming anything
it would capture that value so many
captures the first one of those.
You can use req_flag
to avoid that.
Options { argument: {}, switches: {false} }
For parsers that can succeed without consuming anything such as flag
or switch
- many
only collects values as long as they produce something
Options { argument: {}, switches: {true} }
collect
will collect at most one result that does not consume anything from the argument
list allowing using it in combination of any parsers with a fallback. After the first one
it will keep collecting the results as long as they consume something.
sourcefn some(self, message: &'static str) -> ParseSome<Self>
fn some(self, message: &'static str) -> ParseSome<Self>
Consume one or more items from a command line and collect them into a Vec
Takes a string used as an error message if there are no specified parameters
some
preserves any parsing failures and propagates them outwards, with an extra
catch
statement you can instead stop at the first value
that failed to parse and ignore it and all the subsequent ones.
some
will collect at most one result that does not consume anything from the argument
list allowing using it in combination with any parsers with a fallback. After the first
one, it will keep collecting the results as long as they consume something.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
argument: Vec<u32>,
switches: Vec<bool>,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.some("want at least one argument");
let switches = long("switch")
.help("some switch")
.req_flag(true)
.some("want at least one switch");
construct!(Options { argument, switches }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// important argument
#[bpaf(argument("ARG"), some("want at least one argument"))]
argument: Vec<u32>,
/// some switch
#[bpaf(long("switch"), req_flag(true), some("want at least one switch"))]
switches: Vec<bool>,
}
fn main() {
println!("{:?}", options().run())
}
Output
In usage lines some
items are indicated with ...
Usage: app --argument=ARG... --switch...
- --argument=ARG
- important argument
- --switch
- some switch
- -h, --help
- Prints help information
Run inner parser as many times as possible collecting all the new results, but unlike
many
needs to collect at least one element to succeed
Options { argument: [10, 20], switches: [true] }
With not enough parameters to satisfy both parsers at least once - it fails
Error: want at least one argument
both parsers need to succeed to create a struct
Error: want at least one switch
For parsers that can succeed without consuming anything such as flag
or switch
- some
only collects values as long as they produce something
Options { argument: [10], switches: [true] }
§See also
many
also collects results to a vector but succeeds with
no matching values. collect
collects results into a FromIterator
structure
sourcefn optional(self) -> ParseOptional<Self>
fn optional(self) -> ParseOptional<Self>
Turn a required argument into an optional one
optional
converts any missing items into None
and passes the remaining parsing
failures untouched. With an extra catch
statement, you can handle
those failures too.
§Derive usage
By default, bpaf
would automatically use optional for fields of type Option<T>
,
for as long as it’s not prevented from doing so by present postprocessing options.
But it’s also possible to specify it explicitly.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
version: Option<usize>,
feature: Option<String>,
}
pub fn options() -> OptionParser<Options> {
let version = long("version").argument("VERS").optional();
let feature = long("feature").argument("FEAT").optional();
construct!(Options { version, feature }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(argument("VERS"))]
version: Option<usize>,
#[bpaf(argument("FEAT"))]
feature: Option<String>,
}
fn main() {
println!("{:?}", options().run())
}
Output
bpaf
encases optional arguments in usage with []
Usage: app [--version=VERS] [--feature=FEAT]
- --version=VERS
- --feature=FEAT
- -h, --help
- Prints help information
Missing arguments are turned into None
Options { version: None, feature: None }
Present values are Some
Options { version: Some(10), feature: None }
As usual you can specify both
Options { version: Some(10), feature: Some("feat") }
Examples found in repository?
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
fn user() -> impl Parser<Option<String>> {
// match only literal "-user"
let tag = literal("-user").anywhere();
let value = positional("USER").help("User name");
construct!(tag, value)
.adjacent()
.map(|pair| pair.1)
.optional()
}
// parsers -exec xxx yyy zzz ;
fn exec() -> impl Parser<Option<Vec<OsString>>> {
let tag = literal("-exec")
.help("for every file find finds execute a separate shell command")
.anywhere();
let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
.help("command with its arguments, find will replace {} with a file name")
.many();
let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
.help("anything after literal \";\" will be considered a regular option again");
construct!(tag, item, endtag)
.adjacent()
.map(|triple| triple.1)
.optional()
}
/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
fn perm() -> impl Parser<Option<Perm>> {
fn parse_mode(input: &str) -> Result<Perms, String> {
let mut perms = Perms::default();
for c in input.chars() {
match c {
'r' => perms.read = true,
'w' => perms.write = true,
'x' => perms.exec = true,
_ => return Err(format!("{} is not a valid permission string", input)),
}
}
Ok(perms)
}
let tag = literal("-mode").anywhere();
// `any` here is used to parse an arbitrary string that can also start with dash (-)
// regular positional parser won't work here
let mode = any("MODE", Some)
.help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
.parse::<_, _, String>(|s: String| {
if let Some(m) = s.strip_prefix('-') {
Ok(Perm::All(parse_mode(m)?))
} else if let Some(m) = s.strip_prefix('/') {
Ok(Perm::Any(parse_mode(m)?))
} else {
Ok(Perm::Exact(parse_mode(&s)?))
}
});
construct!(tag, mode)
.adjacent()
.map(|pair| pair.1)
.optional()
}
More examples
16 17 18 19 20 21 22 23 24 25 26 27 28
fn main() {
// defining a parser in a usual way
let width = short('w').argument::<usize>("WIDTH").fallback(10);
let height = short('h').argument::<usize>("HEIGHT").fallback(10);
let parser = construct!(Opts { width, height });
let cmd = literal("cmd").optional().hide();
let combined_parser = construct!(cmd, parser).map(|x| x.1);
let opts = combined_parser.to_options().run();
println!("{:?}", opts);
}
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
fn main() {
let bar = short('b')
.long("bar")
.help("some bar command")
.argument::<String>("BAR")
.optional();
let bar_cmd = construct!(Foo { bar })
.to_options()
.descr("This command will try to do foo given a bar argument");
let opt = bar_cmd
.command("foo")
.help("command for doing foo")
.map(Command::Foo)
.to_options()
.run();
println!("{:#?}", opt);
}
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
fn feature_if() -> impl Parser<Option<String>> {
// here feature starts as any string on a command line that does not start with a dash
positional::<String>("FEATURE")
// guard restricts it such that it can't be a valid version
.guard(move |s| !is_version(s), "")
// last two steps describe what to do with strings in this position but are actually
// versions.
// optional allows parser to represent an ignored value with None
.optional()
// and catch lets optional to handle parse failures coming from guard
.catch()
}
fn version_if() -> impl Parser<Option<String>> {
positional::<String>("VERSION")
.guard(move |s| is_version(s), "")
.optional()
.catch()
}
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
fn main() {
let file = positional::<OsString>("FILE")
.help("File name to concatenate, with no FILE or when FILE is -, read standard input")
.optional()
.parse::<_, Box<dyn Read>, std::io::Error>(|path| {
Ok(if let Some(path) = path {
if path == "-" {
Box::new(stdin())
} else {
Box::new(File::open(path)?)
}
} else {
Box::new(stdin())
})
})
.to_options()
.descr("Concatenate a file to standard output")
.run();
let reader = BufReader::new(file);
for line in reader.lines() {
println!("{}", line.unwrap());
}
}
sourcefn count(self) -> ParseCount<Self, T>
fn count(self) -> ParseCount<Self, T>
Count how many times the inner parser succeeds, and return that number.
When you are dealing with a parser that can succeed without consuming
anything from a command line - bpaf
will count first such success as well.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
verbosity: usize,
}
pub fn options() -> OptionParser<Options> {
let verbosity = short('v')
.long("verbose")
.help("Increase the verbosity level")
.req_flag(())
.count();
construct!(Options { verbosity }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Increase the verbosity level
#[bpaf(short('v'), long("verbose"), req_flag(()), count)]
verbosity: usize,
}
fn main() {
println!("{:?}", options().run())
}
Output
In --help
message req_flag
look similarly to switch
and
flag
Usage: app [-v]...
- -v, --verbose
- Increase the verbosity level
- -h, --help
- Prints help information
Since parser uses req_flag
it succeeds exactly 0 times if there’s no parameters
Options { verbosity: 0 }
If it was specified - count
tracks it a discards parsed values
Options { verbosity: 3 }
Options { verbosity: 2 }
Examples found in repository?
More examples
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
fn main() {
// program takes one or more -v or --verbose flags, more flags = higher verbosity.
// parser handles number and produces a single flag.
//
// let's create it without using any single purpose magical functions
// Let's staty by creating a simple parser that handles a single -v / --verbose
// and fails otherwise;
let verbose = short('v').long("verbose").req_flag(());
// Try to apply the inner parser as many times as it succeeds, return the number
let verbose = verbose.count();
// And add a simple sanity checker.
// By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
let verbose = verbose.guard(|&x| x <= 3, "it doesn't get any more verbose than 3");
// program takes --trimor --no-trimflag, but not both at once. If none is given -
// fallback value is to disable trimming. Trim enum is set accordingly
// this flag succeeds iff --no-trim is given and produces Trim::Off
let trim_off = long("no-trim").req_flag(Trim::Off);
// this flag handles two remaining cases: --trim is given (Trim::On) an fallback (Trim::Off)
let trim_on = long("trim").flag(Trim::On, Trim::Off);
// combination of previous two.
// if trim_off succeeds - trim_on never runs, otherwise trim_on tries to handle the remaining
// case before falling back to Trim:Off.
// If both --trim and --no-trim are given trim_off succeeds, trim_off never runs and --trim
// remains unused - parser fails
let trim = construct!([trim_off, trim_on]);
let parser = construct!(verbose, trim);
let opt = parser.to_options().run();
println!("{:#?}", opt);
}
sourcefn last(self) -> ParseLast<Self>
fn last(self) -> ParseLast<Self>
Apply the inner parser as many times as it succeeds, return the last value
You can use this to allow users to pick contradicting options
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 {
style: Style,
report: Report,
}
pub fn options() -> OptionParser<Options> {
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]).last();
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])
.last()
.fallback(Report::Undecided);
construct!(Options { style, report }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(last)]
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(last, 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 {
// 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 last
shows that inner parser can run multiple times
Usage: app (--intel | --att | --llvm)... [--detailed | --minimal]...
- --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
style
takes one of several possible values and last
lets user to pass it several times
Options { style: Intel, report: Undecided }
Options { style: Att, report: Undecided }
Options { style: Intel, report: Undecided }
same goes with report
Options { style: Intel, report: Detailed }
Options { style: Att, report: Minimal }
Examples found in repository?
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
pub fn parse_binary() -> impl Parser<bool> {
#[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
enum Mode {
/// Use binary mode
#[bpaf(short, long)]
Binary,
/// Use text mode
#[bpaf(short, long)]
Text,
}
mode()
.last()
.fallback(Mode::Text)
.debug_fallback()
.map(|mode| mode == Mode::Binary)
}
sourcefn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
fn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
Apply a failing transformation to a contained value
Transformation preserves the present/absent state of the value: to parse an optional value you
can either first try to parse
it and then mark it as optional
or first
deal with the optionality and then parse a value wrapped in Option
. In most cases
the former approach is more concise.
Similarly, it is possible to parse multiple items with many
or
some
by either parsing a single item first and then turning it into a Vec
or collecting them into a Vec
first and then parsing the whole vector. The former approach
is more concise.
This is a most general of transforming parsers and you can express
map
and guard
in terms of it.
Examples are a bit artificial, to parse a value from a string you can specify
the type directly in the argument
’s turbofish and then apply map
.
§Derive usage:
parse
takes a single parameter: function name to call. Function type should match
parameter F
used by parse
in combinatoric API.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
number: u32,
}
pub fn options() -> OptionParser<Options> {
let number = long("number")
.argument::<String>("N")
// normally you'd use argument::<u32> to get a numeric
// value and `map` to double it
.parse::<_, _, ParseIntError>(|s| Ok(u32::from_str(&s)? * 2));
construct!(Options { number }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
fn twice_the_num(s: String) -> Result<u32, ParseIntError> {
Ok(u32::from_str(&s)? * 2)
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(argument::<String>("N"), parse(twice_the_num))]
number: u32,
}
fn main() {
println!("{:?}", options().run())
}
Output
parse
don’t make any changes to generated --help
message
Usage: app --number=N
- --number=N
- -h, --help
- Prints help information
You can use parse
to apply arbitrary failing transformation to any input.
For example here --number
takes a numerical value and doubles it
Options { number: 20 }
But if function inside the parser fails - user will get the error back unless it’s handled in some other way
Error: couldn't parse ten: invalid digit found in string
Examples found in repository?
6 7 8 9 10 11 12 13 14 15 16 17
fn args() -> impl Parser<Vec<u16>> {
long("ports")
.help("Comma separated list of ports")
.argument::<String>("PORTS")
.parse(|s| {
s.split(',')
.map(u16::from_str)
.collect::<Result<Vec<_>, _>>()
})
.many()
.map(|nested| nested.into_iter().flatten().collect())
}
More examples
16 17 18 19 20 21 22 23 24 25 26 27 28 29
fn tag<T>(name: &'static str, meta: &str, help: &'static str) -> impl Parser<T>
where
T: FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
// 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
.metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
.help(help)
.anywhere()
.parse(|s| s.parse())
}
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
fn parse_manifest_path() -> impl Parser<PathBuf> {
long("manifest-path")
.help("Path to Cargo.toml")
.argument::<PathBuf>("PATH")
.complete_shell(ShellComp::File {
mask: Some("*.toml"),
})
.parse(|p| {
// cargo-metadata wants to see
if p.is_absolute() {
Ok(p)
} else {
std::env::current_dir()
.map(|d| d.join(p))
.and_then(|full_path| full_path.canonicalize())
}
})
.fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
fn main() {
let file = positional::<OsString>("FILE")
.help("File name to concatenate, with no FILE or when FILE is -, read standard input")
.optional()
.parse::<_, Box<dyn Read>, std::io::Error>(|path| {
Ok(if let Some(path) = path {
if path == "-" {
Box::new(stdin())
} else {
Box::new(File::open(path)?)
}
} else {
Box::new(stdin())
})
})
.to_options()
.descr("Concatenate a file to standard output")
.run();
let reader = BufReader::new(file);
for line in reader.lines() {
println!("{}", line.unwrap());
}
}
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
fn perm() -> impl Parser<Option<Perm>> {
fn parse_mode(input: &str) -> Result<Perms, String> {
let mut perms = Perms::default();
for c in input.chars() {
match c {
'r' => perms.read = true,
'w' => perms.write = true,
'x' => perms.exec = true,
_ => return Err(format!("{} is not a valid permission string", input)),
}
}
Ok(perms)
}
let tag = literal("-mode").anywhere();
// `any` here is used to parse an arbitrary string that can also start with dash (-)
// regular positional parser won't work here
let mode = any("MODE", Some)
.help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
.parse::<_, _, String>(|s: String| {
if let Some(m) = s.strip_prefix('-') {
Ok(Perm::All(parse_mode(m)?))
} else if let Some(m) = s.strip_prefix('/') {
Ok(Perm::Any(parse_mode(m)?))
} else {
Ok(Perm::Exact(parse_mode(&s)?))
}
});
construct!(tag, mode)
.adjacent()
.map(|pair| pair.1)
.optional()
}
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
fn main() {
let token = long("token")
.help("Token used for complex commands")
.argument::<String>("TOKEN")
.optional();
// start with defining 3 commands: simple, complex1 and complex2
let simple_parser = pure(PreCommand::Simple).to_options();
let simple = simple_parser.command("simple");
let complex1_parser = positional::<i32>("ARG");
let complex1 = construct!(PreCommand::Complex1(complex1_parser))
.to_options()
.descr("This is complex command 1")
.command("complex1");
let complex2_parser = positional::<i16>("ARG");
let complex2 = construct!(PreCommand::Complex2(complex2_parser))
.to_options()
.descr("This is complex command 2")
.command("complex2");
// compose then to accept any of those
let preparser = construct!([simple, complex1, complex2]);
// make a parser that accepts optional token and one of incomplete commands
// then create complete command or fail
let parser = construct!(token, preparser).parse(|(token, cmd)| match cmd {
PreCommand::Simple => Ok(Command::Simple),
PreCommand::Complex1(a) => match token {
Some(token) => Ok(Command::Complex1(token, a)),
None => Err("You must specify token to use with --token"),
},
PreCommand::Complex2(a) => match token {
Some(token) => Ok(Command::Complex2(token, a)),
None => Err("You must specify token to use with --token"),
},
});
let cmd = parser.to_options().run();
println!("{:?}", cmd);
}
sourcefn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
Apply a pure transformation to a contained value
A common case of the parse
method, exists mostly for convenience.
§Derive usage:
The map
takes a single parameter: function name to call. This function should transform
the value produced by the parser into a new value of the same or different type.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
number: u32,
}
pub fn options() -> OptionParser<Options> {
let number = long("number").argument::<u32>("N").map(|x| x * 2);
construct!(Options { number }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
fn twice_the_num(n: u32) -> u32 {
n * 2
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(argument::<u32>("N"), map(twice_the_num))]
number: u32,
}
fn main() {
println!("{:?}", options().run())
}
Output
map
don’t make any changes to generated --help
message
You can use map
to apply arbitrary pure transformation to any input.
Here --number
takes a numerical value and doubles it
Options { number: 20 }
But if function inside the parser fails - user will get the error back unless it’s handled
in some way. In fact here execution never reaches map
function -
argument
tries to parse ten
as a number, fails and reports the error
Error: couldn't parse ten: invalid digit found in string
Examples found in repository?
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
fn number(name: &'static str) -> impl Parser<(String, Value)> {
let label = name.to_string();
long(name)
.argument::<usize>("NUM")
.map(move |n| (label.clone(), Value::Number(n)))
}
fn bool(name: &'static str) -> impl Parser<(String, Value)> {
let label = name.to_string();
long(name)
.switch()
.map(move |n| (label.clone(), Value::Bool(n)))
}
fn string(name: &'static str) -> impl Parser<(String, Value)> {
let label = name.to_string();
long(name)
.help("this can use a help message")
.argument::<String>("NUM")
.map(move |n| (label.clone(), Value::String(n)))
}
fn cons<T>(acc: Box<dyn Parser<Vec<T>>>, cur: Box<dyn Parser<T>>) -> Box<dyn Parser<Vec<T>>>
where
T: 'static,
{
construct!(acc, cur)
.map(|(mut acc, cur)| {
acc.push(cur);
acc
})
.boxed()
}
More examples
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
fn my_last(self) -> ParseLast<T> {
let p = self
.some("need to specify at least once")
.map(|mut xs| xs.pop().unwrap());
ParseLast { inner: p.boxed() }
}
}
}
pub mod shared {
use super::boilerplate::*;
use bpaf::*;
#[derive(Debug, Clone, Copy, Bpaf)]
pub enum Verbosity {
/// Display warnings
#[bpaf(short, long)]
Warn,
/// Display only diagnostics
#[bpaf(short, long)]
Quiet,
/// Display status only
#[bpaf(short, long)]
Status,
}
pub fn parse_verbosity() -> impl Parser<Verbosity> {
verbosity().my_last().fallback(Verbosity::Status)
}
pub fn parse_binary() -> impl Parser<bool> {
#[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
enum Mode {
/// Use binary mode
#[bpaf(short, long)]
Binary,
/// Use text mode
#[bpaf(short, long)]
Text,
}
mode()
.last()
.fallback(Mode::Text)
.debug_fallback()
.map(|mode| mode == Mode::Binary)
}
}
mod arch {
use bpaf::*;
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command)]
/// Print machine architecture.
pub struct Arch;
}
mod b2sum {
use super::shared::*;
use bpaf::*;
use std::path::PathBuf;
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command("b2sum"))]
/// Print or check BLAKE2 (512-bit) checksums.
pub struct B2Sum {
#[bpaf(external(parse_binary))]
pub binary: bool,
/// read BLAKE2 sums from the FILEs and check them
#[bpaf(short, long)]
pub check: bool,
/// create a BSD-style checksum
pub tag: bool,
#[bpaf(external(parse_verbosity))]
pub check_output: Verbosity,
/// exit non-zero for improperly formatted checksum lines
pub strict: bool,
#[bpaf(positional("FILE"))]
pub files: Vec<PathBuf>,
}
}
mod base32 {
use bpaf::*;
use std::path::PathBuf;
fn non_zero(val: Option<usize>) -> Option<usize> {
val.and_then(|v| (v > 0).then_some(v))
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command)]
/// Base32 encode or decode FILE, or standard input, to standard output.
pub struct Base32 {
/// decode data
#[bpaf(long, short)]
pub decode: bool,
#[bpaf(long, short)]
/// when decoding, ignore non-alphabet characters
pub ignore_garbage: bool,
#[bpaf(
long,
short,
argument("COLS"),
optional,
map(non_zero),
fallback(Some(76)),
debug_fallback
)]
/// wrap encoded lines after COLS character
/// Use 0 to disable line wrapping
pub wrap: Option<usize>,
#[bpaf(positional("FILE"))]
/// With no FILE, or when FILE is -, read standard input.
pub file: Option<PathBuf>,
}
}
mod basename {
use bpaf::*;
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command)]
pub struct Basename {
/// support multiple arguments and treat each as a NAME
#[bpaf(short('a'), long)]
pub multiple: bool,
/// remove a trailing SUFFIX; implies -a
#[bpaf(short, long, argument("SUFFIX"), optional)]
pub suffix: Option<String>,
/// end each output line with NUL, not newline
#[bpaf(short, long)]
pub zero: bool,
/// Print NAME with any leading directory components removed.
#[bpaf(positional("NAME"), many)]
pub names: Vec<String>,
}
pub fn parse_basename() -> impl Parser<Basename> {
basename().map(|mut b| {
if b.suffix.is_some() {
b.multiple = true;
}
b
})
}
}
mod cat {
use std::path::PathBuf;
use bpaf::*;
#[derive(Debug, Clone, Bpaf)]
struct Extra {
#[bpaf(short('A'), long)]
/// equivalent to -vET
show_all: bool,
#[bpaf(short('b'), long)]
/// number nonempty output lines, overrides -n
number_nonblank: bool,
#[bpaf(short('e'))]
/// equivalent to -vE
show_non_printing_ends: bool,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(fallback(NumberingMode::None))]
pub enum NumberingMode {
#[bpaf(hide)]
/// Don't number lines, default behavior
None,
/// Number nonempty output lines, overrides -n
#[bpaf(short('b'), long("number-nonblank"))]
NonEmpty,
/// Number all output lines
#[bpaf(short('n'), long("number"))]
All,
}
#[derive(Debug, Clone, Bpaf)]
pub struct Cat {
#[bpaf(short('T'), long)]
/// display TAB characters as ^I
pub show_tabs: bool,
/// display $ at end of each line
#[bpaf(short('E'))]
pub show_ends: bool,
/// use ^ and M- notation, except for LFD and TAB
#[bpaf(short('n'), long("number"))]
show_nonprinting: bool,
#[bpaf(external(numbering_mode))]
pub number: NumberingMode,
#[bpaf(short('s'), long)]
/// suppress repeated empty output lines
pub squeeze_blank: bool,
#[bpaf(positional("FILE"), many)]
/// Concatenate FILE(s) to standard output.
pub files: Vec<PathBuf>,
}
pub fn parse_cat() -> impl Parser<Cat> {
construct!(extra(), cat())
.map(|(extra, mut cat)| {
if extra.show_all {
cat.show_tabs = true;
cat.show_ends = true;
cat.show_nonprinting = true;
}
if extra.show_non_printing_ends {
cat.show_nonprinting = true;
cat.show_ends = true;
}
if extra.number_nonblank {
cat.number = NumberingMode::NonEmpty;
}
cat
})
.to_options()
.command("cat")
}
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
fn user() -> impl Parser<Option<String>> {
// match only literal "-user"
let tag = literal("-user").anywhere();
let value = positional("USER").help("User name");
construct!(tag, value)
.adjacent()
.map(|pair| pair.1)
.optional()
}
// parsers -exec xxx yyy zzz ;
fn exec() -> impl Parser<Option<Vec<OsString>>> {
let tag = literal("-exec")
.help("for every file find finds execute a separate shell command")
.anywhere();
let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
.help("command with its arguments, find will replace {} with a file name")
.many();
let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
.help("anything after literal \";\" will be considered a regular option again");
construct!(tag, item, endtag)
.adjacent()
.map(|triple| triple.1)
.optional()
}
/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
fn perm() -> impl Parser<Option<Perm>> {
fn parse_mode(input: &str) -> Result<Perms, String> {
let mut perms = Perms::default();
for c in input.chars() {
match c {
'r' => perms.read = true,
'w' => perms.write = true,
'x' => perms.exec = true,
_ => return Err(format!("{} is not a valid permission string", input)),
}
}
Ok(perms)
}
let tag = literal("-mode").anywhere();
// `any` here is used to parse an arbitrary string that can also start with dash (-)
// regular positional parser won't work here
let mode = any("MODE", Some)
.help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
.parse::<_, _, String>(|s: String| {
if let Some(m) = s.strip_prefix('-') {
Ok(Perm::All(parse_mode(m)?))
} else if let Some(m) = s.strip_prefix('/') {
Ok(Perm::Any(parse_mode(m)?))
} else {
Ok(Perm::Exact(parse_mode(&s)?))
}
});
construct!(tag, mode)
.adjacent()
.map(|pair| pair.1)
.optional()
}
sourcefn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
Validate or fail with a message
If the value doesn’t satisfy the constraint - the parser fails with the specified error message.
§Derive usage
Derive variant of the guard
takes a function name instead of a closure, mostly to keep things
clean. The second argument can be either a string literal or a constant name for a static str
.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
number: u32,
}
pub fn options() -> OptionParser<Options> {
let number = long("number").argument::<u32>("N").guard(
|n| *n <= 10,
"Values greater than 10 are only available in the DLC pack!",
);
construct!(Options { number }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
fn dlc_check(number: &u32) -> bool {
*number <= 10
}
const DLC_NEEDED: &str = "Values greater than 10 are only available in the DLC pack!";
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(argument("N"), guard(dlc_check, DLC_NEEDED))]
number: u32,
}
fn main() {
println!("{:?}", options().run())
}
Output
guard
don’t make any changes to generated --help
message
Usage: app --number=N
- --number=N
- -h, --help
- Prints help information
You can use guard to set boundary limits or perform other checks on parsed values. Parser accepts numbers below 10
Options { number: 5 }
And fails with the error message on higher values:
Error: 11: Values greater than 10 are only available in the DLC pack!
But if function inside the parser fails - user will get the error back unless it’s handled in some way
Error: couldn't parse ten: invalid digit found in string
Examples found in repository?
More examples
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
fn feature_if() -> impl Parser<Option<String>> {
// here feature starts as any string on a command line that does not start with a dash
positional::<String>("FEATURE")
// guard restricts it such that it can't be a valid version
.guard(move |s| !is_version(s), "")
// last two steps describe what to do with strings in this position but are actually
// versions.
// optional allows parser to represent an ignored value with None
.optional()
// and catch lets optional to handle parse failures coming from guard
.catch()
}
fn version_if() -> impl Parser<Option<String>> {
positional::<String>("VERSION")
.guard(move |s| is_version(s), "")
.optional()
.catch()
}
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
fn main() {
// program takes one or more -v or --verbose flags, more flags = higher verbosity.
// parser handles number and produces a single flag.
//
// let's create it without using any single purpose magical functions
// Let's staty by creating a simple parser that handles a single -v / --verbose
// and fails otherwise;
let verbose = short('v').long("verbose").req_flag(());
// Try to apply the inner parser as many times as it succeeds, return the number
let verbose = verbose.count();
// And add a simple sanity checker.
// By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
let verbose = verbose.guard(|&x| x <= 3, "it doesn't get any more verbose than 3");
// program takes --trimor --no-trimflag, but not both at once. If none is given -
// fallback value is to disable trimming. Trim enum is set accordingly
// this flag succeeds iff --no-trim is given and produces Trim::Off
let trim_off = long("no-trim").req_flag(Trim::Off);
// this flag handles two remaining cases: --trim is given (Trim::On) an fallback (Trim::Off)
let trim_on = long("trim").flag(Trim::On, Trim::Off);
// combination of previous two.
// if trim_off succeeds - trim_on never runs, otherwise trim_on tries to handle the remaining
// case before falling back to Trim:Off.
// If both --trim and --no-trim are given trim_off succeeds, trim_off never runs and --trim
// remains unused - parser fails
let trim = construct!([trim_off, trim_on]);
let parser = construct!(verbose, trim);
let opt = parser.to_options().run();
println!("{:#?}", opt);
}
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
fn opts() -> OptionParser<Out> {
// A flag, true if used in the command line. Can be required, this one is optional
let debug = short('d') // start with a short name
.long("debug") // also add a long name
.help("Activate debug mode") // and a help message to use
.switch(); // turn this into a switch
// number of occurrences of the v/verbose flag capped at 3 with an error here but you can also
// use `max` inside `map`
let verbose = short('v')
.long("verbose")
.help("Increase the verbosity\n You can specify it up to 3 times\n either as -v -v -v or as -vvv")
.req_flag(())
.many()
.map(|xs| xs.len())
.guard(|&x| x <= 3, "It doesn't get any more verbose than this");
// an argument, parsed and with default value
let speed = short('s')
.long("speed")
.help("Set speed")
.argument::<f64>("SPEED") // you can specify a type to parse
.fallback(42.0)
.display_fallback();
let output = short('o')
.long("output")
.help("output file")
.argument::<PathBuf>("OUTPUT") // but it's optional when rustc can derive it
.complete_shell(ShellComp::File { mask: None });
// no magical name transmogrifications in combinatoric API,
let nb_cars = short('n')
.long("nb-cars")
.help("Number of items to process")
.argument::<u32>("N")
.fallback(1)
.display_fallback();
// a parser that consumes one argument
// you can build the inner parser in one go or as multiple steps giving each step a name
// you can also add some static shell completion functionality
let file_to_proces = short('f')
.long("file")
.help("File to process")
.argument::<PathBuf>("FILE")
.complete_shell(ShellComp::File { mask: Some("*.rs") });
let files_to_process = file_to_proces.many();
// packing things in a struct assumes parser for each field is in scope.
construct!(Out {
debug,
verbose,
speed,
output,
nb_cars,
files_to_process
})
.to_options()
.descr("This is a description")
}
sourcefn fallback(self, value: T) -> ParseFallback<Self, T>
fn fallback(self, value: T) -> ParseFallback<Self, T>
Use this value as default if the value isn’t present on a command line
Parser would still fail if the value is present but failure comes from some transformation
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
jobs: usize,
}
pub fn options() -> OptionParser<Options> {
let jobs = long("jobs")
.help("Number of jobs")
.argument("JOBS")
.fallback(42)
.display_fallback();
construct!(Options { jobs }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
#[allow(dead_code)]
pub struct Options {
/// Number of jobs
#[bpaf(argument("JOBS"), fallback(42), display_fallback)]
jobs: usize,
}
fn main() {
println!("{:?}", options().run())
}
Output
fallback
changes parser to fallback to a default value used when argument is not specified
Options { jobs: 42 }
If value is present - fallback value is ignored
Options { jobs: 10 }
Parsing errors are preserved and preserved to user
Error: couldn't parse ten: invalid digit found in string
With display_fallback
and
debug_fallback
you can make it so default value
is visible in --help
output
Usage: app [--jobs=JOBS]
- --jobs=JOBS
- Number of jobs
- [default: 42]
- -h, --help
- Prints help information
§See also
fallback_with
would allow to try to fallback to a value that
comes from a failing computation such as reading a file. By default fallback value will
not be shown in the --help
output, you can change that by using
display_fallback
and
debug_fallback
.
Examples found in repository?
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
pub fn parse_verbosity() -> impl Parser<Verbosity> {
verbosity().my_last().fallback(Verbosity::Status)
}
pub fn parse_binary() -> impl Parser<bool> {
#[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
enum Mode {
/// Use binary mode
#[bpaf(short, long)]
Binary,
/// Use text mode
#[bpaf(short, long)]
Text,
}
mode()
.last()
.fallback(Mode::Text)
.debug_fallback()
.map(|mode| mode == Mode::Binary)
}
More examples
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
fn in_file() -> impl Parser<String> {
tag::<String>("if=", "FILE", "read from FILE")
.fallback(String::from("-"))
.display_fallback()
}
fn out_file() -> impl Parser<String> {
tag::<String>("of=", "FILE", "write to FILE")
.fallback(String::from("-"))
.display_fallback()
}
fn block_size() -> impl Parser<usize> {
// it is possible to parse notation used by dd itself as well,
// using usuze only for simplicity
tag::<usize>("bs=", "SIZE", "read/write SIZE blocks at once")
.fallback(512)
.display_fallback()
}
16 17 18 19 20 21 22 23 24 25 26 27 28
fn main() {
// defining a parser in a usual way
let width = short('w').argument::<usize>("WIDTH").fallback(10);
let height = short('h').argument::<usize>("HEIGHT").fallback(10);
let parser = construct!(Opts { width, height });
let cmd = literal("cmd").optional().hide();
let combined_parser = construct!(cmd, parser).map(|x| x.1);
let opts = combined_parser.to_options().run();
println!("{:?}", opts);
}
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
pub fn options() -> OptionParser<Options> {
let backing = toggle_options("(+|-)backing", "backing", "Set backing status").fallback(false);
let xinerama =
toggle_options("(+|-)xinerama", "xinerama", "Set Xinerama status").fallback(true);
let turbo = short('t')
.long("turbo")
.help("Engage the turbo mode")
.switch();
let extensions = extension().many();
construct!(Options {
turbo,
backing,
xinerama,
extensions,
})
.to_options()
}
sourcefn fallback_with<F, E>(self, fallback: F) -> ParseFallbackWith<T, Self, F, E>
fn fallback_with<F, E>(self, fallback: F) -> ParseFallbackWith<T, Self, F, E>
Use value produced by this function as default if the value isn’t present
Would still fail if the value is present but failure comes from some earlier transformation
Combinatoric example
fn try_to_get_version() -> Result<usize, &'static str> {
Ok(42)
}
#[derive(Debug, Clone)]
pub struct Options {
version: usize,
}
pub fn options() -> OptionParser<Options> {
let version = long("version")
.help("Specify protocol version")
.argument("VERS")
.fallback_with(try_to_get_version)
.display_fallback();
construct!(Options { version }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
fn try_to_get_version() -> Result<usize, &'static str> {
Ok(42)
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(argument("VERS"), fallback_with(try_to_get_version), display_fallback)]
/// Specify protocol version
version: usize,
}
fn main() {
println!("{:?}", options().run())
}
Output
fallback_with
changes parser to fallback to a value that comes from a potentially failing
computation when argument is not specified
Options { version: 42 }
If value is present - fallback value is ignored
Options { version: 10 }
Parsing errors are preserved and preserved to user
Error: couldn't parse ten: invalid digit found in string
bpaf
encases parsers with fallback value of some sort in usage with []
Usage: app [--version=VERS]
- --version=VERS
- Specify protocol version
- [default: 42]
- -h, --help
- Prints help information
§See also
fallback
implements similar logic expect that failures aren’t expected.
By default fallback value will not be shown in the --help
output, you can change that by using
display_fallback
and
debug_fallback
.
Examples found in repository?
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
fn parse_manifest_path() -> impl Parser<PathBuf> {
long("manifest-path")
.help("Path to Cargo.toml")
.argument::<PathBuf>("PATH")
.complete_shell(ShellComp::File {
mask: Some("*.toml"),
})
.parse(|p| {
// cargo-metadata wants to see
if p.is_absolute() {
Ok(p)
} else {
std::env::current_dir()
.map(|d| d.join(p))
.and_then(|full_path| full_path.canonicalize())
}
})
.fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
#[derive(Debug, Clone, Bpaf)]
/// How to render output
pub struct Format {
/// Print interleaved Rust code
pub rust: bool,
#[bpaf(external(color_detection))]
pub color: bool,
/// include full demangled name instead of just prefix
pub full_name: bool,
}
#[derive(Debug, Clone, Bpaf)]
/// Pick output type
///
/// included help
///
///
/// Extended help
pub enum Syntax {
/// Generate assembly using Intel style
Intel,
/// Generate assembly using AT&T style
Att,
}
fn color_detection() -> impl Parser<bool> {
let yes = long("color")
.help("Enable color highlighting")
.req_flag(true);
let no = long("no-color")
.help("Disable color highlighting")
.req_flag(false);
construct!([yes, no]).fallback_with::<_, Infallible>(|| {
// we can call for supports-color crate here
Ok(true)
})
}
sourcefn hide(self) -> ParseHide<Self>
fn hide(self) -> ParseHide<Self>
Ignore this parser during any sort of help generation
Best used for optional parsers or parsers with a defined fallback, usually for implementing backward compatibility or hidden aliases
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
argument: u32,
switch: bool,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.fallback(30);
let switch = long("switch").help("secret switch").switch().hide();
construct!(Options { argument, switch }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// important argument
#[bpaf(fallback(30))]
argument: u32,
/// secret switch
#[bpaf(hide)]
switch: bool,
}
fn main() {
println!("{:?}", options().run())
}
Output
hide
removes the inner parser from any help or autocompletion logic
Usage: app [--argument=ARG]
- --argument=ARG
- important argument
- -h, --help
- Prints help information
But doesn’t change the parsing behavior in any way otherwise
Options { argument: 32, switch: false }
Options { argument: 42, switch: true }
Examples found in repository?
16 17 18 19 20 21 22 23 24 25 26 27 28
fn main() {
// defining a parser in a usual way
let width = short('w').argument::<usize>("WIDTH").fallback(10);
let height = short('h').argument::<usize>("HEIGHT").fallback(10);
let parser = construct!(Opts { width, height });
let cmd = literal("cmd").optional().hide();
let combined_parser = construct!(cmd, parser).map(|x| x.1);
let opts = combined_parser.to_options().run();
println!("{:?}", opts);
}
sourcefn hide_usage(self) -> ParseUsage<Self>
fn hide_usage(self) -> ParseUsage<Self>
Ignore this parser when generating a usage line
Parsers hidden from usage will still show up in the available arguments list. Best used on
optional things that augment the main application functionality but not define it.
Alternatively, you can use custom_usage
to replace a single
option or a group of them with some other text.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
argument: u32,
switch: bool,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.fallback(30);
let switch = long("switch")
.help("not that important switch")
.switch()
.hide_usage();
construct!(Options { argument, switch }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[allow(dead_code)]
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// important argument
#[bpaf(fallback(30))]
argument: u32,
/// not that important switch
#[bpaf(hide_usage)]
switch: bool,
}
fn main() {
println!("{:?}", options().run())
}
Output
hide_usage
hides the inner parser from the generated usage line, but not from the rest of the help or completion
Usage: app [--argument=ARG]
- --argument=ARG
- important argument
- --switch
- not that important switch
- -h, --help
- Prints help information
But doesn’t change the parsing behavior in any way otherwise
Options { argument: 32, switch: false }
Options { argument: 32, switch: true }
sourcefn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
Customize how this parser looks like in the usage line
Combinatoric example
const BINARY_USAGE: &[(&str, Style)] = &[
("--binary", Style::Literal),
("=", Style::Text),
("BINARY", Style::Metavar),
];
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Binary to run
#[bpaf(short, long, argument("BIN"), custom_usage(BINARY_USAGE))]
binary: Option<String>,
/// Package to check
#[bpaf(short, long, argument("PACKAGE"))]
package: Option<String>,
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone)]
pub struct Options {
binary: Option<String>,
package: Option<String>,
}
pub fn options() -> OptionParser<Options> {
let binary = short('b')
.long("binary")
.help("Binary to run")
.argument("BIN")
.optional()
.custom_usage(&[
("--binary", Style::Literal),
("=", Style::Text),
("BINARY", Style::Metavar),
]);
let package = short('p')
.long("package")
.help("Package to check")
.argument("PACKAGE")
.optional();
construct!(Options { binary, package }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Output
custom_usage
changes how parser shows up in the “Usage” section of generated --help
, note
lack of []
, long name instead of a short one and different metavariable value
Usage: app --binary=BINARY [-p=PACKAGE]
- -b, --binary=BIN
- Binary to run
- -p, --package=PACKAGE
- Package to check
- -h, --help
- Prints help information
Parsing behavior stays unchanged
Options { binary: Some("cargo-asm"), package: Some("cargo-show-asm") }
sourcefn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
Attach a help message to a complex parser
bpaf
inserts the group help message before the block with all the fields
from the inner parser and an empty line after the block.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Rectangle {
width: u32,
height: u32,
}
#[derive(Debug, Clone)]
pub struct Options {
argument: u32,
rectangle: Rectangle,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.fallback(30);
let width = long("width")
.help("Width of the rectangle")
.argument("W")
.fallback(10);
let height = long("height")
.help("Height of the rectangle")
.argument("H")
.fallback(10);
let rectangle = construct!(Rectangle { width, height }).group_help("Takes a rectangle");
construct!(Options {
argument,
rectangle
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
pub struct Rectangle {
/// Width of the rectangle
#[bpaf(argument("W"), fallback(10))]
width: u32,
/// Height of the rectangle
#[bpaf(argument("H"), fallback(10))]
height: u32,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// important argument
#[bpaf(fallback(30))]
argument: u32,
/// secret switch
#[bpaf(external, group_help("Takes a rectangle"))]
rectangle: Rectangle,
}
fn main() {
println!("{:?}", options().run())
}
Output
group_help
adds extra decoration for the inner group in --help
message
Usage: app [--argument=ARG] [--width=W] [--height=H]
- --width=W
- Width of the rectangle
- --height=H
- Height of the rectangle
- --argument=ARG
- important argument
- -h, --help
- Prints help information
And doesn’t change the parsing behavior in any way
Options { argument: 32, rectangle: Rectangle { width: 20, height: 13 } }
Examples found in repository?
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
fn main() {
let width = short('w')
.long("width")
.help("Width of the rectangle")
.argument::<usize>("PX");
let height = short('h')
.long("height")
.help("Height of the rectangle")
.argument::<usize>("PX");
let rect = construct!(Rect { width, height })
.group_help("Rectangle is defined by width and height in meters")
.optional();
let verbose = short('v')
.long("verbose")
.help("Print computation steps")
.switch();
let opt = construct!(Out { verbose, rect })
.to_options()
.descr("This program calculates rectangle's area")
.header("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
.footer("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
.run();
println!("{:#?}", opt);
}
sourcefn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
Make a help message for a complex parser from its MetaInfo
use bpaf::doc::*;
use bpaf::*;
#[derive(Debug, Clone)]
pub struct Rectangle {
width: u32,
height: u32,
}
#[derive(Debug, Clone)]
pub struct Options {
argument: u32,
rectangle: Rectangle,
}
fn generate_rectangle_help(meta: MetaInfo) -> Doc {
let mut buf = Doc::default();
buf.text("The app takes a rectangle defined by width and height\n\nYou can customize the screen size using ");
buf.meta(meta, true);
buf.text(" parameters");
buf
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("important argument")
.argument("ARG")
.fallback(30);
let width = long("width")
.help("Width of the rectangle")
.argument("W")
.fallback(10);
let height = long("height")
.help("Height of the rectangle")
.argument("H")
.fallback(10);
let rectangle =
construct!(Rectangle { width, height }).with_group_help(generate_rectangle_help);
construct!(Options {
argument,
rectangle
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Output
with_group_help
lets you write longer description for group of options that can also refer to
those options. Similar to group_help
encased optios are separated from
the rest by a blank line.
Invoking help with a single --help
flag renders shot(er) version of the help message
that contanis only the first paragraph for each block:
Usage: app [--argument=ARG] [--width=W] [--height=H]
- --width=W
- Width of the rectangle
- --height=H
- Height of the rectangle
- --argument=ARG
- important argument
- -h, --help
- Prints help information
Invoking help with double --help --help
flag renders the full help message with all the
descriptions added
Usage: app [--argument=ARG] [--width=W] [--height=H]
- --width=W
- Width of the rectangle
- --height=H
- Height of the rectangle
- --argument=ARG
- important argument
- -h, --help
- Prints help information
Other than rendering the help message that there’s no interactions with other parsers
Options { argument: 30, rectangle: Rectangle { width: 120, height: 11 } }
Options { argument: 12, rectangle: Rectangle { width: 10, height: 10 } }
sourcefn complete<M, F>(self, op: F) -> ParseComp<Self, F>
Available on crate feature autocomplete
only.
fn complete<M, F>(self, op: F) -> ParseComp<Self, F>
autocomplete
only.Dynamic shell completion
Allows to generate autocompletion information for the shell. Completer places generated input
in place of metavar placeholders, so running completer
on something that doesn’t have a
positional
or an argument
doesn’t make much sense.
Takes a function as a parameter that tries to complete partial input to a full one with an
optional description. bpaf
would substitute a current positional item or an argument with an empty
string if a value isn’t available yet so it’s best to run complete
where parsing can’t fail:
right after argument
or positional
, but this isn’t enforced.
§Example
$ app --name L<TAB>
$ app --name Lupusregina _
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
name: String,
}
fn completer(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
let names = ["Yuri", "Lupusregina", "Solution", "Shizu", "Entoma"];
names
.iter()
.filter(|name| name.starts_with(input))
.map(|name| (*name, None))
.collect::<Vec<_>>()
}
pub fn options() -> OptionParser<Options> {
let name = short('n')
.long("name")
.help("Specify character's name")
.argument("NAME")
.complete(completer);
construct!(Options { name }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
/// suggest completions for the input
fn completer(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
let names = ["Yuri", "Lupusregina", "Solution", "Shizu", "Entoma"];
names
.iter()
.filter(|name| name.starts_with(input))
.map(|name| (*name, None))
.collect::<Vec<_>>()
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(short, long, argument("NAME"), complete(completer))]
/// Specify character's name
name: String,
}
fn main() {
println!("{:?}", options().run())
}
Output
complete
annotation does not affect parsing results or generated help message
Usage: app -n=NAME
- -n, --name=NAME
- Specify character's name
- -h, --help
- Prints help information
Options { name: "Bob" }
But when invoked with shell completion can generate suggestions for user to what to type:
$ app --name L<TAB>
$ app --name Lupisregina
§A simple example
examples/simple_dynamic.rs
//! Simple dynamic completion example
#![allow(dead_code)]
use bpaf::*;
fn crates(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
let crates = [
(
"cargo-hackerman",
"Workspace hack management and package/feature query",
),
("cargo-prebuilt", "Download prebuilt crate binaries"),
("cargo-show-asm", "Display generated assembly"),
(
"cargo-supply-chain",
"Gather author, contributor, publisher data on crates",
),
("chezmoi_modify_manager", "Chezmoi addon to patch ini files"),
("xvf", "Easy archive extraction"),
("newdoc", "Generate pre-populated module files"),
(
"nust64",
"Tools for compiling a Rust project into an N64 ROM",
),
("uggo", "CLI tool to query builds from u.gg"),
];
crates
.iter()
.filter(|p| p.0.starts_with(input))
.map(|name| (name.0, Some(name.1)))
.collect::<Vec<_>>()
}
#[derive(Debug, Clone, Copy, Bpaf)]
/// Format for generated report
#[bpaf(fallback(Format::Text))]
enum Format {
/// Generate report in JSON format
Json,
/// Generate report in XML format
Xml,
/// Generate report in plaintext format
Text,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Select crate for analysis
#[bpaf(long("crate"), argument("NAME"), complete(crates))]
name: String,
/// Include dependencies into report
dependencies: bool,
#[bpaf(external)]
format: Format,
/// Upload report to a url
#[bpaf(positional("URL"))]
upload: Option<String>,
}
fn main() {
println!("{:?}", options().run());
}
Output
Let’s consider a simple application that performs crate analysis
Application generates help message as usual
Usage: app --crate=NAME [--dependencies] [--json | --xml | --text] [URL]
- --json
- Generate report in JSON format
- --xml
- Generate report in XML format
- --text
- Generate report in plaintext format
- URL
- Upload report to a url
- --crate=NAME
- Select crate for analysis
- --dependencies
- Include dependencies into report
- -h, --help
- Prints help information
Shell (zsh in this case) with help of completion system can request possible items to type along with some description
% simple_dynamic \t % simple_dynamic --crate=NAME -- Select crate for analysis --dependencies -- Include dependencies into report URL: Upload report to a url Format for generated report --json -- Generate report in JSON format --xml -- Generate report in XML format --text -- Generate report in plaintext format
When user provides enough input to identify a possible item - shell substitutes it and allows to perform more completions
% simple_dynamic --j\t % simple_dynamic --json
Since all output format keys are mutually exclusive - with --json
already present on a
command line --xml
and --text
won’t show up
% simple_dynamic --json \t % simple_dynamic --json --crate=NAME -- Select crate for analysis --dependencies -- Include dependencies into report URL: Upload report to a url
With dynamic completion it is easy to provide shell with more details. For example one of the
options your application can take can be a crate name from reverse dependencies. Using
complete
method you can tell bpaf
what values your parser expects and bpaf
would
communicate this to shell. In this example possible completions are generated by crates
function from a static list, but you can use any other source. bpaf
would only call crates
function when trying to complete a crate name.
% simple_dynamic --json --crate \t % simple_dynamic --json --crate NAME: Select crate for analysis cargo-hackerman -- Workspace hack management and package/feature query cargo-prebuilt -- Download prebuilt crate binaries cargo-show-asm -- Display generated assembly cargo-supply-chain -- Gather author, contributor, publisher data on crates chezmoi_modify_manager -- Chezmoi addon to patch ini files xvf -- Easy archive extraction newdoc -- Generate pre-populated module files nust64 -- Tools for compiling a Rust project into an N64 ROM uggo -- CLI tool to query builds from u.gg
As usual completion system uses input to filter on possible variants
% simple_dynamic --json --crate cargo-\t % simple_dynamic --json --crate cargo- cargo-hackerman -- Workspace hack management and package/feature query cargo-prebuilt -- Download prebuilt crate binaries cargo-show-asm -- Display generated assembly cargo-supply-chain -- Gather author, contributor, publisher data on crates
And as soon as there’s enough to identify input in a unique way - shell would substitute it.
% simple_dynamic --json --crate cargo-ha\t % simple_dynamic --json --crate cargo-hackerman
Outside of generating completion info - complete
annotation does not affect the results
Options { name: "cargo-hackerman", dependencies: false, format: Json, upload: None }
§More detailed example
examples/derive_show_asm.rs
//! Parsing snippet from cargo-show-asm
//! Derive + typed fallback + external both with and without name
use bpaf::{construct, long, Bpaf, Parser, ShellComp};
use std::{convert::Infallible, path::PathBuf};
#[derive(Clone, Debug, Bpaf)]
#[bpaf(options("asm"))] // derives cargo helper for cargo-asm
#[allow(clippy::struct_excessive_bools)]
pub struct Options {
#[bpaf(external(parse_manifest_path))]
pub manifest_path: PathBuf,
/// Custom target directory for generated artifacts
#[bpaf(argument("DIR"))]
pub target_dir: Option<PathBuf>,
/// Package to use if ambigous
#[bpaf(long, short, argument("SPEC"))]
pub package: Option<String>,
#[bpaf(external, optional)]
pub focus: Option<Focus>,
/// Produce a build plan instead of actually building
pub dry: bool,
/// Requires Cargo.lock and cache are up to date
pub frozen: bool,
/// Requires Cargo.lock is up to date
pub locked: bool,
/// Run without accessing the network
pub offline: bool,
#[bpaf(external)]
pub format: Format,
#[bpaf(external, fallback(Syntax::Intel))]
pub syntax: Syntax,
#[bpaf(external)]
pub selected_function: SelectedFunction,
}
#[derive(Debug, Clone, Bpaf)]
/// Item to pick from the output
pub struct SelectedFunction {
/// Complete or partial function name to filter
#[bpaf(positional("FUNCTION"))]
pub function: Option<String>,
/// Select nth item from a filtered list
#[bpaf(positional("INDEX"), fallback(0))]
pub nth: usize,
}
fn parse_manifest_path() -> impl Parser<PathBuf> {
long("manifest-path")
.help("Path to Cargo.toml")
.argument::<PathBuf>("PATH")
.complete_shell(ShellComp::File {
mask: Some("*.toml"),
})
.parse(|p| {
// cargo-metadata wants to see
if p.is_absolute() {
Ok(p)
} else {
std::env::current_dir()
.map(|d| d.join(p))
.and_then(|full_path| full_path.canonicalize())
}
})
.fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
#[derive(Debug, Clone, Bpaf)]
/// How to render output
pub struct Format {
/// Print interleaved Rust code
pub rust: bool,
#[bpaf(external(color_detection))]
pub color: bool,
/// include full demangled name instead of just prefix
pub full_name: bool,
}
#[derive(Debug, Clone, Bpaf)]
/// Pick output type
///
/// included help
///
///
/// Extended help
pub enum Syntax {
/// Generate assembly using Intel style
Intel,
/// Generate assembly using AT&T style
Att,
}
fn color_detection() -> impl Parser<bool> {
let yes = long("color")
.help("Enable color highlighting")
.req_flag(true);
let no = long("no-color")
.help("Disable color highlighting")
.req_flag(false);
construct!([yes, no]).fallback_with::<_, Infallible>(|| {
// we can call for supports-color crate here
Ok(true)
})
}
fn comp_examples(prefix: &String) -> Vec<(String, Option<String>)> {
// in the actual app we can ask cargo-metadata for this info
let examples = ["derive_show_asm", "coreutils", "comonad"];
examples
.iter()
.filter_map(|e| {
if e.starts_with(prefix) {
Some((e.to_string(), None))
} else {
None
}
})
.collect()
}
#[derive(Debug, Clone, Bpaf)]
/// Select artifact to use for analysis
///
/// Only one is valid
pub enum Focus {
/// Show results from library code
Lib,
Test(
/// Show results from a test
#[bpaf(long("test"), argument("TEST"))]
String,
),
Bench(
/// Show results from a benchmark
#[bpaf(long("bench"), argument("BENCH"))]
String,
),
Example(
/// Show results from an example
#[bpaf(long("example"), argument("EXAMPLE"), complete(comp_examples))]
String,
),
Bin(
/// Show results from a binary
#[bpaf(long("bin"), argument("BIN"))]
String,
),
}
fn main() {
println!("{:#?}", options().run());
}
Output
Example defines this parser
Usage: app [--manifest-path=PATH] [--target-dir=DIR] [-p=SPEC] [--lib | --test=TEST | --bench=BENCH | --example=EXAMPLE | --bin=BIN] [--dry] [--frozen] [--locked] [--offline] [--rust] [--color | --no-color] [--full-name] [--intel | --att] [FUNCTION] [INDEX]
- --lib
- Show results from library code
- --test=TEST
- Show results from a test
- --bench=BENCH
- Show results from a benchmark
- --example=EXAMPLE
- Show results from an example
- --bin=BIN
- Show results from a binary
- --rust
- Print interleaved Rust code
- --color
- Enable color highlighting
- --no-color
- Disable color highlighting
- --full-name
- include full demangled name instead of just prefix
- --intel
- Generate assembly using Intel style
- --att
- Generate assembly using AT&T style
- FUNCTION
- Complete or partial function name to filter
- INDEX
- Select nth item from a filtered list
- --manifest-path=PATH
- Path to Cargo.toml
- --target-dir=DIR
- Custom target directory for generated artifacts
- -p, --package=SPEC
- Package to use if ambigous
- --dry
- Produce a build plan instead of actually building
- --frozen
- Requires Cargo.lock and cache are up to date
- --locked
- Requires Cargo.lock is up to date
- --offline
- Run without accessing the network
- -h, --help
- Prints help information
By default completion system lists all possible cases
% derive_show_asm \t % derive_show_asm --manifest-path=PATH -- Path to Cargo.toml --target-dir=DIR -- Custom target directory for generated artifacts --package=SPEC -- Package to use if ambigous --dry -- Produce a build plan instead of actually building --frozen -- Requires Cargo.lock and cache are up to date --locked -- Requires Cargo.lock is up to date --offline -- Run without accessing the network Select artifact to use for analysis --lib -- Show results from library code --test=TEST -- Show results from a test --bench=BENCH -- Show results from a benchmark --example=EXAMPLE -- Show results from an example --bin=BIN -- Show results from a binary How to render output --rust -- Print interleaved Rust code --color -- Enable color highlighting --no-color -- Disable color highlighting --full-name -- include full demangled name instead of just prefix Pick output type --intel -- Generate assembly using Intel style --att -- Generate assembly using AT&T style Item to pick from the output FUNCTION: Complete or partial function name to filter
But when user tries to complete example name - it only lists examples produced by
comp_examples
function
% derive_show_asm --example \t % derive_show_asm --example Select artifact to use for analysis EXAMPLE: Show results from an example derive_show_asm coreutils comonad
And completes the full name when user gives enough information
% derive_show_asm --example cor\t % derive_show_asm --example coreutils
Examples found in repository?
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
fn opts() -> Opts {
let sensor = long("sensor").req_flag(());
let device = long("sensor-device")
.argument::<String>("DEVICE")
.complete(sensor_device_comp);
let name = long("sensor-name").argument::<String>("NAME");
// from_str needs to be replaced with `parse` that can deal with hex digits
let bus_id = long("sensor-i2c-bus").argument::<usize>("BUS");
let address = long("sensor-i2c-address").argument::<usize>("ADDRESS");
let sensors = construct!(Sensor {
sensor,
device,
name,
bus_id,
address
})
.adjacent()
.many();
construct!(Opts { sensors }).to_options().run()
}
More examples
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
fn main() {
use bpaf::*;
let a = short('a').long("avocado").help("Use avocado").switch();
let b = short('b').long("banana").help("Use banana").switch();
let bb = long("bananananana").help("I'm Batman").switch();
let c = long("calculator")
.help("calculator expression")
.argument::<String>("EXPR")
.complete(complete_calculator);
let parser = construct!(a, b, bb, c)
.to_options()
.descr("Dynamic autocomplete example")
.footer(
"\
bpaf supports dynamic autocompletion for a few shells, make sure your binary is in $PATH
and try using one of those this output should go into a file that depends on your shell:
$ csample --bpaf-complete-style-bash
$ csample --bpaf-complete-style-zsh
$ csample --bpaf-complete-style-fish
$ csample --bpaf-complete-style-elvish",
);
println!("{:?}", parser.fallback_to_usage().run());
}
sourcefn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
Available on crate feature autocomplete
only.
fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
autocomplete
only.Static shell completion
Allows to ask existing shell completion to provide some information such as a file or
directory names or pass through existing shell completion scripts, see
ShellComp
for accessible functionality
Places function calls in place of metavar placeholder, so running complete_shell
on
something that doesn’t have a positional
or argument
doesn’t
make much sense.
§Example
$ app --output C<TAB>
$ app --output Cargo.toml _
§Combinatoric usage
fn output() -> impl Parser<String> {
long("output")
.help("Cargo.toml file to use as output")
.argument("OUTPUT")
.complete_shell(ShellComp::File { mask: Some("*.toml") })
}
§Derive usage
#[derive(Debug, Clone, Bpaf)]
struct Options {
/// Cargo.toml file to use as output
#[bpaf(argument("OUTPUT"), complete_shell(ShellComp::File { mask: Some("*.toml") }))]
output: String,
}
For multiple file types correct mask syntax is "*.(toml|md)"
.
Examples found in repository?
More examples
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
fn parse_manifest_path() -> impl Parser<PathBuf> {
long("manifest-path")
.help("Path to Cargo.toml")
.argument::<PathBuf>("PATH")
.complete_shell(ShellComp::File {
mask: Some("*.toml"),
})
.parse(|p| {
// cargo-metadata wants to see
if p.is_absolute() {
Ok(p)
} else {
std::env::current_dir()
.map(|d| d.join(p))
.and_then(|full_path| full_path.canonicalize())
}
})
.fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
fn opts() -> OptionParser<Out> {
// A flag, true if used in the command line. Can be required, this one is optional
let debug = short('d') // start with a short name
.long("debug") // also add a long name
.help("Activate debug mode") // and a help message to use
.switch(); // turn this into a switch
// number of occurrences of the v/verbose flag capped at 3 with an error here but you can also
// use `max` inside `map`
let verbose = short('v')
.long("verbose")
.help("Increase the verbosity\n You can specify it up to 3 times\n either as -v -v -v or as -vvv")
.req_flag(())
.many()
.map(|xs| xs.len())
.guard(|&x| x <= 3, "It doesn't get any more verbose than this");
// an argument, parsed and with default value
let speed = short('s')
.long("speed")
.help("Set speed")
.argument::<f64>("SPEED") // you can specify a type to parse
.fallback(42.0)
.display_fallback();
let output = short('o')
.long("output")
.help("output file")
.argument::<PathBuf>("OUTPUT") // but it's optional when rustc can derive it
.complete_shell(ShellComp::File { mask: None });
// no magical name transmogrifications in combinatoric API,
let nb_cars = short('n')
.long("nb-cars")
.help("Number of items to process")
.argument::<u32>("N")
.fallback(1)
.display_fallback();
// a parser that consumes one argument
// you can build the inner parser in one go or as multiple steps giving each step a name
// you can also add some static shell completion functionality
let file_to_proces = short('f')
.long("file")
.help("File to process")
.argument::<PathBuf>("FILE")
.complete_shell(ShellComp::File { mask: Some("*.rs") });
let files_to_process = file_to_proces.many();
// packing things in a struct assumes parser for each field is in scope.
construct!(Out {
debug,
verbose,
speed,
output,
nb_cars,
files_to_process
})
.to_options()
.descr("This is a description")
}
sourcefn to_options(self) -> OptionParser<T>
fn to_options(self) -> OptionParser<T>
Transform Parser
into OptionParser
to get ready to run
it
§Derive usage
Add a top-level options
annotation to generate OptionParser
instead of default
Parser
.
In addition to options
annotation, you can also specify either version
or
version(value)
annotation. The former uses version from cargo
, later uses the
specified value which should be an expression of type &'static str
, see
version
.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
argument: u32,
}
pub fn options() -> OptionParser<Options> {
let argument = short('i').argument::<u32>("ARG");
construct!(Options { argument })
.to_options()
.version("3.1415")
.descr("This is a short description")
.header("It can contain multiple blocks, this block goes before options")
.footer("This one goes after")
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options, version("3.1415"))]
/// This is a short description
///
///
/// It can contain multiple blocks, this block goes before options
///
///
/// This one goes after
pub struct Options {
#[bpaf(short('i'))]
argument: u32,
}
fn main() {
println!("{:?}", options().run())
}
Output
In addition to all the arguments specified by user bpaf
adds a few more. One of them is
--help
:
This is a short description
Usage: app -i=ARG
It can contain multiple blocks, this block goes before options
- -i=ARG
- -h, --help
- Prints help information
- -V, --version
- Prints version information
This one goes after
The other one is --version
- passing a string literal or something like
env!("CARGO_PKG_VERSION")
to get version from cargo
directly usually works
Version: 3.1415
Other than that bpaf
tries its best to provide a helpful error messages
Error: expected -i=ARG, pass --help for usage information
And if all parsers are satisfied run
produces the result
Options { argument: 10 }
§See also
There’s some methods implemented on OptionParser
directly to customize the appearance
Examples found in repository?
More examples
- examples/env_variable.rs
- examples/filenames.rs
- examples/dd.rs
- examples/ex_positional.rs
- examples/top_to_bottom.rs
- examples/numeric_prefix.rs
- examples/shared_args.rs
- examples/flatten.rs
- examples/cargo-cmd.rs
- examples/enum_tuple.rs
- examples/negative.rs
- examples/dynamic-tree.rs
- examples/customize_help.rs
- examples/xorg.rs
- examples/no_import.rs
- examples/multiple_fallback.rs
- examples/sensors.rs
- examples/coreutils.rs
- examples/cat.rs
- examples/rectangle.rs
- examples/git.rs
- examples/csample.rs
- examples/confusing.rs
- examples/verbose.rs
- examples/basic.rs
- examples/travel.rs
sourcefn run(self) -> T
fn run(self) -> T
Finalize and run the parser
Generally, you’d want to use Parser::to_options
to finalize the parser and OptionParser::run
,
but this also works for simple cases:
fn main() {
let name = short('n').long("name").argument::<String>("USER").run();
// do things with name
}
Examples found in repository?
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
fn main() {
let items = &[
("banana", Ty::Bool),
("width", Ty::Number),
("name", Ty::String),
];
let mut parser = pure(Vec::<(String, Value)>::new()).boxed();
for (name, ty) in items {
parser = cons(
parser,
match ty {
Ty::Bool => bool(name).boxed(),
Ty::Number => number(name).boxed(),
Ty::String => string(name).boxed(),
},
)
}
let options = parser.run();
println!("{:?}", options);
}
sourcefn boxed(self) -> Box<dyn Parser<T>>
fn boxed(self) -> Box<dyn Parser<T>>
Create a boxed representation for a parser
The boxed parser doesn’t expose internal representation in its type and allows to return of different parsers in different conditional branches
You can create it with a single argument construct
macro or by using boxed
annotation
pub fn options() -> OptionParser<f64> {
let miles = long("distance")
.help("distance in miles")
.argument::<f64>("MILES")
.map(|d| d * 1.609344);
let km = long("distance")
.help("distance in km")
.argument::<f64>("KM");
// suppose this is reading from config fule
let use_metric = true;
// without use of `boxed` here branches have different types so it won't typecheck
// boxed make it so branches have the same type as long as they return the same type
let distance = if use_metric {
km.boxed()
} else {
miles.boxed()
};
distance.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Output
It is also possible to make dynamic choice about the parsers. This example defines two parsers for distance - imperial and metric and picks one from some source available at runtime only.
Help message will contain only one parser
Usage: app --distance=KM
- --distance=KM
- distance in km
- -h, --help
- Prints help information
and only one parser will produce a result
10.0
Examples found in repository?
More examples
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
fn cons<T>(acc: Box<dyn Parser<Vec<T>>>, cur: Box<dyn Parser<T>>) -> Box<dyn Parser<Vec<T>>>
where
T: 'static,
{
construct!(acc, cur)
.map(|(mut acc, cur)| {
acc.push(cur);
acc
})
.boxed()
}
enum Ty {
Bool,
Number,
String,
}
fn main() {
let items = &[
("banana", Ty::Bool),
("width", Ty::Number),
("name", Ty::String),
];
let mut parser = pure(Vec::<(String, Value)>::new()).boxed();
for (name, ty) in items {
parser = cons(
parser,
match ty {
Ty::Bool => bool(name).boxed(),
Ty::Number => number(name).boxed(),
Ty::String => string(name).boxed(),
},
)
}
let options = parser.run();
println!("{:?}", options);
}
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
fn choose<T>(xs: Vec<Box<dyn Parser<T> + 'static>>) -> Box<dyn Parser<T>>
where
T: 'static,
{
let mut items = xs.into_iter();
let mut res = items.next().unwrap();
for next in items {
res = construct!([res, next]).boxed()
}
res
}
fn make_parser(item: &Cog) -> Box<dyn Parser<&'static str>> {
match item {
Cog::Command {
help,
name,
operation,
} => Box::new(pure(*operation).to_options().descr(*help).command(name)),
Cog::Group { name, help, nested } => {
let nested = nested.iter().map(make_parser).collect::<Vec<_>>();
let inner = choose(nested);
inner.to_options().descr(*help).command(name).boxed()
}
}
}
Trait Implementations§
source§impl<T> Parser<T> for Box<dyn Parser<T>>
impl<T> Parser<T> for Box<dyn Parser<T>>
source§fn collect<C>(self) -> ParseCollect<Self, C, T>where
C: FromIterator<T>,
Self: Sized,
fn collect<C>(self) -> ParseCollect<Self, C, T>where
C: FromIterator<T>,
Self: Sized,
source§fn optional(self) -> ParseOptional<Self>
fn optional(self) -> ParseOptional<Self>
source§fn count(self) -> ParseCount<Self, T>
fn count(self) -> ParseCount<Self, T>
source§fn last(self) -> ParseLast<Self>
fn last(self) -> ParseLast<Self>
source§fn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
fn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
source§fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
source§fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
source§fn fallback(self, value: T) -> ParseFallback<Self, T>
fn fallback(self, value: T) -> ParseFallback<Self, T>
source§fn fallback_with<F, E>(self, fallback: F) -> ParseFallbackWith<T, Self, F, E>
fn fallback_with<F, E>(self, fallback: F) -> ParseFallbackWith<T, Self, F, E>
source§fn hide(self) -> ParseHide<Self>
fn hide(self) -> ParseHide<Self>
source§fn hide_usage(self) -> ParseUsage<Self>
fn hide_usage(self) -> ParseUsage<Self>
source§fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
source§fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
source§fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
source§fn complete<M, F>(self, op: F) -> ParseComp<Self, F>
fn complete<M, F>(self, op: F) -> ParseComp<Self, F>
autocomplete
only.source§fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
autocomplete
only.source§fn to_options(self) -> OptionParser<T>
fn to_options(self) -> OptionParser<T>
Implementations on Foreign Types§
impl<T> Parser<T> for Box<dyn Parser<T>>
Implementors§
impl<P, T> Parser<T> for ParseCompShell<P>
autocomplete
only.