Function bpaf::positional

source ·
pub fn positional<T>(metavar: &'static str) -> ParsePositional<T>
Expand description

Parse a positional argument

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 *nix cat hello world and cat world hello would display contents of the same two files but in a different order.

When using combinatoric API you can specify the type with turbofish, for parsing types that don’t implement FromStr you can use consume a String/OsString first and parse it by hand.

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. The 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 this restriction holds

Without using -- bpaf would only accept items that don’t start with - as positional, you can use any to work around this restriction.

By default bpaf accepts positional items with or without -- where values permit, you can further restrict the parser to accept positional items only on the right side of -- using strict.

Combinatoric example
#[derive(Debug, Clone)]
pub struct Options {
    verbose: bool,
    crate_name: String,
    feature_name: Option<String>,
}

pub fn options() -> OptionParser<Options> {
    let verbose = short('v')
        .long("verbose")
        .help("Display detailed information")
        .switch();

    let crate_name = positional("CRATE").help("Crate name to use");

    let feature_name = positional("FEATURE")
        .help("Display information about this feature")
        .optional();

    construct!(Options {
        verbose,
        // You must place positional items and commands after
        // all other parsers
        crate_name,
        feature_name
    })
    .to_options()
}

fn main() {
    println!("{:?}", options().run())
}
Derive example
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Options {
    /// Display detailed information
    #[bpaf(short, long)]
    verbose: bool,

    // You must place positional items and commands after
    // all other parsers
    #[bpaf(positional("CRATE"))]
    /// Crate name to use
    crate_name: String,

    #[bpaf(positional("FEATURE"))]
    /// Display information about this feature
    feature_name: Option<String>,
}

fn main() {
    println!("{:?}", options().run())
}
Output

Positional items show up in a separate group of arguments if they contain a help message, otherwise they will show up only in Usage part.

$ app --help

Usage: app [-v] CRATE [FEATURE]

Available positional items:
CRATE
Crate name to use
FEATURE
Display information about this feature

Available options:
-v, --verbose
Display detailed information
-h, --help
Prints help information

You can mix positional items with regular items

$ app --verbose bpaf
Options { verbose: true, crate_name: "bpaf", feature_name: None }

And since bpaf API expects to have non positional items consumed before positional ones - you can use them in a different order. In this example bpaf corresponds to a crate_name field and --verbose – to verbose.

$ app bpaf --verbose
Options { verbose: true, crate_name: "bpaf", feature_name: None }

In previous examples optional field feature was missing, this one contains it.

$ app bpaf autocomplete
Options { verbose: false, crate_name: "bpaf", feature_name: Some("autocomplete") }

Users can use -- to tell bpaf to treat remaining items as positionals - this might be required to handle unusual items.

$ app bpaf -- --verbose
Options { verbose: false, crate_name: "bpaf", feature_name: Some("--verbose") }
$ app -- bpaf --verbose
Options { verbose: false, crate_name: "bpaf", feature_name: Some("--verbose") }

Without using -- bpaf would only accept items that don’t start with - as positional.

$ app --detailed
Error: expected CRATE, got --detailed. Pass --help for usage information
$ app --verbose
Error: expected CRATE, pass --help for usage information

You can use any to work around this restriction.

Examples found in repository?
examples/shared_args.rs (line 25)
24
25
26
fn shared() -> impl Parser<Vec<String>> {
    positional("ARG").many()
}
More examples
Hide additional examples
examples/find.rs (line 34)
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
106
107
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()
}

pub fn options() -> OptionParser<Options> {
    let paths = positional::<PathBuf>("PATH").many();

    construct!(Options {
        exec(),
        user(),
        perm(),
        paths,
    })
    .to_options()
}
examples/ex_positional.rs (line 17)
12
13
14
15
16
17
18
19
20
21
fn main() {
    let value = long("value")
        .help("Mysterious value")
        .argument::<u32>("VAL")
        .fallback(42);
    let files = positional::<PathBuf>("FILE").many();
    let opts = construct!(Options { value, files }).to_options().run();

    println!("{:#?}", opts);
}
examples/numeric_prefix.rs (line 22)
21
22
23
24
25
26
27
28
29
pub fn options() -> OptionParser<Options> {
    let prefix = positional::<usize>("PREFIX")
        .help("Optional numeric command prefix")
        .optional()
        .catch();
    let command = positional::<String>("COMMAND").help("Required command name");

    construct!(Options { prefix, command }).to_options()
}
examples/xorg.rs (line 36)
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))
}
examples/derive_commands.rs (line 35)
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
fn feature_if() -> impl Parser<Option<String>> {
    // here feature starts as any string on a command line that does not start with a dash
    positional::<String>("FEATURE")
        // guard restricts it such that it can't be a valid version
        .guard(move |s| !is_version(s), "")
        // last two steps describe what to do with strings in this position but are actually
        // versions.
        // optional allows parser to represent an ignored value with None
        .optional()
        // and catch lets optional to handle parse failures coming from guard
        .catch()
}

fn version_if() -> impl Parser<Option<String>> {
    positional::<String>("VERSION")
        .guard(move |s| is_version(s), "")
        .optional()
        .catch()
}