from __future__ import annotations import argparse import json import re from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_STATIC_DIR = ROOT / "plugins" / "1c" / "rag" / "official-docs" / "static" DEFAULT_OUTPUT = ROOT / "reports" / "1c-official-docs-static-check.json" LOCAL_REF_RE = re.compile(r"""(?:href|src)=["']([^"']+)["']""", re.IGNORECASE) EXTERNAL_IMG_RE = re.compile(r"""]+src=["']https?://""", re.IGNORECASE) EXTERNAL_ASSET_RE = re.compile(r"""<(?:link|script)[^>]+(?:href|src)=["']https?://""", re.IGNORECASE) EXTERNAL_LINK_RE = re.compile(r"""]+href=["']https?://""", re.IGNORECASE) def html_files(static_dir: Path) -> list[Path]: if not static_dir.exists(): return [] return sorted(static_dir.rglob("*.html")) def is_local_ref(value: str) -> bool: lowered = value.casefold() return not ( lowered.startswith("http://") or lowered.startswith("https://") or lowered.startswith("mailto:") or lowered.startswith("javascript:") or lowered.startswith("#") ) def check_local_refs(path: Path, text: str) -> list[dict[str, str]]: broken = [] for match in LOCAL_REF_RE.finditer(text): ref = match.group(1) if not is_local_ref(ref): continue target = (path.parent / ref.split("#", 1)[0].split("?", 1)[0]).resolve() if not target.exists(): broken.append({"file": str(path), "ref": ref}) return broken def build_report(static_dir: Path) -> dict[str, Any]: files = html_files(static_dir) external_images = [] external_assets = [] external_links = [] broken_refs = [] for path in files: text = path.read_text(encoding="utf-8-sig", errors="replace") if EXTERNAL_IMG_RE.search(text): external_images.append(str(path)) if EXTERNAL_ASSET_RE.search(text): external_assets.append(str(path)) external_links.extend({"file": str(path), "count": len(EXTERNAL_LINK_RE.findall(text))} for _ in [0] if EXTERNAL_LINK_RE.search(text)) broken_refs.extend(check_local_refs(path, text)) manifest_path = static_dir / "manifest.json" manifest = {} if manifest_path.exists(): manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig")) counts = { "html_files": len(files), "pages": len((manifest.get("pages") or [])) if isinstance(manifest, dict) else 0, "media_files": len(list((static_dir / "media").glob("*"))) if (static_dir / "media").exists() else 0, "asset_files": len(list((static_dir / "assets").glob("*"))) if (static_dir / "assets").exists() else 0, "external_image_pages": len(external_images), "external_asset_pages": len(external_assets), "external_link_pages": len(external_links), "broken_local_refs": len(broken_refs), "asset_errors": len((manifest.get("asset_errors") or [])) if isinstance(manifest, dict) else 0, } findings = [] if not (static_dir / "index.html").exists(): findings.append({"severity": "error", "message": "static index.html is missing"}) if counts["external_image_pages"]: findings.append({"severity": "error", "message": "some static pages still reference remote images"}) if counts["external_asset_pages"]: findings.append({"severity": "warning", "message": "some raw pages still reference remote CSS/JS assets"}) if counts["broken_local_refs"]: findings.append({"severity": "error", "message": "some local href/src references are broken"}) if counts["asset_errors"]: findings.append({"severity": "warning", "message": "some CSS/JS assets failed to download"}) return { "schema": "onec_its_static_site_check.v1", "passed": not any(item["severity"] == "error" for item in findings), "static_dir": str(static_dir), "counts": counts, "findings": findings, "samples": { "external_images": external_images[:20], "external_assets": external_assets[:20], "external_links": external_links[:20], "broken_local_refs": broken_refs[:20], "asset_errors": (manifest.get("asset_errors") or [])[:20] if isinstance(manifest, dict) else [], }, } def main() -> int: parser = argparse.ArgumentParser(description="Check local static 1C:ITS archive self-containment and links.") parser.add_argument("--static-dir", type=Path, default=DEFAULT_STATIC_DIR) parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) parser.add_argument("--print", action="store_true", dest="print_report") args = parser.parse_args() report = build_report(args.static_dir) args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") if args.print_report: print(json.dumps(report, ensure_ascii=False, indent=2)) return 0 if report["passed"] else 1 if __name__ == "__main__": raise SystemExit(main())