Function bpaf::literal

source ·
pub fn literal(val: &'static str) -> ParseAny<()>
Expand description

A specialized version of any that consumes an arbitrary string

By default literal 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.

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

any - a generic version of literal that uses function to decide if value is to be parsed or not.

Examples found in repository?
examples/find.rs (line 33)
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
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()
}
More examples
Hide additional examples
examples/cargo-cmd.rs (line 22)
16
17
18
19
20
21
22
23
24
25
26
27
28
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 = literal("cmd").optional().hide();
    let combined_parser = construct!(cmd, parser).map(|x| x.1);

    let opts = combined_parser.to_options().run();

    println!("{:?}", opts);
}