Skip to content

Commit 054c5bd

Browse files
committed
CI rendering
1 parent 30cd812 commit 054c5bd

File tree

2 files changed

+375
-0
lines changed

2 files changed

+375
-0
lines changed

.github/workflows/render.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: Render Papers
2+
3+
on:
4+
push:
5+
branches: [master]
6+
7+
concurrency:
8+
group: render-papers
9+
cancel-in-progress: true
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
render:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Check out master
19+
uses: actions/checkout@v4
20+
with:
21+
path: master
22+
23+
- name: Check out rendered branch
24+
run: |
25+
if git ls-remote --heads \
26+
"https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" \
27+
rendered | grep -q rendered; then
28+
git clone --single-branch --branch rendered \
29+
"https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" \
30+
rendered
31+
else
32+
mkdir -p rendered
33+
cd rendered
34+
git init
35+
git config user.name "github-actions[bot]"
36+
git config user.email "github-actions[bot]@users.noreply.github.com"
37+
git checkout --orphan rendered
38+
git commit --allow-empty -m "Initial rendered branch"
39+
git remote add origin \
40+
"https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git"
41+
fi
42+
43+
- name: Install pandoc
44+
uses: pandoc/actions/setup@v1
45+
46+
- name: Install mermaid-filter
47+
run: npm install -g mermaid-filter
48+
49+
# Google Chrome is pre-installed on ubuntu-latest.
50+
# Puppeteer (used by mermaid-filter) needs --no-sandbox on CI.
51+
# Create a wrapper so puppeteer's internal Chrome launch gets
52+
# the flag automatically.
53+
- name: Configure Chrome for CI
54+
run: |
55+
CHROME=$(which google-chrome || which chromium-browser || which chromium)
56+
cat > /tmp/chrome-no-sandbox <<WRAPPER
57+
#!/bin/bash
58+
exec "$CHROME" --no-sandbox --disable-setuid-sandbox "\$@"
59+
WRAPPER
60+
chmod +x /tmp/chrome-no-sandbox
61+
echo "PUPPETEER_EXECUTABLE_PATH=/tmp/chrome-no-sandbox" >> "$GITHUB_ENV"
62+
63+
- name: Render papers
64+
id: render
65+
continue-on-error: true
66+
run: |
67+
set -o pipefail
68+
bash master/tools/render.sh master rendered 2>&1 | tee /tmp/render.log
69+
70+
- name: Commit and push
71+
working-directory: rendered
72+
run: |
73+
git config user.name "github-actions[bot]"
74+
git config user.email "github-actions[bot]@users.noreply.github.com"
75+
76+
git add -A
77+
78+
if git diff --cached --quiet; then
79+
echo "No changes to commit"
80+
else
81+
SUBJECT=$(git -C "$GITHUB_WORKSPACE/master" log -1 --format=%s "$GITHUB_SHA")
82+
git commit -m "$(cat <<EOF
83+
Render from ${GITHUB_SHA::7}: $SUBJECT
84+
85+
Source commit: ${{ github.repository }}@${GITHUB_SHA}
86+
EOF
87+
)"
88+
git push origin rendered
89+
fi
90+
91+
- name: Fail if rendering had errors
92+
if: steps.render.outcome == 'failure'
93+
run: |
94+
cat /tmp/render.log
95+
exit 1

