sebastienrousseau/metadata-gen

GitHub: sebastienrousseau/metadata-gen

Rust 生态中集 YAML/TOML/JSON frontmatter 解析与 SEO meta 标签生成于一体的强类型、供应链审计库。

Stars: 2 | Forks: 0

Metadata Gen logo

metadata-gen

一个为 Rust 设计的强类型、经过审计的 frontmatter 解析器 —— 支持 YAML、TOML 和 JSON 提取,并可为 SEO、Open Graph、Twitter Cards 和 Apple Web Apps 生成 HTML meta 标签。

Build Crates.io Docs.rs Coverage lib.rs

## 目录 - [功能简介](#what-it-does) - [适用场景](#when-to-use-it) - [安装说明](#install) - [快速开始](#quick-start) - [示例](#examples) - [YAML frontmatter](#yaml-frontmatter) - [TOML frontmatter](#toml-frontmatter) - [JSON frontmatter](#json-frontmatter) - [HTML meta 标签生成](#html-meta-tag-generation) - [异步文件提取](#asynchronous-file-extraction) - [对比](#comparisons) - [性能](#performance) - [供应链](#supply-chain) - [MSRV 策略](#msrv-policy) - [路线图](#roadmap) - [常见问题](#faq) - [贡献指南](#contributing) - [安全性](#security) - [许可证](#license) ## 功能简介 `metadata-gen` 解析内容文件的 *frontmatter* —— 即 Markdown/HTML 文档顶部的结构化块 —— 并将其转化为可用的 Rust 值,以及一组 SEO meta 标签分组 (`primary`、`og`、`twitter`、`apple`、`ms`)。 它开箱即用地支持三种 frontmatter 格式: | 格式 | 分隔符 | 示例头部 | |--------|------------------|------------------------------| | YAML | `---` / `---` | `title: Hello` | | TOML | `+++` / `+++` | `title = "Hello"` | | JSON | `{` / `}` | `{"title": "Hello"}` | 格式检测是自动进行的。检测顺序为 YAML → TOML → JSON;将使用首个起始分隔符匹配成功的格式。嵌套值会通过以点分隔的键(例如 `author.name`)进行扁平化处理,而在当前基于 map 的 API 中,序列会被序列化为 `[a, b, c]` 字符串。 ## 适用场景 在以下需求时,请选择 `metadata-gen`: - 一个 **单一依赖项**,可同时处理 YAML、TOML *和* JSON frontmatter, 而无需你提前选择序列化器。 - 内置 **HTML meta 标签生成器**(支持 Open Graph、Twitter Cards、Apple Mobile、Microsoft Tiles、基础 SEO),并连接到同一个元数据 map。 - 一个具有 **明确的供应链安全姿态** 的库 —— 强制执行 `cargo-deny`、 `cargo-audit`、SBOM 生成和 `#![forbid(unsafe_code)]`。 - 一个正积极向 **强类型提取**、**零拷贝值** 和 **WASI 0.2 组件** 迈进的库(参见[路线图](#roadmap))。 在仅需解析原始 YAML(直接使用 `serde_yaml_ng` 或 `noyalib`),或*现在*就需要强类型提取(请关注 v0.0.6 的 issue [#42](https://github.com/sebastienrousseau/metadata-gen/issues/42))的情况下,请考虑其他选择。 ## 安装说明 ``` cargo add metadata-gen ``` 或者添加到 `Cargo.toml` 中: ``` [dependencies] metadata-gen = "0.0.6" ``` 最低支持的 Rust 版本:**1.88.0** —— 请参阅 [MSRV 策略](#msrv-policy)。已在 x86_64 和 ARM64 架构的 Linux、macOS 和 Windows 上经过测试。 ## 快速开始 ``` use metadata_gen::extract_and_prepare_metadata; let content = "---\n\ title: Hello, world!\n\ description: A short greeting\n\ keywords: rust, frontmatter, seo\n\ ---\n\ # 正文从这里开始"; let (metadata, keywords, tags) = extract_and_prepare_metadata(content).expect("valid frontmatter"); assert_eq!(metadata.get("title"), Some(&"Hello, world!".to_string())); assert_eq!(keywords, vec!["rust", "frontmatter", "seo"]); assert!(tags.primary.contains("description")); ``` ## 示例 使用 `cargo run --example ` 运行任意示例: | 示例 | 演示内容 | |------------------------|-------------------------------------------------------------| | `lib_example` | 高级 `extract_and_prepare_metadata` + meta 标签流程 | | `metadata_example` | 按格式提取 (YAML, TOML, JSON) + 嵌套映射 | | `metatags_example` | 生成 + 提取 HTML `` 标签 | | `utils_example` | HTML 转义/反转义,异步文件提取 | | `error_example` | 每一个 `MetadataError` 变体 + 恢复模式 | ### YAML frontmatter ``` use metadata_gen::metadata::extract_metadata; let content = "---\n\ title: My Post\n\ date: 2026-06-28\n\ author:\n name: Ada\n handle: ada@example.com\n\ tags:\n - rust\n - parsing\n---\n"; let meta = extract_metadata(content).unwrap(); assert_eq!(meta.get("title"), Some(&"My Post".to_string())); assert_eq!(meta.get("author.name"), Some(&"Ada".to_string())); assert_eq!(meta.get("tags"), Some(&"[rust, parsing]".to_string())); ``` ### TOML frontmatter ``` use metadata_gen::metadata::extract_metadata; let content = "+++\n\ title = \"My Post\"\n\ date = \"2026-06-28\"\n\ \n\ [author]\n\ name = \"Ada\"\n\ +++\n"; let meta = extract_metadata(content).unwrap(); assert_eq!(meta.get("author.name"), Some(&"Ada".to_string())); ``` ### JSON frontmatter ``` use metadata_gen::metadata::extract_metadata; let content = "{\ \"title\":\"My Post\",\ \"description\":\"Inline JSON header\"\ }\n# Body"; let meta = extract_metadata(content).unwrap(); assert_eq!(meta.get("title"), Some(&"My Post".to_string())); ``` ### HTML meta 标签生成 ``` use std::collections::HashMap; use metadata_gen::metatags::generate_metatags; let mut map = HashMap::new(); map.insert("description".to_string(), "About the page".to_string()); map.insert("og:title".to_string(), "Page Title".to_string()); map.insert("twitter:card".to_string(),"summary_large_image".to_string()); let groups = generate_metatags(&map); assert!(groups.primary.contains("description")); assert!(groups.og.contains("og:title")); assert!(groups.twitter.contains("twitter:card")); ``` ### 异步文件提取 ``` use metadata_gen::utils::async_extract_metadata_from_file; #[tokio::main] async fn main() -> Result<(), Box> { let (metadata, keywords, tags) = async_extract_metadata_from_file("post.md").await?; println!("title = {:?}", metadata.get("title")); println!("keywords = {:?}", keywords); println!("og tags =\n{}", tags.og); Ok(()) } ``` ## 对比 | Crate | YAML | TOML | JSON | 强类型提取 | Meta 标签生成 | no_std (计划中) | |-----------------------|:----:|:----:|:----:|:----------------:|:-------------:|:----------------:| | **`metadata-gen`** | ✅ | ✅ | ✅ | v0.0.6 路线图 | ✅ | v0.0.9 路线图 | | `gray_matter` | ✅ | ✅ | ✅ | ✅ | — | — | | `yaml-front-matter` | ✅ | — | — | ✅ | — | — | | `matter` | ✅ | — | — | — | — | ✅ | `gray_matter` 是最相近的现有库。`metadata-gen` 的差异在于 内置的 meta 标签生成器、供应链安全姿态以及 WASI/no_std 路线图。请参阅 [审计演示文稿](docs/AUDIT-2026.md) 了解战略背景。 ## 性能 在 2024 年参考笔记本电脑(M-class CPU,单线程)上的单次调用延迟: | 目标 | 输入大小 | 延迟 | |-----------------------------------|-----------:|---------:| | `extract_metadata` (YAML) | ~200 B | ~10 µs | | `process_metadata` | ~200 B | ~1 µs | | `generate_metatags` | ~200 B | ~1 µs | | `escape_html` | ~80 B | ~0.3 µs | 自行运行测试套件: ``` cargo bench --bench metadata_benchmark ``` 计划在 v0.0.7 中通过 `Cow<'a, str>` 值、`LazyLock` 静态变量、单次遍历 HTML 转义以及 `memchr::memmem` 分隔符扫描,实现 10–100 倍的吞吐量提升。详情请参见 [v0.0.7](https://github.com/sebastienrousseau/metadata-gen/milestones)。 ## 供应链 `metadata-gen` 执行明确的供应链安全姿态: - **`cargo-deny`** 在每个 PR 上运行(`advisories`、`licenses`、`bans`、 `sources`);任何违规都会导致 CI 失败。 - **`cargo-audit`** 在每个 PR 上按计划每日运行,对 RUSTSEC 数据库进行比对。 - **`#![forbid(unsafe_code)]`** 在整个 crate 范围内强制执行。 - **第一方 0.0.x 依赖项**(`noyalib`、`dtt`)在 `Cargo.toml` 中被严格锁定, 因此上游补件无法在没有经过严格的 `metadata-gen` 发版的情况下 破坏下游使用者。 - **SBOM 生成**(CycloneDX)和 cosign 签名将在 v0.0.5 中落地 —— 请参阅 [路线图](#roadmap)。 已记录的豁免清单位于 [`audit.toml`](audit.toml) 中;每个条目都带有引用 上游追踪 issue 的理由说明。 ## MSRV 策略 最低支持的 Rust 版本:**1.88.0**。 我们将 MSRV 视为公共 API 的一部分:版本提升会被打包到 次要(`0.x.0`)发布中,并在 `CHANGELOG.md` 中注明。当前的 1.88.0 基线是由 `dtt 0.0.10 → time 0.3.47 → time-core =0.1.8` (edition2024) 传递性锁定的。降低基线将在 `time` 中重新引入一个中等严重程度的栈耗尽安全公告,因此我们坚持这一底线。 如果您需要旧的工具链,请提交一个描述您限制的 issue —— 我们很乐意讨论 MSRV 分段分支。 ## 路线图 v0.0.4 之后的路线图分为六个主题发布。每个里程碑都在 [GitHub Milestones](https://github.com/sebastienrousseau/metadata-gen/milestones) 上进行跟踪,每个 issue 都包含完整的用户故事和验收标准。 | 版本 | 主题 | 亮点 | |----------|----------------------------------|---------------------------------------------------------------------------| | v0.0.5 | **基础强化** | 移除 `tokio = "full"`,`LazyLock` 静态变量,修复 JSON 嵌套花括号 bug,`cargo-deny`/`cargo-audit` 门控,SBOM 生成,rustdoc Actions 部署,README/FAQ 大修。 | | v0.0.6 | **强类型 API 与人机工程学** | `extract_typed::`,`(Metadata, body: &str)` 返回,构建者模式,按格式划分的 Cargo feature,schema 验证。 | | v0.0.7 | **零拷贝与性能** | `Cow<'a, str>` 值 API,单次遍历 HTML 转义,`memchr::memmem` 扫描,在 1 KB → 10 MB 下的吞吐量基准测试,Codspeed CI 门控。 | | v0.0.8 | **正确性与验证** | `proptest` 测试套件,`cargo-fuzz` 目标,夜间 CI 中的 Miri,`cargo-mutants` ≥ 85 % 杀伤率,Kani 证明,≥ 98 % 覆盖率门控。 | | v0.0.9 | **可移植性** | `no_std + alloc` 核心,与异步运行时无关的 IO,可选的 Tokio/smol/Embassy 适配器,嵌入式 CI 矩阵。 | | v0.0.10 | **WASI / 蓝海 / 1.0 RC** | 带有 WIT 接口的 `wasm32-wasip2` Component,Cloudflare Workers / Spin / wasmCloud 指南,PQC 签名元数据,MCP 服务器示例,ADR 系列。 | ## 常见问题 ### 1. 为什么有三种 frontmatter 格式,而不是仅使用 YAML? 现实世界中的内容流水线并非同质化的。Jekyll/Hugo 使用 YAML 和 TOML;基于 `serde_json` 构建的静态站点生成器首选 JSON;文档 工具链通常会同时遇到这三种情况。`metadata-gen` 接受这三种格式,因此 您的下游代码只需依赖一个 crate。 ### 2. 这与 `gray_matter` 相比如何? `gray_matter` 是 Rust 生态中主流的 frontmatter 解析器,自 2020 年起便占据主导地位。它目前已实现强类型提取(通过其 `Pod`),而这 正是 `metadata-gen` 将在 v0.0.6 中达成的目标。`metadata-gen` 的区别在于 捆绑的 HTML meta 标签生成器、记录在案的供应链安全姿态、 WASI/no_std 路线图,以及对第一方传递依赖项的严格锁定。如果您*现在*就需要强类型提取,请使用 `gray_matter`。如果 您想要 v0.0.10 的 WASI Component,请关注此 crate。 ### 3. 使用此库需要异步运行时吗? 不需要。同步入口点(`extract_metadata`、`process_metadata`、 `extract_and_prepare_metadata`、`generate_metatags`、`escape_html`)不需要 Tokio。异步辅助函数 `async_extract_metadata_from_file` 是为 已经使用 Tokio 的调用者提供的便利;我们将 Tokio 精简为其 `fs` + `io-util` 特性,因此它不会使您的构建变得臃肿。运行时无关的 `AsyncRead` 边界将在 v0.0.9 中落地。 ### 4. 我可以在 `no_std` / WASM 中使用 `metadata-gen` 吗? 在 v0.0.5 中不可以 —— `regex`、`scraper` 和 `tokio` 都是 被无条件引入。`no_std + alloc` 支持是 v0.0.9 的里程碑,而完整的 `wasm32-wasip2` Component 将在 v0.0.10 中落地。请追踪里程碑 [v0.0.9](https://github.com/sebastienrousseau/metadata-gen/milestones) 和 [v0.0.10](https://github.com/sebastienrousseau/metadata-gen/milestones) 以获取状态。 ### 5. MSRV 策略是什么? MSRV 是公共 API 的一部分。版本提升会在 `0.x.0` 边界发生,并且 会在 `CHANGELOG.md` 中记录。当前基线 (1.88.0) 被 `time` 中的 安全公告传递性锁定;降低它将重新引入该漏洞。 ### 6. 日期是如何解析的? `process_metadata` 会按顺序尝试: 1. ISO-8601 / RFC 3339 (`2026-06-28`, `2026-06-28T15:30:00Z`)。 2. `YYYY-MM-DD` 显式格式。 3. `MM/DD/YYYY` 美国格式。 4. `DD/MM/YYYY` 欧洲格式(通过长度 + 斜杠模式识别)。 输出始终标准化为 `YYYY-MM-DD`。范围超出或模棱两可的 输入将返回 `MetadataError::DateParseError`。 ### 7. 如何添加自定义必填字段? 在 v0.0.5 中,必填字段被硬编码为 `title` 和 `date`。可配置的 `MetadataProcessor` 构建器将在 v0.0.6 中落地 (issue [#47](https://github.com/sebastienrousseau/metadata-gen/issues/47))。 在此之前,请在 `extract_metadata` 之后使用 `metadata.contains_key("…")` 验证您自己的必填字段。 ### 8. HTML 转义是如何处理的? `escape_html` 将 `& < > " '` 映射为它们对应的实体字符。`unescape_html` 将它们映射回来(并将 `/` / `/` 映射为 `/`)。这对组合在 所有 ASCII 输入上都是往返安全的 —— 这一 属性的属性测试语料库和 Kani 证明将在 v0.0.8 中落地。目前的实现是一个五次遍历的 `str::replace` 链;单次遍历重写(可选通过 `v_htmlescape` 启用 SIMD)将在 v0.0.7 中交付 ([#52](https://github.com/sebastienrousseau/metadata-gen/issues/52))。 ### 9. 如何提取类型化的结构体(而不是 `HashMap`)? 在 v0.0.5 中不支持。v0.0.6 里程碑添加了 `metadata_gen::extract_typed::(content)`,它 会保留类型化信息(日期作为 `time::Date`,整数作为整数, 嵌套对象作为嵌套结构体)。请追踪 [issue #45](https://github.com/sebastienrousseau/metadata-gen/issues/45)。 ### 10. 我应该在哪里报告漏洞? 请**不要**公开 GitHub issue。请根据 [SECURITY.md](.github/SECURITY.md) 策略给维护者发送电子邮件。我们将在 48 小时内确认,并 在最新的稳定线上发布修复程序。新的漏洞类别将 触发 `cargo-fuzz` 目标,从而防止相同特征的漏洞再次出现。 ### 11. 添加此 crate 会让我的依赖树变庞大吗? 比以前少了。v0.0.5 淘汰了 `scraper` → `html5ever` → `selectors` → `fxhash` / `phf_generator` 链,转而采用由 `quick-xml` 支持的 `` 提取器 —— 减少了约 30 个传递依赖 crate,并消除了 RUSTSEC-2025-0057 (`fxhash`) 和 RUSTSEC-2026-0097 (`rand 0.8` 通过 `phf_generator`)。剩余的运行时 crate:`tokio`(精简为 `fs`+`io-util`)、`regex`、`serde`、`serde_json`、`noyalib`、`toml`、 `yaml-rust2`、`thiserror`、`quick-xml`、`time`、`dtt`。按格式划分的 Cargo 特性门控将在 v0.0.6 中落地 ([#41](https://github.com/sebastienrousseau/metadata-gen/issues/41)),因此 您可以选择不使用不需要的格式。 ### 12. 有 CLI 吗? 没有。`metadata-gen` 是一个库 crate。我们在 v0.0.5 中从 `Cargo.toml` 移除了 `command-line-utilities` 类别,因为没有附带 `[[bin]]` 目标。如果您想要一个 CLI 包装器,请发起讨论 —— 对于 `metadata-gen-cli` 的伴随 crate 存在合理的理由。 ## 安全性 - 根据 [`.github/SECURITY.md`](.github/SECURITY.md) 报告漏洞。 - 此 crate 强制执行 `#![forbid(unsafe_code)]`。 - 供应链控制(`cargo-deny`、`cargo-audit`、SBOM、`cargo-vet` 审计)已在[供应链](#supply-chain)部分记录。 ## 许可证 根据您的选择,受 [Apache 2.0](LICENSE-APACHE) 或 [MIT](LICENSE-MIT) 双重许可。

回到顶部

标签:Frontmatter解析, Rust, SEO, WebAssembly, 元数据生成, 可视化界面, 瑞士军刀, 网络流量审计, 通知系统