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 abool
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 ofshort
,long
andenv
. At the same time you can attachhelp
. NamedArg::switch
- simple switch that returnstrue
if it’s present on a command line andfalse
otherwise.NamedArg::flag
- a variant ofswitch
that lets you return one of two custom values, for exampleColor::On
andColor::Off
.NamedArg::req_flag
- a variant ofswitch
that only only succeeds when it’s name is present on a command lineNamedArg::argument
- named argument containing a value, you can further customize it withadjacent
positional
- positional argument, you can further customize it withstrict
OptionParser::command
- subcommand parser.any
and its specialized versionliteral
are escape hatches that can parse anything not fitting into usual classification.pure
andpure_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.
fallback
andfallback_with
- return a different value if parser fails to find what it is looking for. Generated help for former can be updated to include default value usingdisplay_fallback
anddebug_fallback
.optional
- returnNone
if value is missing instead of failing, see alsocatch
.many
andsome
- collect multiple values into a vector, see their respectivecatch
andcatch
.map
,parse
andguard
- transform and/or validate value produced by a parserto_options
- finalize the parser and prepare to run it
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
andhide
- hide the parser from generated Usage line or whole generated helpgroup_help
andwith_group_help
- add a common description shared by several parserscustom_usage
- customize usage for a primitive or composite parserusage
andwith_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
- You can
OptionParser::run
the parser on the arguments passed on the command line check_invariants
checks for a few invariants in the parserbpaf
relies onrun_inner
runs the parser with customArgs
you can create either explicitly or implicitly using one of theFrom
implementations,Args
can be customized withset_comp
andset_name
.ParseFailure
contains the parse outcome, you can consume it either by hands or using one ofexit_code
,unwrap_stdout
andunwrap_stderr
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
- Parse a single arbitrary item from a command line
- Parse an environment variable
- Fail with a fixed error message
- A specialized version of
any
that consumes an arbitrary string - Parse a positional argument
- Wrap a value into a
Parser
- Wrap a calculated value into a
Parser
Derive Macros
- Derive macro for bpaf command line parser