#!/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*', "", html)
html = re.sub(r'\s*', f"\n ", 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 "\n\n'
html, n = re.subn(r'',
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'', lambda m: f"", html)
if k != 1:
sys.exit(f"expected one ', "", 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'', 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"\n " + 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()