ci: Add docs assignment-table auto-updater
Update docs assignments / update (push) Successful in 57s

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.
This commit is contained in:
2026-07-24 00:24:45 +01:00
parent d51f2d62b6
commit f89ad33c26
3 changed files with 170 additions and 0 deletions
+38
View File
@@ -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
+128
View File
@@ -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"^<!--\s*assignments:\s*(\S+)\s*-->$")
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() == "<!-- assignments-end -->":
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]
+ ["<!-- assignments-start -->"]
+ new_table
+ ["<!-- assignments-end -->"]
+ 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())
+4
View File
@@ -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} "$@"
'';
}