#!/usr/bin/env bash # Build every Marp deck in the repository into static HTML. # # A deck is any *.md carrying `marp: true` in its front matter. Two source # roots, two output conventions: # # docs/presentations/.md -> .html (flat, historical) # slides//.md -> /.html (path mirrors URL) # # So slides/tappaas/how-to/new-module/index.md is served at # https://slides.makerfloss.eu/tappaas/how-to/new-module/ . set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/slides}" SLIDES_TITLE="${SLIDES_TITLE:-MakerFLOSS Slides}" REPO_WEB_URL="${REPO_WEB_URL:-https://forgejo.makerfloss.eu/sjat/MakerFLOSS}" mkdir -p "$OUTPUT_DIR" # Collect decks as "|" pairs. DECKS=() while IFS= read -r f; do rel="${f#"$REPO_ROOT"/}" DECKS+=("$rel|$(basename "${rel%.md}").html") done < <(grep -rl "^marp: true" "$REPO_ROOT/docs" --include="*.md" 2>/dev/null | sort || true) if [ -d "$REPO_ROOT/slides" ]; then while IFS= read -r f; do rel="${f#"$REPO_ROOT"/}" out="${rel#slides/}" DECKS+=("$rel|${out%.md}.html") done < <(grep -rl "^marp: true" "$REPO_ROOT/slides" --include="*.md" 2>/dev/null | sort || true) fi if [ ${#DECKS[@]} -eq 0 ]; then echo "No marp presentations found." exit 0 fi echo "Found ${#DECKS[@]} presentation(s):" printf ' %s\n' "${DECKS[@]%%|*}" # Wipe previously built HTML so deleted/de-tagged decks disappear. Only *.html # is removed, so deck sources living inside slides/ are never touched. find "$OUTPUT_DIR" -name "*.html" -type f -delete render() { # render local src="$1" dst="$2" mkdir -p "$OUTPUT_DIR/$(dirname "$dst")" if command -v marp &>/dev/null; then marp --html --output "$OUTPUT_DIR/$dst" "$REPO_ROOT/$src" elif command -v npx &>/dev/null; then npx --yes @marp-team/marp-cli --html --output "$OUTPUT_DIR/$dst" "$REPO_ROOT/$src" else # marpteam/marp-cli drops privileges to its own "marp" user, so the mounted # output tree has to be writable by anyone — and --user must NOT be passed. chmod -R a+rwX "$OUTPUT_DIR" docker run --rm \ -v "$REPO_ROOT":/home/marp/app:ro \ -v "$OUTPUT_DIR":/home/marp/out \ marpteam/marp-cli --html --output "/home/marp/out/$dst" "$src" fi } if ! command -v marp &>/dev/null && ! command -v npx &>/dev/null; then if command -v docker &>/dev/null; then echo "marp not found locally — using Docker (marpteam/marp-cli)..." else echo "error: need one of marp, npx or docker on PATH" >&2 exit 1 fi fi for deck in "${DECKS[@]}"; do render "${deck%%|*}" "${deck#*|}" done # Post-process each built page: highlight unfinished `TODO ...` markers, and # turn mermaid code blocks into live diagrams. # # Marp emits fenced mermaid blocks as
.
# The script replaces those elements with 
, then loads and # runs mermaid.js from CDN. postprocess() { local html_file="$1" python3 - "$html_file" << 'PYEOF' import re import sys path = sys.argv[1] snippet = """\ """ with open(path, encoding='utf-8') as f: content = f.read() # Inline TODO… -> …; decks style that class. content, todos = re.subn(r'(TODO\b[^<]*)', r'\1', content) if 'class="language-mermaid"' in content: if '' not in content: print(f"Warning: not found in {path}", file=sys.stderr) sys.exit(1) content = content.replace('', snippet + '\n', 1) print(f" Injected mermaid.js into {path}") with open(path, 'w', encoding='utf-8') as f: f.write(content) if todos: print(f" {todos} unfinished TODO marker(s) in {path}") PYEOF } if command -v python3 &>/dev/null; then # The landing index.html is generated further down, so every *.html present # right now is a built deck. while IFS= read -r html_file; do postprocess "$html_file" done < <(find "$OUTPUT_DIR" -name "*.html" -type f) else echo "Warning: python3 not found — skipping post-processing" fi # Copy deck assets (images) that live next to a source under slides/. Marp keeps # relative image URLs in the HTML, so the files must exist at the same relative # path in the output tree. Only slides/ can carry these: docs/presentations/ # decks are flattened to .html, so a relative path would not survive. # When OUTPUT_DIR is the in-repo slides/ dir, source and target are the same # file and there is nothing to copy. if [ -d "$REPO_ROOT/slides" ]; then while IFS= read -r -d '' asset; do rel="${asset#"$REPO_ROOT"/slides/}" dest="$OUTPUT_DIR/$rel" [ "$asset" -ef "$dest" ] && continue mkdir -p "$(dirname "$dest")" cp "$asset" "$dest" echo " Copied asset $rel" done < <(find "$REPO_ROOT/slides" -type f \ \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \ -o -iname "*.gif" -o -iname "*.svg" -o -iname "*.webp" \) -print0) fi # Regenerate index.html listing every built deck. INDEX="$OUTPUT_DIR/index.html" { printf '' printf '' printf '%s' "$SLIDES_TITLE" printf '' printf '

%s

' "$SLIDES_TITLE" printf '

Built from %s

' \ "$REPO_WEB_URL" "${REPO_WEB_URL#https://}" printf '
    ' for deck in "${DECKS[@]}"; do href="${deck#*|}" label="${href%/index.html}"; label="${label%.html}" printf '
  • %s
  • ' "$href" "$label" done printf '
' printf '' "$(date -Iseconds)" printf '' } > "$INDEX" echo "Done — slides in $OUTPUT_DIR/"