[][src]Crate cmdmat

A command matching engine

This library matches a list of input parameters such as: ["example", "input", "123"] to a handler that is able to handle these inputs.

The handlers are registered using the Spec (specification) format:

use cmdmat::{Decider, Decision, Spec, SVec};

type Accept = i32;
type Deny = String;
type Context = i32;

fn handler(_ctx: &mut Context, _args: &[Accept]) -> Result<String, String> {
    Ok("".into())
}

fn accept_integer(input: &[&str], out: &mut SVec<Accept>) -> Decision<Deny> {
    if input.len() != 1 {
        return Decision::Deny("Require exactly 1 input".into());
    }
    if let Ok(num) = input[0].parse::<Accept>() {
        out.push(num);
        Decision::Accept(1)
    } else {
        Decision::Deny("Unable to get number".into())
    }
}

const DEC: Decider<Accept, Deny> = Decider {
    description: "<i32>",
    decider: accept_integer,
};

const SPEC: Spec<Accept, Deny, Context> = (&[("example", None), ("input", Some(&DEC))], handler);

In the above the SPEC variable defines a path to get to the handler, requiring first "example" with no validator None, then followed by "input" which takes a single integer.

If the validator accept_integer fails, then the command lookup will also fail.

The Specs will be collected inside a Mapping, where lookup will happen among a tree of merged Specs.

The reason we have a separate literal string and validator is to make it easy and unambiguous to find the next node in the search tree. If we only used validators (which can be completely arbitrary), then we can not sort a tree to make searching O(log n). These fixed literal search points also give us a good way to debug commands if they happen to not match anything.

Here is an example with actual lookup where we call a handler: (Unfortunately, a bit of setup is required.)

use cmdmat::{Decider, Decision, Mapping, Spec, SVec};

// The accept type is the type enum containing accepted tokens, parsed into useful formats
// the list of accepted input is at last passed to the finalizer
#[derive(Debug)]
enum Accept {
    I32(i32),
}

// Deny is the type returned by a decider when it denies an input (the input is invalid)
type Deny = String;

// The context is the type on which "finalizers" (the actual command handler) will run
type Context = i32;

// This is a `spec` (command specification)
const SPEC: Spec<Accept, Deny, Context> = (&[("my-command-name", Some(&DEC))], print_hello);

fn print_hello(_ctx: &mut Context, args: &[Accept]) -> Result<String, String> {
    println!["Hello world!"];
    assert_eq![1, args.len()];
    println!["The args I got: {:?}", args];
    Ok("".into())
}

// This decider accepts only a single number
fn decider_function(input: &[&str], out: &mut SVec<Accept>) -> Decision<Deny> {
    if input.is_empty() {
        return Decision::Deny("No argument provided".into());
    }
    let num = input[0].parse::<i32>();
    if let Ok(num) = num {
        out.push(Accept::I32(num));
        Decision::Accept(1)
    } else {
        Decision::Deny("Number is not a valid i32".into())
    }
}

const DEC: Decider<Accept, Deny> = Decider {
    description: "<i32>",
    decider: decider_function,
};

fn main() {
    let mut mapping = Mapping::default();
    mapping.register(SPEC).unwrap();

    let handler = mapping.lookup(&["my-command-name", "123"]);

    match handler {
        Ok((func, buf)) => {
            let mut ctx = 0i32;
            func(&mut ctx, &buf); // prints hello world
        }
        Err(look_err) => {
            println!["{:?}", look_err];
            assert![false];
        }
    }
}

This library also allows partial lookups and iterating over the direct descendants in order to make autocomplete easy to implement for terminal interfaces.

use cmdmat::{Decider, Decision, Mapping, MappingEntry, Spec, SVec};

#[derive(Debug)]
enum Accept {
    I32(i32),
}
type Deny = String;
type Context = i32;

const SPEC: Spec<Accept, Deny, Context> =
    (&[("my-command-name", Some(&DEC)), ("something", None)], print_hello);

fn print_hello(_ctx: &mut Context, args: &[Accept]) -> Result<String, String> {
    Ok("".into())
}

fn decider_function(input: &[&str], out: &mut SVec<Accept>) -> Decision<Deny> {
    if input.is_empty() {
        return Decision::Deny("No argument provided".into());
    }
    let num = input[0].parse::<i32>();
    if let Ok(num) = num {
        out.push(Accept::I32(num));
        Decision::Accept(1)
    } else {
        Decision::Deny("Number is not a valid i32".into())
    }
}

const DEC: Decider<Accept, Deny> = Decider {
    description: "<i32>",
    decider: decider_function,
};

fn main() {
    let mut mapping = Mapping::default();
    mapping.register(SPEC).unwrap();

    // When a decider is "next-up", we get its description
    // We can't know in advance what the decider will consume because it is arbitrary code,
    // so we will have to trust its description to be valuable.
    let decider_desc = mapping.partial_lookup(&["my-command-name"]).unwrap().right().unwrap();
    assert_eq!["<i32>", decider_desc];

    // In this case the decider succeeded during the partial lookup, so the next step in the
    // tree is the "something" node.
    let mapping = mapping.partial_lookup(&["my-command-name", "123"]).unwrap().left().unwrap();
    let MappingEntry { literal, decider, finalizer, submap } = mapping.get_direct_keys().next().unwrap();
    assert_eq!["something", literal];
    assert![decider.is_none()];
    assert![finalizer.is_some()];
}

Structs

Decider

A decider is a function taking in a sequence of tokens and an output array

Mapping

Node in the matching tree

MappingEntry

An entry in the mapping table

Enums

Decision

A decision contains information about token consumption by the decider

Either

Either sum type

LookError

Errors happening during lookup of a command.

RegError

Errors we can get by registering specs.

Type Definitions

FinWithArgs

Finalizer with associated vector of arguments

Finalizer

A finalizer is the function that runs to handle the entirety of the command after it has been verified by the deciders.

MapOrDesc

Either a mapping or a descriptor of a mapping

SVec

A specific-sized small vector

Spec

The command specification format