|
| 1 | +<!-- |
| 2 | +SPDX-License-Identifier: CC-BY-SA-4.0 |
| 3 | +SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 4 | +--> |
| 5 | + |
| 6 | +[](https://www.bestpractices.dev/en/projects/new?repo_url=https://github.com/hyperpolymath/casket-ssg) |
| 7 | +[](https://github.com/hyperpolymath/casket-ssg/blob/main/LICENSE) |
| 8 | + |
| 9 | +[](https://github.com/hyperpolymath/poly-ssg) |
| 10 | +[](https://github.com/hyperpolymath/rhodium-standard-repositories) |
| 11 | + |
| 12 | +**An accessibility-first, metadata-driven static site generator.** |
| 13 | +Markdown in, an accessible website out — WCAG-aware output *by default*, |
| 14 | +plus a metadata layer (**Gnosis**) that renders pages from typed project |
| 15 | +data. The Haskell member of |
| 16 | +[poly-ssg](https://github.com/hyperpolymath/poly-ssg). |
| 17 | + |
| 18 | +> [!NOTE] |
| 19 | +> **Status: alpha.** The `build` / `clean` pipeline works and generates |
| 20 | +> real sites. Items marked *Planned* below are not yet implemented. The |
| 21 | +> engine’s direction — demote Pandoc, adopt a Djot prose core + `a2ml` |
| 22 | +> typed content on the |
| 23 | +> [boj](https://github.com/hyperpolymath/boj-server) substrate — is |
| 24 | +> recorded in [ADR-0001](docs/decisions/ADR-0001-content-engine.adoc). |
| 25 | +> Architecture & invariants: [EXPLAINME](EXPLAINME.adoc). |
| 26 | +
|
| 27 | +<div id="toc"> |
| 28 | + |
| 29 | +</div> |
| 30 | + |
| 31 | +# Example |
| 32 | + |
| 33 | +A minimal site is a tree of Markdown with front-matter: |
| 34 | + |
| 35 | + content/ |
| 36 | + ├── index.md |
| 37 | + └── posts/ |
| 38 | + └── 2026-06-23-hello.md |
| 39 | + |
| 40 | +```markdown |
| 41 | +--- |
| 42 | +title: Hello, world |
| 43 | +date: 2026-06-23 |
| 44 | +tags: [intro] |
| 45 | +description: A first page built with casket. |
| 46 | +--- |
| 47 | +# Hello |
| 48 | + |
| 49 | +Markdown with **rich** formatting, tables, footnotes and highlighted code. |
| 50 | +``` |
| 51 | + |
| 52 | +Build it: |
| 53 | + |
| 54 | +```bash |
| 55 | +casket-ssg build ./content ./_site |
| 56 | +``` |
| 57 | + |
| 58 | +You get an **accessible** `_site/` with no configuration: |
| 59 | + |
| 60 | +- `index.html` and `posts/2026-06-23-hello/index.html` rendered through |
| 61 | + an accessible default theme (skip link, labelled landmarks, light/dark |
| 62 | + `color-scheme`), with table headers auto-scoped for WCAG 1.3.1; |
| 63 | + |
| 64 | +- a date-sorted collection index for `posts/`, and per-tag pages under |
| 65 | + `/tags/`; |
| 66 | + |
| 67 | +- `sitemap.xml` and an Atom `feed.xml`. |
| 68 | + |
| 69 | +No Node, no npm, no required config file. |
| 70 | + |
| 71 | +# Installation |
| 72 | + |
| 73 | +casket is not yet on Hackage — build from source (GHC 9.4+, Cabal 3 or |
| 74 | +Stack 2): |
| 75 | + |
| 76 | +```bash |
| 77 | +git clone https://github.com/hyperpolymath/casket-ssg |
| 78 | +cd casket-ssg |
| 79 | +stack build # or: cabal build |
| 80 | +stack run casket-ssg -- --version # or put the built binary on PATH |
| 81 | +``` |
| 82 | + |
| 83 | +CLI: |
| 84 | + |
| 85 | +```bash |
| 86 | +casket-ssg build <input-dir> [output-dir] # default output: _site |
| 87 | +casket-ssg build --drafts <input-dir> # include draft: true pages (or CASKET_DRAFTS=1) |
| 88 | +casket-ssg build --no-clean-urls <input-dir> # emit foo.html, not foo/index.html |
| 89 | +casket-ssg clean [output-dir] |
| 90 | +casket-ssg --version |
| 91 | +``` |
| 92 | + |
| 93 | +# What makes it different |
| 94 | + |
| 95 | +Accessibility by construction |
| 96 | +The default theme is accessible by default, casket rewrites Pandoc table |
| 97 | +headers to add `scope="col"`, and the Gnosis `FlexiText` type makes |
| 98 | +**empty alt-text a constructor error** — generated badges cannot ship |
| 99 | +without alt text. Accessibility is a property of the engine, not a |
| 100 | +checklist for the author. |
| 101 | + |
| 102 | +Metadata-driven (Gnosis) |
| 103 | +An optional layer renders pages from a typed metadata context: |
| 104 | +`(:placeholder)` substitution, `{{#if}}` / `{{#for}}` conditionals and |
| 105 | +loops (DAX), and Shields.io badges generated from typed values — sourced |
| 106 | +from the repo’s `.machine_readable/descriptiles/` data. |
| 107 | + |
| 108 | +Verified & agent-native — the trajectory |
| 109 | +casket is heading toward typed `a2ml` content (build fails on malformed |
| 110 | +data), a formally-verifiable Djot core, and a verified agent surface as |
| 111 | +a [boj](https://github.com/hyperpolymath/boj-server) cartridge rather |
| 112 | +than an embedded MCP server. See |
| 113 | +[ADR-0001](docs/decisions/ADR-0001-content-engine.adoc). |
| 114 | + |
| 115 | +Permissively licensed |
| 116 | +**MPL-2.0** — build proprietary or differently-licensed sites on top, |
| 117 | +unlike copyleft alternatives. |
| 118 | + |
| 119 | +# Features |
| 120 | + |
| 121 | +**Implemented** |
| 122 | + |
| 123 | +- Recursive multi-page build mirroring the source tree |
| 124 | + |
| 125 | +- Front-matter: `title`, `date`, `description`, `layout`, `draft`, |
| 126 | + `tags`, `slug`, plus arbitrary keys as `{{key}}` template vars |
| 127 | + |
| 128 | +- Rich Markdown via Pandoc — anchors, pipe tables, fenced/highlighted |
| 129 | + code, footnotes, task lists, strikethrough, smart punctuation |
| 130 | + |
| 131 | +- File templates with per-page `layout`, partials (`{{>` `name}}`), |
| 132 | + `{{nav}}`, `{macro}`, `{{site.*}}` (from `site.conf`) |
| 133 | + |
| 134 | +- Collections (date-sorted directory index) and per-tag pages |
| 135 | + (`/tags/<tag>/`) |
| 136 | + |
| 137 | +- `sitemap.xml` + Atom `feed.xml` |
| 138 | + |
| 139 | +- Accessible default theme + automatic `scope="col"` on table headers |
| 140 | + |
| 141 | +- Gnosis layer — `(:placeholder)`, `{{#if}}`/`{{#for}}` (DAX), |
| 142 | + `FlexiText` badges |
| 143 | + |
| 144 | +- Clean URLs, toggleable |
| 145 | + |
| 146 | +**Planned** (do not assume these work today — see ADR-0001) |
| 147 | + |
| 148 | +- `init` scaffolding and a `serve` / live-reload dev server |
| 149 | + |
| 150 | +- Incremental rebuilds; asset fingerprinting |
| 151 | + |
| 152 | +- `a2ml`-native typed content + the Djot core; Pandoc relegated to an |
| 153 | + importer |
| 154 | + |
| 155 | +- `boj` cartridge registration; `llms.txt` / per-page machine views |
| 156 | + |
| 157 | +# Documentation |
| 158 | + |
| 159 | +- [EXPLAINME](EXPLAINME.adoc) — architecture & invariants (developer |
| 160 | + deep-dive) |
| 161 | + |
| 162 | +- [ADR-0001](docs/decisions/ADR-0001-content-engine.adoc) — |
| 163 | + content-engine direction |
| 164 | + |
| 165 | +- [Contributing](CONTRIBUTING.adoc) · [Governance](GOVERNANCE.adoc) |
| 166 | + |
| 167 | +# Where it fits |
| 168 | + |
| 169 | +- [**poly-ssg**](https://github.com/hyperpolymath/poly-ssg) — the family |
| 170 | + of language-native SSGs casket belongs to, unified via MCP. |
| 171 | + |
| 172 | +- [**ddraig**](https://github.com/hyperpolymath/ddraig-ssg) — the |
| 173 | + dependently-typed (Idris2) sibling; casket’s "proven twin" (casket |
| 174 | + validates, ddraig proves). |
| 175 | + |
| 176 | +- [**boj-server**](https://github.com/hyperpolymath/boj-server) — the |
| 177 | + estate’s verified MCP/capability server casket plugs into as a |
| 178 | + cartridge. |
| 179 | + |
| 180 | +# Requirements |
| 181 | + |
| 182 | +- GHC 9.4+ (CI builds with 9.4.8), Cabal 3.0+ or Stack 2.0+ |
| 183 | + |
| 184 | +- Pandoc (current library dependency; see ADR-0001 for the planned move |
| 185 | + off it) |
| 186 | + |
| 187 | +# License |
| 188 | + |
| 189 | +Code: **MPL-2.0** ([LICENSE](LICENSE)). Prose documentation: |
| 190 | +`CC-BY-SA-4.0`. |
0 commit comments