5 个版本
0.1.4 | 2023年2月25日 |
---|---|
0.1.3 | 2023年2月25日 |
0.1.2 | 2020年2月3日 |
0.1.1 | 2019年9月15日 |
0.1.0 | 2019年9月15日 |
#808 在 命令行界面 中
每月下载量 37 次
在 gameshell 中使用
31KB
568 行
命令匹配引擎
此库将输入参数列表(如: ["example", "input", "123"]
)与能够处理这些输入的处理程序相匹配。
处理程序使用 Spec
(规范)格式注册
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);
在上面的示例中,SPEC
变量定义了到达 handler
的路径,首先需要 "example"
,然后是 "input"
,它接受单个整数。
如果验证器 accept_integer
失败,则命令查找也会失败。
规范将被收集到一个 Mapping
中,查找将在合并的 Spec
树中进行。
我们有一个独立的字面字符串和验证器是为了使在搜索树中找到下一个节点变得简单且明确。如果我们只使用验证器(可以是完全任意的),那么我们无法对树进行排序以实现 O(log n)
的搜索。这些固定的字面字符串搜索点也为我们提供了一个很好的方法来调试命令,如果它们没有匹配任何内容的话。
以下是实际查找示例,其中我们调用处理程序:(不幸的是,需要一些设置。)
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,
};
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);
}
}
此库还允许部分查找和遍历直接后代,以便于在终端界面中实现自动完成。
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,
};
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());
依赖项
~74KB