Crate bpaf

source ·
Expand description

Lightweight and flexible command line argument parser with derive and combinatoric style API

1. Design considerations for types produced by the parser

Parsing usually starts from deciding what kind of data your application wants to get from the user. You should try to take advantage of Rust typesystem, try to represent the result such that more validation can be done during parsing.

A few examples Use enums instead of structs for mutually exclusive options:
/// Good format selection
enum OutputFormat {
    Intel,
    Att,
    Llvm
}

fn main() {
    ...
    // `rustc` ensures you handle each case, parser won't try to consume
    // combinations of flags it can't represent. For example it won't accept
    // both `--intel` and `--att` at once
    // (unless it can collect multiple of them in a vector)
    match format {
        OutputFormat::Intel => ...,
        OutputFormat::Att=> ...,
        OutputFormat::Llvm=> ...,
    }
}

While it’s easy to see how flags like --intel and --att maps to each of those bools, consuming inside your app is more fragile

/// Bad format selection
struct OutputFormat {
    intel: bool,
    att: bool,
    llvm: bool,
}

fn main() {
    ...
    // what happens when none matches? Or all of them?
    // What happens when you add a new output format?
    if format.intel {
        ...
    } else if format.att {
        ...
    } else if format.llvm {
        ...
    } else {
        // can this branch be reached?
    }
}

Mutually exclusive things are not limited to just flags. For example if your program can take input from several different sources such as file, database or interactive input it’s a good idea to use enum as well:

/// Good input selection
enum Input {
    File {
        filepath: PathBuf,
    }
    Database {
        user: String,
        password: String.
    }
    Interactive,
}

If your codebase uses newtype pattern - it’s a good idea to use it starting from the command options:

struct Options {
    // better than taking a String and parsing internally
    date: NaiveDate,
    // f64 might work too, but you can start from some basic sanity checks
    speed: Speed
    ...
}

2. Primitive items on the command line

If we are not talking about exotic cases most of the command line arguments can be narrowed down to a few items:

An overview of primitive parser shapes
  • an option with a short or a long name: -v or --verbose, short options can sometimes be squashed together: -vvv can be parsed the same as -v -v -v passed separately. If such option is parsed into a bool bpaf documentation calls them switches, if it parses into some fixed value - it’s a flag.

    Examples of flags and switches
    cargo build --release
    cargo test -q
    cargo asm --intel
    
  • an option with a short or a long name with extra value attached: -p PACKAGE or --package PACKAGE. Value can also be separated by = sign from the name or, in case of a short name, be adjacent to it: --package=bpaf and -pbpaf. bpaf documentation calls them arguments.

    Examples of arguments
    cargo build --package bpaf
    cargo test -j2
    cargo check --bin=megapotato
    
  • value taken from a command line just by being in the correct position and not being a flag. bpaf documentation calls them positionals.

    Examples of positionals
    cat /etc/passwd
    rm -rf target
    man gcc
    
  • a positional item that starts a whole new set of options with a separate help message. bpaf documentation calls them commands or subcommands.

    Examples of subcommands
    cargo build --release
    cargo clippy
    cargo asm --intel --everything
    
  • value can be taken from an environment variable.

    Examples of environment variable
    CARGO_TARGET_DIR=~/shared cargo build --release
    PASSWORD=secret encrypt file
    

bpaf allows you to describe the parsers using a mix of two APIs: combinatoric and derive. Both APIs can achieve the same results, you can use one that better suits your needs. You can find documentation with more examples following those links.

  • For an argument with a name you define NamedArg using a combination of short, long and env. At the same time you can attach help.
  • NamedArg::switch - simple switch that returns true if it’s present on a command line and false otherwise.
  • NamedArg::flag - a variant of switch that lets you return one of two custom values, for example Color::On and Color::Off.
  • NamedArg::req_flag - a variant of switch that only only succeeds when it’s name is present on a command line
  • NamedArg::argument - named argument containing a value, you can further customize it with adjacent
  • positional - positional argument, you can further customize it with strict
  • OptionParser::command - subcommand parser.
  • any and its specialized version literal are escape hatches that can parse anything not fitting into usual classification.
  • pure and pure_with - a way to generate a value that can be composed without parsing it from the command line.

3. Transforming and changing parsers

By default primitive parsers gives you back a single bool, a single PathBuf or a single value produced by FromStr trait, etc. You can further transform it by chaining methods from Parser trait, some of those methods are applied automagically if you are using derive API.

bpaf distinguishes two types of parse failures - “value is absent” and “value is present but invalid”, most parsers listed in this section only handle the first type of falure by default, but you can use their respective catch method to handle the later one.

4. Combining multiple parsers together

Once you have parsers for all the primitive fields figured out you can start combining them together to produce a parser for a final result - data type you designed in the step one. For derive API you apply annotations to data types with #[derive(Bpaf)] and #[bpaf(..)], with combinatoric API you use construct! macro.

All fields in a struct needs to be successfully parsed in order for the parser to succeed and only one variant from enum will consume its values at a time.

You can use adjacent annotation to parse multiple flags as an adjacent group allowing for more unusual scenarios such as multiple value arguments or chained commands.

5. Improving user experience

bpaf would use doc comments on fields and structures in derive mode and and values passed in various help methods to generate --help documentation, you can further improve it using those methods:

  • hide_usage and hide - hide the parser from generated Usage line or whole generated help
  • group_help and with_group_help - add a common description shared by several parsers
  • custom_usage - customize usage for a primitive or composite parser
  • usage and with_usage lets you to customize whole usage line as a whole either by completely overriding it or by building around it.

By default with completion enabled bpaf would complete names for flags, arguments and commands. You can also generate completion for argument values, possible positionals, etc. This requires enabling autocomplete cargo feature.

And finally you can generate documentation for command line in html-markdown mix and manpage formats using render_html and render_manpage, for more detailed info see doc module

6. Testing your parsers and running them

Modules

  • Applicative functors? What is it about?
  • Using the library in combinatoric style
  • Using the library in derive style
  • Some of the more unusual examples
  • Batteries included - helpful parsers that use only public API
  • Documentation generation system
  • Tools to define primitive parsers
  • This module exposes parsers that accept further configuration with builder pattern

Macros

  • Compose several parsers to produce a single result

Structs

  • All currently present command line parameters with some extra metainfo
  • String with styled segments.
  • Ready to run Parser with additional information attached

Enums

  • Unsuccessful command line parsing outcome, use it for unit tests
  • Shell specific completion

Traits

  • Simple or composed argument parser

Functions

Derive Macros

  • Derive macro for bpaf command line parser