#!/usr/bin/env python3 """Check catalog homepage + PDF URI targets after a site build.""" from __future__ import annotations import sys from html.parser import HTMLParser from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SITE = ROOT / "site" WEB = "https://zapier.georgelambert.org/" class Anchors(HTMLParser): def __init__(self) -> None: super().__init__() self.hrefs: list[str] = [] def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: if tag == "a": d = dict(attrs) if d.get("href"): self.hrefs.append(d["href"] or "") def _exists(dest: Path) -> bool: if dest.is_file(): return True if dest.is_dir() and any( (dest / n).exists() for n in ("index.html", "index.pdf", "README.html", "README.pdf") ): return True return False def local_ok(url: str) -> bool: if url.startswith(WEB) or url.startswith("http://zapier.georgelambert.org/"): rel = url.split("://", 1)[-1].split("/", 1)[-1].split("#", 1)[0] return _exists(SITE / rel) if url.startswith("file:"): return False return True # http(s) off-site; homepage checker uses GET separately def pdf_dest_ok(pdf: Path, raw: str) -> bool: raw = raw.split("#", 1)[0] if raw.startswith("file:"): return False if raw.startswith("mailto:") or raw.startswith("data:"): return True if raw.startswith("http://") or raw.startswith("https://"): if raw.startswith(WEB) or raw.startswith("http://zapier.georgelambert.org/"): return local_ok(raw) return True if raw.startswith("/"): dest = SITE / raw.lstrip("/") else: dest = (pdf.parent / raw).resolve() try: dest.relative_to(SITE.resolve()) except ValueError: return False return _exists(dest) def main() -> int: idx = SITE / "index.html" if not idx.exists(): print("no site/index.html — run scripts/build-docs-site.py") return 1 p = Anchors() p.feed(idx.read_text(encoding="utf-8", errors="replace")) broken: list[str] = [] for h in sorted(set(p.hrefs)): if h.startswith("#") or h.startswith("mailto:"): continue if h.startswith("https://git.georgelambert.org"): continue if h.startswith("https://") and not h.startswith(WEB): continue if h.startswith(WEB): if not local_ok(h): broken.append(f"homepage {h}") continue rel = h.lstrip("/") dest = SITE / rel if not dest.exists(): broken.append(f"homepage missing {h}") from pypdf import PdfReader n_file = 0 for pdf in SITE.rglob("*.pdf"): try: reader = PdfReader(str(pdf)) except Exception as exc: broken.append(f"unreadable {pdf.relative_to(SITE)} {exc}") continue for page in reader.pages: annots = page.get("/Annots") if not annots: continue for annot in annots: obj = annot.get_object() action = obj.get("/A") if not action: continue uri = action.get("/URI") if not uri: continue raw = str(uri) if raw.startswith("file:"): n_file += 1 broken.append(f"file URI {pdf.relative_to(SITE)} -> {raw[:120]}") continue if "/research/" in raw: continue if not pdf_dest_ok(pdf, raw): broken.append(f"pdf missing {pdf.relative_to(SITE)} -> {raw}") print(f"file:// leftover {n_file}") print(f"broken {len(broken)}") for b in broken[:80]: print(" ", b) if len(broken) > 80: print(f" … {len(broken) - 80} more") return 1 if broken else 0 if __name__ == "__main__": sys.exit(main())