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 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 hands.

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 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::<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("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 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") }

You can consume items of any type that implements FromStr.

% 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?
examples/shared_args.rs (line 25)
24
25
26
fn shared() -> impl Parser<Vec<String>> {
    positional("ARG").many()
}
More examples
Hide additional examples
examples/derive_commands.rs (line 34)
33
34
35
36
37
38
39
40
41
42
43
fn feature_if() -> impl Parser<Option<String>> {
    positional::<String>("FEATURE")
        .guard(move |s| !is_version(s), "")
        .optional()
}

fn version_if() -> impl Parser<Option<String>> {
    positional::<String>("VERSION")
        .guard(move |s| is_version(s), "")
        .optional()
}
src/batteries.rs (line 161)
157
158
159
160
161
162
163
164
165
166
167
pub fn cargo_helper<P, T>(cmd: &'static str, parser: P) -> impl Parser<T>
where
    P: Parser<T>,
{
    let skip = positional::<String>("cmd")
        .guard(move |s| s == cmd, "")
        .optional()
        .catch()
        .hide();
    construct!(skip, parser).map(|x| x.1)
}
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);
}
src/lib.rs (line 1661)
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
pub fn cargo_helper<P, T>(cmd: &'static str, parser: P) -> impl Parser<T>
where
    T: 'static,
    P: Parser<T>,
{
    let skip = positional::<String>("cmd")
        .guard(move |s| s == cmd, "")
        .optional()
        .catch()
        .hide();
    construct!(skip, parser).map(|x| x.1)
}
examples/find.rs (line 34)
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()
}