Contents
Getting started
- Install: Cargo, source
- Requirements: toolchain floor, platforms
- Quick Start: front matter to meta tags in ten lines
The metadata-gen ecosystem
- The metadata-gen ecosystem: front-matter and content generation companion crates
Library reference
- Capabilities at a glance: the current surface by theme
- Ecosystem comparison: how the crate compares with neighbouring crates
- Benchmarks: measured numbers with the host stated
- Features: module-level capability list
- Configuration: core options
- Examples: runnable example index
Operational
- When not to use metadata-gen: limitations
- Development: make targets, fuzzing, CI
- Security: guarantees and compliance
- Documentation: all reference docs
- Stability guarantees: SemVer axis, output stability, minimum toolchain discipline
- License
Install
As a Rust library
[]
= "0.0.8"
Or from the command line:
There is no CLI binary. metadata-gen is a library crate and ships no [[bin]].
Build from source
Cargo features
Every format and integration is a feature, and all of them are on by default, so a default build offers everything 0.0.7 did. Turn off what you do not parse with default-features = false.
| Feature | Default | Enables |
|---|---|---|
std |
yes | File helpers in metadata_gen::io, std::io::Error in MetadataError, HashMap-backed MetadataMap |
yaml |
yes | --- front matter (noyalib) |
toml |
yes | +++ front matter (toml) |
json |
yes | JSON-object front matter (serde_json) |
html |
yes | extract_meta_tags (quick-xml); implies std |
tokio |
yes | metadata_gen::tokio and async_extract_metadata_from_file; implies std |
# YAML only, no standard library:
= { = "0.0.8", = false, = ["yaml"] }
A fence whose format is not compiled in is reported as UnsupportedFormatError, not as missing front matter.
Requirements
- Rust 1.88.0 or newer.
rust-versioninCargo.tomlis the floor and Cargo enforces it; CI builds on stable across Linux, macOS and Windows. alloc, withstdoptional. Withdefault-features = falsethe crate isno_std + allocandMetadataMapis aBTreeMap;cargo build --no-default-features --features yaml --target thumbv7em-none-eabihfbuilds.- No async runtime is required. Every synchronous entry point works without one. The
tokiofeature addsmetadata_gen::tokiofor callers who run Tokio; the dependency is trimmed tofsandio-util.
Quick Start
use extract_and_prepare_metadata;
let content = "---\n\
title: Hello, world!\n\
description: A short greeting\n\
keywords: rust, frontmatter, seo\n\
---\n\
# Body starts here";
let =
extract_and_prepare_metadata.expect;
assert_eq!;
assert_eq!;
assert!;
extract_and_prepare_metadata detects the format, flattens the metadata into a map, extracts keywords, and generates formatted meta tags in one call.
The metadata-gen ecosystem
The family releases along the 0.0.x line, with each repository owning a focused role in static site and content pipelines.
| Component | Purpose | Use case |
|---|---|---|
metadata-gen |
Extract and process front matter in YAML, TOML, JSON; generate meta tags | Content pipelines, documentation, static site generators |
frontmatter-gen |
Front-matter validation and schema generation | Build-time front-matter validation |
mdx-gen |
MDX compiler and component renderer | Embedded interactive components in Markdown |
sitemap-gen |
XML sitemap generator conforming to sitemaps.org | Search engine index generation |
rssgen |
RSS, Atom, and JSON feed generator | Content syndication feeds |
staticdatagen |
Static data engine for template processing | Template data hydration |
Capabilities at a glance
| Area | Capability | Status |
|---|---|---|
| Front-matter extraction | YAML, TOML, and JSON delimited headers | Stable |
| Document separation | Split front-matter block and document body in one call | Stable |
| Typed extraction | Deserialise front matter into user-defined structs | Stable |
| Flat metadata | Flatten nested structures to dot-separated string maps | Stable |
| Meta tag generation | Open Graph, Twitter, Apple, Microsoft, and primary tags | Stable |
| Meta tag extraction | Single streaming pass over HTML documents with quick-xml |
Stable |
| Utilities | Single-pass HTML entity escaping and async file loading | Stable |
Ecosystem comparison
This summary identifies API shape, not a universal winner. Workload-specific trade-offs and the evidence behind each cell are documented separately.
| Project | YAML | TOML | JSON | Typed | Body returned | Meta tags |
|---|---|---|---|---|---|---|
metadata-gen |
Yes | Yes | Yes | Yes | Yes | Yes |
gray_matter |
Yes | Yes | Yes | Yes | Yes | No |
yaml-front-matter |
Yes | No | No | Yes | Yes | No |
matter |
Yes | No | No | No | Yes | No |
The matrix reflects each crate's documented surface at the time of this release.
Benchmarks
Measured with Criterion on an Apple A18 Pro, rustc 1.98.0, single thread. Middle estimate of the confidence interval; run cargo bench to reproduce on your own hardware.
| Scenario | Result | Environment |
|---|---|---|
extract_metadata (YAML, 1 KB) |
631 µs (1.5 MiB/s) | Apple A18 Pro, rustc 1.98.0 |
extract_metadata (YAML, 10 KB) |
1.81 ms (5.4 MiB/s) | Apple A18 Pro, rustc 1.98.0 |
extract_metadata (YAML, 1 MB) |
181 ms (5.5 MiB/s) | Apple A18 Pro, rustc 1.98.0 |
extract_meta_tags (1 KB) |
50 µs (18.7 MiB/s) | Apple A18 Pro, rustc 1.98.0 |
extract_meta_tags (1 MB) |
34.9 ms (28.7 MiB/s) | Apple A18 Pro, rustc 1.98.0 |
escape_html (10 KB) |
31.9 µs (295 MiB/s) | Apple A18 Pro, rustc 1.98.0 |
extract_and_prepare_metadata (~250 B) |
23 µs | Apple A18 Pro, rustc 1.98.0 |
Reproduce with cargo bench --all-features; the harnesses live in benches/.
Features
- Three front-matter shapes: YAML (
---...---), TOML (+++...+++), and JSON ({ ... }at top of file). - Dual extraction APIs:
extract_metadata: returns flatMetadata(HashMap<String, String>) with dot-separated keys (author.name), ideal for templates.extract_typed::<T>: hands the raw block to serde, returning your strongly typed struct;extract_typed_borrowedlets&strfields point into the document.
- Document body access:
extract_metadata_with_bodyreturns(Metadata, &str)with the body following the closing delimiter.detect_front_matterexposes format, raw block, and byte offset without parsing. - Bounded parsing: every parse runs under
ParseLimits(block size, nesting depth); the YAML parser gets its strict budget preset on the flat and typed paths alike.extract_metadata_with_limitstakes your own limits. - Errors that locate the fault:
MetadataError::Parsecarries the format, the byte span inside the block and the parser's error; an unclosed fence reports its byte offset. - Processing and normalization:
process_metadataandprocess_metadata_withnormalize dates toYYYY-MM-DD(DD/MM/YYYYby default,MM/DD/YYYYwithDateOrder::MonthFirst), verify required fields, and derive URL slugs from titles. - Meta tag synthesis:
generate_metatagscreates grouped tags (primary,og,twitter,apple,ms); Open Graph tags useproperty=, and attribute values pass throughescape_attribute.MetaTagandMetaTagGroups::itergive the same tags as values. - Streaming HTML tag extraction:
extract_meta_tagsextracts<meta>tags in a single streaming pass usingquick-xmlwithout full DOM allocation;extract_meta_tags_lenientalso reports where a malformed page stopped the scan. - HTML escaping and file utilities: single-pass, single-allocation
escape_htmlandunescape_html, readers and files inmetadata_gen::io, and Tokio-backed helpers inmetadata_gen::tokio.
Configuration
process_metadata uses a default policy: title and date are required, and slug is derived from title. process_metadata_with accepts caller-configured ProcessOptions:
use ;
use HashMap;
let options = default
.required_fields
.derive_slug;
let mut map = new;
map.insert;
map.insert;
let processed = process_metadata_with.unwrap;
assert!;
| Option | Default | Effect |
|---|---|---|
required_fields |
["title", "date"] |
Missing field returns MissingFieldError naming it |
derive_slug |
true |
Derives slug from title when absent |
date_order |
DateOrder::DayFirst |
How a slash-separated date such as 01/02/2024 is read |
ProcessOptions is #[non_exhaustive], so options can be added without breaking releases.
Examples
Run any example with cargo run --example <name>:
| Example | Shows |
|---|---|
lib_example |
The high-level extract_and_prepare_metadata flow |
metadata_example |
Per-format extraction, nested tables, typed extraction, the body |
metatags_example |
Generating <meta> groups and reading them back |
utils_example |
HTML escape/unescape and the async file helper |
error_example |
Every MetadataError variant and how to recover |
make examples runs all examples; CI executes the same suite on every push.
When not to use metadata-gen
- You need element-level access to arrays of objects from the flat map.
[a, b]is rendered as a string in the flat map by design. Useextract_typed::<T>instead, which preserves structure. - You need to round-trip front matter byte-for-byte. The crate parses; it does not preserve comments, key order or quoting style, and there is no serialiser back to a fenced block.
- You need every
<meta>element from arbitrary broken HTML. Extraction stops at the first unrecoverable reader error and returns what was found so far. A full HTML5 parser (html5ever,scraper) is the right tool if you need error recovery over broken whole pages.
Development
DEVELOPMENT.md maps each CI job to its local equivalent and explains reproduction steps. docs/MIGRATION.md lists what changes for consumers between releases.
Security
Reporting: never open a public issue for a vulnerability. See SECURITY.md for disclosure instructions.
#![forbid(unsafe_code)]proves the absence of unsafe code (ADR-0001).- No C dependencies, no FFI, no network I/O, no environment reads.
- Meta-tag attribute values are escaped on generation, preventing markup injection.
- Supply chain audited via
cargo-audit,cargo-deny, andcargo-vetwith a locked exemption baseline. - Front matter is parsed under
ParseLimits(block size and depth), with the YAML parser's strict resource budgets on every path, the typed one included. - The first-party dependency
noyalibis pinned exactly (ADR-0004). - Property-based testing via Proptest for parser round-trips and HTML escape involution.
- Concurrency testing scaffold via Loom (
tests/loom_smoke.rs) for thread schedule exploration. - Formal verification via Kani (
tests/kani/) proving HTML escape totality and ASCII round-trip. - Mutation testing via
cargo-mutantsconfigured with.cargo/mutants.toml. - Three fuzz targets replaying seed and regression corpora per push.
Documentation
The canonical entry points across the repository family:
- API reference: rustdoc on docs.rs
- Developer docs: toolchain, task map, reproducing CI gates
- Architecture: module map, pipeline, design decisions
- Engineering policies: MSRV, SemVer, security, concurrency
- Decision records: irreversible architectural decisions
| Document | Covers |
|---|---|
CHANGELOG.md |
Per-release notes, Keep a Changelog format |
SECURITY.md |
Disclosure policy, supported versions, security design |
CONTRIBUTING.md |
Branch and commit conventions, PR expectations |
GOVERNANCE.md |
Project stewardship, how changes land |
SUPPORT.md |
Support channels and expectations |
AGENTS.md |
Invariants for AI-assisted contributions |
Stability guarantees
Versioning
SemVer 2.0.0 is followed, with the pre-1.0 posture that releases increment strictly by +0.0.1 along the 0.0.x line. Every breaking change is documented in CHANGELOG.md.
Output stability
What the crate produces is part of the API: a change to how a document flattens, which key a value lands under, or what a meta-tag group renders is treated as a breaking change even when no Rust signature moves.
Deprecations
Deprecations live for at least two releases with a #[deprecated] attribute naming the replacement before removal.
Minimum-toolchain discipline
The floor is Rust 1.88.0, declared as rust-version in Cargo.toml. Raising it is a breaking change and occurs only on releases with rationale documented in the changelog. See docs/POLICIES.md for the complete policy.
Version-bearing files
Checked against the manifest by scripts/verify-release-versions.sh before a tag exists, ensuring install snippets and metadata stay synchronised.
License
Dual-licensed under Apache 2.0 or MIT, at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you shall be dual-licensed as above, without any additional terms or conditions.