Crate parkour[][src]

A fast, extensible, command-line arguments parser.

This library is very new, so expect regular breaking changes. If you find a bug or lacking documentation, don't hesitate to open an issue or a pull request.

This crate started as an experiment, so I'm not sure yet if I want to maintain it long-term. See here for more.

Getting started

Parkour requires const generics. The first rust version that supports them is Rust 1.51 (rustc 1.51.0-beta.2). You can install it with rustup default beta.

It's recommended to import the prelude:

use parkour::prelude::*;

First, create a struct containing all the data you want to parse. For example:

struct Command {
    color: Option<bool>,
    show: Option<Show>,
}

struct Show {
    pos1: String,
    out: ColorSpace,
    size: u8,
}

enum ColorSpace {
    Rgb,
    Cmy,
    Cmyk,
    Hsv,
    Hsl,
    CieLab,
}

bool, u8 and String can all be parsed by default. To parse ColorSpace, we have to implement the FromInputValue trait. This easiest by using the derive macro:

#[derive(FromInputValue)]
enum ColorSpace {
    Rgb,
    Cmy,
    Cmyk,
    Hsv,
    Hsl,
    CieLab,
}

This parses the names of the enum variants case-insensitively. When an invalid value is provided, the error message will say something like:

unexpected value, got `foo`, expected rgb, cmy, cmyk, hsv, hsl or cielab

Now let's implement Show as a subcommand. Unfortunately, there's no convenient derive macro (yet):

struct Show {
    pos1: String,
    color_space: ColorSpace,
    size: u8,
}

impl FromInput for Show {
    type Context = ();

    fn from_input<P: Parse>(input: &mut P, _: &()) -> Result<Self, parkour::Error> {
        if input.parse_command("show") {
            let mut pos1 = None;
            let mut color_space = None;
            let mut size = None;

            while !input.is_empty() {
                if SetOnce(&mut color_space)
                    .apply(input, &Flag::LongShort("color-space", "c").into())? {
                    continue;
                }

                if SetOnce(&mut size)
                    .apply(input, &Flag::LongShort("size", "s").into())? {
                    continue;
                }

                if pos1.is_none()
                    && SetPositional(&mut pos1).apply(input, &"pos1".into())? {
                    continue;
                }

                input.expect_empty()?;
            }

            Ok(Show {
                pos1: pos1.ok_or_else(|| parkour::Error::missing_argument("pos1"))?,
                color_space: color_space
                    .ok_or_else(|| parkour::Error::missing_argument("--color-space"))?,
                size: size.unwrap_or(4),
            })
        } else {
            Err(parkour::Error::no_value())
        }
    }
}

To parse a subcommand, we implement the FromInput trait. We first check if the next argument is the word show. If that's the case, we iterate over the remaining input, until it is empty.

In the subcommand, we expect two named arguments (--color-space and --size) and a positional argument (pos). Therefore, in each iteration, we first check if we can parse the named arguments, and then the positional argument. If none of them succeeds and there is still input left, then input.expect_empty()? throws an error.

Producing the Show struct is rather straightforward (pos and --color-space are required, --size defaults to 4). However, parsing the values involves some type system magic. SetOnce and SetPositional are actions, they check if the referenced types can be parsed, and if so, assign the parsed value to the variable automatically. They also ensure that each argument is parsed at most once.

Whenever something is parsed, a context is provided that can contain information about how the value should be parsed. In the above example, Flag::LongShort("color-space", "c").into() is a context that instructs the parser to parse the color space after the --color-space or the -c flag.

The main command can be implemented similarly:

struct Command {
    color: Option<bool>,
    show: Option<Show>,
}

impl FromInput for Command {
    type Context = ();

    fn from_input<P: Parse>(input: &mut P, _: &()) -> Result<Self, parkour::Error> {
        // discard the first argument, which is the path to the executable
        input.bump_argument().unwrap();

        let mut show = None;
        let mut color = None;

        while !input.is_empty() {
            if SetOnce(&mut color).apply(input, &Flag::LongShort("color", "c").into())? {
                continue;
            }

            if SetSubcommand(&mut show).apply(input, &())? {
                continue;
            }

            input.expect_empty()?;
        }
        Ok(Command { show, color })
    }
}

This is pretty self-explanatory. Now let's proceed to the main function:

use std::error::Error;

fn main() {
    match Command::from_input(&mut parkour::parser(), &()) {
        Ok(command) => {
            println!("parsed successfully");
        }
        Err(e) if e.is_early_exit() => {}
        Err(e) => {
            eprint!("{}", e);
            let mut source = e.source();
            while let Some(s) = source {
                eprint!(": {}", s);
                source = s.source();
            }
            eprintln!();
        }
    }
}

The parser function creates a new parser instance, which implements Parse. This is used to parse the Command. If it fails, we print the error with its sources. I will implement a more convenient method for this, I just haven't gotten around to it yet. I also plan to implement ANSI color support.

What's with the e.is_early_exit(), you might wonder? This error is returned when parsing was aborted and can be ignored. This error can be used e.g. when the --help flag is encountered:

impl FromInput for Command {
    type Context = ();

    fn from_input<P: Parse>(input: &mut P, _: &()) -> Result<Self, parkour::Error> {
        // <snip>
        while !input.is_empty() {
            if input.parse_long_flag("help") || input.parse_short_flag("h") {
                println!("Usage:\n\
                    my-program [-h,--help]\n\
                    my-program show POS1 -c,--color-space VALUE [-s,--size N]");

                return Err(parkour::Error::early_exit());
            }

            // <snip>
        }
        Ok(Command { show, color })
    }
}

There is one special case that isn't handled yet: The argument -- usually causes the remaining tokens to be treated as positional arguments, even if they start with a dash. This is easily implemented:

impl FromInput for Command {
    type Context = ();

    fn from_input<P: Parse>(input: &mut P, _: &()) -> Result<Self, parkour::Error> {
        // <snip>
        while !input.is_empty() {
            if input.parse_long_flag("") {
                input.set_ignore_dashes(true);
                continue;
            }

            // <snip>
        }
        Ok(Command { show, color })
    }
}

Unfortunately, this must be repeated in every subcommand.

Modules

actions

Actions are used to store the parsed command-line arguments in local variables. Actions can make sure that arguments are specified at most once.

impls

This module contains the implementations of the crate::FromInput and crate::FromInputValue traits.

prelude

A prelude to make it easier to import all the needed types and traits. Use it like this:

util

Several utility types.

Structs

Error

The error type when parsing command-line arguments. You can create an Error by creating an ErrorInner and converting it with .into().

StringInput

The default input type for argument parsing. This is generic over its iterator type and can be used with std::env::args. See StringInput::new() for more information.

Enums

ErrorInner

The error type when parsing command-line arguments

Traits

FromInput

Trait for extracting information from the command-line input. This is implemented for flags, positional and named arguments, subcommands, etc.

FromInputValue

Trait for parsing a value. A value can be

Input

The trait for types that can produce tokens from a list of command-line arguments.

Parse

An extension trait of palex::Input, the trait for types that can produce tokens from a list of command-line arguments.

Functions

parser

Create a new instance of the Parse trait, which can be used to parse the command-line arguments of the program.

Derive Macros

FromInputValue