Bounded offline Sieve mail-filter analysis and execution in MoonBit
git clone https://github.com/Han-Wentao/moonsieve.git
cd moonsieve
moon run cmd/main --target js -- run examples/list.sieve examples/mail.json
moon run cmd/main --target js -- replay examples/list.sieve examples/cases.json
moon run examples/embed --target jsmoon add Han-Wentao/moonsieveimport {
"Han-Wentao/moonsieve" @sieve,
}let program = @sieve.compile(
"require [\"fileinto\", \"copy\"]; if header :contains \"List-Id\" \"moonbit\" { fileinto :copy \"Lists/MoonBit\"; }",
)
let message = @sieve.Message::new(
[{ name: "List-Id", value: "dev.moonbit.example.org" }],
2048,
envelope_from="sender@example.org",
envelope_to=["reader@example.org"],
)
let plan = program.run(message)
println(plan.as_json(include_trace=false, redact_destinations=true).stringify()){
"headers": [
{"name": "List-Id", "value": "dev.moonbit.example.org"},
{"name": "Subject", "value": "MoonBit 开发讨论"}
],
"size": 2048,
"envelope": {"from": "sender@example.org", "to": ["reader@example.org"]}
}| 层面 | 已实现 | 限制 |
|---|---|---|
| 语法 | # 和块注释、引号字符串、text: 点转义、多值字符串、K/M/G 数字、if/elsif/else、not/anyof/allof | 无循环、include、用户函数;LF 输入按 CRLF 字符串换行规范化 |
| 基础测试 | true/false、exists、size、header、address、envelope | envelope 仅 from/to;字段名为静态 from/to,不支持变量生成信封字段名 |
| 比较 | :is、:contains、:matches、i;octet、i;ascii-casemap | 输入为已解码 Unicode 字符串;? 匹配一个 Unicode 标量,不是任意原始字节;不做 Unicode 不区分大小写比较 |
| 动作 | keep、discard、stop、fileinto、redirect | 只返回计划,不承担投递重试、权限校验或目的邮箱存在性检查 |
| copy | fileinto/redirect :copy | 不取消已存在的隐式 keep,也不重新启用先前被取消的 keep |
| variables | set、单次变量展开、6 种修改器、0..9 捕获 | 不递归替换;超长值报错而不截断,与 RFC 5229 的建议不同;超过 9 的捕获引用运行时报错 |
| subaddress | :user、:detail,可配置单个 ASCII 分隔符 | 缺失 detail 与空 detail 分开;默认 +,由宿主选定站点语义 |
| relational | :value、:count、6 种关系、i;ascii-numeric | 数字按前缀数字串比较,不转整数;非数字为无穷大;数值比较器不支持 contains/matches |
| 地址提取 | 常见 ASCII addr-spec、引号本地部分、显示名、组、嵌套注释 | 不是完整 RFC 5322 解析器;不支持 EAI/IDNA/过时路由语法,不能作为 SMTP 地址有效性认证器 |
| 工具 | 格式化、带范围诊断、建议式静态审计、回放、差异、JSON | 不支持 body、vacation、regex、imap4flags、spamtest、ManageSieve 等扩展 |
moon run cmd/main --target js -- help
moon run cmd/main --target js -- lint SCRIPT.sieve
moon run cmd/main --target js -- format SCRIPT.sieve
moon run cmd/main --target js -- run SCRIPT.sieve MESSAGE.json
moon run cmd/main --target js -- replay SCRIPT.sieve CASES.json
moon run cmd/main --target js -- diff BEFORE.sieve AFTER.sieve CASES.json| 退出码 | 意义 |
|---|---|
| 0 | 成功,且已声明的期望通过;diff 无变化 |
| 1 | 回放业务期望不符,或 diff 存在动作/错误差异 |
| 2 | 参数、文件、编译或非预期执行错误 |
| 默认限制 | 值 |
|---|---|
| 脚本或 JSON 文本 / 归一化邮件总长 | 262144 UTF-16 单元 |
| 单字符串 | 65536 UTF-16 单元 |
| Token / 语法嵌套深度 | 32768 / 64 |
| 执行步 / 比较与数据收集预算 | 100000 / 2000000 |
| 不同投递动作 / 头字段 / 命名变量 | 128 / 1000 / 128 |
| JSON 容器嵌套 | 32 |
| 回放夹具 / 总执行步 / 总比较预算 | 1000 / 5000000 / 100000000 |
node tools/verify.mjs
# 已安装 C 编译器的环境,包括 CI:
node tools/verify.mjs --native
# 独立查看源码统计,不将测试、注释、示例、生成文件、JS FFI 算作实现:
python tools/count_loc.pypub(all) struct CaseResult {
id : String
plan : Plan?
error : Diagnostic?
expectation_met : Bool?
} derive(Eq, Debug)fn Program::replay(self : Program, cases : Array[ReplayCase], limits? : ReplayLimits, options? : ExecutionOptions) -> ReplayReport raise SieveErrorfn Program::run(self : Program, message : Message, options? : ExecutionOptions) -> Plan raise SieveErrorpub(all) struct ReplayReport {
results : Array[CaseResult]
failed_expectations : Int
execution_errors : Int
charged_steps : Int
charged_comparisons : Int
} derive(Eq, Debug)fn compare_replays(before : ReplayReport, after : ReplayReport) -> Array[PolicyChange] raise SieveErrorfn match_text(value : String, key : String, mode : MatchType, comparator? : Comparator, limits? : Limits) -> Bool raise SieveErrorfn run_json(source : String, message_json : String, limits? : Limits, options? : ExecutionOptions) -> Json raise SieveErrorfn test_expression(source : String, message : Message, capabilities? : Array[String], limits? : Limits, options? : ExecutionOptions) -> (Bool, Array[TraceEvent]) raise SieveErrorInstall
Download zipBounded offline Sieve mail-filter analysis and execution in MoonBit