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: P
inner parser closure
meta: Meta
metas for inner parsers
failfast: bool
To 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
.
Examples found in repository?
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
fn user() -> impl Parser<Option<String>> {
// match only literal "-user"
let tag = literal("-user").anywhere();
let value = positional("USER").help("User name");
construct!(tag, value)
.adjacent()
.map(|pair| pair.1)
.optional()
}
// parsers -exec xxx yyy zzz ;
fn exec() -> impl Parser<Option<Vec<OsString>>> {
let tag = literal("-exec")
.help("for every file find finds execute a separate shell command")
.anywhere();
let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
.help("command with its arguments, find will replace {} with a file name")
.many();
let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
.help("anything after literal \";\" will be considered a regular option again");
construct!(tag, item, endtag)
.adjacent()
.map(|triple| triple.1)
.optional()
}
/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
fn perm() -> impl Parser<Option<Perm>> {
fn parse_mode(input: &str) -> Result<Perms, String> {
let mut perms = Perms::default();
for c in input.chars() {
match c {
'r' => perms.read = true,
'w' => perms.write = true,
'x' => perms.exec = true,
_ => return Err(format!("{} is not a valid permission string", input)),
}
}
Ok(perms)
}
let tag = literal("-mode").anywhere();
// `any` here is used to parse an arbitrary string that can also start with dash (-)
// regular positional parser won't work here
let mode = any("MODE", Some)
.help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
.parse::<_, _, String>(|s: String| {
if let Some(m) = s.strip_prefix('-') {
Ok(Perm::All(parse_mode(m)?))
} else if let Some(m) = s.strip_prefix('/') {
Ok(Perm::Any(parse_mode(m)?))
} else {
Ok(Perm::Exact(parse_mode(&s)?))
}
});
construct!(tag, mode)
.adjacent()
.map(|pair| pair.1)
.optional()
}
More examples
28 29 30 31 32 33 34 35 36 37 38 39
fn extension() -> impl Parser<(String, bool)> {
let state = any("(+|-)ext", |s: String| match s.as_str() {
"-ext" => Some(false),
"+ext" => Some(true),
_ => None,
})
.anywhere();
let name = positional::<String>("EXT")
.help("Extension to enable or disable, see documentation for the full list");
construct!(state, name).adjacent().map(|(a, b)| (b, a))
}
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
fn opts() -> Opts {
let sensor = long("sensor").req_flag(());
let device = long("sensor-device")
.argument::<String>("DEVICE")
.complete(sensor_device_comp);
let name = long("sensor-name").argument::<String>("NAME");
// from_str needs to be replaced with `parse` that can deal with hex digits
let bus_id = long("sensor-i2c-bus").argument::<usize>("BUS");
let address = long("sensor-i2c-address").argument::<usize>("ADDRESS");
let sensors = construct!(Sensor {
sensor,
device,
name,
bus_id,
address
})
.adjacent()
.many();
construct!(Opts { sensors }).to_options().run()
}
Trait Implementations§
source§impl<T, P> Parser<T> for ParseCon<P>
impl<T, P> Parser<T> for ParseCon<P>
source§fn collect<C>(self) -> ParseCollect<Self, C, T>where
C: FromIterator<T>,
Self: Sized,
fn collect<C>(self) -> ParseCollect<Self, C, T>where
C: FromIterator<T>,
Self: Sized,
source§fn optional(self) -> ParseOptional<Self>
fn optional(self) -> ParseOptional<Self>
source§fn count(self) -> ParseCount<Self, T>
fn count(self) -> ParseCount<Self, T>
source§fn last(self) -> ParseLast<Self>
fn last(self) -> ParseLast<Self>
source§fn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
fn parse<F, R, E>(self, f: F) -> ParseWith<T, Self, F, E, R>
source§fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
fn map<F, R>(self, map: F) -> ParseMap<T, Self, F, R>
source§fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
fn guard<F>(self, check: F, message: &'static str) -> ParseGuard<Self, F>
source§fn fallback(self, value: T) -> ParseFallback<Self, T>
fn fallback(self, value: T) -> ParseFallback<Self, T>
source§fn fallback_with<F, E>(self, fallback: F) -> ParseFallbackWith<T, Self, F, E>
fn fallback_with<F, E>(self, fallback: F) -> ParseFallbackWith<T, Self, F, E>
source§fn hide(self) -> ParseHide<Self>
fn hide(self) -> ParseHide<Self>
source§fn hide_usage(self) -> ParseUsage<Self>
fn hide_usage(self) -> ParseUsage<Self>
source§fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
fn custom_usage<M>(self, usage: M) -> ParseUsage<Self>
source§fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
fn group_help<M: Into<Doc>>(self, message: M) -> ParseGroupHelp<Self>
source§fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
fn with_group_help<F>(self, f: F) -> ParseWithGroupHelp<Self, F>
source§fn complete<M, F>(self, op: F) -> ParseComp<Self, F>
fn complete<M, F>(self, op: F) -> ParseComp<Self, F>
autocomplete
only.source§fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
fn complete_shell(self, op: ShellComp) -> ParseCompShell<Self>
autocomplete
only.