Module bpaf::params

source ·
Expand description

Tools to define primitive parsers

§Ways to consume data

§Flag

  • flag - a string that consists of two dashes (--flag) and a name and a single dash and a single character (-f) created with long and short respectively. Depending if this name is present or absent on the command line primitive flag parser produces one of two values. User can combine several short flags in a single invocation: -a -b -c is the same as -abc.
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    decision: Decision,
}

#[derive(Debug, Clone)]
pub enum Decision {
    Yes,
    No,
}

fn parse_decision() -> impl Parser<Decision> {
    long("decision")
        .help("Positive decision")
        .flag(Decision::Yes, Decision::No)
}

pub fn options() -> OptionParser<Options> {
    let decision = parse_decision();
    construct!(Options { decision }).to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// Positive decision
    #[bpaf(flag(Decision::Yes, Decision::No))]
    decision: Decision,
}

#[derive(Debug, Clone)]
pub enum Decision {
    Yes,
    No,
}

fn main() {
    println!("{:?}", options().run())
}
Output

In --help output bpaf shows flags with no meta variable attached

$ app --help

Usage: app [--decision]

Available options:
--decision
Positive decision
-h, --help
Prints help information

Presense of a long name is decoded into Yes

$ app --decision
Options { decision: Yes }

Absense is No

$ app
Options { decision: No }

§Required flag

Similar to flag, but instead of falling back to the second value required flag parser would fail. Mostly useful in combination with other parsers, created with NamedArg::req_flag.

Combinatoric example
#[derive(Debug, Clone)]
pub enum Style {
    Intel,
    Att,
    Llvm,
}

#[derive(Debug, Clone)]
pub enum Report {
    /// Include defailed report
    Detailed,
    /// Include minimal report
    Minimal,
    /// No preferences
    Undecided,
}

#[derive(Debug, Clone)]
pub struct Options {
    agree: (),
    style: Style,
    report: Report,
}

pub fn options() -> OptionParser<Options> {
    let agree = long("agree")
        .help("You must agree to perform the action")
        .req_flag(());

    let intel = long("intel")
        .help("Show assembly using Intel style")
        .req_flag(Style::Intel);
    let att = long("att")
        .help("Show assembly using AT&T style")
        .req_flag(Style::Att);
    let llvm = long("llvm").help("Show llvm-ir").req_flag(Style::Llvm);
    let style = construct!([intel, att, llvm]);

    let detailed = long("detailed")
        .help("Include detailed report")
        .req_flag(Report::Detailed);
    let minimal = long("minimal")
        .help("Include minimal report")
        .req_flag(Report::Minimal);
    let report = construct!([detailed, minimal]).fallback(Report::Undecided);

    construct!(Options {
        agree,
        style,
        report
    })
    .to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
pub enum Style {
    /// Show assembly using Intel style
    Intel,
    /// Show assembly using AT&T style
    Att,
    /// Show llvm-ir
    Llvm,
}

#[derive(Debug, Clone, Bpaf)]
#[bpaf(fallback(Report::Undecided))]
pub enum Report {
    /// Include detailed report
    Detailed,
    /// Include minimal report
    Minimal,
    #[bpaf(skip)]
    /// No preferences
    Undecided,
}

#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// You must agree to perform the action
    agree: (),
    // external here uses explicit reference to function `style`
    // generated above
    #[bpaf(external(style))]
    style: Style,
    // here reference is implicit and derived from field name: `report`
    #[bpaf(external)]
    report: Report,
}

fn main() {
    println!("{:?}", options().run())
}
Output

In --help message req_flag look similarly to switch and flag

$ app --help

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

Available options:
--agree
You must agree to perform the action
--intel
Show assembly using Intel style
--att
Show assembly using AT&T style
--llvm
Show llvm-ir
--detailed
Include detailed report
--minimal
Include minimal report
-h, --help
Prints help information

Example contains two parsers that fails without any input: agree requires passing --agree

$ app
Error: expected --agree, pass --help for usage information

While style takes one of several possible values

$ app --agree
Error: expected --intel, --att, or more, pass --help for usage information

