2 个版本

0.1.1 2022 年 6 月 7 日
0.1.0 2022 年 3 月 16 日

#1343 in 解析器实现

MIT/Apache

160KB
2K SLoC

Github MIT/Apache 2.0 Crates.io Documentation

cmdparse

cmdparse,正如其名,将用户命令解析成任意的 Rust 类型。

通常,这个crate可以被看作是一个数据反序列化框架。它定义了一种易于交互式输入的语法,并包含将这种格式的输入转换为任意 Rust 类型的工具,以及自动为不完整用户输入建议补全。

它不适用于解析命令行参数,尽管它支持的语法与那些看起来相当相似。相反,它被设计用于解析在应用程序内部交互式输入的命令。当然,你并不局限于这个用例,可以自由地将 cmdparse 作为通用的数据反序列化框架以任何你喜欢的方式使用。

示例

让我们考虑以下示例。它定义了一个名为 MailSendCommand 的 struct 并为它派生了 Parsable trait。这足以解析它。

use cmdparse::{Parsable, parse};

#[derive(Debug, PartialEq, Eq, Parsable)]
struct MailSendCommand {
   text: String,
   #[cmd(attr(subject), default = "\"no subject\".to_string()")]
   subject: String,
   #[cmd(attr(to))]
   to: Vec<String>,
}

let input = "\"Hello, world\" --to [email protected] [email protected] --subject Greeting";
let result = parse::<_, MailSendCommand>(input, ())?;
assert_eq!(result, MailSendCommand {
    text: "Hello, world".to_string(),
    subject: "Greeting".to_string(),
    to: vec!["[email protected]".to_string(), "[email protected]".to_string()],
});

此示例演示了 cmdparse 的几个功能

  • 只要内部类型是 Parsable 或存在适当的 Parser,就可以自动为任意 struct 或 enum 派生解析功能。(要了解可解析和解析器之间的区别,请参阅这些 trait 的文档)。
  • 派生的解析器是可配置的:您可以指定字段是必需的还是可选的。可以通过名称属性(-- 符号)指定可选字段。它们可以明确指定默认值(如 subject 字段的默认属性所示)或不指定(to 字段默认为空向量,如其 Default 实现)。
  • 可解析的值可以包含嵌套的可解析值:MailSendCommand 是可解析的,它包含一个可解析的 Vec,它重复解析可解析的 String。注意 cmdparse 如何识别当它遇到既不是 String 也不是 Vec 所识别的属性时列表中的电子邮件地址结束。

cmdparse 可以生成完成建议

use cmdparse::complete;
use std::collections::BTreeSet;

let suggestions = complete::<_, MailSendCommand>("\"Hello, world\" --", ());
assert_eq!(suggestions, BTreeSet::from(["to".into(), "subject".into()]));

它还支持解析枚举。对于枚举,它期望一个区分符(由 Parsable 派生宏自动转换为短横线命名法)

use cmdparse::{parse, Parsable};

#[derive(Debug, PartialEq, Eq, Parsable)]
enum Priority {
   High,
   Medium,
   Low,
}

impl Default for Priority {
    fn default() -> Self {
        Priority::Medium
    }
}

#[derive(Debug, PartialEq, Eq, Parsable)]
enum Command {
    AddTask(String, #[cmd(attr(priority))] Priority),
    Remove(usize),
}

assert_eq!(
    parse::<_, Command>("add-task parse-all-commands", ())?,
    Command::AddTask("parse-all-commands".to_string(), Priority::Medium),
);
assert_eq!(
    parse::<_, Command>("add-task enjoy-your-day --priority high", ())?,
    Command::AddTask("enjoy-your-day".to_string(), Priority::High),
);
assert_eq!(parse::<_, Command>("remove 1", ())?, Command::Remove(1));

语法

cmdparse支持的语法相对简单。解析器将输入视为由空格分隔的字符序列。标记是任何由空格分隔的字符序列。如果您想在标记中包含空格,可以将输入的任何子串放在一对引号(双引号或单引号)中;cmdparse支持使用反斜杠(\)在引号标记中转义这些符号。

输入可以包含以井号(#)开始的注释。引号标记内的井号不被认为是注释的开始。

标记和属性的含义高度特定于每个解析器。通常,每个解析器按顺序消费标记,直到每个必需字段的值都填充完毕。它也可以按任意顺序和任意位置处理属性。

由于命令语法的特性,解析可能会显得模糊。例如,cmdparse可以解析嵌套结构,如Vec<Vec<u32>>。对于最终用户来说,可能很难解释数字序列(它们都将放入外部向量的唯一项中)。最好是设计简单的命令,避免高度嵌套的结构,以获得更好的用户体验。在某些情况下,复杂性是不可避免的。在这种情况下,用户可能会发现使用括号(())将属于同一数据结构的标记分组是有用的。这样,用户可以将一个值vec![vec![1, 2], vec![3, 4, 5]]作为(1 2) (3 4 5)

有关标记化和解析算法的更多详细信息,请参阅tokens模块和Parser trait的文档。

许可证

根据以下任何一个许可进行许可:

任选其一。

贡献

除非您明确说明,否则根据Apache-2.0许可中定义的,您有意提交以包含在工作中的任何贡献,都将根据上述条款进行双重许可,不附加任何额外条款或条件。

依赖关系

~1.5MB
~37K SLoC