Data model v3: content-addressed chunk stores, Patch, Node.chunks/langs

This commit is contained in:
2026-09-02 01:30:04 +00:00
parent 8558345eda
commit 9bc71b8717
+50 -5
View File
@@ -2,8 +2,9 @@
The site structure is a tree of Nodes. Every node is a menu label with a
configurable title and slug (its key in the parent's ``children``); the
URL path is the chain of slugs from the top level. ``content`` is the
node's Markdown page, or None for a pure category label, whose URL renders
URL path is the chain of slugs from the top level. ``chunks`` is the
node's Markdown page as ordered content-hash keys into ``Data.chunks``
(docs/migrate.md), or None for a pure category label, whose URL renders
a placeholder page while nav links point at its first child.
"""
@@ -11,6 +12,16 @@ from datetime import UTC, datetime
import msgspec
from pagerite.chunks import join_chunks
class Patch(msgspec.Struct, omit_defaults=True):
"""One editing session's overrides on a translated view, applied
independently per hunk (docs/localization.md)."""
#: (search, replace) pairs on the served hybrid Markdown.
hunks: list[tuple[str, str]] = []
class Node(msgspec.Struct, omit_defaults=True):
"""One item of the site hierarchy.
@@ -28,9 +39,21 @@ class Node(msgspec.Struct, omit_defaults=True):
title: str = ""
order: float = 0
#: Markdown source of the node's page; None = pure category label
#: (its URL renders a placeholder page).
content: str | None = None
#: Ordered chunk hashes into ``Data.chunks``; None = pure category
#: label (its URL renders a placeholder page), a list (possibly
#: empty) = a page.
chunks: list[str] | None = None
#: Primary language of the article (BCP-47 base tag). "" = inherit
#: (nearest ancestor, front page last, site default "en" final).
language: str = ""
#: Chunk hashes the editor marked "do not translate" (always served
#: from the original). Presence-keys, value always True.
no_trans: dict[str, bool] = {}
#: Languages this article is available in (besides its primary
#: language). Presence-keys, value always True — the availability
#: index for rendering, language selection and hreflang alternates;
#: maintained by whoever writes translation data (docs/migrate.md).
langs: dict[str, bool] = {}
#: Raw HTML for the header banner (img, styled div, canvas+script...),
#: rendered after the banner design's artwork so author code always
#: wins over the design's own styles.
@@ -78,6 +101,28 @@ class Data(msgspec.Struct):
#: linked as <link rel="icon"> on every page. Empty = the build's
#: /favicon.ico.
favicon: str = ""
#: All original-language page text, content-addressed:
#: chunk_key -> Markdown chunk. Shared by every article.
chunks: dict[str, str] = {}
#: Machine translations: f"{chunk_hash}:{lang}" -> translated
#: Markdown. Also used for node titles (hash of the title text).
trans: dict[str, str] = {}
#: User override patches per article and language:
#: f"{path}:{lang}" -> ordered patches (paths without leading slash).
patches: dict[str, list[Patch]] = {}
def node_markdown(data: Data, node: Node) -> str | None:
"""The node's original Markdown assembled from the chunk store.
None for category labels (chunks is None); an empty page gives "".
Hashes missing from the store (shouldn't happen) are skipped.
"""
if node.chunks is None:
return None
return join_chunks(
[t for h in node.chunks if (t := data.chunks.get(h)) is not None]
)
def prettify(slug: str) -> str: