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>
90 lines
4.4 KiB
Python
Executable File
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()
|