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 usage
#[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()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    #[bpaf(flag(Decision::Yes, Decision::No))]
    decision: Decision,
}

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

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 usage
#[derive(Debug, Clone)]
pub struct Options {
    decision: Decision,
}

#[derive(Debug, Clone)]
pub enum Decision {
    On,
    Off,
    Undecided,
}

// user can specify either --on or --off, parser would fallback to `Undecided`
fn parse_decision() -> impl Parser<Decision> {
    let on = long("on").help("Positive decision").req_flag(Decision::On);
    let off = long("off")
        .help("Negative decision")
        .req_flag(Decision::Off);
    construct!([on, off]).fallback(Decision::Undecided)
}

pub fn options() -> OptionParser<Options> {
    let decision = parse_decision();
    construct!(Options { decision }).to_options()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    #[bpaf(external)]
    decision: Decision,
}

#[derive(Debug, Clone, Bpaf)]
#[bpaf(fallback(Decision::Undecided))]
pub enum Decision {
    /// Positive decision
    On,
    /// Negative decision
    Off,
    #[bpaf(skip)]
    Undecided,
}
Examples

This example implements a tri-state switch: wether decision was made positive, negative or not at all. Alternative implementation can use optional and None to indicate “no decision” case. A case with positive decision:

% app --on
Options { decision: On }

A case with no decision:

% app 
Options { decision: Undecided }

--on and --off are mutually exclusive:

% app --on --off
--off is not expected in this context

help

% app --help
Usage: [--on | --off]

Available options:
        --on    Positive decision
        --off   Negative decision
    -h, --help  Prints help information

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 usage
#[derive(Debug, Clone)]
pub struct Options {
    decision: bool,
}

fn parse_decision() -> impl Parser<bool> {
    long("decision").help("Positive decision").switch()
}

pub fn options() -> OptionParser<Options> {
    let decision = parse_decision();
    construct!(Options { decision }).to_options()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    decision: bool,
}
Examples

Presense of a long name is decoded into true

% app --decision
Options { decision: true }

Absense is false

% app 
Options { decision: false }

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 usage
#[derive(Debug, Clone)]
pub struct Options {
    value: isize,
    shorty: u64,
}

pub fn options() -> OptionParser<Options> {
    let value = long("value").argument::<isize>("ARG").fallback(100);
    // You can use FromUtf8 type tag to parse things that only implement `FromStr`, but not `FromOsStr`
    // `u64` implements both and only used as an example
    let shorty = short('s').argument::<FromUtf8<u64>>("ARG");
    construct!(Options { value, shorty }).to_options()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    #[bpaf(fallback(100))]
    value: isize,
    // You can use FromUtf8 type tag to parse things that only implement FromStr, but not FromOsStr
    // `u64` implements both and only used as an example
    #[bpaf(short, argument::<FromUtf8<u64>>("ARG"))]
    shorty: u64,
}
Examples

Names for arguments could be short or long, and they can be parsed as mutiple different types, this example uses isize and u64

% app --value 50 -s=18446744073709551615
Options { value: 50, shorty: 18446744073709551615 }

Value can be separated from the flag by space, = or for short ones - be immediately adjacent

% app --value=1 -s42
Options { value: 1, shorty: 42 }

You can apply fallback and other transformation

% app -s0
Options { value: 100, shorty: 0 }

But if there’s no fallback - the value is required

% app --value 1
Expected -s ARG, pass --help for usage information

Argument is required

% app -s
-s requires an argument

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 usage
#[derive(Debug, Clone)]
pub struct Options {
    coin: Coin,
    file: PathBuf,
    name: Option<String>,
}

/// A custom datatype that doesn't implement [`FromOsStr`] but implements [`FromStr`]
#[derive(Debug, Clone, Copy)]
enum Coin {
    Heads,
    Tails,
}
impl FromStr for Coin {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "heads" => Ok(Coin::Heads),
            "tails" => Ok(Coin::Tails),
            _ => Err(format!("Expected 'heads' or 'tails', got '{}'", s)),
        }
    }
}

pub fn options() -> OptionParser<Options> {
    let file = positional::<PathBuf>("FILE").help("File to use");
    // sometimes you can get away with not specifying type in positional's turbofish
    let coin = long("coin")
        .help("Coin toss results")
        .argument::<FromUtf8<Coin>>("COIN")
        .fallback(Coin::Heads);
    let name = positional::<String>("NAME")
        .help("Name to look for")
        .optional();
    construct!(Options { coin, file, name }).to_options()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// Coin toss results
    #[bpaf(argument::<FromUtf8<Coin>>("COIN"), fallback(Coin::Heads))]
    coin: Coin,

    /// File to use
    #[bpaf(positional::<PathBuf>("FILE"))]
    file: PathBuf,
    /// Name to look for
    #[bpaf(positional("NAME"))]
    name: Option<String>,
}

/// A custom datatype that doesn't implement [`FromOsStr`] but implements [`FromStr`]
#[derive(Debug, Clone, Copy)]
enum Coin {
    Heads,
    Tails,
}
impl FromStr for Coin {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "heads" => Ok(Coin::Heads),
            "tails" => Ok(Coin::Tails),
            _ => Err(format!("Expected 'heads' or 'tails', got '{}'", s)),
        }
    }
}
Examples

Positionals are consumed left to right, one at a time, no skipping unless the value is optional

% app main.rs
Options { coin: Heads, file: "main.rs", name: None }

Both positionals are present

% app main.rs hello
Options { coin: Heads, file: "main.rs", name: Some("hello") }

To parse items without having to write FromOsStr instance you can use FromUtf8 helper type?

% app main.rs --coin tails
Options { coin: Tails, file: "main.rs", name: None }

Only name is optional in this example, not specifying file is a failure

% app 
Expected <FILE>, pass --help for usage information

And usage information

% app --help
Usage: [--coin COIN] <FILE> [<NAME>]

Available positional items:
    <FILE>  File to use
    <NAME>  Name to look for

Available options:
        --coin <COIN>  Coin toss results
    -h, --help         Prints help information

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 usage
#[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")
        .help("app will pass anything unused to a child process")
        .guard(|x| x != "--help", "keep help")
        .many();
    construct!(Options { turbo, rest }).to_options()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    #[bpaf(short, long)]
    /// Engage the turbo mode
    turbo: bool,
    #[bpaf(any("REST"), guard(not_help, "keep help"), many)]
    /// app will pass anything unused to a child process
    rest: Vec<OsString>,
}

fn not_help(s: &OsString) -> bool {
    s != "--help"
}
Examples

Capture --turbo flag for internal use and return everything else as is so it can be passed to some other program. Anything except for --turbo here and in following examples is consumed by any

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

Or just capture and return everything

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

Doesn’t have to be in order either

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

You can keep --help working, but you need to add extra guard for that

% app --turbo --help
Usage: [-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

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 usage
#[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")
        .help("Command to do something")
}

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()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(command)]
/// 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,
}
Examples

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

% app 
Expected COMMAND ..., pass --help for usage information

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

Commands show up on both outer level help

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

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

Available commands:
    cmd  Command to do something

As well as showing their own help

% app cmd --help
Command to do something

Usage: [--flag] --arg ARG

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

Structs

A named thing used to create flag, switch or argument
Parse the next available item on a command line with no restrictions, created with any.
Parser for a named argument, created with argument.
Builder structure for the command
Parse a positional item, created with positional

Functions

Take next unconsumed item on the command line as raw String or OsString
Subcommand parser
Environment variable fallback
A flag/switch/argument that has a long name
Positional argument in utf8 (String) encoding
A flag/switch/argument that has a short name