Trait bpaf::Parser

source ·
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§

source

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 ...

$ app --help

Usage: app [--argument=ARG]... [--switch]...

Available options:
--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

$ app --argument 10 --argument 20
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.

$ app
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

$ app --switch --switch
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?
examples/shared_args.rs (line 25)
24
25
26
fn shared() -> impl Parser<Vec<String>> {
    positional("ARG").many()
}
More examples
Hide additional examples
examples/at_least_two.rs (line 10)
7
8
9
10
11
12
13
14
15
16
fn main() {
    let opt = short('f')
        .req_flag(())
        .many()
        .guard(|x| x.len() >= 2, "at least two arguments are required")
        .to_options()
        .run();

    println!("{:?}", opt);
}
examples/ex_positional.rs (line 17)
12
13
14
15
16
17
18
19
20
21
fn main() {
    let value = long("value")
        .help("Mysterious value")
        .argument::<u32>("VAL")
        .fallback(42);
    let files = positional::<PathBuf>("FILE").many();
    let opts = construct!(Options { value, files }).to_options().run();

    println!("{:#?}", opts);
}
examples/top_to_bottom.rs (line 45)
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
fn verbose() -> impl Parser<usize> {
    short('v')
        .long("verbose")
        .help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this")
}

// an argument, parsed and with default value
fn speed() -> impl Parser<f64> {
    short('s')
        .long("speed")
        .help("Set speed")
        .argument::<f64>("SPEED")
        .fallback(42.0)
}

fn output() -> impl Parser<PathBuf> {
    short('o')
        .long("output")
        .help("output file")
        .argument::<PathBuf>("OUTPUT")
}

// no magical name transmogrifications.
fn nb_cars() -> impl Parser<u32> {
    short('n').long("nb-cars").argument::<u32>("N")
}

fn files_to_process() -> impl Parser<Vec<PathBuf>> {
    short('f')
        .long("file")
        .help("File to process")
        .argument::<PathBuf>("FILE")
        .many()
}
examples/many_comma_separated_args.rs (line 15)
6
7
8
9
10
11
12
13
14
15
16
17
fn args() -> impl Parser<Vec<u16>> {
    long("ports")
        .help("Comma separated list of ports")
        .argument::<String>("PORTS")
        .parse(|s| {
            s.split(',')
                .map(u16::from_str)
                .collect::<Result<Vec<_>, _>>()
        })
        .many()
        .map(|nested| nested.into_iter().flatten().collect())
}
examples/derive.rs (line 33)
27
28
29
30
31
32
33
34
35
36
fn verbose() -> impl Parser<usize> {
    // number of occurrences of the v/verbose flag capped at 3
    short('v')
        .long("verbose")
        .help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this")
}
source

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 ...

$ app --help

Usage: app --argument=ARG... [--switch]...

Available options:
--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

$ app --argument 10 --argument 20 --argument 20
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.

$ app
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

$ app --switch --switch
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.

source

fn some(self, message: &'static str) -> ParseSome<Self>
where Self: Sized + Parser<T>,

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 ...

$ app --help

Usage: app --argument=ARG... --switch...

Available options:
--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

$ app --argument 10 --argument 20 --switch
Options { argument: [10, 20], switches: [true] }

With not enough parameters to satisfy both parsers at least once - it fails

$ app
Error: want at least one argument

both parsers need to succeed to create a struct

$ app --argument 10
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

$ app --switch --argument 10
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

Examples found in repository?
examples/coreutils.rs (line 34)
32
33
34
35
36
37
        fn my_last(self) -> ParseLast<T> {
            let p = self
                .some("need to specify at least once")
                .map(|mut xs| xs.pop().unwrap());
            ParseLast { inner: p.boxed() }
        }
source

fn optional(self) -> ParseOptional<Self>
where Self: Sized + Parser<T>,

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 []

$ app --help

Usage: app [--version=VERS] [--feature=FEAT]

Available options:
--version=VERS
--feature=FEAT
-h, --help
Prints help information

Missing arguments are turned into None

$ app
Options { version: None, feature: None }

Present values are Some

$ app --version 10
Options { version: Some(10), feature: None }

As usual you can specify both

$ app --version 10 --feature feat
Options { version: Some(10), feature: Some("feat") }
Examples found in repository?
examples/find.rs (line 38)
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
fn user() -> impl Parser<Option<String>> {
    // match only literal "-user"
    let tag = literal("-user").anywhere();
    let value = positional("USER").help("User name");
    construct!(tag, value)
        .adjacent()
        .map(|pair| pair.1)
        .optional()
}

// parsers -exec xxx yyy zzz ;
fn exec() -> impl Parser<Option<Vec<OsString>>> {
    let tag = literal("-exec")
        .help("for every file find finds execute a separate shell command")
        .anywhere();

    let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
        .help("command with its arguments, find will replace {} with a file name")
        .many();

    let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
        .help("anything after literal \";\" will be considered a regular option again");

    construct!(tag, item, endtag)
        .adjacent()
        .map(|triple| triple.1)
        .optional()
}

/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
fn perm() -> impl Parser<Option<Perm>> {
    fn parse_mode(input: &str) -> Result<Perms, String> {
        let mut perms = Perms::default();
        for c in input.chars() {
            match c {
                'r' => perms.read = true,
                'w' => perms.write = true,
                'x' => perms.exec = true,
                _ => return Err(format!("{} is not a valid permission string", input)),
            }
        }
        Ok(perms)
    }

    let tag = literal("-mode").anywhere();

    // `any` here is used to parse an arbitrary string that can also start with dash (-)
    // regular positional parser won't work here
    let mode = any("MODE", Some)
        .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
        .parse::<_, _, String>(|s: String| {
            if let Some(m) = s.strip_prefix('-') {
                Ok(Perm::All(parse_mode(m)?))
            } else if let Some(m) = s.strip_prefix('/') {
                Ok(Perm::Any(parse_mode(m)?))
            } else {
                Ok(Perm::Exact(parse_mode(&s)?))
            }
        });

    construct!(tag, mode)
        .adjacent()
        .map(|pair| pair.1)
        .optional()
}
More examples
Hide additional examples
examples/numeric_prefix.rs (line 24)
21
22
23
24
25
26
27
28
29
pub fn options() -> OptionParser<Options> {
    let prefix = positional::<usize>("PREFIX")
        .help("Optional numeric command prefix")
        .optional()
        .catch();
    let command = positional::<String>("COMMAND").help("Required command name");

    construct!(Options { prefix, command }).to_options()
}
examples/cargo-cmd.rs (line 22)
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
    // defining a parser in a usual way
    let width = short('w').argument::<usize>("WIDTH").fallback(10);
    let height = short('h').argument::<usize>("HEIGHT").fallback(10);
    let parser = construct!(Opts { width, height });

    let cmd = literal("cmd").optional().hide();
    let combined_parser = construct!(cmd, parser).map(|x| x.1);

    let opts = combined_parser.to_options().run();

    println!("{:?}", opts);
}
examples/enum_tuple.rs (line 20)
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
fn main() {
    let bar = short('b')
        .long("bar")
        .help("some bar command")
        .argument::<String>("BAR")
        .optional();

    let bar_cmd = construct!(Foo { bar })
        .to_options()
        .descr("This command will try to do foo given a bar argument");

    let opt = bar_cmd
        .command("foo")
        .help("command for doing foo")
        .map(Command::Foo)
        .to_options()
        .run();

    println!("{:#?}", opt);
}
examples/derive_commands.rs (line 41)
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
fn feature_if() -> impl Parser<Option<String>> {
    // here feature starts as any string on a command line that does not start with a dash
    positional::<String>("FEATURE")
        // guard restricts it such that it can't be a valid version
        .guard(move |s| !is_version(s), "")
        // last two steps describe what to do with strings in this position but are actually
        // versions.
        // optional allows parser to represent an ignored value with None
        .optional()
        // and catch lets optional to handle parse failures coming from guard
        .catch()
}

