diff options
| author | Christian Cleberg <[email protected]> | 2026-08-07 03:27:37 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-08-07 03:27:37 -0500 |
| commit | 89c90d11d80fde63997ca029e14806df0319a541 (patch) | |
| tree | d4b6b57bc7648d8b1137b1d22bb09d6f2d44b093 /scripts | |
| parent | 6c26cf048119bf4ff6af039991d3e34d76bac2ad (diff) | |
| download | hutch-89c90d11d80fde63997ca029e14806df0319a541.tar.gz hutch-89c90d11d80fde63997ca029e14806df0319a541.tar.bz2 hutch-89c90d11d80fde63997ca029e14806df0319a541.zip | |
Sync bundled man page catalog from man.sr.ht (#7)
The More tab's official man-page list was a hardcoded array, updated by
hand. Move it to a checked-in Hutch/Resources/man-pages.json (bundled via
the synchronized group) loaded by a new ManPageCatalog, with the previous
list kept as a built-in fallback.
Add scripts/sync_man_pages.py, which re-derives the catalog from the
man.sr.ht landing page, and a weekly scheduled workflow that runs it and
opens a pull request when the list diverges upstream. The script refuses to
write a suspiciously short list so a markup change can't gut the catalog.
This first sync also picks up chat.sr.ht, which upstream added since the
list was last hand-edited. Tests cover catalog loading, the fallback, and
JSON decoding.
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/sync_man_pages.py | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/scripts/sync_man_pages.py b/scripts/sync_man_pages.py new file mode 100755 index 0000000..c59197a --- /dev/null +++ b/scripts/sync_man_pages.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Regenerate the bundled sr.ht man page catalog from man.sr.ht. + +Fetches the man.sr.ht landing page, extracts the per-service "User Manual" +links, and writes them to ``Hutch/Resources/man-pages.json``. The scheduled +GitHub workflow that runs this opens a pull request whenever the result differs +from the committed copy, so the in-app list stays in sync with upstream without +hand edits. + +Run locally with ``python3 scripts/sync_man_pages.py``; exits non-zero (without +writing) if upstream markup changed enough that too few pages were found, so a +bad scrape can never wipe the bundled list. +""" +import json +import re +import sys +import urllib.request +from pathlib import Path + +INDEX_URL = "https://man.sr.ht/" +OUTPUT = Path(__file__).resolve().parent.parent / "Hutch" / "Resources" / "man-pages.json" +# The suite has ~12 service manuals; a scrape returning far fewer means the page +# structure changed and we should fail loudly rather than commit a gutted list. +MINIMUM_EXPECTED = 8 + + +def fetch(url: str) -> str: + request = urllib.request.Request(url, headers={"User-Agent": "hutch-man-page-sync"}) + with urllib.request.urlopen(request, timeout=30) as response: + return response.read().decode("utf-8") + + +def build_catalog(html: str) -> list[dict[str, str]]: + """Extract official man-page links, deduplicated and sorted by title.""" + entries: dict[str, str] = {} + for href in re.findall(r'href="([^"]+)"', html): + service = re.fullmatch(r"/([a-z0-9][a-z0-9.-]*\.sr\.ht)/?", href) + if service: + title = service.group(1) + entries[title] = f"https://man.sr.ht/{title}/" + elif re.fullmatch(r"sr\.ht/?", href): + entries["sr.ht"] = "https://man.sr.ht/sr.ht/" + elif re.fullmatch(r"https://srht\.site/?", href): + entries["srht.site"] = "https://srht.site/" + return [{"title": title, "url": entries[title]} for title in sorted(entries)] + + +def main() -> int: + catalog = build_catalog(fetch(INDEX_URL)) + if len(catalog) < MINIMUM_EXPECTED: + print( + f"Refusing to write catalog with only {len(catalog)} entries; " + "man.sr.ht markup may have changed.", + file=sys.stderr, + ) + return 1 + OUTPUT.write_text(json.dumps(catalog, indent=2, ensure_ascii=False) + "\n") + print(f"Wrote {len(catalog)} man page(s) to {OUTPUT}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |
