From f89ad33c2677aa499f3be3bc983486980157ce05 Mon Sep 17 00:00:00 2001 From: Jack O'Sullivan Date: Fri, 24 Jul 2026 00:24:45 +0100 Subject: [PATCH] ci: Add docs assignment-table auto-updater Regenerate the marked network-assignment tables in docs/ from nixos.allAssignments (nix run .#update-docs-assignments, registered in pkgs/default.nix), preserving hand-written Notes cells. The workflow runs on pushes to the docs branch and commits any table updates back; flip its trigger to master when the docs branch merges. --- .gitea/workflows/update-docs.yaml | 38 +++++++++ ci/update-docs-assignments.py | 128 ++++++++++++++++++++++++++++++ pkgs/default.nix | 4 + 3 files changed, 170 insertions(+) create mode 100644 .gitea/workflows/update-docs.yaml create mode 100644 ci/update-docs-assignments.py diff --git a/.gitea/workflows/update-docs.yaml b/.gitea/workflows/update-docs.yaml new file mode 100644 index 0000000..8c83669 --- /dev/null +++ b/.gitea/workflows/update-docs.yaml @@ -0,0 +1,38 @@ +name: Update docs assignments + +on: + push: + branches: [docs-kimi-new] + +jobs: + update: + if: "!contains(github.event.head_commit.message, 'docs: update assignment tables')" + runs-on: ubuntu-26.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + + - uses: cachix/install-nix-action@v31 + with: + github_access_token: ${{ secrets.GH_PULL_TOKEN }} + extra_nix_config: | + extra-substituters = https://nix-cache.nul.ie + extra-trusted-public-keys = nix-cache.nul.ie-1:BzH5yMfF4HbzY1C977XzOxoPhEc9Zbu39ftPkUbH+m4= + + - name: Update assignment tables + run: nix run .#update-docs-assignments + + - name: Commit and push if changed + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + REPO_URL: ${{ gitea.repositoryUrl }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git remote set-url origin "${REPO_URL/https:\/\//https:\/\/oauth2:${GITEA_TOKEN}@}" + git add docs/ + if ! git diff --cached --quiet; then + git commit -m "docs: update assignment tables" + git push + fi diff --git a/ci/update-docs-assignments.py b/ci/update-docs-assignments.py new file mode 100644 index 0000000..2d93cab --- /dev/null +++ b/ci/update-docs-assignments.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Update assignment tables in docs/ from nixos.allAssignments.""" + +import json +import re +import subprocess +import sys +from pathlib import Path + +DOCS_DIR = Path("docs") +HEADER = "| Name | Assignment | IPv4 | IPv6 | Domain | Notes |" + + +def fmt_ip(ip: dict) -> str: + addr = ip.get("address") + if addr is None: + return "—" + mask = ip.get("mask") + gateway = ip.get("gateway") + parts = [f"{addr}/{mask}"] + if gateway is not None: + parts.append(f"gw {gateway}") + return f"`{' '.join(parts)}`" + + +def parse_notes(lines: list[str]) -> dict[str, str]: + """Extract existing Notes indexed by Assignment from a marked table.""" + notes: dict[str, str] = {} + for line in lines: + stripped = line.strip() + if not stripped.startswith("|"): + continue + cells = [c.strip() for c in stripped.split("|")] + # Splitting "| a | b |" produces ['', 'a', 'b', ''] + if len(cells) < 8: + continue + key = cells[2] + if key == "Assignment": + continue + notes[key] = cells[6] + return notes + + +def render_table(assignments: dict, notes: dict[str, str]) -> list[str]: + lines = [ + HEADER, + "|---|---|---|---|---|---|", + ] + for key, a in assignments.items(): + name = a.get("name", key) + alt = ", ".join(a.get("altNames", [])) + if alt: + name = f"{name} ({alt})" + lines.append( + "| " + + " | ".join( + [ + name, + key, + fmt_ip(a.get("ipv4", {})), + fmt_ip(a.get("ipv6", {})), + a.get("domain") or "—", + notes.get(key, ""), + ] + ) + + " |" + ) + return lines + + +def process_file(path: Path, all_assignments: dict) -> bool: + box_name = path.stem + if box_name not in all_assignments: + return False + + text = path.read_text() + lines = text.splitlines() + + marker_re = re.compile(r"^$") + for i, line in enumerate(lines): + m = marker_re.match(line.strip()) + if m and m.group(1) == box_name: + end_idx = None + for j in range(i + 1, len(lines)): + if lines[j].strip() == "": + end_idx = j + break + if end_idx is None: + return False + + old_inner = lines[i + 1 : end_idx] + notes = parse_notes(old_inner) + new_table = render_table(all_assignments[box_name], notes) + new_lines = ( + lines[: i + 1] + + [""] + + new_table + + [""] + + lines[end_idx + 1 :] + ) + if new_lines != lines: + path.write_text("\n".join(new_lines) + "\n") + return True + return False + + return False + + +def main() -> int: + result = subprocess.run( + ["nix", "eval", ".#nixfiles.config.nixos.allAssignments", "--json"], + capture_output=True, + text=True, + check=True, + ) + all_assignments = json.loads(result.stdout) + + changed = False + for path in sorted(DOCS_DIR.rglob("*.md")): + if process_file(path, all_assignments): + print(f"updated {path}") + changed = True + + return 1 if changed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pkgs/default.nix b/pkgs/default.nix index a96a2cc..a6fd6b2 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -11,4 +11,8 @@ in chocolate-doom2xx = callPackage ./chocolate-doom2xx { }; windowtolayer = callPackage ./windowtolayer.nix { }; swaylock-plugin = callPackage ./swaylock-plugin.nix { }; + + update-docs-assignments = pkgs.writeShellScriptBin "update-docs-assignments" '' + exec ${pkgs.python3}/bin/python3 ${../ci/update-docs-assignments.py} "$@" + ''; }