fn version_if() -> impl Parser<Option<String>> {
    positional::<String>("VERSION")
        .guard(move |s| is_version(s), "")
        .optional()
        .catch()
}
examples/cat.rs (line 18)
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
fn main() {
    let file = positional::<OsString>("FILE")
        .help("File name to concatenate, with no FILE or when FILE is -, read standard input")
        .optional()
        .parse::<_, Box<dyn Read>, std::io::Error>(|path| {
            Ok(if let Some(path) = path {
                if path == "-" {
                    Box::new(stdin())
                } else {
                    Box::new(File::open(path)?)
                }
            } else {
                Box::new(stdin())
            })
        })
        .to_options()
        .descr("Concatenate a file to standard output")
        .run();

    let reader = BufReader::new(file);

    for line in reader.lines() {
        println!("{}", line.unwrap());
    }
}
source

fn count(self) -> ParseCount<Self, T>
where Self: Sized + Parser<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

$ app --help

Usage: app [-v]...

Available options:
-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

$ app
Options { verbosity: 0 }

If it was specified - count tracks it a discards parsed values

$ app -vvv
Options { verbosity: 3 }
$ app --verbose --verbose
Options { verbosity: 2 }
Examples found in repository?
examples/env_logger.rs (line 32)
28
29
30
31
32
33
34
35
36
37
fn verbose() -> impl Parser<LevelFilter> {
    short('v')
        .help("Verbosity level, use multiple times for more verbosity")
        .req_flag(())
        .count()
        .map(|l| {
            use LevelFilter::*;
            [Off, Error, Warn, Info, Debug, Trace][l.max(5)]
        })
}
More examples
Hide additional examples
examples/verbose.rs (line 23)
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
fn main() {
    // program takes one or more -v or --verbose flags, more flags = higher verbosity.
    // parser handles number and produces a single flag.
    //
    // let's create it without using any single purpose magical functions

    // Let's staty by creating a simple parser that handles a single -v / --verbose
    // and fails otherwise;
    let verbose = short('v').long("verbose").req_flag(());

    // Try to apply the inner parser as many times as it succeeds, return the number
    let verbose = verbose.count();

    // And add a simple sanity checker.
    // By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
    let verbose = verbose.guard(|&x| x <= 3, "it doesn't get any more verbose than 3");

    // program takes --trimor --no-trimflag, but not both at once. If none is given -
    // fallback value is to disable trimming. Trim enum is set accordingly

    // this flag succeeds iff --no-trim is given and produces Trim::Off
    let trim_off = long("no-trim").req_flag(Trim::Off);

    // this flag handles two remaining cases: --trim is given (Trim::On) an fallback (Trim::Off)
    let trim_on = long("trim").flag(Trim::On, Trim::Off);

    // combination of previous two.
    // if trim_off succeeds - trim_on never runs, otherwise trim_on tries to handle the remaining
    // case before falling back to Trim:Off.
    // If both --trim and --no-trim are given trim_off succeeds, trim_off never runs and --trim
    // remains unused - parser fails
    let trim = construct!([trim_off, trim_on]);

    let parser = construct!(verbose, trim);

    let opt = parser.to_options().run();
    println!("{:#?}", opt);
}
source

fn last(self) -> ParseLast<Self>
where Self: Sized + Parser<T>,

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

$ app --help

Usage: app (--intel | --att | --llvm)... [--detailed | --minimal]...

Available options:
--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

$ app --intel
Options { style: Intel, report: Undecided }
$ app --intel --att
Options { style: Att, report: Undecided }
$ app --intel --att --intel
Options { style: Intel, report: Undecided }

same goes with report

$ app --intel --detailed
Options { style: Intel, report: Detailed }
$ app --att --detailed --minimal
Options { style: Att, report: Minimal }
Examples found in repository?
examples/coreutils.rs (line 72)
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    pub fn parse_binary() -> impl Parser<bool> {
        #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
        enum Mode {
            /// Use binary mode
            #[bpaf(short, long)]
            Binary,
            /// Use text mode
            #[bpaf(short, long)]
            Text,
        }
        mode()
            .last()
            .fallback(Mode::Text)
            .debug_fallback()
            .map(|mode| mode == Mode::Binary)
    }
source

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,

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

$ app --help

Usage: app --number=N

Available options:
--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

$ app --number 10
Options { number: 20 }

But if function inside the parser fails - user will get the error back unless it’s handled in some other way

