pub struct ParseCon<P> {
pub inner: P,
pub meta: Meta,
pub failfast: bool,
}Expand description
Create parser from a function, construct! uses it internally
Fields§
§inner: Pinner parser closure
meta: Metametas for inner parsers
failfast: boolTo produce a better error messages while parsing constructed values we want to look at all the items so values that can be consumed are consumed autocomplete relies on the same logic
However when dealing with adjacent restriction detecting the first item relies on failing fast
Implementations§
Source§impl<T> ParseCon<T>
impl<T> ParseCon<T>
Sourcepub fn adjacent(self) -> ParseAdjacent<Self>
pub fn adjacent(self) -> ParseAdjacent<Self>
Automagically restrict the inner parser scope to accept adjacent values only
adjacent can solve surprisingly wide variety of problems: sequential command chaining,
multi-value arguments, option-structs to name a few. If you want to run a parser on a
sequential subset of arguments - adjacent might be able to help you. Check the examples
for better intuition.
Let’s consider two examples with consumed items marked in bold and constructor containing
parsers for -c and -d.
-a -b -c -d-a -c -b -d
In the first example -b breaks the adjacency for all the consumed items so parsing will fail,
while here in the second one all the consumed items are adjacent to each other so
parsing will succeed.
§Multi-value arguments
Parsing things like --point X Y Z
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
point: Vec<Point>,
rotate: bool,
}
#[derive(Debug, Clone)]
struct Point {
point: (),
x: usize,
y: usize,
z: f64,
}
fn point() -> impl Parser<Point> {
let point = short('p')
.long("point")
.help("Point coordinates")
.req_flag(());
let x = positional::<usize>("X").help("X coordinate of a point");
let y = positional::<usize>("Y").help("Y coordinate of a point");
let z = positional::<f64>("Z").help("Height of a point above the plane");
construct!(Point { point, x, y, z }).adjacent()
}
pub fn options() -> OptionParser<Options> {
let rotate = short('r')
.long("rotate")
.help("Face the camera towards the first point")
.switch();
let point = point().many();
construct!(Options { point, rotate }).to_options()
}
fn main() {
println!("{:?}", options().run())
}Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(external, many)]
point: Vec<Point>,
#[bpaf(short, long)]
/// Face the camera towards the first point
rotate: bool,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(adjacent)]
struct Point {
#[bpaf(short, long)]
/// Point coordinates
point: (),
#[bpaf(positional("X"))]
/// X coordinate of a point
x: usize,
#[bpaf(positional("Y"))]
/// Y coordinate of a point
y: usize,
#[bpaf(positional("Z"))]
/// Height of a point above the plane
z: f64,
}
fn main() {
println!("{:?}", options().run())
}Output
Fields can have different types, including Option or Vec, in this example they are two
usize and one f64.
Usage: app [-p X Y Z]... [-r]
- -p, --point
- Point coordinates
- X
- X coordinate of a point
- Y
- Y coordinate of a point
- Z
- Height of a point above the plane
- -r, --rotate
- Face the camera towards the first point
- -h, --help
- Prints help information
flag --point takes 3 positional arguments: two integers for X and Y coordinates and one floating point for height, order is
important, switch --rotate can go on either side of it
Options { point: [Point { point: (), x: 10, y: 20, z: 3.1415 }], rotate: true }
parser accepts multiple points, they must not interleave
Options { point: [Point { point: (), x: 10, y: 20, z: 3.1415 }, Point { point: (), x: 1, y: 2, z: 0.0 }], rotate: false }
--rotate can’t go in the middle of the point definition as the parser expects the second item
Error: expected Z, pass --help for usage information
§Structure groups
Parsing things like --rect --width W --height H --rect --height H --width W
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
rect: Vec<Rect>,
mirror: bool,
}
#[derive(Debug, Clone)]
struct Rect {
rect: (),
width: usize,
height: usize,
painted: bool,
}
fn rect() -> impl Parser<Rect> {
let rect = long("rect").help("Define a new rectangle").req_flag(());
let width = short('w')
.long("width")
.help("Rectangle width in pixels")
.argument::<usize>("PX");
let height = short('h')
.long("height")
.help("Rectangle height in pixels")
.argument::<usize>("PX");
let painted = short('p')
.long("painted")
.help("Should rectangle be filled?")
.switch();
construct!(Rect {
rect,
width,
height,
painted,
})
.adjacent()
}
pub fn options() -> OptionParser<Options> {
let mirror = long("mirror").help("Mirror the image").switch();
let rect = rect().many();
construct!(Options { rect, mirror }).to_options()
}
fn main() {
println!("{:?}", options().run())
}Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(external, many)]
rect: Vec<Rect>,
/// Mirror the image
mirror: bool,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(adjacent)]
struct Rect {
/// Define a new rectangle
rect: (),
#[bpaf(short, long, argument("PX"))]
/// Rectangle width in pixels
width: usize,
#[bpaf(short, long, argument("PX"))]
/// Rectangle height in pixels
height: usize,
#[bpaf(short, long)]
/// Should rectangle be filled?
painted: bool,
}
fn main() {
println!("{:?}", options().run())
}Output
This example parses multipe rectangles from a command line defined by dimensions and the fact
if its filled or not, to make things more interesting - every group of coordinates must be
prefixed with --rect
Usage: app [--rect -w=PX -h=PX [-p]]... [--mirror]
- --rect
- Define a new rectangle
- -w, --width=PX
- Rectangle width in pixels
- -h, --height=PX
- Rectangle height in pixels
- -p, --painted
- Should rectangle be filled?
- --mirror
- Mirror the image
- -h, --help
- Prints help information
Order of items within the rectangle is not significant and you can have several of them, because fields are still regular arguments - order doesn’t matter for as long as they belong to some rectangle
Options { rect: [Rect { rect: (), width: 10, height: 10, painted: false }, Rect { rect: (), width: 10, height: 10, painted: false }], mirror: false }
You can have optional values that belong to the group inside and outer flags in the middle
Options { rect: [Rect { rect: (), width: 10, height: 10, painted: true }, Rect { rect: (), width: 10, height: 10, painted: false }], mirror: true }
But with adjacent they cannot interleave
Error: expected --width=PX, pass --help for usage information
Or have items that don’t belong to the group inside them
Error: expected --height=PX, pass --help for usage information
§Chaining commands
This example explains adjacent, but the same idea holds.
Parsing things like cmd1 --arg1 cmd2 --arg2 --arg3 cmd3 --flag
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
premium: bool,
commands: Vec<Cmd>,
}
#[derive(Debug, Clone)]
// shape of the variants doesn't really matter, let's use all of them :)
enum Cmd {
Eat(String),
Drink { coffee: bool },
Sleep { time: usize },
}
fn cmd() -> impl Parser<Cmd> {
let eat = positional::<String>("FOOD")
.to_options()
.descr("Performs eating action")
.command("eat")
.adjacent()
.map(Cmd::Eat);
let coffee = long("coffee")
.help("Are you going to drink coffee?")
.switch();
let drink = construct!(Cmd::Drink { coffee })
.to_options()
.descr("Performs drinking action")
.command("drink")
.adjacent();
let time = long("time").argument::<usize>("HOURS");
let sleep = construct!(Cmd::Sleep { time })
.to_options()
.descr("Performs taking a nap action")
.command("sleep")
.adjacent();
construct!([eat, drink, sleep])
}
pub fn options() -> OptionParser<Options> {
let premium = short('p')
.long("premium")
.help("Opt in for premium serivces")
.switch();
let commands = cmd().many();
construct!(Options { premium, commands }).to_options()
}
fn main() {
println!("{:?}", options().run())
}Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(short, long)]
/// Opt in for premium serivces
pub premium: bool,
#[bpaf(external(cmd), many)]
pub commands: Vec<Cmd>,
}
#[derive(Debug, Clone, Bpaf)]
pub enum Cmd {
#[bpaf(command, adjacent)]
/// Performs eating action
Eat(#[bpaf(positional("FOOD"))] String),
#[bpaf(command, adjacent)]
/// Performs drinking action
Drink {
/// Are you going to drink coffee?
coffee: bool,
},
#[bpaf(command, adjacent)]
/// Performs taking a nap action
Sleep {
#[bpaf(argument("HOURS"))]
time: usize,
},
}
fn main() {
println!("{:?}", options().run())
}Output
Example implements a parser that supports one of three possible commands:
Usage: app [-p] [COMMAND ...]...
- -p, --premium
- Opt in for premium serivces
- -h, --help
- Prints help information
- eat
- Performs eating action
- drink
- Performs drinking action
- sleep
- Performs taking a nap action
As usual every command comes with its own help
Performs drinking action
Usage: app drink [--coffee]
- --coffee
- Are you going to drink coffee?
- -h, --help
- Prints help information
Normally you can use one command at a time, but making commands adjacent lets
parser to succeed after consuming an adjacent block only and leaving leftovers for the rest of
the parser, consuming them as a Vec<Cmd> with many allows to chain multiple
items sequentially
Options { premium: false, commands: [Eat("Fastfood"), Drink { coffee: true }, Sleep { time: 5 }] }
The way this works is by running parsers for each command. In the first iteration eat succeeds,
it consumes eat fastfood portion and appends its value to the resulting vector. Then second
iteration runs on leftovers, in this case it will be drink --coffee sleep --time=5.
Here drink succeeds and consumes drink --coffee portion, then sleep parser runs, etc.
You can mix chained commands with regular arguments that belong to the top level parser
Options { premium: true, commands: [Sleep { time: 10 }, Eat("Bak Kut Teh"), Drink { coffee: false }] }
But not inside the command itself since values consumed by the command are not going to be adjacent
Error: expected FOOD, pass --help for usage information
§Capturing everything between markers
Parsing things like find . --exec foo {} -bar ; --more
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
exec: Option<Vec<OsString>>,
switch: bool,
}
fn exec() -> impl Parser<Option<Vec<OsString>>> {
// this defines starting token - "--exec"
let start = long("exec")
.help("Spawn a process for each file found")
.req_flag(());
// this consumes everything that is not ";"
let body = any("COMMAND", |s| (s != ";").then_some(s))
.help("Command and arguments, {} will be replaced with a file name")
.some("You need to pass some arguments to exec");
// this defines endint goken - ";"
let end = literal(";");
// this consumes everything between starting token and ending token
construct!(start, body, end)
// this makes it so everything between those tokens is consumed
.adjacent()
// drop the surrounding tokens leaving just the arguments
.map(|x| x.1)
// and make it optional so that instead of an empty Vec
// it is `None` when no `--exec` flags was passed.
.optional()
}
pub fn options() -> OptionParser<Options> {
let switch = short('s')
.long("switch")
.help("Regular top level switch")
.switch();
construct!(Options { exec(), switch }).to_options()
}
fn main() {
println!("{:?}", options().run())
}Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(external(execs))]
exec: Option<Vec<OsString>>,
#[bpaf(long, short)]
/// Regular top level switch
switch: bool,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(adjacent)]
struct Exec {
/// Spawn a process for each file found
exec: (),
#[bpaf(
any("COMMAND", not_semi),
some("Command and arguments, {} will be replaced with a file name")
)]
/// Command and arguments, {} will be replaced with a file name
body: Vec<OsString>,
#[bpaf(external(is_semi))]
end: (),
}
fn not_semi(s: OsString) -> Option<OsString> {
(s != ";").then_some(s)
}
fn is_semi() -> impl Parser<()> {
// TODO - support literal in bpaf_derive
literal(";")
}
// a different alternative would be to put a singular Exec
fn execs() -> impl Parser<Option<Vec<OsString>>> {
exec().map(|e| e.body).optional()
}
fn main() {
println!("{:?}", options().run())
}Output
Generated --help message is somewhat descriptive of the purpose
Usage: app [--exec COMMAND... ;] [-s]
- --exec
- Spawn a process for each file found
- COMMAND
- Command and arguments, {} will be replaced with a file name
- -s, --switch
- Regular top level switch
- -h, --help
- Prints help information
You can have as many items between --exec and ; as you want, they all will be captured
inside the exec vector. Extra options can go either before or after the block.
Options { exec: Some(["foo", "--bar"]), switch: true }
This example uses some to make sure there are some parameters, but that’s
optional.
Error: --exec is not expected in this context
§Multi-value arguments with optional flags
Parsing things like --foo ARG1 --flag --inner ARG2
So you can parse things while parsing things. Not sure why you might need this, but you can :)
#[derive(Debug, Clone)]
pub struct Options {
meal: Vec<Meal>,
premium: bool,
}
#[derive(Debug, Clone)]
struct Meal {
m: (),
spicy: Option<usize>,
drink: bool,
dish: usize,
}
/// You can mix all sorts of things inside the adjacent group
fn meal() -> impl Parser<Meal> {
let m = short('o')
.long("meal")
.help("A meal [o]rder consists of a main dish with an optional drink")
.req_flag(());
let spicy = long("spicy")
.help("On a scale from 1 to a lot, how spicy do you want your meal?")
.argument::<usize>("SPICY")
.optional();
let drink = long("drink")
.help("Do you want drink with your meal?")
.switch();
let dish = positional::<usize>("DISH").help("Main dish number");
construct!(Meal {
m,
spicy,
drink,
dish
})
.adjacent()
}
pub fn options() -> OptionParser<Options> {
let premium = short('p')
.long("premium")
.help("Do you want to opt in for premium service?")
.switch();
let meal = meal().many();
construct!(Options { meal, premium }).to_options()
}
fn main() {
println!("{:?}", options().run())
}Output
Usage: app [-o [--spicy=SPICY] [--drink] DISH]... [-p]
- -o, --meal
- A meal [o]rder consists of a main dish with an optional drink
- --spicy=SPICY
- On a scale from 1 to a lot, how spicy do you want your meal?
- --drink
- Do you want drink with your meal?
- DISH
- Main dish number
- -p, --premium
- Do you want to opt in for premium service?
- -h, --help
- Prints help information
Let’s start simple - a single flag accepts a bunch of stuff, and eveything is present
Options { meal: [Meal { m: (), spicy: Some(10), drink: true, dish: 330 }], premium: false }
You can omit some parts, but also have multiple groups thank to many
Options { meal: [Meal { m: (), spicy: None, drink: true, dish: 100 }, Meal { m: (), spicy: Some(10), drink: false, dish: 30 }, Meal { m: (), spicy: None, drink: false, dish: 50 }], premium: false }
As usual it can be mixed with standalone flags
Options { meal: [Meal { m: (), spicy: None, drink: false, dish: 42 }], premium: true }
Thanks to many whole meal part is optional
Options { meal: [], premium: true }
Error messages should be somewhat descriptive
Error: expected DISH, pass --help for usage information
§Performance and other considerations
bpaf can run adjacently restricted parsers multiple times to refine the guesses. It’s
best not to have complex inter-fields verification since they might trip up the detection
logic: instead of restricting, for example “sum of two fields to be 5 or greater” inside the
adjacent parser, you can restrict it outside, once adjacent done the parsing.
There’s also similar method adjacent that allows to restrict argument
parser to work only for arguments where both key and a value are in the same shell word:
-f=bar or -fbar, but not -f bar.