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
40fn verbose() -> impl Parser<usize> {
41 short('v')
42 .long("verbose")
43 .help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
44 .req_flag(())
45 .many()
46 .map(|xs| xs.len())
47 .guard(|&x| x <= 3, "It doesn't get any more verbose than this")
48}
49
50// an argument, parsed and with default value
51fn speed() -> impl Parser<f64> {
52 short('s')
53 .long("speed")
54 .help("Set speed")
55 .argument::<f64>("SPEED")
56 .fallback(42.0)
57}
58
59fn output() -> impl Parser<PathBuf> {
60 short('o')
61 .long("output")
62 .help("output file")
63 .argument::<PathBuf>("OUTPUT")
64}
65
66// no magical name transmogrifications.
67fn nb_cars() -> impl Parser<u32> {
68 short('n').long("nb-cars").argument::<u32>("N")
69}
70
71fn files_to_process() -> impl Parser<Vec<PathBuf>> {
72 short('f')
73 .long("file")
74 .help("File to process")
75 .argument::<PathBuf>("FILE")
76 .many()
77}6fn args() -> impl Parser<Vec<u16>> {
7 long("ports")
8 .help("Comma separated list of ports")
9 .argument::<String>("PORTS")
10 .parse(|s| {
11 s.split(',')
12 .map(u16::from_str)
13 .collect::<Result<Vec<_>, _>>()
14 })
15 .many()
16 .map(|nested| nested.into_iter().flatten().collect())
17}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?
31fn user() -> impl Parser<Option<String>> {
32 // match only literal "-user"
33 let tag = literal("-user").anywhere();
34 let value = positional("USER").help("User name");
35 construct!(tag, value)
36 .adjacent()
37 .map(|pair| pair.1)
38 .optional()
39}
40
41// parsers -exec xxx yyy zzz ;
42fn exec() -> impl Parser<Option<Vec<OsString>>> {
43 let tag = literal("-exec")
44 .help("for every file find finds execute a separate shell command")
45 .anywhere();
46
47 let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
48 .help("command with its arguments, find will replace {} with a file name")
49 .many();
50
51 let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
52 .help("anything after literal \";\" will be considered a regular option again");
53
54 construct!(tag, item, endtag)
55 .adjacent()
56 .map(|triple| triple.1)
57 .optional()
58}
59
60/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
61fn perm() -> impl Parser<Option<Perm>> {
62 fn parse_mode(input: &str) -> Result<Perms, String> {
63 let mut perms = Perms::default();
64 for c in input.chars() {
65 match c {
66 'r' => perms.read = true,
67 'w' => perms.write = true,
68 'x' => perms.exec = true,
69 _ => return Err(format!("{} is not a valid permission string", input)),
70 }
71 }
72 Ok(perms)
73 }
74
75 let tag = literal("-mode").anywhere();
76
77 // `any` here is used to parse an arbitrary string that can also start with dash (-)
78 // regular positional parser won't work here
79 let mode = any("MODE", Some)
80 .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
81 .parse::<_, _, String>(|s: String| {
82 if let Some(m) = s.strip_prefix('-') {
83 Ok(Perm::All(parse_mode(m)?))
84 } else if let Some(m) = s.strip_prefix('/') {
85 Ok(Perm::Any(parse_mode(m)?))
86 } else {
87 Ok(Perm::Exact(parse_mode(&s)?))
88 }
89 });
90
91 construct!(tag, mode)
92 .adjacent()
93 .map(|pair| pair.1)
94 .optional()
95}More examples
16fn main() {
17 // defining a parser in a usual way
18 let width = short('w').argument::<usize>("WIDTH").fallback(10);
19 let height = short('h').argument::<usize>("HEIGHT").fallback(10);
20 let parser = construct!(Opts { width, height });
21
22 let cmd = literal("cmd").optional().hide();
23 let combined_parser = construct!(cmd, parser).map(|x| x.1);
24
25 let opts = combined_parser.to_options().run();
26
27 println!("{:?}", opts);
28}15fn main() {
16 let bar = short('b')
17 .long("bar")
18 .help("some bar command")
19 .argument::<String>("BAR")
20 .optional();
21
22 let bar_cmd = construct!(Foo { bar })
23 .to_options()
24 .descr("This command will try to do foo given a bar argument");
25
26 let opt = bar_cmd
27 .command("foo")
28 .help("command for doing foo")
29 .map(Command::Foo)
30 .to_options()
31 .run();
32
33 println!("{:#?}", opt);
34}33fn feature_if() -> impl Parser<Option<String>> {
34 // here feature starts as any string on a command line that does not start with a dash
35 positional::<String>("FEATURE")
36 // guard restricts it such that it can't be a valid version
37 .guard(move |s| !is_version(s), "")
38 // last two steps describe what to do with strings in this position but are actually
39 // versions.
40 // optional allows parser to represent an ignored value with None
41 .optional()
42 // and catch lets optional to handle parse failures coming from guard
43 .catch()
44}
45
46fn version_if() -> impl Parser<Option<String>> {
47 positional::<String>("VERSION")
48 .guard(move |s| is_version(s), "")
49 .optional()
50 .catch()
51}15fn main() {
16 let file = positional::<OsString>("FILE")
17 .help("File name to concatenate, with no FILE or when FILE is -, read standard input")
18 .optional()
19 .parse::<_, Box<dyn Read>, std::io::Error>(|path| {
20 Ok(if let Some(path) = path {
21 if path == "-" {
22 Box::new(stdin())
23 } else {
24 Box::new(File::open(path)?)
25 }
26 } else {
27 Box::new(stdin())
28 })
29 })
30 .to_options()
31 .descr("Concatenate a file to standard output")
32 .run();
33
34 let reader = BufReader::new(file);
35
36 for line in reader.lines() {
37 println!("{}", line.unwrap());
38 }
39}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
12fn main() {
13 // program takes one or more -v or --verbose flags, more flags = higher verbosity.
14 // parser handles number and produces a single flag.
15 //
16 // let's create it without using any single purpose magical functions
17
18 // Let's staty by creating a simple parser that handles a single -v / --verbose
19 // and fails otherwise;
20 let verbose = short('v').long("verbose").req_flag(());
21
22 // Try to apply the inner parser as many times as it succeeds, return the number
23 let verbose = verbose.count();
24
25 // And add a simple sanity checker.
26 // By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
27 let verbose = verbose.guard(|&x| x <= 3, "it doesn't get any more verbose than 3");
28
29 // program takes --trimor --no-trimflag, but not both at once. If none is given -
30 // fallback value is to disable trimming. Trim enum is set accordingly
31
32 // this flag succeeds iff --no-trim is given and produces Trim::Off
33 let trim_off = long("no-trim").req_flag(Trim::Off);
34
35 // this flag handles two remaining cases: --trim is given (Trim::On) an fallback (Trim::Off)
36 let trim_on = long("trim").flag(Trim::On, Trim::Off);
37
38 // combination of previous two.
39 // if trim_off succeeds - trim_on never runs, otherwise trim_on tries to handle the remaining
40 // case before falling back to Trim:Off.
41 // If both --trim and --no-trim are given trim_off succeeds, trim_off never runs and --trim
42 // remains unused - parser fails
43 let trim = construct!([trim_off, trim_on]);
44
45 let parser = construct!(verbose, trim);
46
47 let opt = parser.to_options().run();
48 println!("{:#?}", opt);
49}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 pub fn parse_binary() -> impl Parser<bool> {
62 #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
63 enum Mode {
64 /// Use binary mode
65 #[bpaf(short, long)]
66 Binary,
67 /// Use text mode
68 #[bpaf(short, long)]
69 Text,
70 }
71 mode()
72 .last()
73 .fallback(Mode::Text)
74 .debug_fallback()
75 .map(|mode| mode == Mode::Binary)
76 }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?
6fn args() -> impl Parser<Vec<u16>> {
7 long("ports")
8 .help("Comma separated list of ports")
9 .argument::<String>("PORTS")
10 .parse(|s| {
11 s.split(',')
12 .map(u16::from_str)
13 .collect::<Result<Vec<_>, _>>()
14 })
15 .many()
16 .map(|nested| nested.into_iter().flatten().collect())
17}More examples
16fn tag<T>(name: &'static str, meta: &str, help: &'static str) -> impl Parser<T>
17where
18 T: FromStr,
19 <T as std::str::FromStr>::Err: std::fmt::Display,
20{
21 // it is possible to parse OsString here and strip the prefix with
22 // `os_str_bytes` or a similar crate
23 any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
24 // this defines custom metavar for the help message
25 .metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
26 .help(help)
27 .anywhere()
28 .parse(|s| s.parse())
29}48fn parse_manifest_path() -> impl Parser<PathBuf> {
49 long("manifest-path")
50 .help("Path to Cargo.toml")
51 .argument::<PathBuf>("PATH")
52 .complete_shell(ShellComp::File {
53 mask: Some("*.toml"),
54 })
55 .parse(|p| {
56 // cargo-metadata wants to see
57 if p.is_absolute() {
58 Ok(p)
59 } else {
60 std::env::current_dir()
61 .map(|d| d.join(p))
62 .and_then(|full_path| full_path.canonicalize())
63 }
64 })
65 .fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
66}15fn main() {
16 let file = positional::<OsString>("FILE")
17 .help("File name to concatenate, with no FILE or when FILE is -, read standard input")
18 .optional()
19 .parse::<_, Box<dyn Read>, std::io::Error>(|path| {
20 Ok(if let Some(path) = path {
21 if path == "-" {
22 Box::new(stdin())
23 } else {
24 Box::new(File::open(path)?)
25 }
26 } else {
27 Box::new(stdin())
28 })
29 })
30 .to_options()
31 .descr("Concatenate a file to standard output")
32 .run();
33
34 let reader = BufReader::new(file);
35
36 for line in reader.lines() {
37 println!("{}", line.unwrap());
38 }
39}61fn perm() -> impl Parser<Option<Perm>> {
62 fn parse_mode(input: &str) -> Result<Perms, String> {
63 let mut perms = Perms::default();
64 for c in input.chars() {
65 match c {
66 'r' => perms.read = true,
67 'w' => perms.write = true,
68 'x' => perms.exec = true,
69 _ => return Err(format!("{} is not a valid permission string", input)),
70 }
71 }
72 Ok(perms)
73 }
74
75 let tag = literal("-mode").anywhere();
76
77 // `any` here is used to parse an arbitrary string that can also start with dash (-)
78 // regular positional parser won't work here
79 let mode = any("MODE", Some)
80 .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
81 .parse::<_, _, String>(|s: String| {
82 if let Some(m) = s.strip_prefix('-') {
83 Ok(Perm::All(parse_mode(m)?))
84 } else if let Some(m) = s.strip_prefix('/') {
85 Ok(Perm::Any(parse_mode(m)?))
86 } else {
87 Ok(Perm::Exact(parse_mode(&s)?))
88 }
89 });
90
91 construct!(tag, mode)
92 .adjacent()
93 .map(|pair| pair.1)
94 .optional()
95}30fn main() {
31 let token = long("token")
32 .help("Token used for complex commands")
33 .argument::<String>("TOKEN")
34 .optional();
35
36 // start with defining 3 commands: simple, complex1 and complex2
37 let simple_parser = pure(PreCommand::Simple).to_options();
38 let simple = simple_parser.command("simple");
39
40 let complex1_parser = positional::<i32>("ARG");
41 let complex1 = construct!(PreCommand::Complex1(complex1_parser))
42 .to_options()
43 .descr("This is complex command 1")
44 .command("complex1");
45
46 let complex2_parser = positional::<i16>("ARG");
47
48 let complex2 = construct!(PreCommand::Complex2(complex2_parser))
49 .to_options()
50 .descr("This is complex command 2")
51 .command("complex2");
52
53 // compose then to accept any of those
54 let preparser = construct!([simple, complex1, complex2]);
55
56 // make a parser that accepts optional token and one of incomplete commands
57 // then create complete command or fail
58 let parser = construct!(token, preparser).parse(|(token, cmd)| match cmd {
59 PreCommand::Simple => Ok(Command::Simple),
60 PreCommand::Complex1(a) => match token {
61 Some(token) => Ok(Command::Complex1(token, a)),
62 None => Err("You must specify token to use with --token"),
63 },
64 PreCommand::Complex2(a) => match token {
65 Some(token) => Ok(Command::Complex2(token, a)),
66 None => Err("You must specify token to use with --token"),
67 },
68 });
69
70 let cmd = parser.to_options().run();
71 println!("{:?}", cmd);
72}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?
12fn number(name: &'static str) -> impl Parser<(String, Value)> {
13 let label = name.to_string();
14 long(name)
15 .argument::<usize>("NUM")
16 .map(move |n| (label.clone(), Value::Number(n)))
17}
18
19fn bool(name: &'static str) -> impl Parser<(String, Value)> {
20 let label = name.to_string();
21 long(name)
22 .switch()
23 .map(move |n| (label.clone(), Value::Bool(n)))
24}
25
26fn string(name: &'static str) -> impl Parser<(String, Value)> {
27 let label = name.to_string();
28 long(name)
29 .help("this can use a help message")
30 .argument::<String>("NUM")
31 .map(move |n| (label.clone(), Value::String(n)))
32}
33
34fn cons<T>(acc: Box<dyn Parser<Vec<T>>>, cur: Box<dyn Parser<T>>) -> Box<dyn Parser<Vec<T>>>
35where
36 T: 'static,
37{
38 construct!(acc, cur)
39 .map(|(mut acc, cur)| {
40 acc.push(cur);
41 acc
42 })
43 .boxed()
44}More examples
32 fn my_last(self) -> ParseLast<T> {
33 let p = self
34 .some("need to specify at least once")
35 .map(|mut xs| xs.pop().unwrap());
36 ParseLast { inner: p.boxed() }
37 }
38 }
39}
40pub mod shared {
41 use super::boilerplate::*;
42 use bpaf::*;
43
44 #[derive(Debug, Clone, Copy, Bpaf)]
45 pub enum Verbosity {
46 /// Display warnings
47 #[bpaf(short, long)]
48 Warn,
49 /// Display only diagnostics
50 #[bpaf(short, long)]
51 Quiet,
52 /// Display status only
53 #[bpaf(short, long)]
54 Status,
55 }
56
57 pub fn parse_verbosity() -> impl Parser<Verbosity> {
58 verbosity().my_last().fallback(Verbosity::Status)
59 }
60
61 pub fn parse_binary() -> impl Parser<bool> {
62 #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
63 enum Mode {
64 /// Use binary mode
65 #[bpaf(short, long)]
66 Binary,
67 /// Use text mode
68 #[bpaf(short, long)]
69 Text,
70 }
71 mode()
72 .last()
73 .fallback(Mode::Text)
74 .debug_fallback()
75 .map(|mode| mode == Mode::Binary)
76 }
77}
78
79mod arch {
80 use bpaf::*;
81
82 #[derive(Debug, Clone, Bpaf)]
83 #[bpaf(command)]
84 /// Print machine architecture.
85 pub struct Arch;
86}
87
88mod b2sum {
89 use super::shared::*;
90 use bpaf::*;
91 use std::path::PathBuf;
92
93 #[derive(Debug, Clone, Bpaf)]
94 #[bpaf(command("b2sum"))]
95 /// Print or check BLAKE2 (512-bit) checksums.
96 pub struct B2Sum {
97 #[bpaf(external(parse_binary))]
98 pub binary: bool,
99
100 /// read BLAKE2 sums from the FILEs and check them
101 #[bpaf(short, long)]
102 pub check: bool,
103
104 /// create a BSD-style checksum
105 pub tag: bool,
106
107 #[bpaf(external(parse_verbosity))]
108 pub check_output: Verbosity,
109
110 /// exit non-zero for improperly formatted checksum lines
111 pub strict: bool,
112
113 #[bpaf(positional("FILE"))]
114 pub files: Vec<PathBuf>,
115 }
116}
117
118mod base32 {
119 use bpaf::*;
120 use std::path::PathBuf;
121
122 fn non_zero(val: Option<usize>) -> Option<usize> {
123 val.and_then(|v| (v > 0).then_some(v))
124 }
125
126 #[derive(Debug, Clone, Bpaf)]
127 #[bpaf(command)]
128 /// Base32 encode or decode FILE, or standard input, to standard output.
129 pub struct Base32 {
130 /// decode data
131 #[bpaf(long, short)]
132 pub decode: bool,
133 #[bpaf(long, short)]
134 /// when decoding, ignore non-alphabet characters
135 pub ignore_garbage: bool,
136
137 #[bpaf(
138 long,
139 short,
140 argument("COLS"),
141 optional,
142 map(non_zero),
143 fallback(Some(76)),
144 debug_fallback
145 )]
146 /// wrap encoded lines after COLS character
147 /// Use 0 to disable line wrapping
148 pub wrap: Option<usize>,
149
150 #[bpaf(positional("FILE"))]
151 /// With no FILE, or when FILE is -, read standard input.
152 pub file: Option<PathBuf>,
153 }
154}
155
156mod basename {
157 use bpaf::*;
158
159 #[derive(Debug, Clone, Bpaf)]
160 #[bpaf(command)]
161 pub struct Basename {
162 /// support multiple arguments and treat each as a NAME
163 #[bpaf(short('a'), long)]
164 pub multiple: bool,
165
166 /// remove a trailing SUFFIX; implies -a
167 #[bpaf(short, long, argument("SUFFIX"), optional)]
168 pub suffix: Option<String>,
169
170 /// end each output line with NUL, not newline
171 #[bpaf(short, long)]
172 pub zero: bool,
173
174 /// Print NAME with any leading directory components removed.
175 #[bpaf(positional("NAME"), many)]
176 pub names: Vec<String>,
177 }
178
179 pub fn parse_basename() -> impl Parser<Basename> {
180 basename().map(|mut b| {
181 if b.suffix.is_some() {
182 b.multiple = true;
183 }
184 b
185 })
186 }
187}
188
189mod cat {
190 use std::path::PathBuf;
191
192 use bpaf::*;
193
194 #[derive(Debug, Clone, Bpaf)]
195 struct Extra {
196 #[bpaf(short('A'), long)]
197 /// equivalent to -vET
198 show_all: bool,
199
200 #[bpaf(short('b'), long)]
201 /// number nonempty output lines, overrides -n
202 number_nonblank: bool,
203
204 #[bpaf(short('e'))]
205 /// equivalent to -vE
206 show_non_printing_ends: bool,
207 }
208
209 #[derive(Debug, Clone, Bpaf)]
210 #[bpaf(fallback(NumberingMode::None))]
211 pub enum NumberingMode {
212 #[bpaf(hide)]
213 /// Don't number lines, default behavior
214 None,
215
216 /// Number nonempty output lines, overrides -n
217 #[bpaf(short('b'), long("number-nonblank"))]
218 NonEmpty,
219
220 /// Number all output lines
221 #[bpaf(short('n'), long("number"))]
222 All,
223 }
224
225 #[derive(Debug, Clone, Bpaf)]
226 pub struct Cat {
227 #[bpaf(short('T'), long)]
228 /// display TAB characters as ^I
229 pub show_tabs: bool,
230
231 /// display $ at end of each line
232 #[bpaf(short('E'))]
233 pub show_ends: bool,
234
235 /// use ^ and M- notation, except for LFD and TAB
236 #[bpaf(short('n'), long("number"))]
237 show_nonprinting: bool,
238
239 #[bpaf(external(numbering_mode))]
240 pub number: NumberingMode,
241
242 #[bpaf(short('s'), long)]
243 /// suppress repeated empty output lines
244 pub squeeze_blank: bool,
245
246 #[bpaf(positional("FILE"), many)]
247 /// Concatenate FILE(s) to standard output.
248 pub files: Vec<PathBuf>,
249 }
250
251 pub fn parse_cat() -> impl Parser<Cat> {
252 construct!(extra(), cat())
253 .map(|(extra, mut cat)| {
254 if extra.show_all {
255 cat.show_tabs = true;
256 cat.show_ends = true;
257 cat.show_nonprinting = true;
258 }
259 if extra.show_non_printing_ends {
260 cat.show_nonprinting = true;
261 cat.show_ends = true;
262 }
263 if extra.number_nonblank {
264 cat.number = NumberingMode::NonEmpty;
265 }
266 cat
267 })
268 .to_options()
269 .command("cat")
270 }31fn user() -> impl Parser<Option<String>> {
32 // match only literal "-user"
33 let tag = literal("-user").anywhere();
34 let value = positional("USER").help("User name");
35 construct!(tag, value)
36 .adjacent()
37 .map(|pair| pair.1)
38 .optional()
39}
40
41// parsers -exec xxx yyy zzz ;
42fn exec() -> impl Parser<Option<Vec<OsString>>> {
43 let tag = literal("-exec")
44 .help("for every file find finds execute a separate shell command")
45 .anywhere();
46
47 let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
48 .help("command with its arguments, find will replace {} with a file name")
49 .many();
50
51 let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
52 .help("anything after literal \";\" will be considered a regular option again");
53
54 construct!(tag, item, endtag)
55 .adjacent()
56 .map(|triple| triple.1)
57 .optional()
58}
59
60/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
61fn perm() -> impl Parser<Option<Perm>> {
62 fn parse_mode(input: &str) -> Result<Perms, String> {
63 let mut perms = Perms::default();
64 for c in input.chars() {
65 match c {
66 'r' => perms.read = true,
67 'w' => perms.write = true,
68 'x' => perms.exec = true,
69 _ => return Err(format!("{} is not a valid permission string", input)),
70 }
71 }
72 Ok(perms)
73 }
74
75 let tag = literal("-mode").anywhere();
76
77 // `any` here is used to parse an arbitrary string that can also start with dash (-)
78 // regular positional parser won't work here
79 let mode = any("MODE", Some)
80 .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
81 .parse::<_, _, String>(|s: String| {
82 if let Some(m) = s.strip_prefix('-') {
83 Ok(Perm::All(parse_mode(m)?))
84 } else if let Some(m) = s.strip_prefix('/') {
85 Ok(Perm::Any(parse_mode(m)?))
86 } else {
87 Ok(Perm::Exact(parse_mode(&s)?))
88 }
89 });
90
91 construct!(tag, mode)
92 .adjacent()
93 .map(|pair| pair.1)
94 .optional()
95}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
33fn feature_if() -> impl Parser<Option<String>> {
34 // here feature starts as any string on a command line that does not start with a dash
35 positional::<String>("FEATURE")
36 // guard restricts it such that it can't be a valid version
37 .guard(move |s| !is_version(s), "")
38 // last two steps describe what to do with strings in this position but are actually
39 // versions.
40 // optional allows parser to represent an ignored value with None
41 .optional()
42 // and catch lets optional to handle parse failures coming from guard
43 .catch()
44}
45
46fn version_if() -> impl Parser<Option<String>> {
47 positional::<String>("VERSION")
48 .guard(move |s| is_version(s), "")
49 .optional()
50 .catch()
51}12fn main() {
13 // program takes one or more -v or --verbose flags, more flags = higher verbosity.
14 // parser handles number and produces a single flag.
15 //
16 // let's create it without using any single purpose magical functions
17
18 // Let's staty by creating a simple parser that handles a single -v / --verbose
19 // and fails otherwise;
20 let verbose = short('v').long("verbose").req_flag(());
21
22 // Try to apply the inner parser as many times as it succeeds, return the number
23 let verbose = verbose.count();
24
25 // And add a simple sanity checker.
26 // By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
27 let verbose = verbose.guard(|&x| x <= 3, "it doesn't get any more verbose than 3");
28
29 // program takes --trimor --no-trimflag, but not both at once. If none is given -
30 // fallback value is to disable trimming. Trim enum is set accordingly
31
32 // this flag succeeds iff --no-trim is given and produces Trim::Off
33 let trim_off = long("no-trim").req_flag(Trim::Off);
34
35 // this flag handles two remaining cases: --trim is given (Trim::On) an fallback (Trim::Off)
36 let trim_on = long("trim").flag(Trim::On, Trim::Off);
37
38 // combination of previous two.
39 // if trim_off succeeds - trim_on never runs, otherwise trim_on tries to handle the remaining
40 // case before falling back to Trim:Off.
41 // If both --trim and --no-trim are given trim_off succeeds, trim_off never runs and --trim
42 // remains unused - parser fails
43 let trim = construct!([trim_off, trim_on]);
44
45 let parser = construct!(verbose, trim);
46
47 let opt = parser.to_options().run();
48 println!("{:#?}", opt);
49}17fn opts() -> OptionParser<Out> {
18 // A flag, true if used in the command line. Can be required, this one is optional
19
20 let debug = short('d') // start with a short name
21 .long("debug") // also add a long name
22 .help("Activate debug mode") // and a help message to use
23 .switch(); // turn this into a switch
24
25 // number of occurrences of the v/verbose flag capped at 3 with an error here but you can also
26 // use `max` inside `map`
27 let verbose = short('v')
28 .long("verbose")
29 .help("Increase the verbosity\n You can specify it up to 3 times\n either as -v -v -v or as -vvv")
30 .req_flag(())
31 .many()
32 .map(|xs| xs.len())
33 .guard(|&x| x <= 3, "It doesn't get any more verbose than this");
34
35 // an argument, parsed and with default value
36 let speed = short('s')
37 .long("speed")
38 .help("Set speed")
39 .argument::<f64>("SPEED") // you can specify a type to parse
40 .fallback(42.0)
41 .display_fallback();
42
43 let output = short('o')
44 .long("output")
45 .help("output file")
46 .argument::<PathBuf>("OUTPUT") // but it's optional when rustc can derive it
47 .complete_shell(ShellComp::File { mask: None });
48
49 // no magical name transmogrifications in combinatoric API,
50 let nb_cars = short('n')
51 .long("nb-cars")
52 .help("Number of items to process")
53 .argument::<u32>("N")
54 .fallback(1)
55 .display_fallback();
56
57 // a parser that consumes one argument
58 // you can build the inner parser in one go or as multiple steps giving each step a name
59 // you can also add some static shell completion functionality
60 let file_to_proces = short('f')
61 .long("file")
62 .help("File to process")
63 .argument::<PathBuf>("FILE")
64 .complete_shell(ShellComp::File { mask: Some("*.rs") });
65
66 let files_to_process = file_to_proces.many();
67
68 // packing things in a struct assumes parser for each field is in scope.
69 construct!(Out {
70 debug,
71 verbose,
72 speed,
73 output,
74 nb_cars,
75 files_to_process
76 })
77 .to_options()
78 .descr("This is a description")
79}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 presented to the user
Error: couldn't parse ten: invalid digit found in string
With display_fallback,
debug_fallback, and
format_fallback, you can make it so the default value
is visible in the --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, the fallback value will
not be shown in the --help output; you can change that by using
display_fallback,
debug_fallback, or
format_fallback.
Examples found in repository?
57 pub fn parse_verbosity() -> impl Parser<Verbosity> {
58 verbosity().my_last().fallback(Verbosity::Status)
59 }
60
61 pub fn parse_binary() -> impl Parser<bool> {
62 #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
63 enum Mode {
64 /// Use binary mode
65 #[bpaf(short, long)]
66 Binary,
67 /// Use text mode
68 #[bpaf(short, long)]
69 Text,
70 }
71 mode()
72 .last()
73 .fallback(Mode::Text)
74 .debug_fallback()
75 .map(|mode| mode == Mode::Binary)
76 }More examples
31fn in_file() -> impl Parser<String> {
32 tag::<String>("if=", "FILE", "read from FILE")
33 .fallback(String::from("-"))
34 .display_fallback()
35}
36
37fn out_file() -> impl Parser<String> {
38 tag::<String>("of=", "FILE", "write to FILE")
39 .fallback(String::from("-"))
40 .display_fallback()
41}
42
43fn block_size() -> impl Parser<usize> {
44 // it is possible to parse notation used by dd itself as well,
45 // using usuze only for simplicity
46 tag::<usize>("bs=", "SIZE", "read/write SIZE blocks at once")
47 .fallback(512)
48 .display_fallback()
49}16fn main() {
17 // defining a parser in a usual way
18 let width = short('w').argument::<usize>("WIDTH").fallback(10);
19 let height = short('h').argument::<usize>("HEIGHT").fallback(10);
20 let parser = construct!(Opts { width, height });
21
22 let cmd = literal("cmd").optional().hide();
23 let combined_parser = construct!(cmd, parser).map(|x| x.1);
24
25 let opts = combined_parser.to_options().run();
26
27 println!("{:?}", opts);
28}41pub fn options() -> OptionParser<Options> {
42 let backing = toggle_options("(+|-)backing", "backing", "Set backing status").fallback(false);
43 let xinerama =
44 toggle_options("(+|-)xinerama", "xinerama", "Set Xinerama status").fallback(true);
45 let turbo = short('t')
46 .long("turbo")
47 .help("Engage the turbo mode")
48 .switch();
49 let extensions = extension().many();
50 construct!(Options {
51 turbo,
52 backing,
53 xinerama,
54 extensions,
55 })
56 .to_options()
57}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 presented to the 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, the fallback value will
not be shown in the --help output; you can change that by using
display_fallback,
debug_fallback, or
format_fallback.
Examples found in repository?
48fn parse_manifest_path() -> impl Parser<PathBuf> {
49 long("manifest-path")
50 .help("Path to Cargo.toml")
51 .argument::<PathBuf>("PATH")
52 .complete_shell(ShellComp::File {
53 mask: Some("*.toml"),
54 })
55 .parse(|p| {
56 // cargo-metadata wants to see
57 if p.is_absolute() {
58 Ok(p)
59 } else {
60 std::env::current_dir()
61 .map(|d| d.join(p))
62 .and_then(|full_path| full_path.canonicalize())
63 }
64 })
65 .fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
66}
67
68#[derive(Debug, Clone, Bpaf)]
69/// How to render output
70pub struct Format {
71 /// Print interleaved Rust code
72 pub rust: bool,
73
74 #[bpaf(external(color_detection))]
75 pub color: bool,
76
77 /// include full demangled name instead of just prefix
78 pub full_name: bool,
79}
80
81#[derive(Debug, Clone, Bpaf)]
82/// Pick output type
83///
84/// included help
85///
86///
87/// Extended help
88pub enum Syntax {
89 /// Generate assembly using Intel style
90 Intel,
91 /// Generate assembly using AT&T style
92 Att,
93}
94
95fn color_detection() -> impl Parser<bool> {
96 let yes = long("color")
97 .help("Enable color highlighting")
98 .req_flag(true);
99 let no = long("no-color")
100 .help("Disable color highlighting")
101 .req_flag(false);
102 construct!([yes, no]).fallback_with::<_, Infallible>(|| {
103 // we can call for supports-color crate here
104 Ok(true)
105 })
106}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?
16fn main() {
17 // defining a parser in a usual way
18 let width = short('w').argument::<usize>("WIDTH").fallback(10);
19 let height = short('h').argument::<usize>("HEIGHT").fallback(10);
20 let parser = construct!(Opts { width, height });
21
22 let cmd = literal("cmd").optional().hide();
23 let combined_parser = construct!(cmd, parser).map(|x| x.1);
24
25 let opts = combined_parser.to_options().run();
26
27 println!("{:?}", opts);
28}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?
19fn main() {
20 let width = short('w')
21 .long("width")
22 .help("Width of the rectangle")
23 .argument::<usize>("PX");
24
25 let height = short('h')
26 .long("height")
27 .help("Height of the rectangle")
28 .argument::<usize>("PX");
29
30 let rect = construct!(Rect { width, height })
31 .group_help("Rectangle is defined by width and height in meters")
32 .optional();
33
34 let verbose = short('v')
35 .long("verbose")
36 .help("Print computation steps")
37 .switch();
38
39 let opt = construct!(Out { verbose, rect })
40 .to_options()
41 .descr("This program calculates rectangle's area")
42 .header("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
43 .footer("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
44 .run();
45 println!("{:#?}", opt);
46}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?
55fn opts() -> Opts {
56 let sensor = long("sensor").req_flag(());
57 let device = long("sensor-device")
58 .argument::<String>("DEVICE")
59 .complete(sensor_device_comp);
60 let name = long("sensor-name").argument::<String>("NAME");
61
62 // from_str needs to be replaced with `parse` that can deal with hex digits
63 let bus_id = long("sensor-i2c-bus").argument::<usize>("BUS");
64 let address = long("sensor-i2c-address").argument::<usize>("ADDRESS");
65 let sensors = construct!(Sensor {
66 sensor,
67 device,
68 name,
69 bus_id,
70 address
71 })
72 .adjacent()
73 .many();
74 construct!(Opts { sensors }).to_options().run()
75}More examples
12fn main() {
13 use bpaf::*;
14
15 let a = short('a').long("avocado").help("Use avocado").switch();
16 let b = short('b').long("banana").help("Use banana").switch();
17 let bb = long("bananananana").help("I'm Batman").switch();
18 let c = long("calculator")
19 .help("calculator expression")
20 .argument::<String>("EXPR")
21 .complete(complete_calculator);
22 let parser = construct!(a, b, bb, c)
23 .to_options()
24 .descr("Dynamic autocomplete example")
25 .footer(
26 "\
27 bpaf supports dynamic autocompletion for a few shells, make sure your binary is in $PATH
28 and try using one of those this output should go into a file that depends on your shell:
29 $ csample --bpaf-complete-style-bash
30 $ csample --bpaf-complete-style-zsh
31 $ csample --bpaf-complete-style-fish
32 $ csample --bpaf-complete-style-elvish",
33 );
34
35 println!("{:?}", parser.fallback_to_usage().run());
36}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
48fn parse_manifest_path() -> impl Parser<PathBuf> {
49 long("manifest-path")
50 .help("Path to Cargo.toml")
51 .argument::<PathBuf>("PATH")
52 .complete_shell(ShellComp::File {
53 mask: Some("*.toml"),
54 })
55 .parse(|p| {
56 // cargo-metadata wants to see
57 if p.is_absolute() {
58 Ok(p)
59 } else {
60 std::env::current_dir()
61 .map(|d| d.join(p))
62 .and_then(|full_path| full_path.canonicalize())
63 }
64 })
65 .fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
66}17fn opts() -> OptionParser<Out> {
18 // A flag, true if used in the command line. Can be required, this one is optional
19
20 let debug = short('d') // start with a short name
21 .long("debug") // also add a long name
22 .help("Activate debug mode") // and a help message to use
23 .switch(); // turn this into a switch
24
25 // number of occurrences of the v/verbose flag capped at 3 with an error here but you can also
26 // use `max` inside `map`
27 let verbose = short('v')
28 .long("verbose")
29 .help("Increase the verbosity\n You can specify it up to 3 times\n either as -v -v -v or as -vvv")
30 .req_flag(())
31 .many()
32 .map(|xs| xs.len())
33 .guard(|&x| x <= 3, "It doesn't get any more verbose than this");
34
35 // an argument, parsed and with default value
36 let speed = short('s')
37 .long("speed")
38 .help("Set speed")
39 .argument::<f64>("SPEED") // you can specify a type to parse
40 .fallback(42.0)
41 .display_fallback();
42
43 let output = short('o')
44 .long("output")
45 .help("output file")
46 .argument::<PathBuf>("OUTPUT") // but it's optional when rustc can derive it
47 .complete_shell(ShellComp::File { mask: None });
48
49 // no magical name transmogrifications in combinatoric API,
50 let nb_cars = short('n')
51 .long("nb-cars")
52 .help("Number of items to process")
53 .argument::<u32>("N")
54 .fallback(1)
55 .display_fallback();
56
57 // a parser that consumes one argument
58 // you can build the inner parser in one go or as multiple steps giving each step a name
59 // you can also add some static shell completion functionality
60 let file_to_proces = short('f')
61 .long("file")
62 .help("File to process")
63 .argument::<PathBuf>("FILE")
64 .complete_shell(ShellComp::File { mask: Some("*.rs") });
65
66 let files_to_process = file_to_proces.many();
67
68 // packing things in a struct assumes parser for each field is in scope.
69 construct!(Out {
70 debug,
71 verbose,
72 speed,
73 output,
74 nb_cars,
75 files_to_process
76 })
77 .to_options()
78 .descr("This is a description")
79}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?
52fn main() {
53 let items = &[
54 ("banana", Ty::Bool),
55 ("width", Ty::Number),
56 ("name", Ty::String),
57 ];
58
59 let mut parser = pure(Vec::<(String, Value)>::new()).boxed();
60 for (name, ty) in items {
61 parser = cons(
62 parser,
63 match ty {
64 Ty::Bool => bool(name).boxed(),
65 Ty::Number => number(name).boxed(),
66 Ty::String => string(name).boxed(),
67 },
68 )
69 }
70
71 let options = parser.run();
72 println!("{:?}", options);
73}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
34fn cons<T>(acc: Box<dyn Parser<Vec<T>>>, cur: Box<dyn Parser<T>>) -> Box<dyn Parser<Vec<T>>>
35where
36 T: 'static,
37{
38 construct!(acc, cur)
39 .map(|(mut acc, cur)| {
40 acc.push(cur);
41 acc
42 })
43 .boxed()
44}
45
46enum Ty {
47 Bool,
48 Number,
49 String,
50}
51
52fn main() {
53 let items = &[
54 ("banana", Ty::Bool),
55 ("width", Ty::Number),
56 ("name", Ty::String),
57 ];
58
59 let mut parser = pure(Vec::<(String, Value)>::new()).boxed();
60 for (name, ty) in items {
61 parser = cons(
62 parser,
63 match ty {
64 Ty::Bool => bool(name).boxed(),
65 Ty::Number => number(name).boxed(),
66 Ty::String => string(name).boxed(),
67 },
68 )
69 }
70
71 let options = parser.run();
72 println!("{:?}", options);
73}48fn choose<T>(xs: Vec<Box<dyn Parser<T> + 'static>>) -> Box<dyn Parser<T>>
49where
50 T: 'static,
51{
52 let mut items = xs.into_iter();
53
54 let mut res = items.next().unwrap();
55 for next in items {
56 res = construct!([res, next]).boxed()
57 }
58 res
59}
60
61fn make_parser(item: &Cog) -> Box<dyn Parser<&'static str>> {
62 match item {
63 Cog::Command {
64 help,
65 name,
66 operation,
67 } => Box::new(pure(*operation).to_options().descr(*help).command(name)),
68 Cog::Group { name, help, nested } => {
69 let nested = nested.iter().map(make_parser).collect::<Vec<_>>();
70 let inner = choose(nested);
71 inner.to_options().descr(*help).command(name).boxed()
72 }
73 }
74}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.