$ app --number ten
Error: couldn't parse ten: invalid digit found in string
Examples found in repository?
examples/many_comma_separated_args.rs (lines 10-14)
6
7
8
9
10
11
12
13
14
15
16
17
fn args() -> impl Parser<Vec<u16>> {
    long("ports")
        .help("Comma separated list of ports")
        .argument::<String>("PORTS")
        .parse(|s| {
            s.split(',')
                .map(u16::from_str)
                .collect::<Result<Vec<_>, _>>()
        })
        .many()
        .map(|nested| nested.into_iter().flatten().collect())
}
More examples
Hide additional examples
examples/dd.rs (line 28)
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn tag<T>(name: &'static str, meta: &str, help: &'static str) -> impl Parser<T>
where
    T: FromStr,
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    // it is possible to parse OsString here and strip the prefix with
    // `os_str_bytes` or a similar crate
    any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
        // this defines custom metavar for the help message
        .metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
        .help(help)
        .anywhere()
        .parse(|s| s.parse())
}
examples/derive_show_asm.rs (lines 55-64)
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
fn parse_manifest_path() -> impl Parser<PathBuf> {
    long("manifest-path")
        .help("Path to Cargo.toml")
        .argument::<PathBuf>("PATH")
        .complete_shell(ShellComp::File {
            mask: Some("*.toml"),
        })
        .parse(|p| {
            // cargo-metadata wants to see
            if p.is_absolute() {
                Ok(p)
            } else {
                std::env::current_dir()
                    .map(|d| d.join(p))
                    .and_then(|full_path| full_path.canonicalize())
            }
        })
        .fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
examples/cat.rs (lines 19-29)
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
fn main() {
    let file = positional::<OsString>("FILE")
        .help("File name to concatenate, with no FILE or when FILE is -, read standard input")
        .optional()
        .parse::<_, Box<dyn Read>, std::io::Error>(|path| {
            Ok(if let Some(path) = path {
                if path == "-" {
                    Box::new(stdin())
                } else {
                    Box::new(File::open(path)?)
                }
            } else {
                Box::new(stdin())
            })
        })
        .to_options()
        .descr("Concatenate a file to standard output")
        .run();

    let reader = BufReader::new(file);

    for line in reader.lines() {
        println!("{}", line.unwrap());
    }
}
examples/find.rs (lines 81-89)
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
fn perm() -> impl Parser<Option<Perm>> {
    fn parse_mode(input: &str) -> Result<Perms, String> {
        let mut perms = Perms::default();
        for c in input.chars() {
            match c {
                'r' => perms.read = true,
                'w' => perms.write = true,
                'x' => perms.exec = true,
                _ => return Err(format!("{} is not a valid permission string", input)),
            }
        }
        Ok(perms)
    }

    let tag = literal("-mode").anywhere();

    // `any` here is used to parse an arbitrary string that can also start with dash (-)
    // regular positional parser won't work here
    let mode = any("MODE", Some)
        .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
        .parse::<_, _, String>(|s: String| {
            if let Some(m) = s.strip_prefix('-') {
                Ok(Perm::All(parse_mode(m)?))
            } else if let Some(m) = s.strip_prefix('/') {
                Ok(Perm::Any(parse_mode(m)?))
            } else {
                Ok(Perm::Exact(parse_mode(&s)?))
            }
        });

    construct!(tag, mode)
        .adjacent()
        .map(|pair| pair.1)
        .optional()
}
examples/confusing.rs (lines 58-68)
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
fn main() {
    let token = long("token")
        .help("Token used for complex commands")
        .argument::<String>("TOKEN")
        .optional();

    // start with defining 3 commands: simple, complex1 and complex2
    let simple_parser = pure(PreCommand::Simple).to_options();
    let simple = simple_parser.command("simple");

    let complex1_parser = positional::<i32>("ARG");
    let complex1 = construct!(PreCommand::Complex1(complex1_parser))
        .to_options()
        .descr("This is complex command 1")
        .command("complex1");

    let complex2_parser = positional::<i16>("ARG");

    let complex2 = construct!(PreCommand::Complex2(complex2_parser))
        .to_options()
        .descr("This is complex command 2")
        .command("complex2");

    // compose then to accept any of those
    let preparser = construct!([simple, complex1, complex2]);

    // make a parser that accepts optional token and one of incomplete commands
    // then create complete command or fail
    let parser = construct!(token, preparser).parse(|(token, cmd)| match cmd {
        PreCommand::Simple => Ok(Command::Simple),
        PreCommand::Complex1(a) => match token {
            Some(token) => Ok(Command::Complex1(token, a)),
            None => Err("You must specify token to use with --token"),
        },
        PreCommand::Complex2(a) => match token {
            Some(token) => Ok(Command::Complex2(token, a)),
            None => Err("You must specify token to use with --token"),
        },
    });

    let cmd = parser.to_options().run();
    println!("{:?}", cmd);
}
source

fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
where Self: Sized + Parser<T>, F: Fn(T) -> R + 'static,

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

$ app --number 10
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

$ app --number ten
Error: couldn't parse ten: invalid digit found in string
Examples found in repository?
examples/dynamic.rs (line 16)
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
fn number(name: &'static str) -> impl Parser<(String, Value)> {
    let label = name.to_string();
    long(name)
        .argument::<usize>("NUM")
        .map(move |n| (label.clone(), Value::Number(n)))
}

fn bool(name: &'static str) -> impl Parser<(String, Value)> {
    let label = name.to_string();
    long(name)
        .switch()
        .map(move |n| (label.clone(), Value::Bool(n)))
}

fn string(name: &'static str) -> impl Parser<(String, Value)> {
    let label = name.to_string();
    long(name)
        .help("this can use a help message")
        .argument::<String>("NUM")
        .map(move |n| (label.clone(), Value::String(n)))
}

fn cons<T>(acc: Box<dyn Parser<Vec<T>>>, cur: Box<dyn Parser<T>>) -> Box<dyn Parser<Vec<T>>>
where
    T: 'static,
{
    construct!(acc, cur)
        .map(|(mut acc, cur)| {
            acc.push(cur);
            acc
        })
        .boxed()
}
More examples
Hide additional examples
examples/coreutils.rs (line 35)
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
        fn my_last(self) -> ParseLast<T> {
            let p = self
                .some("need to specify at least once")
                .map(|mut xs| xs.pop().unwrap());
            ParseLast { inner: p.boxed() }
        }
    }
}
pub mod shared {
    use super::boilerplate::*;
    use bpaf::*;

    #[derive(Debug, Clone, Copy, Bpaf)]
    pub enum Verbosity {
        /// Display warnings
        #[bpaf(short, long)]
        Warn,
        /// Display only diagnostics
        #[bpaf(short, long)]
        Quiet,
        /// Display status only
        #[bpaf(short, long)]
        Status,
    }

    pub fn parse_verbosity() -> impl Parser<Verbosity> {
        verbosity().my_last().fallback(Verbosity::Status)
    }

    pub fn parse_binary() -> impl Parser<bool> {
        #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
        enum Mode {
            /// Use binary mode
            #[bpaf(short, long)]
            Binary,
            /// Use text mode
            #[bpaf(short, long)]
            Text,
        }
        mode()
            .last()
            .fallback(Mode::Text)
            .debug_fallback()
            .map(|mode| mode == Mode::Binary)
    }
}

mod arch {
    use bpaf::*;

    #[derive(Debug, Clone, Bpaf)]
    #[bpaf(command)]
    /// Print machine architecture.
    pub struct Arch;
}

mod b2sum {
    use super::shared::*;
    use bpaf::*;
    use std::path::PathBuf;

    #[derive(Debug, Clone, Bpaf)]
    #[bpaf(command("b2sum"))]
    /// Print or check BLAKE2 (512-bit) checksums.
    pub struct B2Sum {
        #[bpaf(external(parse_binary))]
        pub binary: bool,

        /// read BLAKE2 sums from the FILEs and check them
        #[bpaf(short, long)]
        pub check: bool,

        /// create a BSD-style checksum
        pub tag: bool,

        #[bpaf(external(parse_verbosity))]
        pub check_output: Verbosity,

        /// exit non-zero for improperly formatted checksum lines
        pub strict: bool,

        #[bpaf(positional("FILE"))]
        pub files: Vec<PathBuf>,
    }
}

mod base32 {
    use bpaf::*;
    use std::path::PathBuf;

    fn non_zero(val: Option<usize>) -> Option<usize> {
        val.and_then(|v| (v > 0).then_some(v))
    }

    #[derive(Debug, Clone, Bpaf)]
    #[bpaf(command)]
    /// Base32 encode or decode FILE, or standard input, to standard output.
    pub struct Base32 {
        /// decode data
        #[bpaf(long, short)]
        pub decode: bool,
        #[bpaf(long, short)]
        /// when decoding, ignore non-alphabet characters
        pub ignore_garbage: bool,

