Files
llm/scripts/check_1c_its_static_site.py

125 lines
5.0 KiB
Python

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"""<img[^>]+src=["']https?://""", re.IGNORECASE)
EXTERNAL_ASSET_RE = re.compile(r"""<(?:link|script)[^>]+(?:href|src)=["']https?://""", re.IGNORECASE)
EXTERNAL_LINK_RE = re.compile(r"""<a[^>]+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())