|
2 | 2 | // Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
3 | 3 | // SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
4 | 4 |
|
5 | | -= casket-ssg |
| 5 | += casket |
6 | 6 | image:https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?logo=github[Sponsor,link="https://github.com/sponsors/hyperpolymath"] |
7 | 7 | image:https://img.shields.io/badge/OpenSSF-Best_Practices-green?logo=opensourcesecurity[OpenSSF Best Practices,link="https://www.bestpractices.dev/en/projects/new?repo_url=https://github.com/hyperpolymath/casket-ssg"] |
8 | | -image:https://img.shields.io/badge/License-MPL_2.0--1.0-blue.svg[License: MPL-2.0,link="https://github.com/hyperpolymath/palimpsest-license"] |
9 | | -image:https://api.thegreenwebfoundation.org/greencheckimage/github.com[Green Web,link="https://www.thegreenwebfoundation.org/green-web-check/?url=github.com"] |
| 8 | +image:https://img.shields.io/badge/License-MPL_2.0-blue.svg[License: MPL-2.0,link="https://github.com/hyperpolymath/casket-ssg/blob/main/LICENSE"] |
| 9 | +image:https://img.shields.io/badge/a11y-WCAG--aware_by_default-success[Accessibility] |
| 10 | +image:https://img.shields.io/badge/part_of-poly--ssg-indigo[poly-ssg,link="https://github.com/hyperpolymath/poly-ssg"] |
| 11 | +image:https://img.shields.io/badge/RSR-compliant-gold[RSR Compliant,link="https://github.com/hyperpolymath/rhodium-standard-repositories"] |
10 | 12 |
|
11 | | - |
12 | | - |
13 | | - |
14 | | -image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] |
15 | | - |
16 | | - |
17 | | -:toc: auto |
| 13 | +:toc: macro |
18 | 14 | :toclevels: 2 |
19 | 15 |
|
20 | | -image:https://img.shields.io/badge/RSR-compliant-gold[RSR Compliant,link=https://github.com/hyperpolymath/rhodium-standard-repositories] |
21 | | - |
22 | | -**A Pandoc-based static site generator in Haskell, with an accessibility-first, metadata-driven rendering layer (Gnosis).** |
| 16 | +**An accessibility-first, metadata-driven static site generator.** Markdown in, |
| 17 | +an accessible website out — WCAG-aware output _by default_, plus a metadata layer |
| 18 | +(*Gnosis*) that renders pages from typed project data. The Haskell member of |
| 19 | +https://github.com/hyperpolymath/poly-ssg[poly-ssg]. |
23 | 20 |
|
24 | 21 | [NOTE] |
25 | 22 | ==== |
26 | 23 | *Status: alpha.* The `build` / `clean` pipeline works and generates real sites. |
27 | | -Several items below are *planned, not yet implemented* — they are called out |
28 | | -explicitly so nothing here over-promises. The content engine's direction |
29 | | -(demote Pandoc; adopt a Djot prose core + `a2ml` typed content) is recorded in |
30 | | -link:docs/decisions/ADR-0001-content-engine.adoc[ADR-0001]. |
| 24 | +Items marked _Planned_ below are not yet implemented. The engine's direction — |
| 25 | +demote Pandoc, adopt a Djot prose core + `a2ml` typed content on the |
| 26 | +https://github.com/hyperpolymath/boj-server[boj] substrate — is recorded in |
| 27 | +link:docs/decisions/ADR-0001-content-engine.adoc[ADR-0001]. Architecture & |
| 28 | +invariants: link:EXPLAINME.adoc[EXPLAINME]. |
31 | 29 | ==== |
32 | 30 |
|
33 | | -== Who Is This For? |
| 31 | +toc::[] |
34 | 32 |
|
35 | | -* **Haskell developers** who want a small, scriptable content-site generator |
36 | | -* **Accessibility-conscious authors** who want WCAG-aware output by default |
37 | | -* **poly-ssg / estate** users who want metadata-driven pages (the Gnosis layer) |
| 33 | +== Example |
38 | 34 |
|
39 | | -== Why casket-ssg? |
| 35 | +A minimal site is a tree of Markdown with front-matter: |
40 | 36 |
|
41 | | -=== Accessibility by construction |
| 37 | +[source] |
| 38 | +---- |
| 39 | +content/ |
| 40 | +├── index.md |
| 41 | +└── posts/ |
| 42 | + └── 2026-06-23-hello.md |
| 43 | +---- |
| 44 | + |
| 45 | +[source,markdown] |
| 46 | +---- |
| 47 | +--- |
| 48 | +title: Hello, world |
| 49 | +date: 2026-06-23 |
| 50 | +tags: [intro] |
| 51 | +description: A first page built with casket. |
| 52 | +--- |
| 53 | +# Hello |
| 54 | +
|
| 55 | +Markdown with **rich** formatting, tables, footnotes and highlighted code. |
| 56 | +---- |
| 57 | + |
| 58 | +Build it: |
| 59 | + |
| 60 | +[source,bash] |
| 61 | +---- |
| 62 | +casket-ssg build ./content ./_site |
| 63 | +---- |
42 | 64 |
|
43 | | -The built-in default theme is accessible by default — `<html lang>`, a skip link, |
44 | | -labelled `header`/`nav`/`main`/`footer` landmarks, viewport meta, and a light/dark |
45 | | -`color-scheme`. casket also rewrites Pandoc's table headers to add `scope="col"` |
46 | | -(WCAG 1.3.1), and the Gnosis `FlexiText` type makes *empty alt-text a constructor |
47 | | -error* — generated badges cannot ship without alt text. |
| 65 | +You get an *accessible* `_site/` with no configuration: |
48 | 66 |
|
49 | | -=== Metadata-driven rendering (Gnosis) |
| 67 | +* `index.html` and `posts/2026-06-23-hello/index.html` rendered through an |
| 68 | + accessible default theme (skip link, labelled landmarks, light/dark |
| 69 | + `color-scheme`), with table headers auto-scoped for WCAG 1.3.1; |
| 70 | +* a date-sorted collection index for `posts/`, and per-tag pages under `/tags/`; |
| 71 | +* `sitemap.xml` and an Atom `feed.xml`. |
50 | 72 |
|
51 | | -An optional layer runs over the Markdown before Pandoc: `(:placeholder)` |
52 | | -substitution from a metadata context, `{{#if}}` / `{{#for}}` conditionals and loops |
53 | | -(DAX), and Shields.io badges generated from typed metadata. |
| 73 | +No Node, no npm, no required config file. |
54 | 74 |
|
55 | | -=== Pandoc-based content (today) |
| 75 | +== Installation |
56 | 76 |
|
57 | | -Rich Markdown via Pandoc — anchors, pipe tables, fenced code with syntax |
58 | | -highlighting, footnotes, task lists, strikethrough, smart punctuation. See |
59 | | -link:docs/decisions/ADR-0001-content-engine.adoc[ADR-0001] for the planned move to |
60 | | -a Djot core with Pandoc relegated to a one-shot importer. |
| 77 | +casket is not yet on Hackage — build from source (GHC 9.4+, Cabal 3 or Stack 2): |
61 | 78 |
|
62 | | -== Quick Start |
| 79 | +[source,bash] |
| 80 | +---- |
| 81 | +git clone https://github.com/hyperpolymath/casket-ssg |
| 82 | +cd casket-ssg |
| 83 | +stack build # or: cabal build |
| 84 | +stack run casket-ssg -- --version # or put the built binary on PATH |
| 85 | +---- |
63 | 86 |
|
64 | | -casket-ssg is not yet published to Hackage; build it from source. |
| 87 | +CLI: |
65 | 88 |
|
66 | 89 | [source,bash] |
67 | 90 | ---- |
68 | | -# Build (Stack or Cabal) |
69 | | -stack build # or: cabal build |
| 91 | +casket-ssg build <input-dir> [output-dir] # default output: _site |
| 92 | +casket-ssg build --drafts <input-dir> # include draft: true pages (or CASKET_DRAFTS=1) |
| 93 | +casket-ssg build --no-clean-urls <input-dir> # emit foo.html, not foo/index.html |
| 94 | +casket-ssg clean [output-dir] |
| 95 | +casket-ssg --version |
| 96 | +---- |
70 | 97 |
|
71 | | -# Build a site: build <input-dir> [output-dir] (default output: _site) |
72 | | -stack run casket-ssg -- build ./content ./_site |
73 | | -# ...or, with the built binary on PATH: |
74 | | -casket-ssg build ./content ./_site |
| 98 | +== What makes it different |
75 | 99 |
|
76 | | -# Include drafts (or set CASKET_DRAFTS=1) |
77 | | -casket-ssg build --drafts ./content |
| 100 | +Accessibility by construction:: |
| 101 | +The default theme is accessible by default, casket rewrites Pandoc table headers |
| 102 | +to add `scope="col"`, and the Gnosis `FlexiText` type makes *empty alt-text a |
| 103 | +constructor error* — generated badges cannot ship without alt text. Accessibility |
| 104 | +is a property of the engine, not a checklist for the author. |
78 | 105 |
|
79 | | -# Emit foo.html instead of foo/index.html |
80 | | -casket-ssg build --no-clean-urls ./content |
| 106 | +Metadata-driven (Gnosis):: |
| 107 | +An optional layer renders pages from a typed metadata context: `(:placeholder)` |
| 108 | +substitution, `{{#if}}` / `{{#for}}` conditionals and loops (DAX), and Shields.io |
| 109 | +badges generated from typed values — sourced from the repo's |
| 110 | +`.machine_readable/descriptiles/` data. |
81 | 111 |
|
82 | | -# Remove an output directory |
83 | | -casket-ssg clean ./_site |
| 112 | +Verified & agent-native — the trajectory:: |
| 113 | +casket is heading toward typed `a2ml` content (build fails on malformed data), a |
| 114 | +formally-verifiable Djot core, and a verified agent surface as a |
| 115 | +https://github.com/hyperpolymath/boj-server[boj] cartridge rather than an embedded |
| 116 | +MCP server. See link:docs/decisions/ADR-0001-content-engine.adoc[ADR-0001]. |
84 | 117 |
|
85 | | -# Version |
86 | | -casket-ssg --version |
87 | | ----- |
| 118 | +Permissively licensed:: |
| 119 | +*MPL-2.0* — build proprietary or differently-licensed sites on top, unlike |
| 120 | +copyleft alternatives. |
88 | 121 |
|
89 | 122 | == Features |
90 | 123 |
|
91 | | -*Implemented:* |
| 124 | +*Implemented* |
92 | 125 |
|
93 | | -* **Recursive multi-page build** mirroring the source tree into the output |
94 | | -* **Frontmatter** — `title`, `date`, `description`, `layout`, `draft`, `tags`, |
95 | | - `slug`, plus arbitrary keys exposed as `{{key}}` template vars |
96 | | -* **Rich Markdown via Pandoc** — anchors, pipe tables, fenced/highlighted code, |
| 126 | +* Recursive multi-page build mirroring the source tree |
| 127 | +* Front-matter: `title`, `date`, `description`, `layout`, `draft`, `tags`, |
| 128 | + `slug`, plus arbitrary keys as `{{key}}` template vars |
| 129 | +* Rich Markdown via Pandoc — anchors, pipe tables, fenced/highlighted code, |
97 | 130 | footnotes, task lists, strikethrough, smart punctuation |
98 | | -* **File templates** in `templates/` with per-page `layout` and partials |
99 | | - (`{{> name}}`), plus `{{nav}}`, `{{toc}}` |
100 | | -* **Site config** (`site.conf`, simple `key = value`) exposed as `{{site.*}}` |
101 | | -* **Collections** (a directory index sorted by date) and **per-tag pages** |
102 | | - (`/tags/<tag>/`) |
103 | | -* **`sitemap.xml`** and an Atom **`feed.xml`** |
104 | | -* **Accessible default theme** + automatic `scope="col"` on table headers |
105 | | -* **Gnosis layer** — `(:placeholder)` rendering, `{{#if}}` / `{{#for}}` (DAX), |
106 | | - and `FlexiText` (alt-text-required) badge generation |
107 | | -* **Clean URLs** (`foo.md` → `foo/index.html`), toggleable with `--no-clean-urls` |
108 | | - |
109 | | -*Planned / not yet implemented* (do not assume these work today): |
| 131 | +* File templates with per-page `layout`, partials (`{{> name}}`), `{{nav}}`, |
| 132 | + `{{toc}}`, `{{site.*}}` (from `site.conf`) |
| 133 | +* Collections (date-sorted directory index) and per-tag pages (`/tags/<tag>/`) |
| 134 | +* `sitemap.xml` + Atom `feed.xml` |
| 135 | +* Accessible default theme + automatic `scope="col"` on table headers |
| 136 | +* Gnosis layer — `(:placeholder)`, `{{#if}}`/`{{#for}}` (DAX), `FlexiText` badges |
| 137 | +* Clean URLs, toggleable |
| 138 | + |
| 139 | +*Planned* (do not assume these work today — see ADR-0001) |
110 | 140 |
|
111 | 141 | * `init` scaffolding and a `serve` / live-reload dev server |
112 | | -* Incremental rebuilds (the build is currently a full rebuild) |
113 | | -* Asset fingerprinting / hash-based cache-busting |
114 | | -* Schema-enforced frontmatter and compile-time template validation |
115 | | -* `a2ml`-native typed content and the Djot core (see |
116 | | - link:docs/decisions/ADR-0001-content-engine.adoc[ADR-0001]) |
| 142 | +* Incremental rebuilds; asset fingerprinting |
| 143 | +* `a2ml`-native typed content + the Djot core; Pandoc relegated to an importer |
| 144 | +* `boj` cartridge registration; `llms.txt` / per-page machine views |
117 | 145 |
|
118 | | -== Requirements |
| 146 | +== Documentation |
119 | 147 |
|
120 | | -* GHC 9.4+ (CI builds with GHC 9.4.8) and Cabal 3.0+ or Stack 2.0+ |
121 | | -* Pandoc (pulled in as a library dependency) |
| 148 | +* link:EXPLAINME.adoc[EXPLAINME] — architecture & invariants (developer deep-dive) |
| 149 | +* link:docs/decisions/ADR-0001-content-engine.adoc[ADR-0001] — content-engine direction |
| 150 | +* link:CONTRIBUTING.adoc[Contributing] · link:GOVERNANCE.adoc[Governance] |
122 | 151 |
|
123 | | -== Part of poly-ssg |
| 152 | +== Where it fits |
| 153 | + |
| 154 | +* https://github.com/hyperpolymath/poly-ssg[*poly-ssg*] — the family of |
| 155 | + language-native SSGs casket belongs to, unified via MCP. |
| 156 | +* https://github.com/hyperpolymath/ddraig-ssg[*ddraig*] — the dependently-typed |
| 157 | + (Idris2) sibling; casket's "proven twin" (casket validates, ddraig proves). |
| 158 | +* https://github.com/hyperpolymath/boj-server[*boj-server*] — the estate's |
| 159 | + verified MCP/capability server casket plugs into as a cartridge. |
| 160 | + |
| 161 | +== Requirements |
124 | 162 |
|
125 | | -casket-ssg is part of the https://github.com/hyperpolymath/poly-ssg[poly-ssg] family of language-native static site generators, unified through https://github.com/hyperpolymath/poly-ssg-mcp[MCP integration]. |
| 163 | +* GHC 9.4+ (CI builds with 9.4.8), Cabal 3.0+ or Stack 2.0+ |
| 164 | +* Pandoc (current library dependency; see ADR-0001 for the planned move off it) |
126 | 165 |
|
127 | 166 | == License |
128 | 167 |
|
129 | | -MPL-2.0 |
| 168 | +Code: *MPL-2.0* (link:LICENSE[LICENSE]). Prose documentation: `CC-BY-SA-4.0`. |
0 commit comments