It is possible to alter the behavior using fallback or hide.

$ app --agree --intel
Options { agree: (), style: Intel, report: Undecided }

While parser for style takes any posted output - it won’t take multiple of them at once (unless other combinators such as many permit it) or last.

$ app --agree --att --llvm
Error: --llvm cannot be used at the same time as --att

§Switch

A special case of a flag that gets decoded into a bool, mostly serves as a convenient shortcut to .flag(true, false). Created with NamedArg::switch.

Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    verbose: bool,
    release: bool,
    default_features: bool,
}

pub fn options() -> OptionParser<Options> {
    let verbose = short('v')
        .long("verbose")
        .help("Produce verbose output")
        .switch();
    let release = long("release")
        .help("Build artifacts in release mode")
        .flag(true, false);
    let default_features = long("no-default-features")
        .help("Do not activate default features")
        // default_features uses opposite values,
        // producing `true` when value is absent
        .flag(false, true);

    construct!(Options {
        verbose,
        release,
        default_features,
    })
    .to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// Produce verbose output
    // bpaf uses `switch` for `bool` fields in named
    // structs unless consumer attribute is present.
    // But it is also possible to give it explicit
    // consumer annotation to serve as a reminder:
    // #[bpaf(short, long, switch)]
    #[bpaf(short, long)]
    verbose: bool,

    #[bpaf(flag(true, false))]
    /// Build artifacts in release mode
    release: bool,

    /// Do not activate default features
    // default_features uses opposite values,
    // producing `true` when value is absent
    #[bpaf(long("no-default-features"), flag(false, true))]
    default_features: bool,
}

fn main() {
    println!("{:?}", options().run())
}
Output

In --help output bpaf shows switches as usual flags with no meta variable attached

$ app --help

Usage: app [-v] [--release] [--no-default-features]

Available options:
-v, --verbose
Produce verbose output
--release
Build artifacts in release mode
--no-default-features
Do not activate default features
-h, --help
Prints help information

Both switch and flag succeed if value is not present, switch returns true, flag returns second value.

$ app
Options { verbose: false, release: false, default_features: true }

When value is present - switch returns true, flag returns first value.

$ app --verbose --no-default-features --detailed
Error: --detailed is not expected in this context

Like with most parsrs unless specified switch and flag consume at most one item from the command line:

$ app --no-default-features --no-default-features
Error: argument --no-default-features cannot be used multiple times in this context

§Argument

A short or long flag followed by either a space or = and then by a string literal. -f foo, --flag bar or -o=- are all valid argument examples. Note, string literal can’t start with - unless separated from the flag with =. For short flags value can follow immediately: -fbar.

Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    name: String,
    age: usize,
}

pub fn options() -> OptionParser<Options> {
    let name = short('n')
        .long("name")
        .help("Specify user name")
        // you can specify exact type argument should produce
        // for as long as it implements `FromStr`
        .argument::<String>("NAME");

    let age = long("age")
        .help("Specify user age")
        // but often rust can figure it out from the context,
        // here age is going to be `usize`
        .argument("AGE")
        .fallback(18)
        .display_fallback();

    construct!(Options { name, age }).to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    // you can specify exact type argument should produce
    // for as long as it implements `FromStr`
    #[bpaf(short, long, argument::<String>("NAME"))]
    /// Specify user name
    name: String,
    // but often rust can figure it out from the context,
    // here age is going to be `usize`
    #[bpaf(argument("AGE"), fallback(18), display_fallback)]
    /// Specify user age
    age: usize,
}

fn main() {
    println!("{:?}", options().run())
}
Output
$ app --help

Usage: app -n=NAME [--age=AGE]

Available options:
-n, --name=NAME
Specify user name
--age=AGE
Specify user age
[default: 18]
-h, --help
Prints help information

--help shows arguments as a short name with attached metavariable

Value can be separated from flag by space, = sign

$ app --name Bob --age 12
Options { name: "Bob", age: 12 }
$ app --name "Bob" --age=12
Options { name: "Bob", age: 12 }
$ app --name=Bob
Options { name: "Bob", age: 18 }
$ app --name="Bob"
Options { name: "Bob", age: 18 }

