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 ;
type Accept = i32;
type Deny = String;
type Context = i32;
const DEC: = Decider ;
const SPEC: = ;
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 ;
// 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
// 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: = ;
// This decider accepts only a single number
const DEC: = Decider ;
let mut mapping = default;
mapping.register.unwrap;
let handler = mapping.lookup;
match handler
This library also allows partial lookups and iterating over the direct descendants in order to make autocomplete easy to implement for terminal interfaces.
use ;
type Deny = String;
type Context = i32;
const SPEC: =
;
const DEC: = Decider ;
let mut mapping = default;
mapping.register.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.unwrap.right.unwrap;
assert_eq!;
// 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.unwrap.left.unwrap;
let MappingEntry = mapping.get_direct_keys.next.unwrap;
assert_eq!;
assert!;
assert!;