Skip to main content

accent_proust/
lib.rs

1//! A Rust implementation of the [Markdoc](https://markdoc.dev) language.
2//!
3//! Markdoc is CommonMark plus a tag syntax that turns documents into
4//! structured, validatable content instead of pre-rendered HTML:
5//!
6//! ```markdown
7//! {% callout type="note" %}
8//! Tags nest, take typed attributes, and are validated against a schema.
9//! {% /callout %}
10//! ```
11//!
12//! This crate implements that language as a pipeline of pure stages:
13//!
14//! ```text
15//! parse  ->  AST  ->  validate  ->  transform  ->  renderable tree  ->  format
16//! ```
17//!
18//! # What this crate does not do
19//!
20//! It performs no I/O, reads no configuration, and decides no HTML policy. It
21//! has no concept of a file, a theme, a template, or a plugin. Everything
22//! host-specific arrives as data the caller passes in, or through a trait the
23//! caller implements:
24//!
25//! - [`Tokenizer`](parse) segments Markdown. A default implementation over
26//!   pulldown-cmark ships behind the `pulldown-cmark-tokenizer` feature, so a
27//!   host that already owns a CommonMark parser can supply its own rather than
28//!   compile a second one.
29//! - [`SchemaSource`](validate::SchemaSource) answers "what is the schema for
30//!   this tag name?". Whether that answer comes from a file, a constant, or a
31//!   sandboxed guest is the host's business, not this crate's;
32//!   [`MapSchemaSource`](validate::MapSchemaSource) is the answer for a host
33//!   that assembles it by hand.
34//! - [`TagRenderer`](render::TagRenderer) turns a validated tag into markup.
35//!   Escaping, void elements, and HTML policy live there; the walk over the
36//!   tree does not, which keeps the document's depth off the host's stack.
37//!
38//! That boundary is deliberate and is enforced by a CI job that builds and
39//! tests this crate with nothing else present.
40//!
41//! # Compatibility
42//!
43//! Ported from upstream Markdoc at revision `afee1a4` (v0.5.9). The tag
44//! language and the validation error ids are the contract; CommonMark edge
45//! behaviour is not, because upstream is built on markdown-it and this crate is
46//! built on pulldown-cmark. Every deliberate difference is recorded in
47//! `DIVERGENCES.md` at the repository root, which is normative rather than a
48//! changelog.
49//!
50//! # Conventions this crate commits to
51//!
52//! - **Public enums are `#[non_exhaustive]`.** Markdoc gained node types across
53//!   its 0.5.x line; spelling them exhaustively would turn each new one into a
54//!   breaking release. The one exception is
55//!   [`SchemaKey`](validate::SchemaKey): its two variants are the two ways a
56//!   node is looked up, not a list that grows with Markdoc, and a source that
57//!   implements [`SchemaSource`](validate::SchemaSource) should stop compiling
58//!   if a third appeared rather than silently answer `None`.
59//! - **Validation errors are data, not failures.** The validator returns a
60//!   `Vec` of them. `Result::Err` is reserved for internal invariants.
61//! - **Output is deterministic.** Attribute order is authored order, never hash
62//!   order, so two runs over the same input produce identical bytes.
63//! - **Panic-freedom is a promise.** Property tests assert the parser never
64//!   panics on arbitrary input, and fuzzing precedes publication. An open
65//!   parser is a claim about its attack surface.
66//!
67//!   The promise covers values a **caller** builds as well as documents this
68//!   crate parses, and it covers every way of touching one. Each public
69//!   recursive type -- [`ast::Node`], [`ast::Value`], [`renderable::Tag`] and
70//!   [`renderable::Scalar`] -- writes out all four of its traversals:
71//!   [`Drop`], [`Clone`], [`PartialEq`] and [`Debug`]. A derived
72//!   implementation of any of them recurses once per level, and a stack
73//!   overflow aborts rather than panics, so a caller could otherwise kill the
74//!   process with a value it assembled through the public API. Nothing here is
75//!   `unsafe`: `Drop` and `PartialEq` walk a worklist, `Clone` walks
76//!   post-order onto a plan and rebuilds bottom-up, and `Debug` emits from a
77//!   token stack.
78//!
79//!   Three costs, stated because they are invisible until met. A variant's
80//!   contents are taken with [`std::mem::take`] rather than moved out, since a
81//!   type with a manual `Drop` forbids the partial move. `Debug` output is
82//!   observable, so the emitters are pinned against a mirror type that still
83//!   derives it, in both `{:?}` and `{:#?}`. And equality over an
84//!   [`indexmap::IndexMap`] field stays unordered, matching what that map's own
85//!   `PartialEq` does rather than what a positional walk would be tempted to.
86
87pub mod ast;
88pub mod builtins;
89pub mod format;
90pub mod functions;
91pub mod grammar;
92pub mod parse;
93pub mod render;
94pub mod renderable;
95pub mod tags;
96pub mod transform;
97pub mod validate;