|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: MPL-2.0 |
| 3 | +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 4 | +# |
| 5 | +# check-docs-render.sh — every .adoc must actually render. |
| 6 | +# |
| 7 | +# The estate rule "AsciiDoc by default" is enforced by extension |
| 8 | +# (check-no-md-in-docs.sh). Nothing enforced that the *content* parses. A |
| 9 | +# malformed table, an unterminated listing block, or a stray level-0 heading |
| 10 | +# renders to silently mangled output and ships unnoticed. |
| 11 | +# |
| 12 | +# This renders every .adoc with --failure-level=WARN and fails on any |
| 13 | +# diagnostic. It is the content-side counterpart to the extension-side check. |
| 14 | +# |
| 15 | +# Known-bad files are quarantined in ALLOWED below rather than silently |
| 16 | +# skipped — the list is meant to shrink, and an empty list is the goal. |
| 17 | +# |
| 18 | +# Exit codes: |
| 19 | +# 0 — every non-allow-listed .adoc renders without warnings |
| 20 | +# 1 — one or more failed to render cleanly |
| 21 | +# 2 — usage / setup error (asciidoctor missing) |
| 22 | + |
| 23 | +set -euo pipefail |
| 24 | + |
| 25 | +REPO_ROOT="${1:-.}" |
| 26 | + |
| 27 | +# Quarantined: half-converted Markdown carrying an .adoc extension (a `= Title` |
| 28 | +# line bolted onto a Markdown body — `#` headings, <!-- --> comments, | tables). |
| 29 | +# These satisfy check-no-md-in-docs.sh by extension while still being Markdown. |
| 30 | +# All are RSR boilerplate, not game design. Fix by converting the body properly, |
| 31 | +# then delete the entry. Do not add game-design docs to this list. |
| 32 | +ALLOWED=( |
| 33 | + "docs/architecture/THREAT-MODEL.adoc" |
| 34 | + "docs/decisions/0000-template.adoc" |
| 35 | + "docs/decisions/0001-adopt-rsr-standard.adoc" |
| 36 | + "docs/developer/ABI-FFI-README.adoc" |
| 37 | + "docs/practice/AI-CONVENTIONS.adoc" |
| 38 | + "docs/STATE-VISUALIZER.adoc" |
| 39 | +) |
| 40 | + |
| 41 | +if ! command -v asciidoctor >/dev/null 2>&1; then |
| 42 | + echo "FAIL: asciidoctor not found. Install with: gem install asciidoctor" >&2 |
| 43 | + exit 2 |
| 44 | +fi |
| 45 | + |
| 46 | +if [ ! -d "$REPO_ROOT" ]; then |
| 47 | + echo "FAIL: not a directory: $REPO_ROOT" >&2 |
| 48 | + exit 2 |
| 49 | +fi |
| 50 | + |
| 51 | +mapfile -t FILES < <(find "$REPO_ROOT" -name '*.adoc' -type f -not -path '*/.git/*' | sort) |
| 52 | + |
| 53 | +if [ ${#FILES[@]} -eq 0 ]; then |
| 54 | + echo "PASS: no .adoc files found (nothing to check)" |
| 55 | + exit 0 |
| 56 | +fi |
| 57 | + |
| 58 | +BROKEN=() |
| 59 | +CLEAN=0 |
| 60 | +SKIPPED=0 |
| 61 | + |
| 62 | +for f in "${FILES[@]}"; do |
| 63 | + rel="${f#"$REPO_ROOT/"}" |
| 64 | + rel="${rel#./}" |
| 65 | + |
| 66 | + skip=0 |
| 67 | + for allowed in "${ALLOWED[@]}"; do |
| 68 | + if [ "$rel" = "$allowed" ]; then skip=1; break; fi |
| 69 | + done |
| 70 | + if [ $skip -eq 1 ]; then |
| 71 | + SKIPPED=$((SKIPPED + 1)) |
| 72 | + continue |
| 73 | + fi |
| 74 | + |
| 75 | + if out=$(asciidoctor --failure-level=WARN -o /dev/null "$f" 2>&1) && [ -z "$out" ]; then |
| 76 | + CLEAN=$((CLEAN + 1)) |
| 77 | + else |
| 78 | + BROKEN+=("$rel") |
| 79 | + BROKEN+=("$(echo "$out" | sed 's/^/ /')") |
| 80 | + fi |
| 81 | +done |
| 82 | + |
| 83 | +if [ ${#BROKEN[@]} -eq 0 ]; then |
| 84 | + echo "PASS: $CLEAN .adoc files render cleanly (${SKIPPED} quarantined in ALLOWED)" |
| 85 | + exit 0 |
| 86 | +fi |
| 87 | + |
| 88 | +echo "FAIL: $(( ${#BROKEN[@]} / 2 )) .adoc file(s) do not render cleanly:" >&2 |
| 89 | +for ((i = 0; i < ${#BROKEN[@]}; i += 2)); do |
| 90 | + echo " - ${BROKEN[i]}" >&2 |
| 91 | + echo "${BROKEN[i + 1]}" >&2 |
| 92 | +done |
| 93 | +echo "" >&2 |
| 94 | +echo "Fix the markup, or — only for pre-existing boilerplate — add a justified" >&2 |
| 95 | +echo "entry to the ALLOWED list in scripts/check-docs-render.sh." >&2 |
| 96 | +exit 1 |
0 commit comments