summaryrefslogtreecommitdiff
path: root/graph/render_docs.py
blob: a0c5ec2654adc3280c0a3c3f884a8be8e1b7b895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""Render documentation snapshots with the same layout and SVG export as make view.

Requires Python Playwright and an installed browser; not needed to build chapters.
Example: python graph/render_docs.py --browser msedge chapter01/docs/01-graph.json
"""
import argparse
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
import json
from pathlib import Path
from threading import Thread
from playwright.sync_api import sync_playwright


class QuietHandler(SimpleHTTPRequestHandler):
    def log_message(self, *args):
        pass


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--browser', default='chromium', choices=['chromium', 'msedge', 'chrome', 'firefox'])
    parser.add_argument('snapshots', nargs='+', type=Path)
    args = parser.parse_args()
    web = Path(__file__).resolve().parent / 'web'
    server = ThreadingHTTPServer(('127.0.0.1', 0), partial(QuietHandler, directory=str(web)))
    Thread(target=server.serve_forever, daemon=True).start()
    try:
        with sync_playwright() as pw:
            engine = pw.firefox if args.browser == 'firefox' else pw.chromium
            options = {'channel': args.browser} if args.browser in ('msedge', 'chrome') else {}
            browser = engine.launch(headless=True, **options)
            page = browser.new_page(reduced_motion='reduce')
            # No connection to a compiler or to the user's live viewer session.
            page.route_web_socket('**', lambda ws: ws.on_message(lambda message: None))
            page.goto(f'http://127.0.0.1:{server.server_port}/index.html')
            page.wait_for_function('rendererReady')
            page.evaluate('''() => {
                layout = new GraphLayout();
                document.getElementById("assocs").checked = true;
                document.getElementById("near").checked = false;
            }''')
            for source in args.snapshots:
                snap = json.loads(source.read_text(encoding='utf-8'))
                page.evaluate('''async snap => {
                    const evt = {kind: "PHASE", phase: "Documentation", near: [], temps: []};
                    const scene = await layout.run(snap, evt);
                    renderer.show(snap, scene, evt);
                    renderer.select(0);
                }''', snap)
                # Capture the normal export's Blob without bulk browser downloads.
                svg = page.evaluate('''async () => {
                    const create = URL.createObjectURL, click = HTMLAnchorElement.prototype.click;
                    let blob;
                    URL.createObjectURL = value => { blob = value; return create(value); };
                    HTMLAnchorElement.prototype.click = () => {};
                    try { renderer.save(1); }
                    finally { URL.createObjectURL = create; HTMLAnchorElement.prototype.click = click; }
                    return await blob.text();
                }''')
                target = source.with_suffix('.svg')
                target.write_text(svg + '\n', encoding='utf-8', newline='\n')
                print(target)
            browser.close()
    finally:
        server.shutdown()
        server.server_close()


if __name__ == '__main__':
    main()