tools/render.sh

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
#!/usr/bin/env bash
2+
# render.sh - Render WG21 papers from markdown to HTML and PDF.
3+
#
4+
# Usage: render.sh <master-checkout> <rendered-worktree>
5+
#
6+
# Processes source/d*.md and archive/p*.md from the master checkout,
7+
# copies markdown into the rendered worktree, and generates HTML + PDF
8+
# in the rendered worktree root. Skips files whose content has not
9+
# changed since the last rendered commit.
10+
#
11+
# Can be run locally (Git Bash, macOS, Linux) or from CI.
12+
13+
set -uo pipefail
14+
15+
MASTER="${1:?Usage: render.sh <master-checkout> <rendered-worktree>}"
16+
RENDERED="${2:?Usage: render.sh <master-checkout> <rendered-worktree>}"
17+
18+
# Resolve to absolute paths
19+
MASTER="$(cd "$MASTER" && pwd)"
20+
RENDERED="$(cd "$RENDERED" && pwd)"
21+
TOOLS="$MASTER/tools"
22+
23+
# ── Detect platform ───────────────────────────────────────────
24+
25+
detect_chrome() {
26+
if [ "${OS:-}" = "Windows_NT" ]; then
27+
for candidate in \
28+
"${LOCALAPPDATA:-}/Google/Chrome/Application/chrome.exe" \
29+
"C:/Program Files/Google/Chrome/Application/chrome.exe" \
30+
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe"; do
31+
if [ -f "$candidate" ]; then
32+
echo "$candidate"
33+
return
34+
fi
35+
done
36+
elif [ "$(uname -s)" = "Darwin" ]; then
37+
for candidate in \
38+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
39+
"/Applications/Chromium.app/Contents/MacOS/Chromium"; do
40+
if [ -x "$candidate" ]; then
41+
echo "$candidate"
42+
return
43+
fi
44+
done
45+
else
46+
for candidate in google-chrome chromium-browser chromium; do
47+
if command -v "$candidate" > /dev/null 2>&1; then
48+
echo "$candidate"
49+
return
50+
fi
51+
done
52+
fi
53+
return 1
54+
}
55+
56+
if [ "${OS:-}" = "Windows_NT" ]; then
57+
MERMAID_FILTER="mermaid-filter.cmd"
58+
else
59+
MERMAID_FILTER="mermaid-filter"
60+
fi
61+
62+
CHROME="$(detect_chrome)" || {
63+
echo "ERROR: Google Chrome or Chromium not found"
64+
exit 1
65+
}
66+
67+
echo "Platform: $(uname -s)"
68+
echo "Chrome: $CHROME"
69+
echo "Mermaid filter: $MERMAID_FILTER"
70+
echo "Master: $MASTER"
71+
echo "Rendered: $RENDERED"
72+
echo ""
73+
74+
# ── Track failures ────────────────────────────────────────────
75+
76+
FAILURES=0
77+
78+
# ── Helper: extract YAML document field ───────────────────────
79+
80+
extract_document_field() {
81+
sed -n '/^---$/,/^---$/p' "$1" \
82+
| sed -n 's/^document:[[:space:]]*//p' \
83+
| tr -d '[:space:]'
84+
}
85+
86+
# ── Helper: render markdown to HTML ───────────────────────────
87+
88+
render_html() {
89+
local mdfile="$1" # relative to $RENDERED, e.g. source/d2583.md
90+
local htmlfile="$2" # relative to $RENDERED, e.g. d2583.html
91+
92+
cp "$TOOLS/mermaid-config.json" "$RENDERED/.mermaid-config.json"
93+
(
94+
cd "$RENDERED"
95+
MERMAID_FILTER_FORMAT=svg pandoc --standalone \
96+
--filter "$MERMAID_FILTER" \
97+
--embed-resources --toc \
98+
--template="$TOOLS/wg21.html5" \
99+
--css="$TOOLS/paperstyle.css" \
100+
-o "$htmlfile" "$mdfile"
101+
)
102+
local rc=$?
103+
rm -f "$RENDERED/.mermaid-config.json"
104+
[ ! -s "$RENDERED/mermaid-filter.err" ] \
105+
&& rm -f "$RENDERED/mermaid-filter.err" 2>/dev/null || true
106+
return $rc
107+
}
108+
109+
# ── Helper: render HTML to PDF ────────────────────────────────
110+
111+
render_pdf() {
112+
local htmlfile="$1" # absolute path
113+
local pdffile="$2" # absolute path
114+
115+
# On MSYS/Cygwin, convert to a Windows path for the file:// URL.
116+
# MSYS auto-converts bare path arguments but not paths embedded
117+
# inside a URL string.
118+
local html_url
119+
if command -v cygpath > /dev/null 2>&1; then
120+
html_url="file://$(cygpath -m "$htmlfile")"
121+
else
122+
html_url="file://$htmlfile"
123+
fi
124+
125+
"$CHROME" --headless --no-pdf-header-footer \
126+
--run-all-compositor-stages-before-draw \
127+
--disable-gpu --no-sandbox \
128+
--print-to-pdf="$pdffile" \
129+
"$html_url" 2> >(grep -v 'dbus/' >&2)
130+
}
131+
132+
# ── Process a directory ───────────────────────────────────────
133+
# Args: $1 = subdir name ("source" or "archive")
134+
# $2 = glob pattern ("d*.md" or "p*.md")
135+
# $3 = "validate" or "skip" (YAML check)
136+
137+
process_dir() {
138+
local subdir="$1"
139+
local glob="$2"
140+
local validate="$3"
141+
142+
local src_dir="$MASTER/$subdir"
143+
local dst_dir="$RENDERED/$subdir"
144+
145+
mkdir -p "$dst_dir"
146+
147+
# Collect matching markdown files
148+
local md_files=()
149+
for f in "$src_dir"/$glob; do
150+
[ -f "$f" ] || continue
151+
md_files+=("$(basename "$f")")
152+
done
153+
154+
if [ ${#md_files[@]} -eq 0 ]; then
155+
echo "No $glob files in $subdir/"
156+
return
157+
fi
158+
159+
echo "=== Processing $subdir/ (${#md_files[@]} file(s)) ==="
160+
161+
for mdname in "${md_files[@]}"; do
162+
local stem="${mdname%.md}"
163+
local md_dst="$dst_dir/$mdname"
164+
local html_dst="$RENDERED/$stem.html"
165+
local pdf_dst="$RENDERED/$stem.pdf"
166+
167+
echo ""
168+
echo "-- $subdir/$mdname --"
169+
170+
# Copy markdown to rendered worktree
171+
cp "$src_dir/$mdname" "$md_dst"
172+
173+
# ── YAML validation (archive only) ────────────────
174+
if [ "$validate" = "validate" ]; then
175+
local doc_field
176+
doc_field="$(extract_document_field "$md_dst")" || true
177+
local doc_lower
178+
doc_lower="$(echo "$doc_field" | tr '[:upper:]' '[:lower:]')"
179+
local file_prefix="${stem%%-*}"
180+
181+
if [ -z "$doc_field" ]; then
182+
echo " "
183+
echo " "
184+
echo -e " \033[31m*** ERROR\033[0m"
185+
echo -e " \033[31m*** ERROR: no document field in YAML front matter\033[0m"
186+
echo -e " \033[31m*** ERROR\033[0m"
187+
echo " "
188+
echo " "
189+
git -C "$RENDERED" rm -f --ignore-unmatch "$(basename "$html_dst")" "$(basename "$pdf_dst")" 2>/dev/null || rm -f "$html_dst" "$pdf_dst"
190+
FAILURES=$((FAILURES + 1))
191+
continue
192+
fi
193+
194+
if [ "$doc_lower" != "$file_prefix" ]; then
195+
echo " "
196+
echo " "
197+
echo -e " \033[31m*** ERROR\033[0m"
198+
echo -e " \033[31m*** ERROR: document '$doc_field' does not match filename prefix '$file_prefix'\033[0m"
199+
echo -e " \033[31m*** ERROR\033[0m"
200+
echo " "
201+
echo " "
202+
git -C "$RENDERED" rm -f --ignore-unmatch "$(basename "$html_dst")" "$(basename "$pdf_dst")" 2>/dev/null || rm -f "$html_dst" "$pdf_dst"
203+
FAILURES=$((FAILURES + 1))
204+
continue
205+
fi
206+
echo " YAML OK: $doc_field"
207+
fi
208+
209+
# ── Detect if HTML needs regeneration ─────────────
210+
local need_html=false
211+
local need_pdf=false
212+
213+
# Check if the markdown content changed in the rendered worktree
214+
if (cd "$RENDERED" && git diff --quiet -- "$subdir/$mdname") 2>/dev/null; then
215+
# File matches what is committed - but is it tracked at all?
216+
if (cd "$RENDERED" && git ls-files --error-unmatch "$subdir/$mdname") > /dev/null 2>&1; then
217+
echo " Markdown unchanged"
218+
else
219+
echo " New file"
220+
need_html=true
221+
fi
222+
else
223+
echo " Markdown changed"
224+
need_html=true
225+
fi
226+
227+
# Force regeneration if HTML is missing
228+
if [ ! -f "$html_dst" ]; then
229+
echo " HTML missing"
230+
need_html=true
231+
fi
232+
233+
# ── Render HTML if needed ─────────────────────────
234+
if [ "$need_html" = true ]; then
235+
echo " Generating HTML..."
236+
if ! render_html "$subdir/$mdname" "$stem.html"; then
237+
echo " ERROR: HTML generation failed for $mdname"
238+
git -C "$RENDERED" rm -f --ignore-unmatch "$(basename "$html_dst")" "$(basename "$pdf_dst")" 2>/dev/null || rm -f "$html_dst" "$pdf_dst"
239+
FAILURES=$((FAILURES + 1))
240+
continue
241+
fi
242+
need_pdf=true
243+
fi
244+
245+
# Force regeneration if PDF is missing
246+
if [ ! -f "$pdf_dst" ]; then
247+
echo " PDF missing"
248+
need_pdf=true
249+
fi
250+
251+
# ── Render PDF if needed ──────────────────────────
252+
if [ "$need_pdf" = true ]; then
253+
echo " Generating PDF..."
254+
if ! render_pdf "$html_dst" "$pdf_dst"; then
255+
echo " ERROR: PDF generation failed for $mdname"
256+
git -C "$RENDERED" rm -f --ignore-unmatch "$(basename "$pdf_dst")" 2>/dev/null || rm -f "$pdf_dst"
257+
FAILURES=$((FAILURES + 1))
258+
continue
259+
fi
260+
fi
261+
262+
if [ "$need_html" = false ] && [ "$need_pdf" = false ]; then
263+
echo " Up to date"
264+
fi
265+
done
266+
}
267+
268+
# ── Main ──────────────────────────────────────────────────────
269+
270+
process_dir "source" "d*.md" "skip"
271+
echo ""
272+
process_dir "archive" "p*.md" "validate"
273+
274+
echo ""
275+
if [ "$FAILURES" -gt 0 ]; then
276+
echo "Completed with $FAILURES failure(s)"
277+
exit 1
278+
else
279+
echo "All papers rendered successfully"
280+
fi

0 commit comments

Comments
 (0)