Function bpaf::any

source ·
pub fn any<I, T, F>(metavar: &str, check: F) -> ParseAny<T>
where I: FromStr + 'static, F: Fn(I) -> Option<T> + 'static, <I as FromStr>::Err: Display,
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

$ app --help

Usage: app [-t] [REST]...

Available positional items:
REST
app will pass anything unused to a child process

Available options:
-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

$ app --turbo git commit -m "hello world"
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

$ app git commit -m="hello world" --turbo
Options { turbo: true, rest: ["git", "commit", "-m=hello world"] }
$ app -- git commit -m="hello world" --turbo
Options { turbo: false, rest: ["git", "commit", "-m=hello world", "--turbo"] }
$ app 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

$ app --help

Usage: app [-t] [+backing | -backing] [+xinerama | -xinerama]

Available options:
-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

$ app
Options { turbo: false, backing: false, xinerama: true }

And can handle custom values

$ app --turbo -xinerama +backing
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

$ app --turbo -xinerama +backin
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

$ app --help

Usage: app [bs=BLOCK] [count=NUM] of=FILE [+turbo]

Available options:
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

$ app
Error: expected of=FILE, pass --help for usage information
$ app of=simple.txt
Options { block_size: 1024, count: 1, output_file: "simple.txt", turbo: false }

Since options are defined with anywhere - order doesn’t matter

$ app bs=10 of=output.rs +turbo
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
$ app +turbo bs=10 of=output.rs
Options { block_size: 10, count: 1, output_file: "output.rs", turbo: true }
$ app bs=65536 count=12 of=hello_world.rs
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?
examples/compression.rs (lines 6-12)
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn compression() -> impl Parser<usize> {
    any::<isize, _, _>("COMP", |x: isize| {
        if (-9..=-1).contains(&x) {
            Some(x.abs().try_into().unwrap())
        } else {
            None
        }
    })
    .metavar(&[
        ("-1", Style::Literal),
        (" to ", Style::Text),
        ("-9", Style::Literal),
    ])
    .help("Compression level")
    .anywhere()
}
More examples
Hide additional examples
examples/xorg.rs (lines 14-22)
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
fn toggle_options(meta: &'static str, name: &'static str, help: &'static str) -> impl Parser<bool> {
    any(meta, move |s: String| {
        if let Some(suf) = s.strip_prefix('+') {
            (suf == name).then_some(true)
        } else if let Some(suf) = s.strip_prefix('-') {
            (suf == name).then_some(false)
        } else {
            None
        }
    })
    .help(help)
    .anywhere()
}

// matches literal +ext and -ext followed by extension name
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/dd.rs (line 23)
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn tag<T>(name: &'static str, meta: &str, help: &'static str) -> impl Parser<T>
where
    T: FromStr,
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    // 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
        .metavar(&[(name, Style::Literal), (meta, Style::Metavar)][..])
        .help(help)
        .anywhere()
        .parse(|s| s.parse())
}
examples/find.rs (line 47)
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 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()
}