        #[bpaf(
            long,
            short,
            argument("COLS"),
            optional,
            map(non_zero),
            fallback(Some(76)),
            debug_fallback
        )]
        /// wrap encoded lines after COLS character
        ///  Use 0 to disable line wrapping
        pub wrap: Option<usize>,

        #[bpaf(positional("FILE"))]
        /// With no FILE, or when FILE is -, read standard input.
        pub file: Option<PathBuf>,
    }
}

mod basename {
    use bpaf::*;

    #[derive(Debug, Clone, Bpaf)]
    #[bpaf(command)]
    pub struct Basename {
        /// support multiple arguments and treat each as a NAME
        #[bpaf(short('a'), long)]
        pub multiple: bool,

        /// remove a trailing SUFFIX; implies -a
        #[bpaf(short, long, argument("SUFFIX"), optional)]
        pub suffix: Option<String>,

        ///  end each output line with NUL, not newline
        #[bpaf(short, long)]
        pub zero: bool,

        /// Print NAME with any leading directory components removed.
        #[bpaf(positional("NAME"), many)]
        pub names: Vec<String>,
    }

    pub fn parse_basename() -> impl Parser<Basename> {
        basename().map(|mut b| {
            if b.suffix.is_some() {
                b.multiple = true;
            }
            b
        })
    }
}

mod cat {
    use std::path::PathBuf;

    use bpaf::*;

    #[derive(Debug, Clone, Bpaf)]
    struct Extra {
        #[bpaf(short('A'), long)]
        /// equivalent to -vET
        show_all: bool,

        #[bpaf(short('b'), long)]
        /// number nonempty output lines, overrides -n
        number_nonblank: bool,

        #[bpaf(short('e'))]
        /// equivalent to -vE
        show_non_printing_ends: bool,
    }

    #[derive(Debug, Clone, Bpaf)]
    #[bpaf(fallback(NumberingMode::None))]
    pub enum NumberingMode {
        #[bpaf(hide)]
        /// Don't number lines, default behavior
        None,

        /// Number nonempty output lines, overrides -n
        #[bpaf(short('b'), long("number-nonblank"))]
        NonEmpty,

        /// Number all output lines
        #[bpaf(short('n'), long("number"))]
        All,
    }

    #[derive(Debug, Clone, Bpaf)]
    pub struct Cat {
        #[bpaf(short('T'), long)]
        /// display TAB characters as ^I
        pub show_tabs: bool,

        /// display $ at end of each line
        #[bpaf(short('E'))]
        pub show_ends: bool,

        /// use ^ and M- notation, except for LFD and TAB
        #[bpaf(short('n'), long("number"))]
        show_nonprinting: bool,

        #[bpaf(external(numbering_mode))]
        pub number: NumberingMode,

        #[bpaf(short('s'), long)]
        /// suppress repeated empty output lines
        pub squeeze_blank: bool,

        #[bpaf(positional("FILE"), many)]
        /// Concatenate FILE(s) to standard output.
        pub files: Vec<PathBuf>,
    }

