Files
ferrix/slides/build.py
Rayhaan JaufeerallyandClaude Fable 5.1 2910a2a800 slides: light theme, final edits, single-file bundle
As presented. The palette is light on request of the reviewer; code blocks
and byte dumps keep a dark ground so the byte colours still work. The OPEN
walk lights the hovered bytes as an inverted block and its table cells are
dark text on a tint of their group colour.

build.py inlines reveal.js, the stylesheets, scripts and Markdown sections
into slides/dist/hands-on-bgp.html for uploading.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-01 20:40:55 +02:00

90 lines
4.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Bundle the deck into one self-contained HTML file for uploading.
python3 slides/build.py # writes slides/dist/hands-on-bgp.html
Everything the page fetches at runtime is inlined: reveal.js and its plugins
(from the vendored copy, so run vendor-reveal.sh first), the stylesheets, the
favicon, our scripts and the Markdown sections. The result opens from any web
server and from disk. Two things still need a network: the IBM Plex fonts
(Google Fonts) and the live DNS and RIPE lookups.
"""
import base64
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
REVEAL = os.path.join(HERE, "reveal.js", "dist")
OUT = os.path.join(HERE, "dist", "hands-on-bgp.html")
def read(*parts):
with open(os.path.join(HERE, *parts), encoding="utf-8") as f:
return f.read()
def main():
if not os.path.isdir(REVEAL):
sys.exit("reveal.js is not vendored; run slides/vendor-reveal.sh first")
html = read("index.html")
# Stylesheets: reveal's first, then ours, as the loader orders them.
css = "\n".join(read("reveal.js", "dist", p) for p in ["reset.css", "reveal.css", "plugin/highlight/monokai.css"])
css += "\n" + read("theme.css") + "\n" + read("style.css")
html = re.sub(r'\s*<link rel="stylesheet" href="theme\.css[^"]*" id="deck-style" />', "", html)
html = re.sub(r'\s*<link rel="stylesheet" href="style\.css[^"]*" />', f"\n <style>\n{css}\n </style>", html, count=1)
icon = "data:image/svg+xml;base64," + base64.b64encode(read("images", "favicon.svg").encode()).decode()
html = html.replace('href="images/favicon.svg"', f'href="{icon}"')
# Sections: the Markdown goes inline in a text/template script.
def inline_section(m):
attrs, path = m.group(1), m.group(2)
text = read(path)
if "</script" in text.lower():
sys.exit(f"{path} contains '</script', which cannot be inlined")
return f'<section data-markdown{attrs}>\n<script type="text/template">\n{text}\n</script>\n</section>'
html, n = re.subn(r'<section\s+data-markdown="([^"]+)"((?:\s+data-[a-z-]+="[^"]*")*)\s*></section>',
lambda m: inline_section(type("M", (), {"group": lambda self, i: (None, m.group(2), m.group(1))[i]})()), html)
if n == 0:
sys.exit("no data-markdown sections found in index.html")
# Our scripts.
for name in ["dns-lookup.js", "prefix-explorer.js"]:
html, k = re.subn(rf'<script src="{re.escape(name)}[^"]*"></script>', lambda m: f"<script>\n{read(name)}\n</script>", html)
if k != 1:
sys.exit(f"expected one <script src=\"{name}\"> tag")
# The help overlay and its watchdog exist to explain a page that could
# not fetch its parts; this one fetches nothing.
html = re.sub(r'\s*<div id="deck-help" hidden>.*?</div>', "", html, count=1, flags=re.S)
html = re.sub(r'\s*<!-- A classic script: it still runs.*?</script>', "", html, count=1, flags=re.S)
# The loader: the module script that picks a reveal.js location and
# imports it becomes reveal.js itself, its plugins, and the deck setup.
m = re.search(r'<script type="module">(.*?)</script>', html, flags=re.S)
if not m:
sys.exit("loader script not found")
loader = m.group(1)
setup_start = loader.index("// BIRD configuration")
setup = loader[setup_start:]
setup = setup.replace("await deck.initialize().catch(fail(\"reveal.js failed to start\"));", "deck.initialize().then(() => {")
setup = setup.replace("plugins: [Markdown, Highlight, Notes],", "plugins: [RevealMarkdown, RevealHighlight, RevealNotes],")
setup = setup.rstrip() + "\n });\n"
libs = "\n".join(read("reveal.js", "dist", p) for p in ["reveal.js", "plugin/markdown.js", "plugin/highlight.js", "plugin/notes.js"])
html = html[: m.start()] + f"<script>\n{libs}\n</script>\n <script>\n{setup} </script>" + html[m.end():]
if "await " in setup or "import(" in setup:
sys.exit("loader still contains module-only code")
html = html.replace("reveal.js is loaded from ./reveal.js/dist if present", "Built by build.py from index.html: everything is inlined. reveal.js was loaded from ./reveal.js/dist if present")
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, "w", encoding="utf-8") as f:
f.write(html)
print(f"wrote {os.path.relpath(OUT)} ({os.path.getsize(OUT) // 1024} KB, {n} sections)")
if __name__ == "__main__":
main()