#!/usr/bin/env python3 """Regenerate knowledge/official-docs-index.json from official-docs-index.sources.json. Edit official-docs-index.sources.json, then run this script. By default each topic URL is checked with a short HTTP GET (some servers block or stall HEAD). Use --no-check for offline generation only. Usage: python3 scripts/generate_official_docs_index.py python3 scripts/generate_official_docs_index.py --no-check python3 scripts/generate_official_docs_index.py --strict Exit code 1 if --strict and any checked URL is not HTTP 2xx. """ from __future__ import annotations import argparse import json import ssl import sys import urllib.error import urllib.request from datetime import datetime, timezone from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent SKILL_DIR = SCRIPT_DIR.parent DEFAULT_SOURCES = SKILL_DIR / "knowledge" / "official-docs-index.sources.json" DEFAULT_OUT = SKILL_DIR / "knowledge" / "official-docs-index.json" USER_AGENT = "investibook-tidb-knowledge-base-index/1.0 (+https://github.com/pingcap/investibook)" def _url_for_topic(doc_bases: dict[str, str], base_key: str, path: str) -> str: base = doc_bases[base_key].rstrip("/") p = path if path.startswith("/") else f"/{path}" return f"{base}{p}" def _check_url(url: str, timeout: float = 12.0) -> tuple[int, str]: """GET the URL; return (status_code, 'GET'). HEAD is avoided: some CDNs hang on HEAD.""" ctx = ssl.create_default_context() req = urllib.request.Request( url, method="GET", headers={"User-Agent": USER_AGENT, "Accept": "text/html,application/xhtml+xml"}, ) try: with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: code = resp.getcode() # Read a small chunk so the connection completes; avoid downloading the whole page. resp.read(2048) return code, "GET" except urllib.error.HTTPError as e: return e.code, "GET" def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--sources", type=Path, default=DEFAULT_SOURCES, help=f"Input JSON (default: {DEFAULT_SOURCES})", ) parser.add_argument( "--out", type=Path, default=DEFAULT_OUT, help=f"Output JSON (default: {DEFAULT_OUT})", ) parser.add_argument( "--no-check", dest="check_urls", action="store_false", default=True, help="Skip HTTP checks (fast, offline)", ) parser.add_argument( "--strict", action="store_true", help="Exit with status 1 if any checked URL is not 200", ) args = parser.parse_args() do_check = args.check_urls raw = json.loads(args.sources.read_text(encoding="utf-8")) doc_bases = raw["doc_bases"] topics = raw["topics"] failures: list[tuple[str, str, int, str]] = [] if do_check: for t in topics: tid = t["id"] url = _url_for_topic(doc_bases, t["base"], t["path"]) try: code, method = _check_url(url) except Exception as e: # noqa: BLE001 — surface all network errors failures.append((tid, url, 0, str(e))) print(f"ERR {tid}: {url}\n -> {e}", file=sys.stderr) continue ok = 200 <= code < 400 status = "OK" if ok else "BAD" print(f"{status} {code} [{method}] {tid}: {url}") if not ok: failures.append((tid, url, code, method)) out_doc = { "version": raw.get("output_version", 1), "generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "generator": "generate_official_docs_index.py", "usage": raw.get( "usage", "Match diagnosis topic via keywords; resolve URL from doc_bases + path. " "Regenerate from official-docs-index.sources.json when docs move.", ), "doc_bases": doc_bases, "topics": topics, "resolve_url": raw.get( "resolve_url", { "pattern": "{doc_bases[base]}{path}", "example_base": "tidb_stable_en", "example_path": "/tidb-resource-control", "example_result": "https://docs.pingcap.com/tidb/stable/tidb-resource-control", }, ), } args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text(json.dumps(out_doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") print(f"Wrote {args.out} ({len(topics)} topics)") if do_check and failures and args.strict: return 1 return 0 if __name__ == "__main__": raise SystemExit(main())