#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""render.py — draw the message-file grammar's railroad diagrams.
Every NNNNN-<rule>.rrd in this folder is one grammar rule written in the
tabatkins railroad-diagrams notation (Diagram, Sequence, Choice, Optional,
ZeroOrMore, OneOrMore, Terminal, NonTerminal, Comment, Stack). This script
evaluates each one with the Python port of that library and writes
NNNNN-<rule>.svg next to it (committed; the source of record)
<png-dir>/<rule>.png with --png <png-dir> (for the web site)
both styled with railroad-style.css. The PNGs are what mstring.fedem.eu
serves: browsers size standalone railroad SVGs unreliably.
Requires: pip install railroad-diagrams cairosvg (cairosvg needs libcairo)
Usage: python3 render.py [--png <dir>] [--scale 1.5]
"""
import argparse
import glob
import io
import os
import re
import sys
import railroad
HERE = os.path.dirname(os.path.abspath(__file__))
def load_css():
with open(os.path.join(HERE, "railroad-style.css"), encoding="utf-8") as f:
text = f.read()
# Drop the leading comment block: only rules go into the SVG.
return re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL).strip()
def diagram(path):
names = {name: getattr(railroad, name) for name in dir(railroad) if not name.startswith("_")}
with open(path, encoding="utf-8") as f:
return eval(f.read(), {"__builtins__": {}}, names)
def svg_of(d, css):
out = io.StringIO()
d.writeStandalone(out.write, css=css)
return out.getvalue()
def main():
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--png", metavar="DIR", help="also write <rule>.png into DIR")
parser.add_argument("--scale", type=float, default=1.5, help="PNG scale factor (default 1.5)")
args = parser.parse_args()
css = load_css()
sources = sorted(glob.glob(os.path.join(HERE, "*.rrd")))
if args.png:
import cairosvg
os.makedirs(args.png, exist_ok=True)
for source in sources:
base = os.path.splitext(os.path.basename(source))[0]
rule = base.split("-", 1)[1]
svg = svg_of(diagram(source), css)
with open(os.path.join(HERE, base + ".svg"), "w", encoding="utf-8") as f:
f.write(svg)
if args.png:
cairosvg.svg2png(bytestring=svg.encode("utf-8"),
write_to=os.path.join(args.png, rule + ".png"),
scale=args.scale, background_color="white")
print(f"{len(sources)} diagrams" + (f", PNGs in {args.png}" if args.png else ""))
if __name__ == "__main__":
sys.exit(main())