    pub fn parse_cat() -> impl Parser<Cat> {
        construct!(extra(), cat())
            .map(|(extra, mut cat)| {
                if extra.show_all {
                    cat.show_tabs = true;
                    cat.show_ends = true;
                    cat.show_nonprinting = true;
                }
                if extra.show_non_printing_ends {
                    cat.show_nonprinting = true;
                    cat.show_ends = true;
                }
                if extra.number_nonblank {
                    cat.number = NumberingMode::NonEmpty;
                }
                cat
            })
            .to_options()
            .command("cat")
    }
examples/find.rs (line 37)
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
fn user() -> impl Parser<Option<String>> {
    // match only literal "-user"
    let tag = literal("-user").anywhere();
    let value = positional("USER").help("User name");
    construct!(tag, value)
        .adjacent()
        .map(|pair| pair.1)
        .optional()
}

// parsers -exec xxx yyy zzz ;
fn exec() -> impl Parser<Option<Vec<OsString>>> {
    let tag = literal("-exec")
        .help("for every file find finds execute a separate shell command")
        .anywhere();

    let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
        .help("command with its arguments, find will replace {} with a file name")
        .many();

    let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
        .help("anything after literal \";\" will be considered a regular option again");

    construct!(tag, item, endtag)
        .adjacent()
        .map(|triple| triple.1)
        .optional()
}

/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
fn perm() -> impl Parser<Option<Perm>> {
    fn parse_mode(input: &str) -> Result<Perms, String> {
        let mut perms = Perms::default();
        for c in input.chars() {
            match c {
                'r' => perms.read = true,
                'w' => perms.write = true,
                'x' => perms.exec = true,
                _ => return Err(format!("{} is not a valid permission string", input)),
            }
        }
        Ok(perms)
    }

    let tag = literal("-mode").anywhere();

    // `any` here is used to parse an arbitrary string that can also start with dash (-)
    // regular positional parser won't work here
    let mode = any("MODE", Some)
        .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
        .parse::<_, _, String>(|s: String| {
            if let Some(m) = s.strip_prefix('-') {
                Ok(Perm::All(parse_mode(m)?))
            } else if let Some(m) = s.strip_prefix('/') {
                Ok(Perm::Any(parse_mode(m)?))
            } else {
                Ok(Perm::Exact(parse_mode(&s)?))
            }
        });

    construct!(tag, mode)
        .adjacent()
        .map(|pair| pair.1)
        .optional()
}
examples/env_logger.rs (lines 33-36)
28
29
30
31
32
33
34
35
36
37
fn verbose() -> impl Parser<LevelFilter> {
    short('v')
        .help("Verbosity level, use multiple times for more verbosity")
        .req_flag(())
        .count()
        .map(|l| {
            use LevelFilter::*;
            [Off, Error, Warn, Info, Debug, Trace][l.max(5)]
        })
}
examples/top_to_bottom.rs (line 46)
40
41
42
43
44
45
46
47
48
fn verbose() -> impl Parser<usize> {
    short('v')
        .long("verbose")
        .help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this")
}
examples/shared_args.rs (line 29)
28
29
30
31
32
33
34
fn parse_command() -> impl Parser<(Command, Vec<String>)> {
    let action = action().map(Command::Action);
    let action = construct!(action, shared()).to_options().command("action");
    let build = build().map(Command::Build);
    let build = construct!(build, shared()).to_options().command("build");
    construct!([action, build])
}
source

fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
where Self: Sized + Parser<T>, F: Fn(&T) -> bool,

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

$ app --help

Usage: app --number=N

Available options:
--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

$ app --number 5
Options { number: 5 }

And fails with the error message on higher values:

$ app --number 11
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

$ app --number ten
Error: couldn't parse ten: invalid digit found in string
Examples found in repository?
examples/at_least_two.rs (line 11)
7
8
9
10
11
12
13
14
15
16
fn main() {
    let opt = short('f')
        .req_flag(())
        .many()
        .guard(|x| x.len() >= 2, "at least two arguments are required")
        .to_options()
        .run();

    println!("{:?}", opt);
}
More examples
Hide additional examples
examples/top_to_bottom.rs (line 47)
40
41
42
43
44
45
46
47
48
fn verbose() -> impl Parser<usize> {
    short('v')
        .long("verbose")
        .help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this")
}
examples/derive.rs (line 35)
27
28
29
30
31
32
33
34
35
36
fn verbose() -> impl Parser<usize> {
    // number of occurrences of the v/verbose flag capped at 3
    short('v')
        .long("verbose")
        .help("Increase the verbosity\nYou can specify it up to 3 times\neither as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this")
}
examples/derive_commands.rs (line 37)
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
fn feature_if() -> impl Parser<Option<String>> {
    // here feature starts as any string on a command line that does not start with a dash
    positional::<String>("FEATURE")
        // guard restricts it such that it can't be a valid version
        .guard(move |s| !is_version(s), "")
        // last two steps describe what to do with strings in this position but are actually
        // versions.
        // optional allows parser to represent an ignored value with None
        .optional()
        // and catch lets optional to handle parse failures coming from guard
        .catch()
}

fn version_if() -> impl Parser<Option<String>> {
    positional::<String>("VERSION")
        .guard(move |s| is_version(s), "")
        .optional()
        .catch()
}
examples/verbose.rs (line 27)
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
fn main() {
    // program takes one or more -v or --verbose flags, more flags = higher verbosity.
    // parser handles number and produces a single flag.
    //
    // let's create it without using any single purpose magical functions

    // Let's staty by creating a simple parser that handles a single -v / --verbose
    // and fails otherwise;
    let verbose = short('v').long("verbose").req_flag(());

    // Try to apply the inner parser as many times as it succeeds, return the number
    let verbose = verbose.count();

    // And add a simple sanity checker.
    // By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
    let verbose = verbose.guard(|&x| x <= 3, "it doesn't get any more verbose than 3");

    // program takes --trimor --no-trimflag, but not both at once. If none is given -
    // fallback value is to disable trimming. Trim enum is set accordingly

    // this flag succeeds iff --no-trim is given and produces Trim::Off
    let trim_off = long("no-trim").req_flag(Trim::Off);

    // this flag handles two remaining cases: --trim is given (Trim::On) an fallback (Trim::Off)
    let trim_on = long("trim").flag(Trim::On, Trim::Off);

    // combination of previous two.
    // if trim_off succeeds - trim_on never runs, otherwise trim_on tries to handle the remaining
    // case before falling back to Trim:Off.
    // If both --trim and --no-trim are given trim_off succeeds, trim_off never runs and --trim
    // remains unused - parser fails
    let trim = construct!([trim_off, trim_on]);

    let parser = construct!(verbose, trim);

    let opt = parser.to_options().run();
    println!("{:#?}", opt);
}
examples/basic.rs (line 33)
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
fn opts() -> OptionParser<Out> {
    // A flag, true if used in the command line. Can be required, this one is optional

    let debug = short('d') // start with a short name
        .long("debug") // also add a long name
        .help("Activate debug mode") // and a help message to use
        .switch(); // turn this into a switch

    // number of occurrences of the v/verbose flag capped at 3 with an error here but you can also
    // use `max` inside `map`
    let verbose = short('v')
        .long("verbose")
        .help("Increase the verbosity\n You can specify it up to 3 times\n either as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this");

    // an argument, parsed and with default value
    let speed = short('s')
        .long("speed")
        .help("Set speed")
        .argument::<f64>("SPEED") // you can specify a type to parse
        .fallback(42.0)
        .display_fallback();

    let output = short('o')
        .long("output")
        .help("output file")
        .argument::<PathBuf>("OUTPUT") // but it's optional when rustc can derive it
        .complete_shell(ShellComp::File { mask: None });

    // no magical name transmogrifications in combinatoric API,
    let nb_cars = short('n')
        .long("nb-cars")
        .help("Number of items to process")
        .argument::<u32>("N")
        .fallback(1)
        .display_fallback();

    // a parser that consumes one argument
    // you can build the inner parser in one go or as multiple steps giving each step a name
    // you can also add some static shell completion functionality
    let file_to_proces = short('f')
        .long("file")
        .help("File to process")
        .argument::<PathBuf>("FILE")
        .complete_shell(ShellComp::File { mask: Some("*.rs") });

    let files_to_process = file_to_proces.many();

    // packing things in a struct assumes parser for each field is in scope.
    construct!(Out {
        debug,
        verbose,
        speed,
        output,
        nb_cars,
        files_to_process
    })
    .to_options()
    .descr("This is a description")
}
source

fn fallback(self, value: T) -> ParseFallback<Self, T>
where Self: Sized + Parser<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

$ app
Options { jobs: 42 }

If value is present - fallback value is ignored

$ app --jobs 10
Options { jobs: 10 }

Parsing errors are preserved and preserved to user

$ app --jobs ten
Error: couldn't parse ten: invalid digit found in string

With display_fallback and debug_fallback you can make it so default value is visible in --help output

$ app --help

Usage: app [--jobs=JOBS]

Available options:
--jobs=JOBS
Number of jobs
[default: 42]
-h, --help
Prints help information

§See also

fallback_with would allow to try to fallback to a value that comes from a failing computation such as reading a file. By default fallback value will not be shown in the --help output, you can change that by using display_fallback and debug_fallback.

Examples found in repository?
examples/coreutils.rs (line 58)
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    pub fn parse_verbosity() -> impl Parser<Verbosity> {
        verbosity().my_last().fallback(Verbosity::Status)
    }

    pub fn parse_binary() -> impl Parser<bool> {
        #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
        enum Mode {
            /// Use binary mode
            #[bpaf(short, long)]
            Binary,
            /// Use text mode
            #[bpaf(short, long)]
            Text,
        }
        mode()
            .last()
            .fallback(Mode::Text)
            .debug_fallback()
            .map(|mode| mode == Mode::Binary)
    }
More examples
Hide additional examples
examples/dd.rs (line 33)
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
fn in_file() -> impl Parser<String> {
    tag::<String>("if=", "FILE", "read from FILE")
        .fallback(String::from("-"))
        .display_fallback()
}

fn out_file() -> impl Parser<String> {
    tag::<String>("of=", "FILE", "write to FILE")
        .fallback(String::from("-"))
        .display_fallback()
}

fn block_size() -> impl Parser<usize> {
    // it is possible to parse notation used by dd itself as well,
    // using usuze only for simplicity
    tag::<usize>("bs=", "SIZE", "read/write SIZE blocks at once")
        .fallback(512)
        .display_fallback()
}
examples/top_to_bottom.rs (line 56)
51
52
53
54
55
56
57
fn speed() -> impl Parser<f64> {
    short('s')
        .long("speed")
        .help("Set speed")
        .argument::<f64>("SPEED")
        .fallback(42.0)
}
examples/ex_positional.rs (line 16)
12
13
14
15
16
17
18
19
20
21
fn main() {
    let value = long("value")
        .help("Mysterious value")
        .argument::<u32>("VAL")
        .fallback(42);
    let files = positional::<PathBuf>("FILE").many();
    let opts = construct!(Options { value, files }).to_options().run();

    println!("{:#?}", opts);
}
examples/cargo-cmd.rs (line 18)
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
    // defining a parser in a usual way
    let width = short('w').argument::<usize>("WIDTH").fallback(10);
    let height = short('h').argument::<usize>("HEIGHT").fallback(10);
    let parser = construct!(Opts { width, height });

