MakerFLOSS/build-slides.sh

185 lines
6.8 KiB
Bash
Raw Normal View History

#!/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/"