Skip to main content

moss_core/
lib.rs

1//! **moss-core is the pure-Rust content engine behind [moss](https://mosspub.com),**
2//! a desktop publishing app. It owns every transformation that turns a folder of
3//! markdown into a website: parsing, wikilink resolution, HTML rendering,
4//! frontmatter typing, and schema validation.
5//!
6//! Everything is **data in, data out** — strings and structs in; parsed ASTs,
7//! diagnostics, and rendered HTML out. **Zero I/O, zero async, no global state.**
8//! The filesystem, the network, and the async runtime all live one layer up, in
9//! moss's host; this crate never touches them. That makes it deterministic,
10//! trivially unit-testable, and embeddable in any Rust program — not just moss.
11//!
12//! # How it's laid out
13//!
14//! The modules cluster into four areas, plus the contract surface:
15//!
16//! - **Parse & render** — [`ast`] turns markdown into a typed tree (over
17//!   `pulldown-cmark`) and renders it back to HTML through interceptable hooks;
18//!   [`render`] emits media HTML (image/video/audio/iframe/pdf) for embeds.
19//! - **Frontmatter & schema** — [`frontmatter`] parses YAML while preserving the
20//!   body byte-for-byte; [`frontmatter_typed`] is the canonical `FrontMatter`
21//!   struct; [`schema_fields`] is the single source of truth for built-in fields;
22//!   [`validation`] produces LSP-style diagnostics against a schema.
23//! - **Links & content model** — [`resolve`] is the one place wikilinks and
24//!   embeds (`[[...]]`) become ordinary markdown links; [`content_graph`] does the
25//!   Obsidian-style fuzzy path matching underneath.
26//! - **Utilities** — small stateless helpers the editor and build share:
27//!   [`slug`], [`date`], [`sort`], [`home`], [`page_kind`], [`heading`],
28//!   [`html_entities`] (the one decoder for text that arrives HTML-escaped), and
29//!   [`inert_regions`] (the one answer to "which byte ranges of this markdown
30//!   are code or comment, and therefore not live syntax?", shared by every
31//!   pre-parse scanner in moss).
32//!
33//! Plus [`contract`]: the design surface (W3C design tokens + the `moss-*` HTML
34//! class table) that theme authors and codegen depend on.
35//!
36//! # Getting started
37//!
38//! Every entry point is a free function — pick the module and call it:
39//!
40//! ```
41//! use moss_core::frontmatter;
42//!
43//! let raw = "---\ntitle: Hello\n---\n\nBody text";
44//! let doc = frontmatter::parse(raw);
45//! assert_eq!(doc.frontmatter.get("title").and_then(|v| v.as_str()), Some("Hello"));
46//! assert_eq!(doc.body.trim(), "Body text"); // body preserved verbatim
47//! ```
48//!
49//! From there: [`ast`] for the body tree, [`resolve`] to flatten wikilinks,
50//! [`validation`] to lint frontmatter, and [`heading`] for anchors.
51//!
52//! # Guarantees
53//!
54//! Total functions: bad input degrades to a best-effort value, never an `Err` or
55//! a panic. No `unsafe` (`#![forbid(unsafe_code)]`). Schema problems are reported
56//! out-of-band as [`validation`] diagnostics, not return values.
57//!
58//! moss ships this crate in a host built with `panic = "abort"` (release
59//! profile), so a panic on user input crashes the whole desktop app (see the
60//! `date.rs` fix for the
61//! editor-mount panic on Chinese filenames). The lint attributes below enforce
62//! the panic-free contract — `string_slice` plus `unwrap_used`/`expect_used`,
63//! all denied outside tests — each with a per-site escape-hatch rule.
64
65#![forbid(unsafe_code)]
66// `clippy::string_slice` flags `&s[..n]` byte-indexed slicing on `&str`. That
67// pattern crashed the editor on `纽约诸法门.md` — `len() < 10` is bytes, not
68// chars, so the guard let the slice cut inside `法`. Safe call sites must
69// carry a per-site `#[allow(clippy::string_slice)]` with a one-line rationale
70// (e.g. "char-aligned: pos came from `find('/')`"). Audited at PR time, not
71// "we hope no one writes the bug shape again."
72//
73// Exempted in tests via `cfg_attr(not(test), ...)`, for the same reason as the
74// `unwrap_used`/`expect_used` pair below: the contract protects PRODUCTION —
75// a panic there crashes the desktop app on user input. A test that slices past
76// a char boundary just fails, which is the outcome a test wants. Prod code is
77// at zero without a single `#[allow]`; keeping the deny unconditional would
78// only tax assertion messages like `&html[..html.len().min(300)]`.
79#![cfg_attr(not(test), deny(clippy::string_slice))]
80// `clippy::unwrap_used` / `clippy::expect_used` enforce the second half of the
81// panic-free contract: production code must never `.unwrap()` / `.expect()`
82// a value that could be `None`/`Err` at runtime. Test code (`#[cfg(test)]
83// mod tests`) is exempted via `cfg_attr(not(test), ...)` because tests
84// legitimately want to fail fast on assertion violations. Safe call sites
85// must annotate with `#[allow(clippy::unwrap_used)]` + per-site rationale,
86// same pattern as `clippy::string_slice`.
87#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
88
89pub mod ast;
90pub mod asset_paths;
91pub(crate) mod path_ext;
92pub mod asset_snapshot;
93pub mod content_graph;
94pub mod contract;
95pub mod csv_table;
96pub mod date;
97pub mod dep_graph;
98pub mod home;
99pub mod frontmatter;
100pub mod frontmatter_union;
101pub mod frontmatter_typed;
102pub mod heading;
103pub mod html_entities;
104pub use heading::{extract_headings, HeadingInfo};
105pub mod inert_regions;
106pub mod link_candidates;
107pub mod link_completions;
108pub mod media;
109pub mod page_kind;
110pub use page_kind::PageKind;
111pub mod render;
112pub mod resolve;
113pub mod resolved;
114pub use resolved::{Resolved, ResolvedOrigin};
115pub mod schema;
116pub mod schema_fields;
117pub mod slug;
118pub mod sort;
119pub mod shortcode_tokens;
120pub mod validation;