    let cmd = literal("cmd").optional().hide();
    let combined_parser = construct!(cmd, parser).map(|x| x.1);

    let opts = combined_parser.to_options().run();

    println!("{:?}", opts);
}
examples/xorg.rs (line 42)
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
pub fn options() -> OptionParser<Options> {
    let backing = toggle_options("(+|-)backing", "backing", "Set backing status").fallback(false);
    let xinerama =
        toggle_options("(+|-)xinerama", "xinerama", "Set Xinerama status").fallback(true);
    let turbo = short('t')
        .long("turbo")
        .help("Engage the turbo mode")
        .switch();
    let extensions = extension().many();
    construct!(Options {
        turbo,
        backing,
        xinerama,
        extensions,
    })
    .to_options()
}
source

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,

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

$ app
Options { version: 42 }

If value is present - fallback value is ignored

$ app --version 10
Options { version: 10 }

Parsing errors are preserved and preserved to user

$ app --version ten
Error: couldn't parse ten: invalid digit found in string

bpaf encases parsers with fallback value of some sort in usage with []

$ app --help

Usage: app [--version=VERS]

Available options:
--version=VERS
Specify protocol version
[default: 42]
-h, --help
Prints help information

§See also

fallback implements similar logic expect that failures aren’t expected. By default fallback value will not be shown in the --help output, you can change that by using display_fallback and debug_fallback.

Examples found in repository?
examples/derive_show_asm.rs (line 65)
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
fn parse_manifest_path() -> impl Parser<PathBuf> {
    long("manifest-path")
        .help("Path to Cargo.toml")
        .argument::<PathBuf>("PATH")
        .complete_shell(ShellComp::File {
            mask: Some("*.toml"),
        })
        .parse(|p| {
            // cargo-metadata wants to see
            if p.is_absolute() {
                Ok(p)
            } else {
                std::env::current_dir()
                    .map(|d| d.join(p))
                    .and_then(|full_path| full_path.canonicalize())
            }
        })
        .fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}

#[derive(Debug, Clone, Bpaf)]
/// How to render output
pub struct Format {
    /// Print interleaved Rust code
    pub rust: bool,

    #[bpaf(external(color_detection))]
    pub color: bool,

    /// include full demangled name instead of just prefix
    pub full_name: bool,
}

#[derive(Debug, Clone, Bpaf)]
/// Pick output type
///
/// included help
///
///
/// Extended help
pub enum Syntax {
    /// Generate assembly using Intel style
    Intel,
    /// Generate assembly using AT&T style
    Att,
}

fn color_detection() -> impl Parser<bool> {
    let yes = long("color")
        .help("Enable color highlighting")
        .req_flag(true);
    let no = long("no-color")
        .help("Disable color highlighting")
        .req_flag(false);
    construct!([yes, no]).fallback_with::<_, Infallible>(|| {
        // we can call for supports-color crate here
        Ok(true)
    })
}
source

fn hide(self) -> ParseHide<Self>
where Self: Sized + Parser<T>,

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

$ app --help

Usage: app [--argument=ARG]

Available options:
--argument=ARG
important argument
-h, --help
Prints help information

But doesn’t change the parsing behavior in any way otherwise

$ app --argument 32
Options { argument: 32, switch: false }
$ app --argument 42 --switch
Options { argument: 42, switch: true }
Examples found in repository?
examples/cargo-cmd.rs (line 22)
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
    // defining a parser in a usual way
    let width = short('w').argument::<usize>("WIDTH").fallback(10);
    let height = short('h').argument::<usize>("HEIGHT").fallback(10);
    let parser = construct!(Opts { width, height });

    let cmd = literal("cmd").optional().hide();
    let combined_parser = construct!(cmd, parser).map(|x| x.1);

    let opts = combined_parser.to_options().run();

    println!("{:?}", opts);
}
source

fn hide_usage(self) -> ParseUsage<Self>
where Self: Sized + Parser<T>,

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

$ app --help

Usage: app [--argument=ARG]

Available options:
--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

$ app --argument 32
Options { argument: 32, switch: false }
$ app --argument 32 --switch
Options { argument: 32, switch: true }
source

fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
where M: Into<Doc>, Self: Sized + Parser<T>,

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

$ app --help

Usage: app --binary=BINARY [-p=PACKAGE]

Available options:
-b, --binary=BIN
Binary to run
-p, --package=PACKAGE
Package to check
-h, --help
Prints help information

Parsing behavior stays unchanged

$ app --binary cargo-asm --package cargo-show-asm
Options { binary: Some("cargo-asm"), package: Some("cargo-show-asm") }
source

fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
where Self: Sized + Parser<T>,

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

$ app --help

Usage: app [--argument=ARG] [--width=W] [--height=H]

Takes a rectangle
--width=W
Width of the rectangle
--height=H
Height of the rectangle

Available options:
--argument=ARG
important argument
-h, --help
Prints help information

And doesn’t change the parsing behavior in any way

