#!/usr/bin/env python3 """ Render a D2 diagram in both light and dark themes, upload the two SVGs to static.rerun.io, and print a `
` runs inside labels.
Requires:
- the `d2` CLI on PATH (https://d2lang.com).
- `scripts/upload_image.py` (same directory) and its dependencies; see
that file's docstring for GCS authentication setup.
Usage:
python3 scripts/render_d2.py path/to/diagram.d2
cat diagram.d2 | python3 scripts/render_d2.py
or via pixi:
pixi run python scripts/render_d2.py path/to/diagram.d2
The final HTML block is printed to stdout (and stderr when interactive),
mirroring how `upload_image.py` reports its results.
Per theme, this is roughly equivalent to:
cat diagram.d2 | d2 --pad=0 --scale=0.8 --stdout-format=svg - -
followed by SVG post-processing:
- Strip D2's embedded "
body = re.sub(r"", _wrap, body, count=1, flags=re.DOTALL)
return '\n' + body
def main() -> None:
ap = argparse.ArgumentParser(
description="Render a D2 diagram in light+dark, upload both SVGs to "
"static.rerun.io, print an HTML block ready to paste "
"into docs markdown.",
)
ap.add_argument(
"source",
nargs="?",
type=Path,
help="Path to a .d2 source file. If omitted, source is read from stdin.",
)
args = ap.parse_args()
import logging
import sys
import tempfile
logging.basicConfig(level=logging.INFO)
if args.source is None:
if sys.stdin.isatty():
ap.error("no source file given and stdin is a tty")
source_text = sys.stdin.read()
else:
source_text = args.source.read_text()
# Imported lazily so the render API can be used as a library without
# pulling in upload_image's heavier dependency tree (PIL, gcloud, …).
from upload_image import Uploader
uploader = Uploader()
urls: dict[str, str] = {}
with tempfile.TemporaryDirectory() as td:
for theme in ("light", "dark"):
logging.info(f"rendering {theme} theme")
svg_path = Path(td) / f"d2-{theme}.svg"
svg_path.write_text(render(source_text, theme))
object_name = uploader.upload_file(svg_path)
urls[theme] = f"https://static.rerun.io/{object_name}"
html = (
'\n'
f'
\n'
f'
\n'
""
)
print(f"\n{html}", file=sys.stderr)
if not sys.stdout.isatty():
# Allow piping into pbcopy/xclip without the stderr banner.
print(html)
if __name__ == "__main__":
main()