Or in case of short name - be directly adjacent to it

$ app -nBob
Options { name: "Bob", age: 18 }

For long names - this doesn’t work since parser can’t tell where name stops and argument begins:

$ app --age12
Error: no such flag: --age12, did you mean --age?

Either way - value is required, passing just the argument name results in parse failure

$ app --name
Error: --name requires an argument NAME

§Positional

A positional argument with no additonal name, for example in vim main.rs main.rs is a positional argument. Can’t start with -, created with positional.

Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    verbose: bool,
    crate_name: String,
    feature_name: Option<String>,
}

pub fn options() -> OptionParser<Options> {
    let verbose = short('v')
        .long("verbose")
        .help("Display detailed information")
        .switch();

    let crate_name = positional("CRATE").help("Crate name to use");

    let feature_name = positional("FEATURE")
        .help("Display information about this feature")
        .optional();

    construct!(Options {
        verbose,
        // You must place positional items and commands after
        // all other parsers
        crate_name,
        feature_name
    })
    .to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// Display detailed information
    #[bpaf(short, long)]
    verbose: bool,

    // You must place positional items and commands after
    // all other parsers
    #[bpaf(positional("CRATE"))]
    /// Crate name to use
    crate_name: String,

    #[bpaf(positional("FEATURE"))]
    /// Display information about this feature
    feature_name: Option<String>,
}

fn main() {
    println!("{:?}", options().run())
}
Output

Positional items show up in a separate group of arguments if they contain a help message, otherwise they will show up only in Usage part.

$ app --help

Usage: app [-v] CRATE [FEATURE]

Available positional items:
CRATE
Crate name to use
FEATURE
Display information about this feature

Available options:
-v, --verbose
Display detailed information
-h, --help
Prints help information

You can mix positional items with regular items

$ app --verbose bpaf
Options { verbose: true, crate_name: "bpaf", feature_name: None }

And since bpaf API expects to have non positional items consumed before positional ones - you can use them in a different order. In this example bpaf corresponds to a crate_name field and --verbose – to verbose.

$ app bpaf --verbose
Options { verbose: true, crate_name: "bpaf", feature_name: None }

In previous examples optional field feature was missing, this one contains it.

$ app bpaf autocomplete
Options { verbose: false, crate_name: "bpaf", feature_name: Some("autocomplete") }

Users can use -- to tell bpaf to treat remaining items as positionals - this might be required to handle unusual items.

$ app bpaf -- --verbose
Options { verbose: false, crate_name: "bpaf", feature_name: Some("--verbose") }
$ app -- bpaf --verbose
Options { verbose: false, crate_name: "bpaf", feature_name: Some("--verbose") }

Without using -- bpaf would only accept items that don’t start with - as positional.

$ app --detailed
Error: expected CRATE, got --detailed. Pass --help for usage information
$ app --verbose
Error: expected CRATE, pass --help for usage information

You can use any to work around this restriction.

§Any

Also a positional argument with no additional name, but unlike positional itself, any isn’t restricted to positional looking structure and would consume any items as they appear on a command line. Can be useful to collect anything unused to pass to other applications.

Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    turbo: bool,
    rest: Vec<OsString>,
}

pub fn options() -> OptionParser<Options> {
    let turbo = short('t')
        .long("turbo")
        .help("Engage the turbo mode")
        .switch();
    let rest = any::<OsString, _, _>("REST", |x| (x != "--help").then_some(x))
        .help("app will pass anything unused to a child process")
        .many();
    construct!(Options { turbo, rest }).to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    #[bpaf(short, long)]
    /// Engage the turbo mode
    turbo: bool,
    #[bpaf(any("REST", not_help), many)]
    /// app will pass anything unused to a child process
    rest: Vec<OsString>,
}

fn not_help(s: OsString) -> Option<OsString> {
    if s == "--help" {
        None
    } else {
        Some(s)
    }
}

fn main() {
    println!("{:?}", options().run())
}
Output