$ app --argument 32 --width 20 --height 13
Options { argument: 32, rectangle: Rectangle { width: 20, height: 13 } }
Examples found in repository?
examples/rectangle.rs (line 31)
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
fn main() {
    let width = short('w')
        .long("width")
        .help("Width of the rectangle")
        .argument::<usize>("PX");

    let height = short('h')
        .long("height")
        .help("Height of the rectangle")
        .argument::<usize>("PX");

    let rect = construct!(Rect { width, height })
        .group_help("Rectangle is defined by width and height in meters")
        .optional();

    let verbose = short('v')
        .long("verbose")
        .help("Print computation steps")
        .switch();

    let opt = construct!(Out { verbose, rect })
        .to_options()
        .descr("This program calculates rectangle's area")
        .header("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv")
        .footer("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
        .run();
    println!("{:#?}", opt);
}
source

fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
where Self: Sized + Parser<T>, F: Fn(MetaInfo<'_>) -> Doc,

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:

$ app --help

Usage: app [--argument=ARG] [--width=W] [--height=H]

The app takes a rectangle defined by width and height
You can customize the screen size using [--width=W] [--height=H] parameters
--width=W
Width of the rectangle
--height=H
Height of the rectangle

Available options:
--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

$ app --help --help

Usage: app [--argument=ARG] [--width=W] [--height=H]

The app takes a rectangle defined by width and height
You can customize the screen size using [--width=W] [--height=H] parameters
--width=W
Width of the rectangle
--height=H
Height of the rectangle

Available options:
--argument=ARG
important argument
-h, --help
Prints help information

Other than rendering the help message that there’s no interactions with other parsers

$ app --width 120 --height 11
Options { argument: 30, rectangle: Rectangle { width: 120, height: 11 } }
$ app --argument 12
Options { argument: 12, rectangle: Rectangle { width: 10, height: 10 } }
source

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>,

Available on crate feature 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

$ app --help

Usage: app -n=NAME

Available options:
-n, --name=NAME
Specify character's name
-h, --help
Prints help information

$ app --name Bob
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

$ app --help

Usage: app --crate=NAME [--dependencies] [--json | --xml | --text] [URL]

Format for generated report
--json
Generate report in JSON format
--xml
Generate report in XML format
--text
Generate report in plaintext format

Available positional items:
URL
Upload report to a url

Available options:
--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

$ app --json --crate cargo-hackerman
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

$ app --help

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]

Select artifact to use for analysis
Only one is valid
--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
included help
--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
INDEX
Select nth item from a filtered list

Available options:
--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?
examples/sensors.rs (line 59)
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
fn opts() -> Opts {
    let sensor = long("sensor").req_flag(());
    let device = long("sensor-device")
        .argument::<String>("DEVICE")
        .complete(sensor_device_comp);
    let name = long("sensor-name").argument::<String>("NAME");

    // from_str needs to be replaced with `parse` that can deal with hex digits
    let bus_id = long("sensor-i2c-bus").argument::<usize>("BUS");
    let address = long("sensor-i2c-address").argument::<usize>("ADDRESS");
    let sensors = construct!(Sensor {
        sensor,
        device,
        name,
        bus_id,
        address
    })
    .adjacent()
    .many();
    construct!(Opts { sensors }).to_options().run()
}
More examples
Hide additional examples
examples/csample.rs (line 21)
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
fn main() {
    use bpaf::*;

    let a = short('a').long("avocado").help("Use avocado").switch();
    let b = short('b').long("banana").help("Use banana").switch();
    let bb = long("bananananana").help("I'm Batman").switch();
    let c = long("calculator")
        .help("calculator expression")
        .argument::<String>("EXPR")
        .complete(complete_calculator);
    let parser = construct!(a, b, bb, c)
        .to_options()
        .descr("Dynamic autocomplete example")
        .footer(
            "\
    bpaf supports dynamic autocompletion for a few shells, make sure your binary is in $PATH
     and try using one of those this output should go into a file that depends on your shell:
    $ csample --bpaf-complete-style-bash
    $ csample --bpaf-complete-style-zsh
    $ csample --bpaf-complete-style-fish
    $ csample --bpaf-complete-style-elvish",
        );

    println!("{:?}", parser.fallback_to_usage().run());
}
source

fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
where Self: Sized + Parser<T>,

Available on crate feature 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,
}
Examples found in repository?
examples/derive_show_asm.rs (lines 52-54)
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
fn parse_manifest_path() -> impl Parser<PathBuf> {
    long("manifest-path")
        .help("Path to Cargo.toml")
        .argument::<PathBuf>("PATH")
        .complete_shell(ShellComp::File {
            mask: Some("*.toml"),
        })
        .parse(|p| {
            // cargo-metadata wants to see
            if p.is_absolute() {
                Ok(p)
            } else {
                std::env::current_dir()
                    .map(|d| d.join(p))
                    .and_then(|full_path| full_path.canonicalize())
            }
        })
        .fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
More examples
Hide additional examples
examples/basic.rs (line 47)
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
fn opts() -> OptionParser<Out> {
    // A flag, true if used in the command line. Can be required, this one is optional

    let debug = short('d') // start with a short name
        .long("debug") // also add a long name
        .help("Activate debug mode") // and a help message to use
        .switch(); // turn this into a switch

    // number of occurrences of the v/verbose flag capped at 3 with an error here but you can also
    // use `max` inside `map`
    let verbose = short('v')
        .long("verbose")
        .help("Increase the verbosity\n You can specify it up to 3 times\n either as -v -v -v or as -vvv")
        .req_flag(())
        .many()
        .map(|xs| xs.len())
        .guard(|&x| x <= 3, "It doesn't get any more verbose than this");

    // an argument, parsed and with default value
    let speed = short('s')
        .long("speed")
        .help("Set speed")
        .argument::<f64>("SPEED") // you can specify a type to parse
        .fallback(42.0)
        .display_fallback();

    let output = short('o')
        .long("output")
        .help("output file")
        .argument::<PathBuf>("OUTPUT") // but it's optional when rustc can derive it
        .complete_shell(ShellComp::File { mask: None });

    // no magical name transmogrifications in combinatoric API,
    let nb_cars = short('n')
        .long("nb-cars")
        .help("Number of items to process")
        .argument::<u32>("N")
        .fallback(1)
        .display_fallback();

    // a parser that consumes one argument
    // you can build the inner parser in one go or as multiple steps giving each step a name
    // you can also add some static shell completion functionality
    let file_to_proces = short('f')
        .long("file")
        .help("File to process")
        .argument::<PathBuf>("FILE")
        .complete_shell(ShellComp::File { mask: Some("*.rs") });

    let files_to_process = file_to_proces.many();

    // packing things in a struct assumes parser for each field is in scope.
    construct!(Out {
        debug,
        verbose,
        speed,
        output,
        nb_cars,
        files_to_process
    })
    .to_options()
    .descr("This is a description")
}
source

fn to_options(self) -> OptionParser<T>
where Self: Sized + Parser<T> + 'static,

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:

$ app --help

This is a short description

Usage: app -i=ARG

It can contain multiple blocks, this block goes before options

Available 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

$ app --version

Version: 3.1415

Other than that bpaf tries its best to provide a helpful error messages

$ app
Error: expected -i=ARG, pass --help for usage information

And if all parsers are satisfied run produces the result

$ app -i 10
Options { argument: 10 }
§See also

There’s some methods implemented on OptionParser directly to customize the appearance

Examples found in repository?
examples/many_comma_separated_args.rs (line 20)
19
20
21
fn main() {
    println!("{:?}", args().to_options().run());
}
More examples
Hide additional examples
examples/many_comma_separated_args_derive.rs (line 30)
29
30
31
fn main() {
    println!("{:?}", opts().to_options().run());
}
examples/compression.rs (line 23)
22
23
24
25
26
fn main() {
    let opts = compression().to_options().run();

    println!("{:?}", opts);
}
examples/enum_in_args.rs (line 33)
28
29
30
31
32
33
34
35
36
37
fn main() {
    let opt = long("baz")
        .short('b')
        .help("choose between foo, bar or foobar")
        .argument::<Baz>("CMD")
        .to_options()
        .run();

    println!("{:#?}", opt);
}
examples/find.rs (line 106)
97
98
99
100
101
102
103
104
105
106
107
pub fn options() -> OptionParser<Options> {
    let paths = positional::<PathBuf>("PATH").many();

    construct!(Options {
        exec(),
        user(),
        perm(),
        paths,
    })
    .to_options()
}
examples/at_least_two.rs (line 12)
7
8
9
10
11
12
13
14
15
16
fn main() {
    let opt = short('f')
        .req_flag(())
        .many()
        .guard(|x| x.len() >= 2, "at least two arguments are required")
        .to_options()
        .run();

    println!("{:?}", opt);
}
source

fn run(self) -> T
where Self: Sized + Parser<T> + 'static,

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?
examples/dynamic.rs (line 71)
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
fn main() {
    let items = &[
        ("banana", Ty::Bool),
        ("width", Ty::Number),
        ("name", Ty::String),
    ];

    let mut parser = pure(Vec::<(String, Value)>::new()).boxed();
    for (name, ty) in items {
        parser = cons(
            parser,
            match ty {
                Ty::Bool => bool(name).boxed(),
                Ty::Number => number(name).boxed(),
                Ty::String => string(name).boxed(),
            },
        )
    }

    let options = parser.run();
    println!("{:?}", options);
}
source

fn boxed(self) -> Box<dyn Parser<T>>
where Self: Sized + Parser<T> + 'static,

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

$ app --help

Usage: app --distance=KM

Available options:
--distance=KM
distance in km
-h, --help
Prints help information

and only one parser will produce a result

$ app --distance 10
10.0
Examples found in repository?
examples/coreutils.rs (line 36)
32
33
34
35
36
37
        fn my_last(self) -> ParseLast<T> {
            let p = self
                .some("need to specify at least once")
                .map(|mut xs| xs.pop().unwrap());
            ParseLast { inner: p.boxed() }
        }
More examples
Hide additional examples
examples/dynamic.rs (line 43)
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
fn cons<T>(acc: Box<dyn Parser<Vec<T>>>, cur: Box<dyn Parser<T>>) -> Box<dyn Parser<Vec<T>>>
where
    T: 'static,
{
    construct!(acc, cur)
        .map(|(mut acc, cur)| {
            acc.push(cur);
            acc
        })
        .boxed()
}

enum Ty {
    Bool,
    Number,
    String,
}

fn main() {
    let items = &[
        ("banana", Ty::Bool),
        ("width", Ty::Number),
        ("name", Ty::String),
    ];

    let mut parser = pure(Vec::<(String, Value)>::new()).boxed();
    for (name, ty) in items {
        parser = cons(
            parser,
            match ty {
                Ty::Bool => bool(name).boxed(),
                Ty::Number => number(name).boxed(),
                Ty::String => string(name).boxed(),
            },
        )
    }

    let options = parser.run();
    println!("{:?}", options);
}
examples/dynamic-tree.rs (line 56)
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
fn choose<T>(xs: Vec<Box<dyn Parser<T> + 'static>>) -> Box<dyn Parser<T>>
where
    T: 'static,
{
    let mut items = xs.into_iter();

    let mut res = items.next().unwrap();
    for next in items {
        res = construct!([res, next]).boxed()
    }
    res
}

fn make_parser(item: &Cog) -> Box<dyn Parser<&'static str>> {
    match item {
        Cog::Command {
            help,
            name,
            operation,
        } => Box::new(pure(*operation).to_options().descr(*help).command(name)),
        Cog::Group { name, help, nested } => {
            let nested = nested.iter().map(make_parser).collect::<Vec<_>>();
            let inner = choose(nested);
            inner.to_options().descr(*help).command(name).boxed()
        }
    }
}

Trait Implementations§

source§

impl<T> Parser<T> for Box<dyn Parser<T>>

source§

fn many(self) -> ParseMany<Self>
where Self: Sized,

Consume zero or more items from a command line and collect them into a Vec Read more
source§

fn collect<C>(self) -> ParseCollect<Self, C, T>
where C: FromIterator<T>, Self: Sized,

Transform parser into a collection parser Read more
source§

fn some(self, message: &'static str) -> ParseSome<Self>
where Self: Sized + Parser<T>,

Consume one or more items from a command line and collect them into a Vec Read more
source§

fn optional(self) -> ParseOptional<Self>
where Self: Sized + Parser<T>,

Turn a required argument into an optional one Read more
source§

fn count(self) -> ParseCount<Self, T>
where Self: Sized + Parser<T>,

Count how many times the inner parser succeeds, and return that number. Read more
source§

fn last(self) -> ParseLast<Self>
where Self: Sized + Parser<T>,

Apply the inner parser as many times as it succeeds, return the last value Read more
source§

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,

Apply a failing transformation to a contained value Read more
source§

fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
where Self: Sized + Parser<T>, F: Fn(T) -> R + 'static,

Apply a pure transformation to a contained value Read more
source§

fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
where Self: Sized + Parser<T>, F: Fn(&T) -> bool,

Validate or fail with a message Read more
source§

fn fallback(self, value: T) -> ParseFallback<Self, T>
where Self: Sized + Parser<T>,

Use this value as default if the value isn’t present on a command line Read more
source§

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,

Use value produced by this function as default if the value isn’t present Read more
source§

fn hide(self) -> ParseHide<Self>
where Self: Sized + Parser<T>,

Ignore this parser during any sort of help generation Read more
source§

fn hide_usage(self) -> ParseUsage<Self>
where Self: Sized + Parser<T>,

Ignore this parser when generating a usage line Read more
source§

fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
where M: Into<Doc>, Self: Sized + Parser<T>,

Customize how this parser looks like in the usage line
source§

fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
where Self: Sized + Parser<T>,

Attach a help message to a complex parser Read more
source§

fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
where Self: Sized + Parser<T>, F: Fn(MetaInfo<'_>) -> Doc,

Make a help message for a complex parser from its MetaInfo Read more
source§

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>,

Available on crate feature autocomplete only.
Dynamic shell completion Read more
source§

fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
where Self: Sized + Parser<T>,

Available on crate feature autocomplete only.
Static shell completion Read more
source§

fn to_options(self) -> OptionParser<T>
where Self: Sized + Parser<T> + 'static,

Transform Parser into OptionParser to get ready to run it Read more
source§

fn run(self) -> T
where Self: Sized + Parser<T> + 'static,

Finalize and run the parser Read more
source§

fn boxed(self) -> Box<dyn Parser<T>>
where Self: Sized + Parser<T> + 'static,

Create a boxed representation for a parser Read more

Implementations on Foreign Types§

source§

impl<T> Parser<T> for Box<dyn Parser<T>>

Implementors§

source§

impl<P, T> Parser<T> for ParseCompShell<P>
where P: Parser<T> + Sized,

Available on crate feature autocomplete only.
source§

impl<P, T> Parser<T> for ParseFallback<P, T>
where P: Parser<T>, T: Clone,

source§

impl<T> Parser<T> for ParseAny<T>

source§

impl<T> Parser<T> for ParseArgument<T>
where T: FromStr + 'static, <T as FromStr>::Err: Display,

source§

impl<T> Parser<T> for ParseCommand<T>

source§

impl<T> Parser<T> for ParsePositional<T>
where T: FromStr + 'static, <T as FromStr>::Err: Display,

source§

impl<T, C, P> Parser<C> for ParseCollect<P, C, T>
where P: Parser<T>, C: FromIterator<T>,

source§

impl<T, P> Parser<Option<T>> for ParseOptional<P>
where P: Parser<T>,

source§

impl<T, P> Parser<usize> for ParseCount<P, T>
where P: Parser<T>,

source§

impl<T, P> Parser<Vec<T>> for ParseMany<P>
where P: Parser<T>,

source§

impl<T, P> Parser<Vec<T>> for ParseSome<P>
where P: Parser<T>,

source§

impl<T, P> Parser<T> for ParseCon<P>
where P: Fn(bool, &mut State) -> Result<T, Error>,

source§

impl<T, P> Parser<T> for ParseLast<P>
where P: Parser<T>,

source§

impl<T, P, F, E> Parser<T> for ParseFallbackWith<T, P, F, E>
where P: Parser<T>, F: Fn() -> Result<T, E>, E: ToString,

source§

impl<T: Clone + 'static> Parser<T> for ParseFlag<T>