MakerFLOSS/build-slides.sh
Lars Rossen 87650dac86
All checks were successful
Build docs site / build (push) Successful in 44s
Build slides / build (push) Successful in 1m4s
slides(tappaas): photograph slide, and publish deck images
Adds a slide after the hardware diagram showing the two racks side by side —
the MakerFLOSS rack at Orange Makerspace and the one at home — to make the
point that the module contract is what lets those two rooms run the same
system.

build-slides.sh now copies image files from slides/ into the output tree at
the same relative path. Marp keeps relative image URLs, so without this the
images would 404 on the published site. Guarded against the local build,
where OUTPUT_DIR is the source directory and the copy would be a no-op onto
itself. This is the first deck in the repo with images; the convention is
documented in docs/services/slides.md.

Photos resampled to 1200px on the long edge (~300 KB each) from the 3072x4080
originals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 18:42:11 +02:00

184 lines
6.8 KiB
Bash
Executable file

#!/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/<name>.md -> <name>.html (flat, historical)
# slides/<path>/<name>.md -> <path>/<name>.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 "<repo-relative source>|<output-relative target>" 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 <source-rel> <target-rel>
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 <pre><code class="language-mermaid">.
# The script replaces those elements with <div class="mermaid">, 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 = """\
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
document.querySelectorAll('code.language-mermaid').forEach(el => {
const div = document.createElement('div');
div.className = 'mermaid';
div.textContent = el.textContent;
el.parentElement.replaceWith(div);
});
mermaid.initialize({ startOnLoad: false, theme: 'dark' });
await mermaid.run();
</script>"""
with open(path, encoding='utf-8') as f:
content = f.read()
# Inline <code>TODO…</code> -> <code class="todo">…</code>; decks style that class.
content, todos = re.subn(r'<code>(TODO\b[^<]*)</code>', r'<code class="todo">\1</code>', content)
if 'class="language-mermaid"' in content:
if '</body>' not in content:
print(f"Warning: </body> not found in {path}", file=sys.stderr)
sys.exit(1)
content = content.replace('</body>', snippet + '\n</body>', 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 <name>.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 '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
printf '<meta name="viewport" content="width=device-width,initial-scale=1">'
printf '<title>%s</title>' "$SLIDES_TITLE"
printf '<style>'
printf 'body{font-family:sans-serif;max-width:700px;margin:3rem auto;padding:0 1.5rem;background:#fafafa;}'
printf 'h1{font-size:1.6rem;margin-bottom:.25rem;}p.sub{color:#666;margin-top:0;}'
printf 'ul{list-style:none;padding:0;margin-top:2rem;}'
printf 'li{border:1px solid #e0e0e0;border-radius:6px;margin:.75rem 0;background:#fff;}'
printf 'li a{display:block;padding:1rem 1.25rem;text-decoration:none;color:#1a1a1a;font-size:1.05rem;}'
printf 'li a:hover{background:#f0f4ff;border-radius:6px;}'
printf 'footer{margin-top:3rem;color:#aaa;font-size:.8rem;}'
printf '</style></head><body>'
printf '<h1>%s</h1>' "$SLIDES_TITLE"
printf '<p class="sub">Built from <a href="%s">%s</a></p>' \
"$REPO_WEB_URL" "${REPO_WEB_URL#https://}"
printf '<ul>'
for deck in "${DECKS[@]}"; do
href="${deck#*|}"
label="${href%/index.html}"; label="${label%.html}"
printf '<li><a href="%s">%s</a></li>' "$href" "$label"
done
printf '</ul>'
printf '<footer>Last built: %s &middot; <a href="https://marp.app">Marp</a></footer>' "$(date -Iseconds)"
printf '</body></html>'
} > "$INDEX"
echo "Done — slides in $OUTPUT_DIR/"