--help keeps working for as long as any captures only intended values - that is it ignores --help flag specifically

$ app --help

Usage: app [-t] [REST]...

Available positional items:
REST
app will pass anything unused to a child process

Available options:
-t, --turbo
Engage the turbo mode
-h, --help
Prints help information

You can mix any with regular options, here switch turbo works because it goes before rest in the parser declaration

$ app --turbo git commit -m "hello world"
Options { turbo: true, rest: ["git", "commit", "-m", "hello world"] }

“before” in the previous line means in the parser definition, not on the user input, here --turbo gets consumed by turbo parser even the argument goes

$ app git commit -m="hello world" --turbo
Options { turbo: true, rest: ["git", "commit", "-m=hello world"] }
$ app -- git commit -m="hello world" --turbo
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
$ app git commit -m="hello world" -- --turbo
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    block_size: usize,
    count: usize,
    output_file: String,
    turbo: bool,
}

/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
fn tag<T>(name: &'static str, meta: &str, help: impl Into<Doc>) -> impl Parser<T>
where
    T: FromStr,
    <T as FromStr>::Err: std::fmt::Display,
{
    // closure inside checks if command line argument starts with a given name
    // and if it is - it accepts it, otherwise it behaves like it never saw it
    // it is possible to parse OsString here and strip the prefix with
    // `os_str_bytes` or a similar crate
    any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
        // this defines custom metavar for the help message
        // so it looks like something it designed to parse
        .metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
        .help(help)
        // this makes it so tag parser tries to read all (unconsumed by earlier parsers)
        // item on a command line instead of trying and failing on the first one
        .anywhere()
        // At this point parser produces `String` while consumer might expect some other
        // type. [`parse`](Parser::parse) handles that
        .parse(|s| s.parse())
}

pub fn options() -> OptionParser<Options> {
    let block_size = tag("bs=", "BLOCK", "How many bytes to read at once")
        .fallback(1024)
        .display_fallback();
    let count = tag("count=", "NUM", "How many blocks to read").fallback(1);
    let output_file = tag("of=", "FILE", "Save results into this file");

    // this consumes literal value of "+turbo" locate and produces `bool`
    let turbo = literal("+turbo")
        .help("Engage turbo mode!")
        .anywhere()
        .map(|_| true)
        .fallback(false);

    construct!(Options {
        block_size,
        count,
        output_file,
        turbo
    })
    .to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
// This example is still technically derive API, but derive is limited to gluing
// things together and keeping macro complexity under control.
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    // `external` here and below derives name from the field name, looking for
    // functions called `block_size`, `count`, etc that produce parsers of
    // the right type.
    // A different way would be to write down the name explicitly:
    // #[bpaf(external(block_size), fallback(1024), display_fallback)]
    #[bpaf(external, fallback(1024), display_fallback)]
    block_size: usize,
    #[bpaf(external, fallback(1))]
    count: usize,
    #[bpaf(external)]
    output_file: String,
    #[bpaf(external)]
    turbo: bool,
}

fn block_size() -> impl Parser<usize> {
    tag("bs=", "BLOCK", "How many bytes to read at once")
}

fn count() -> impl Parser<usize> {
    tag("count=", "NUM", "How many blocks to read")
}

fn output_file() -> impl Parser<String> {
    tag("of=", "FILE", "Save results into this file")
}

fn turbo() -> impl Parser<bool> {
    literal("+turbo")
        .help("Engage turbo mode!")
        .anywhere()
        .map(|_| true)
        .fallback(false)
}

/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
fn tag<T>(name: &'static str, meta: &str, help: impl Into<Doc>) -> impl Parser<T>
where
    T: FromStr,
    <T as FromStr>::Err: std::fmt::Display,
{
    // closure inside checks if command line argument starts with a given name
    // and if it is - it accepts it, otherwise it behaves like it never saw it
    // it is possible to parse OsString here and strip the prefix with
    // `os_str_bytes` or a similar crate
    any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
        // this defines custom metavar for the help message
        // so it looks like something it designed to parse
        .metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
        .help(help)
        // this makes it so tag parser tries to read all (unconsumed by earlier parsers)
        // item on a command line instead of trying and failing on the first one
        .anywhere()
        // At this point parser produces `String` while consumer might expect some other
        // type. [`parse`](Parser::parse) handles that
        .parse(|s| s.parse())
}

fn main() {
    println!("{:?}", options().run())
}
Output

Instead of usual metavariable any parsers take something that can represent any value

$ app --help

Usage: app [bs=BLOCK] [count=NUM] of=FILE [+turbo]

Available options:
bs=BLOCK
How many bytes to read at once
[default: 1024]
count=NUM
How many blocks to read
of=FILE
Save results into this file
+turbo
Engage turbo mode!
-h, --help
Prints help information

Output file is required in this parser, other values are optional

$ app
Error: expected of=FILE, pass --help for usage information
$ app of=simple.txt
Options { block_size: 1024, count: 1, output_file: "simple.txt", turbo: false }

Since options are defined with anywhere - order doesn’t matter

$ app bs=10 of=output.rs +turbo
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
$ app +turbo bs=10 of=output.rs
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
$ app bs=65536 count=12 of=hello_world.rs
Options { block_size: 65536, count: 12, output_file: "hello_world.rs", turbo: false }

§Command

A command defines a starting point for an independent subparser. Name must be a valid utf8 string. For example cargo build invokes command "build" and after "build" cargo starts accepting values it won’t accept otherwise

Combinatoric example
#[derive(Debug, Clone)]
pub struct Cmd {
    flag: bool,
    arg: usize,
}

#[derive(Debug, Clone)]
pub struct Options {
    flag: bool,
    cmd: Cmd,
}

fn cmd() -> impl Parser<Cmd> {
    let flag = long("flag")
        .help("This flag is specific to command")
        .switch();
    let arg = long("arg").argument::<usize>("ARG");
    construct!(Cmd { flag, arg })
        .to_options()
        .descr("Command to do something")
        .command("cmd")
        // you can chain add extra short and long names
        .short('c')
}

pub fn options() -> OptionParser<Options> {
    let flag = long("flag")
        .help("This flag is specific to the outer layer")
        .switch();
    construct!(Options { flag, cmd() }).to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
// `command` annotation with no name gets the name from the object it is attached to,
// but you can override it using something like #[bpaf(command("my_command"))]
// you can chain more short and long names here to serve as aliases
#[bpaf(command("cmd"), short('c'))]
/// Command to do something
pub struct Cmd {
    /// This flag is specific to command
    flag: bool,
    arg: usize,
}

#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// This flag is specific to the outer layer
    flag: bool,
    #[bpaf(external)]
    cmd: Cmd,
}

fn main() {
    println!("{:?}", options().run())
}
Output

Commands show up on both outer level help

$ app --help

Usage: app [--flag] COMMAND ...

Available options:
--flag
This flag is specific to the outer layer
-h, --help
Prints help information

Available commands:
cmd, c
Command to do something

As well as showing their own help

$ app cmd --help

Command to do something

Usage: app cmd [--flag] --arg=ARG

Available options:
--flag
This flag is specific to command
--arg=ARG
-h, --help
Prints help information

In this example there’s only one command and it is required, so is the argument inside of it

$ app cmd --arg 42
Options { flag: false, cmd: Cmd { flag: false, arg: 42 } }

If you don’t specify this command - parsing will fail

You can have the same flag names inside and outside of the command, but it might be confusing for the end user. This example enables the outer flag

$ app --flag cmd --arg 42
Options { flag: true, cmd: Cmd { flag: false, arg: 42 } }

And this one - both inside and outside

$ app --flag cmd --arg 42 --flag
Options { flag: true, cmd: Cmd { flag: true, arg: 42 } }

And that’s the confusing part - unless you add context restrictions with adjacent and parse command first - outer flag wins. So it’s best not to mix names on different levels

$ app cmd --arg 42 --flag
Options { flag: true, cmd: Cmd { flag: false, arg: 42 } }

Structs§