summaryrefslogtreecommitdiffstats
path: root/tools/site/check_site.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/site/check_site.py')
-rw-r--r--tools/site/check_site.py133
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()