Function bpaf::positional
source · [−]pub fn positional<T>(metavar: &'static str) -> ParsePositional<T>
Expand description
Positional argument in utf8 (String
) encoding
For named flags and arguments ordering generally doesn’t matter: most programs would
understand -O2 -v
the same way as -v -O2
, but for positional items order matters: in unix
cat hello world
and cat world hello
would display contents of the same two files but in
different order.
When using combinatoring API you must specify the type with turbofish, for parsing types
that don’t implement FromOsStr
you can use FromUtf8
helper tag.
fn parse_pos() -> impl Parser<usize> {
positional::<usize>("POS")
}
Important restriction
To parse positional arguments from a command line you should place parsers for all your
named values before parsers for positional items and commands. In derive API fields parsed as
positional items or commands should be at the end of your struct
/enum
. Same rule applies
to parsers with positional fields or commands inside: such parsers should go to the end as well.
Use check_invariants
in your test to ensure correctness.
For example for non positional non_pos
and positional pos
parsers
let valid = construct!(non_pos(), pos());
let invalid = construct!(pos(), non_pos());
bpaf
panics during help generation unless if this restriction holds
Combinatoric usage
#[derive(Debug, Clone)]
pub struct Options {
coin: Coin,
file: PathBuf,
name: Option<String>,
}
/// A custom datatype that doesn't implement [`FromOsStr`] but implements [`FromStr`]
#[derive(Debug, Clone, Copy)]
enum Coin {
Heads,
Tails,
}
impl FromStr for Coin {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"heads" => Ok(Coin::Heads),
"tails" => Ok(Coin::Tails),
_ => Err(format!("Expected 'heads' or 'tails', got '{}'", s)),
}
}
}
pub fn options() -> OptionParser<Options> {
let file = positional::<PathBuf>("FILE").help("File to use");
// sometimes you can get away with not specifying type in positional's turbofish
let coin = long("coin")
.help("Coin toss results")
.argument::<FromUtf8<Coin>>("COIN")
.fallback(Coin::Heads);
let name = positional::<String>("NAME")
.help("Name to look for")
.optional();
construct!(Options { coin, file, name }).to_options()
}
Derive usage
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
/// Coin toss results
#[bpaf(argument::<FromUtf8<Coin>>("COIN"), fallback(Coin::Heads))]
coin: Coin,
/// File to use
#[bpaf(positional::<PathBuf>("FILE"))]
file: PathBuf,
/// Name to look for
#[bpaf(positional("NAME"))]
name: Option<String>,
}
/// A custom datatype that doesn't implement [`FromOsStr`] but implements [`FromStr`]
#[derive(Debug, Clone, Copy)]
enum Coin {
Heads,
Tails,
}
impl FromStr for Coin {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"heads" => Ok(Coin::Heads),
"tails" => Ok(Coin::Tails),
_ => Err(format!("Expected 'heads' or 'tails', got '{}'", s)),
}
}
}
Examples
Positionals are consumed left to right, one at a time, no skipping unless the value is optional
% app main.rs
Options { coin: Heads, file: "main.rs", name: None }
Both positionals are present
% app main.rs hello
Options { coin: Heads, file: "main.rs", name: Some("hello") }
To parse items without having to write FromOsStr
instance you can use FromUtf8
helper
type?
% app main.rs --coin tails
Options { coin: Tails, file: "main.rs", name: None }
Only name
is optional in this example, not specifying file
is a failure
% app
Expected <FILE>, pass --help for usage information
And usage information
% app --help
Usage: [--coin COIN] <FILE> [<NAME>]
Available positional items:
<FILE> File to use
<NAME> Name to look for
Available options:
--coin <COIN> Coin toss results
-h, --help Prints help information
Examples found in repository?
More examples
30 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 96 97 98 99 100 101 102 103 104 105
fn user() -> impl Parser<Option<String>> {
let tag = any::<String>("TAG")
.guard(|s| s == "-user", "not user")
.hide();
let value = positional::<String>("USER");
construct!(tag, value)
.anywhere()
.map(|pair| pair.1)
.optional()
.catch()
}
// parsers -exec xxx yyy zzz ;
fn exec() -> impl Parser<Option<Vec<OsString>>> {
let tag = any::<String>("-exec")
.help("-exec /path/to/command flags and options ;")
.guard(|s| s == "-exec", "not find");
let item = any::<OsString>("ITEM")
.guard(|s| s != ";", "not word")
.many()
.catch()
.hide();
let endtag = any::<String>("END").guard(|s| s == ";", "not eot").hide();
construct!(tag, item, endtag)
.anywhere()
.map(|triple| triple.1)
.optional()
.catch()
}
/// 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 = any::<String>("-mode").help("-mode (perm | -perm | /perm)");
let mode = any::<String>("mode")
.parse::<_, _, String>(|s| {
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)?))
}
})
.hide();
construct!(tag, mode)
.anywhere()
.map(|pair| pair.1)
.optional()
.catch()
}
pub fn options() -> OptionParser<Options> {
let paths = positional::<PathBuf>("PATH").many();
construct!(Options {
exec(),
user(),
perm(),
paths,
})
.to_options()
}
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
fn main() {
// defining a parser in a usual way
let width = short('w').argument::<usize>("WIDTH").fallback(10);
let height = short('h').argument::<usize>("HEIGHT").fallback(10);
let parser = construct!(Opts { width, height });
let cmd = positional::<String>("")
.guard(|s| s == "cmd", "")
.optional()
.hide();
let combined_parser = construct!(cmd, parser).map(|x| x.1);
let opts = combined_parser.to_options().run();
println!("{:?}", opts);
}
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
fn extension() -> impl Parser<(String, bool)> {
let on = any::<String>("+ext")
.help("enable ext <EXT>")
.parse::<_, _, String>(|s| {
if s == "+ext" {
Ok(true)
} else {
Err(String::new())
}
});
let off = any::<String>("-ext")
.help("disable ext <EXT>")
.parse::<_, _, String>(|s| {
if s == "-ext" {
Ok(false)
} else {
Err(String::new())
}
});
let state = construct!([on, off]);
let name = positional::<String>("EXT").hide();
construct!(state, name).map(|(a, b)| (b, a)).anywhere()
}