"""Draws the 3.x / 4.0 lane diagram of community/roadmap.md as an inline SVG into docs/images/. The rows come from the page itself: the macro table under "Trying out 4.0 today" gives each macro, its 3.x default, whether 4.0 removes it, and the version that added it; the `#define` block below that table gives the 4.0 value. Run `python3 scripts/figures/integration.py` from docs/mkdocs after changing that section, and commit the generated docs/images/roadmap_lanes.svg. The page includes the SVG text with `--8<-- "images/roadmap_lanes.svg"`, so it follows the light/dark toggle (see the `nj-fig` rules in docs/css/brand.css and the `nj-int-*` rules in docs/css/fig-integration.css). In SVG text write J​SON and A​PI, otherwise the abbreviation extension wraps the word in an and the text disappears. """ import re import sys from pathlib import Path DOCS = Path(__file__).resolve().parents[2] / "docs" OUT = Path(__file__).resolve().parents[2] / "docs" / "images" PAGE = DOCS / "community" / "roadmap.md" def esc(text): return text.replace("&", "&").replace("<", "<").replace(">", ">") def vkey(version): return tuple(int(p) for p in version.split(".")) def parse(markdown): section = markdown.split("### Trying out 4.0 today", 1)[1].split("\n### ", 1)[0] rows = [] for line in section.splitlines(): cells = [c.strip() for c in line.strip().strip("|").split("|")] m = re.match(r"\[`(JSON_[A-Z0-9_]+)`\]", cells[0]) if len(cells) == 5 else None if not m: continue default = re.fullmatch(r"`(\d)`", cells[1]) added = re.fullmatch(r"(\d+\.\d+\.\d+)", cells[4]) if not default or not added: sys.exit("cannot parse row: " + line) rows.append({"macro": m.group(1), "default": default.group(1), "added": added.group(1), "removed": cells[2].startswith("removed")}) values = dict(re.findall(r"^#define (JSON_[A-Z0-9_]+) (\d)$", section, re.MULTILINE)) for row in rows: if row["macro"] not in values: sys.exit("no #define for " + row["macro"]) row["v4"] = values[row["macro"]] if not rows: sys.exit("no macro rows found in " + str(PAGE)) return rows def draw(rows): W, ROW, TOP = 880, 34, 168 H = TOP + ROW * len(rows) + 30 versions = sorted({r["added"] for r in rows}, key=vkey) s = ['' % (W, H)] s.append('Two lanes: the 3.x release train, whose minor and patch releases do not ' 'break code that uses the public A​PI, followed by version 4.0, which has no release date yet. ' 'Each breaking change is a macro that is available in 3.x today with its 3.x default and becomes the ' 'default in 4.0: %s.' % "; ".join("%s, added in %s, 3.x default %s, 4.0 value %s%s" % (r["macro"], r["added"], r["default"], r["v4"], ", macro removed" if r["removed"] else "") for r in rows)) s.append('') # lanes s.append('') s.append('3.x release train') s.append('available today') x0, x1 = 300, 590 step = (x1 - x0) / max(1, len(versions) - 1) if len(versions) > 1 else 0 for i, v in enumerate(versions): x = x0 + i * step if len(versions) > 1 else (x0 + x1) / 2 s.append('' % (x, x)) s.append('%s' % (x, v)) s.append('') s.append('') s.append('') s.append('4.0') s.append('no release date yet') # guarantee s.append('A minor or patch release of 3.x does not break code that uses ' 'the public A​PI, unless that code opts in with a macro.') s.append('4.0 is not developed on a separate branch: it switches the ' 'defaults of these macros and removes the macros.') # header hy = TOP - 18 s.append('Macro' % hy) s.append('added' % hy) s.append('3.x default' % hy) s.append('define to try today' % hy) s.append('4.0 default' % hy) for i, r in enumerate(rows): y = TOP + i * ROW + ROW / 2 s.append('' % (y - ROW / 2 + 2, ROW - 4)) s.append('%s' % (y + 4.5, r["macro"])) s.append('%s' % (y + 4.5, r["added"])) s.append('' % (y - 11)) s.append('%s' % (y + 4.5, r["default"])) s.append('' % (y, y)) s.append('' % (y - 11)) if r["removed"]: s.append('removed' % (y + 4.5)) else: s.append('%s' % (y + 4.5, r["v4"])) s.append('Versions on the 3.x lane are the ones that added a macro, ' 'not to scale. “removed”: see the 4.0 behavior column of the table.' % (H - 8)) s.append('') return "\n".join(s) + "\n" rows = parse(PAGE.read_text(encoding="utf-8")) (OUT / "roadmap_lanes.svg").write_text(draw(rows), encoding="utf-8") print("wrote", OUT / "roadmap_lanes.svg", "with", len(rows), "macros")