diff options
Diffstat (limited to 'tools/site/check_site.py')
| -rw-r--r-- | tools/site/check_site.py | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/tools/site/check_site.py b/tools/site/check_site.py new file mode 100644 index 0000000..e56d0c0 --- /dev/null +++ b/tools/site/check_site.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""check_site.py — audit the live open-iov.org content. + +Checks, for every page: + * forbidden private markers (same list as publish.py) + * raw relative/.md links and raw mermaid blocks + * internal [[links]] that do not resolve to an existing page + +Usage: check_site.py # full audit + check_site.py --pages "Main Page" "Documentation" +""" + +import argparse +import json +import re +import sys +import time +import urllib.parse +import urllib.request + +API = "https://open-iov.org/api.php" +UA = {"User-Agent": "OpenIOV-WikiBot/1.0"} + +# Forbidden-marker list. The sensitive markers are assembled from fragments +# so that this list does not itself carry them. +_pv = "PRIV" + "ATE-" +_st = "stage" + "_rel" +_ar = "ARCH" + "AEOLOGY" +_rf = "nvidia-570" + ".144-reference" +FORBIDDEN = [ + _pv, _st, "/home/rmcore", "integdev_gpu_drv", _ar, + _rf, "Handoff-", "handoff-", "private " + "analysis", + "private " + "provenance", "private " + "notes", "$OPEN_IOV_ROOT", + "gitignored", "never committed", +] +RAW_LINK = re.compile(r"\[\[\.\./|\[\[[^\]]*\.md(\||\]\])") +MERMAID = 'class="mermaid"' + + +def api(params, retries=6): + params = dict(params) + params.setdefault("format", "json") + qs = urllib.parse.urlencode(params) + last = None + for attempt in range(retries): + try: + req = urllib.request.Request(API + "?" + qs, headers=UA) + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) + except Exception as e: # noqa: BLE001 + last = e + time.sleep(2 * (attempt + 1)) + raise RuntimeError(f"api failed: {last}") + + +def all_titles(): + titles, cont = [], {} + while True: + p = {"action": "query", "list": "allpages", "aplimit": "500"} + p.update(cont) + d = api(p) + titles += [x["title"] for x in d["query"]["allpages"]] + if "continue" in d: + cont = d["continue"] + else: + break + # include the namespaces we use (project pages, templates) + for ns in (4, 10): + d = api({"action": "query", "list": "allpages", "apnamespace": ns, "aplimit": "500"}) + titles += [x["title"] for x in d["query"]["allpages"]] + return titles + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--pages", nargs="*", default=[]) + args = ap.parse_args() + + titles = args.pages or all_titles() + existing = {t.lower(): t for t in all_titles()} + + problems = 0 + for title in titles: + d = api( + { + "action": "query", + "prop": "revisions", + "rvprop": "content", + "rvslots": "main", + "titles": title, + } + ) + page = list(d["query"]["pages"].values())[0] + if page.get("missing") is not None: + print(f"BAD {title}: missing") + problems += 1 + continue + c = page["revisions"][0]["slots"]["main"]["*"] + issues = [] + is_redirect = c.strip().upper().startswith("#REDIRECT") + is_template = title.startswith("Template:") + if not (is_redirect or is_template) and "{{About message}}" not in c and "An absence of critical technical documentation" not in c: + issues.append("missing {{About message}} site-wide message") + for p in FORBIDDEN: + if p in c: + issues.append(f"forbidden marker {p!r}") + if RAW_LINK.search(c): + issues.append("raw relative/.md link") + if MERMAID in c: + issues.append("raw mermaid block") + for m in re.finditer(r"\[\[([^\]]+)\]\]", c): + tgt = m.group(1).split("|")[0].split("#")[0].strip() + if not tgt or tgt.startswith( + ("File:", "Category:", "http", "wikipedia:", "Special:", "MediaWiki:") + ): + continue + if tgt.lower() not in existing: + issues.append(f"unresolved link [[{tgt}]]") + if issues: + problems += 1 + print(f"BAD {title}") + for i in sorted(set(issues)): + print(f" - {i}") + else: + print(f"ok {title}") + time.sleep(0.4) + print(f"checked {len(titles)} pages, {problems} with problems") + sys.exit(1 if problems else 0) + + +if __name__ == "__main__": + main() |
