125 lines
5.0 KiB
Python
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())
|