Files
nur-packages/scripts/update-readme.py
Hadi bd3c05f82d new workflow
Signed-off-by: Hadi <[email protected]>
2026-08-27 10:26:01 +02:00

128 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Regenerate the package table in README.md from the flake's own outputs.
No package names are hardcoded here: every package exposed by
`packages.<system>` (except the `default` alias) gets a row, built from its
`meta.description` / `meta.homepage`, plus the upstream commit date for the
pinned `rev` (fetched from the GitHub API), so the table shows how fresh
each pinned version actually is.
"""
import json
import os
import pathlib
import re
import subprocess
import sys
import urllib.error
import urllib.request
ROOT = pathlib.Path(__file__).resolve().parent.parent
PKGS_DIR = ROOT / "pkgs"
README = ROOT / "README.md"
START = "<!-- PACKAGES:START -->"
END = "<!-- PACKAGES:END -->"
def current_system() -> str:
return subprocess.run(
["nix", "eval", "--raw", "--impure", "--expr", "builtins.currentSystem"],
check=True, capture_output=True, text=True,
).stdout.strip()
def package_metadata(system: str) -> dict:
expr = (
"pkgs: builtins.mapAttrs (name: pkg: {"
' description = pkg.meta.description or "";'
' homepage = pkg.meta.homepage or "";'
"}) (builtins.removeAttrs pkgs [\"default\"])"
)
out = subprocess.run(
["nix", "eval", "--json", f".#packages.{system}", "--apply", expr],
check=True, capture_output=True, text=True, cwd=ROOT,
).stdout
return json.loads(out)
def pinned_rev(name: str) -> str | None:
"""Extract the literal `rev` pinned in pkgs/<name>/default.nix, resolving
a `${version}` interpolation if present."""
default_nix = PKGS_DIR / name / "default.nix"
if not default_nix.is_file():
return None
content = default_nix.read_text()
rev_match = re.search(r'\brev\s*=\s*"([^"]+)"', content)
if not rev_match:
return None
rev = rev_match.group(1)
if "${version}" in rev:
version_match = re.search(r'\bversion\s*=\s*"([^"]+)"', content)
if not version_match:
return None
rev = rev.replace("${version}", version_match.group(1))
return rev
def upstream_commit_date(homepage: str, rev: str) -> str | None:
match = re.match(r"https://github\.com/([^/]+)/([^/]+)/?$", homepage)
if not match:
return None
owner, repo = match.groups()
url = f"https://api.github.com/repos/{owner}/{repo}/commits/{rev}"
req = urllib.request.Request(url, headers={"User-Agent": "nur-packages-readme-bot"})
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
if token:
req.add_header("Authorization", f"Bearer {token}")
try:
with urllib.request.urlopen(req, timeout=15) as resp:
data = json.load(resp)
except (urllib.error.URLError, urllib.error.HTTPError) as exc:
print(f"warning: could not fetch commit date for {owner}/{repo}@{rev}: {exc}", file=sys.stderr)
return None
date = data.get("commit", {}).get("committer", {}).get("date")
return date.split("T")[0] if date else None
def render_table(meta: dict) -> str:
lines = [
"| Package | Description | Source | Updated |",
"| --- | --- | --- | --- |",
]
for name in sorted(meta):
info = meta[name]
description = info["description"].strip()
homepage = info["homepage"]
label = homepage.removeprefix("https://github.com/") if homepage else homepage
source = f"[{label}]({homepage})" if homepage else ""
updated = ""
rev = pinned_rev(name)
if homepage and rev:
updated = upstream_commit_date(homepage, rev) or ""
lines.append(f"| `{name}` | {description} | {source} | {updated} |")
return "\n".join(lines)
def main() -> None:
system = current_system()
meta = package_metadata(system)
table = render_table(meta)
content = README.read_text()
pattern = re.compile(re.escape(START) + r".*" + re.escape(END), re.S)
if not pattern.search(content):
raise SystemExit(f"README.md is missing {START}/{END} markers")
README.write_text(pattern.sub(f"{START}\n{table}\n{END}", content))
if __name__ == "__main__":
main()