pub fn any<I, T, F>(metavar: &str, check: F) -> ParseAny<T>
Expand description
Parse a single arbitrary item from a command line
any
is designed to consume items that don’t fit into the usual flag
/switch
/argument
/positional
/
command
classification, in most cases you don’t need to use it
By default, any
behaves similarly to positional
so you should be using it near the
rightmost end of the consumer struct and it will only try to parse the first unconsumed item
on the command line. It is possible to lift this restriction by calling
anywhere
on the parser.
check
argument is a function from any type I
that implements FromStr
to T
.
Usually this should be String
or OsString
, but feel free to experiment. When
running any
tries to parse an item on a command line into that I
and applies the check
function. If the check
succeeds - parser any
succeeds and produces T
, otherwise it behaves
as if it hasn’t seen it. If any
works in anywhere
mode - it will try to parse all other
unconsumed items, otherwise, any
fails.
§Use any
to capture the remaining arguments
Normally you would use positional
with strict
annotation for
that, but using any allows you to blur the boundary between arguments for child process and self
process a bit more.
Combinatoric example
#[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", |x| (x != "--help").then_some(x))
.help("app will pass anything unused to a child process")
.many();
construct!(Options { turbo, rest }).to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
#[bpaf(short, long)]
/// Engage the turbo mode
turbo: bool,
#[bpaf(any("REST", not_help), many)]
/// app will pass anything unused to a child process
rest: Vec<OsString>,
}
fn not_help(s: OsString) -> Option<OsString> {
if s == "--help" {
None
} else {
Some(s)
}
}
fn main() {
println!("{:?}", options().run())
}
Output
--help
keeps working for as long as any
captures only intended values - that is it ignores
--help
flag specifically
Usage: app [-t] [REST]...
- REST
- app will pass anything unused to a child process
- -t, --turbo
- Engage the turbo mode
- -h, --help
- Prints help information
You can mix any
with regular options, here switch
turbo
works because it goes
before rest
in the parser declaration
Options { turbo: true, rest: ["git", "commit", "-m", "hello world"] }
“before” in the previous line means in the parser definition, not on the user input, here
--turbo
gets consumed by turbo
parser even the argument goes
Options { turbo: true, rest: ["git", "commit", "-m=hello world"] }
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
§Use any
to parse a non standard flag
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
turbo: bool,
backing: bool,
xinerama: bool,
}
fn toggle_option(name: &'static str, help: &'static str) -> impl Parser<bool> {
// parse +name and -name into a bool
any::<String, _, _>(name, move |s: String| {
if let Some(rest) = s.strip_prefix('+') {
(rest == name).then_some(true)
} else if let Some(rest) = s.strip_prefix('-') {
(rest == name).then_some(false)
} else {
None
}
})
// set a custom usage and help metavariable
.metavar(
&[
("+", Style::Literal),
(name, Style::Literal),
(" | ", Style::Text),
("-", Style::Literal),
(name, Style::Literal),
][..],
)
// set a custom help description
.help(help)
// apply this parser to all unconsumed items
.anywhere()
}
pub fn options() -> OptionParser<Options> {
let backing = toggle_option("backing", "Enable or disable backing")
.fallback(false)
.debug_fallback();
let xinerama = toggle_option("xinerama", "enable or disable Xinerama")
.fallback(true)
.debug_fallback();
let turbo = short('t')
.long("turbo")
.help("Engage the turbo mode")
.switch();
construct!(Options {
turbo,
backing,
xinerama,
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Engage the turbo mode
#[bpaf(short, long)]
turbo: bool,
#[bpaf(external(backing), fallback(false), debug_fallback)]
backing: bool,
#[bpaf(external(xinerama), fallback(true), debug_fallback)]
xinerama: bool,
}
fn toggle_option(name: &'static str, help: &'static str) -> impl Parser<bool> {
// parse +name and -name into a bool
any::<String, _, _>(name, move |s: String| {
if let Some(rest) = s.strip_prefix('+') {
(rest == name).then_some(true)
} else if let Some(rest) = s.strip_prefix('-') {
(rest == name).then_some(false)
} else {
None
}
})
// set a custom usage and help metavariable
.metavar(
&[
("+", Style::Literal),
(name, Style::Literal),
(" | ", Style::Text),
("-", Style::Literal),
(name, Style::Literal),
][..],
)
// set a custom help description
.help(help)
// apply this parser to all unconsumed items
.anywhere()
}
fn backing() -> impl Parser<bool> {
toggle_option("backing", "Enable or disable backing")
}
fn xinerama() -> impl Parser<bool> {
toggle_option("xinerama", "enable or disable Xinerama")
}
fn main() {
println!("{:?}", options().run())
}
Output
--help
message describes all the flags as expected
Usage: app [-t] [+backing | -backing] [+xinerama | -xinerama]
- -t, --turbo
- Engage the turbo mode
- +backing | -backing
- Enable or disable backing
- [default: false]
- +xinerama | -xinerama
- enable or disable Xinerama
- [default: true]
- -h, --help
- Prints help information
Parser obeys the defaults
Options { turbo: false, backing: false, xinerama: true }
And can handle custom values
Options { turbo: true, backing: true, xinerama: false }
bpaf
won’t be able to generate good error messages or suggest to fix typos to users since it
doesn’t really knows what the function inside any
is going to consume
Error: +backin is not expected in this context
§Use any
to parse a non standard argument
Normally any
would try to display itself as a usual metavariable in the usage line and
generated help, you can customize that with metavar
method:
Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
block_size: usize,
count: usize,
output_file: String,
turbo: bool,
}
/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
fn tag<T>(name: &'static str, meta: &str, help: impl Into<Doc>) -> impl Parser<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
// closure inside checks if command line argument starts with a given name
// and if it is - it accepts it, otherwise it behaves like it never saw it
// it is possible to parse OsString here and strip the prefix with
// `os_str_bytes` or a similar crate
any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
// this defines custom metavar for the help message
// so it looks like something it designed to parse
.metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
.help(help)
// this makes it so tag parser tries to read all (unconsumed by earlier parsers)
// item on a command line instead of trying and failing on the first one
.anywhere()
// At this point parser produces `String` while consumer might expect some other
// type. [`parse`](Parser::parse) handles that
.parse(|s| s.parse())
}
pub fn options() -> OptionParser<Options> {
let block_size = tag("bs=", "BLOCK", "How many bytes to read at once")
.fallback(1024)
.display_fallback();
let count = tag("count=", "NUM", "How many blocks to read").fallback(1);
let output_file = tag("of=", "FILE", "Save results into this file");
// this consumes literal value of "+turbo" locate and produces `bool`
let turbo = literal("+turbo")
.help("Engage turbo mode!")
.anywhere()
.map(|_| true)
.fallback(false);
construct!(Options {
block_size,
count,
output_file,
turbo
})
.to_options()
}
fn main() {
println!("{:?}", options().run())
}
Derive example
// This example is still technically derive API, but derive is limited to gluing
// things together and keeping macro complexity under control.
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
// `external` here and below derives name from the field name, looking for
// functions called `block_size`, `count`, etc that produce parsers of
// the right type.
// A different way would be to write down the name explicitly:
// #[bpaf(external(block_size), fallback(1024), display_fallback)]
#[bpaf(external, fallback(1024), display_fallback)]
block_size: usize,
#[bpaf(external, fallback(1))]
count: usize,
#[bpaf(external)]
output_file: String,
#[bpaf(external)]
turbo: bool,
}
fn block_size() -> impl Parser<usize> {
tag("bs=", "BLOCK", "How many bytes to read at once")
}
fn count() -> impl Parser<usize> {
tag("count=", "NUM", "How many blocks to read")
}
fn output_file() -> impl Parser<String> {
tag("of=", "FILE", "Save results into this file")
}
fn turbo() -> impl Parser<bool> {
literal("+turbo")
.help("Engage turbo mode!")
.anywhere()
.map(|_| true)
.fallback(false)
}
/// Parses a string that starts with `name`, returns the suffix parsed in a usual way
fn tag<T>(name: &'static str, meta: &str, help: impl Into<Doc>) -> impl Parser<T>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
// closure inside checks if command line argument starts with a given name
// and if it is - it accepts it, otherwise it behaves like it never saw it
// it is possible to parse OsString here and strip the prefix with
// `os_str_bytes` or a similar crate
any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
// this defines custom metavar for the help message
// so it looks like something it designed to parse
.metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
.help(help)
// this makes it so tag parser tries to read all (unconsumed by earlier parsers)
// item on a command line instead of trying and failing on the first one
.anywhere()
// At this point parser produces `String` while consumer might expect some other
// type. [`parse`](Parser::parse) handles that
.parse(|s| s.parse())
}
fn main() {
println!("{:?}", options().run())
}
Output
Instead of usual metavariable any
parsers take something that can represent any value
Usage: app [bs=BLOCK] [count=NUM] of=FILE [+turbo]
- bs=BLOCK
- How many bytes to read at once
- [default: 1024]
- count=NUM
- How many blocks to read
- of=FILE
- Save results into this file
- +turbo
- Engage turbo mode!
- -h, --help
- Prints help information
Output file is required in this parser, other values are optional
Error: expected of=FILE, pass --help for usage information
Options { block_size: 1024, count: 1, output_file: "simple.txt", turbo: false }
Since options are defined with anywhere
- order doesn’t matter
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
Options { block_size: 65536, count: 12, output_file: "hello_world.rs", turbo: false }
§See also
literal
- a specialized version of any
that tries to parse a fixed literal
Examples found in repository?
5fn compression() -> impl Parser<usize> {
6 any::<isize, _, _>("COMP", |x: isize| {
7 if (-9..=-1).contains(&x) {
8 Some(x.abs().try_into().unwrap())
9 } else {
10 None
11 }
12 })
13 .metavar(&[
14 ("-1", Style::Literal),
15 (" to ", Style::Text),
16 ("-9", Style::Literal),
17 ])
18 .help("Compression level")
19 .anywhere()
20}
More examples
13fn toggle_options(meta: &'static str, name: &'static str, help: &'static str) -> impl Parser<bool> {
14 any(meta, move |s: String| {
15 if let Some(suf) = s.strip_prefix('+') {
16 (suf == name).then_some(true)
17 } else if let Some(suf) = s.strip_prefix('-') {
18 (suf == name).then_some(false)
19 } else {
20 None
21 }
22 })
23 .help(help)
24 .anywhere()
25}
26
27// matches literal +ext and -ext followed by extension name
28fn extension() -> impl Parser<(String, bool)> {
29 let state = any("(+|-)ext", |s: String| match s.as_str() {
30 "-ext" => Some(false),
31 "+ext" => Some(true),
32 _ => None,
33 })
34 .anywhere();
35
36 let name = positional::<String>("EXT")
37 .help("Extension to enable or disable, see documentation for the full list");
38 construct!(state, name).adjacent().map(|(a, b)| (b, a))
39}
16fn tag<T>(name: &'static str, meta: &str, help: &'static str) -> impl Parser<T>
17where
18 T: FromStr,
19 <T as std::str::FromStr>::Err: std::fmt::Display,
20{
21 // it is possible to parse OsString here and strip the prefix with
22 // `os_str_bytes` or a similar crate
23 any("", move |s: String| Some(s.strip_prefix(name)?.to_owned()))
24 // this defines custom metavar for the help message
25 .metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
26 .help(help)
27 .anywhere()
28 .parse(|s| s.parse())
29}
42fn exec() -> impl Parser<Option<Vec<OsString>>> {
43 let tag = literal("-exec")
44 .help("for every file find finds execute a separate shell command")
45 .anywhere();
46
47 let item = any::<OsString, _, _>("ITEM", |s| (s != ";").then_some(s))
48 .help("command with its arguments, find will replace {} with a file name")
49 .many();
50
51 let endtag = any::<String, _, _>(";", |s| (s == ";").then_some(()))
52 .help("anything after literal \";\" will be considered a regular option again");
53
54 construct!(tag, item, endtag)
55 .adjacent()
56 .map(|triple| triple.1)
57 .optional()
58}
59
60/// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
61fn perm() -> impl Parser<Option<Perm>> {
62 fn parse_mode(input: &str) -> Result<Perms, String> {
63 let mut perms = Perms::default();
64 for c in input.chars() {
65 match c {
66 'r' => perms.read = true,
67 'w' => perms.write = true,
68 'x' => perms.exec = true,
69 _ => return Err(format!("{} is not a valid permission string", input)),
70 }
71 }
72 Ok(perms)
73 }
74
75 let tag = literal("-mode").anywhere();
76
77 // `any` here is used to parse an arbitrary string that can also start with dash (-)
78 // regular positional parser won't work here
79 let mode = any("MODE", Some)
80 .help("(perm | -perm | /perm), where perm is any subset of rwx characters, ex +rw")
81 .parse::<_, _, String>(|s: String| {
82 if let Some(m) = s.strip_prefix('-') {
83 Ok(Perm::All(parse_mode(m)?))
84 } else if let Some(m) = s.strip_prefix('/') {
85 Ok(Perm::Any(parse_mode(m)?))
86 } else {
87 Ok(Perm::Exact(parse_mode(&s)?))
88 }
89 });
90
91 construct!(tag, mode)
92 .adjacent()
93 .map(|pair| pair.1)
94 .optional()
95}