"""Validate the exact embedded graph-browser HTML, CSS, and JavaScript.""" from __future__ import annotations import ast import os import shutil import subprocess import sys import tempfile from pathlib import Path from typing import cast from docforge.index import ProjectIndex from docforge.project import Project from docforge.rendering import RenderService ROOT = Path(__file__).resolve().parents[1] SOURCE = ROOT / "src" / "docforge" / "visualization.py" def _graph_browser_html() -> str: tree = ast.parse(SOURCE.read_text(encoding="utf-8"), filename=str(SOURCE)) for statement in tree.body: if not isinstance(statement, ast.Assign): continue if any( isinstance(target, ast.Name) and target.id == "_GRAPH_BROWSER_HTML" for target in statement.targets ): value = ast.literal_eval(statement.value) if isinstance(value, str): return value raise RuntimeError("Could not find the literal graph-browser HTML asset") def _embedded(html: str, tag: str) -> str: opening = f"<{tag}>" closing = f"" if html.count(opening) != 1 or html.count(closing) != 1: raise RuntimeError(f"Expected exactly one embedded {tag} asset") return html.split(opening, 1)[1].split(closing, 1)[0] def _rendered_manual_html() -> str: with tempfile.TemporaryDirectory() as directory: fixture = ROOT / "tests" / "fixtures" / "alpha" project_root = Path(directory) / "alpha" shutil.copytree(fixture, project_root) project = Project.open(project_root) ProjectIndex(project).build() result = RenderService(project).render("manual") output = result.get("output") if not isinstance(output, dict): raise RuntimeError("Manual render did not return output identity") relative_path = cast(dict[str, object], output).get("path") if not isinstance(relative_path, str): raise RuntimeError("Manual render did not return an output path") return (project_root / relative_path).read_text(encoding="utf-8") def _tool(name: str) -> str: suffix = ".cmd" if os.name == "nt" else "" path = ROOT / "node_modules" / ".bin" / f"{name}{suffix}" if not path.is_file(): raise RuntimeError(f"Missing web validator {name}; run npm ci") return str(path) def _run(label: str, command: list[str], content: str) -> bool: print(f"Checking {label}...") result = subprocess.run( command, cwd=ROOT, input=content, text=True, check=False, ) return result.returncode == 0 def main() -> int: html = _graph_browser_html() checks = ( ( "HTML", [ _tool("html-validate"), "--stdin", "--stdin-filename=graph-browser.html", "--max-warnings=0", ], html, ), ( "rendered manual HTML", [ _tool("html-validate"), "--stdin", "--stdin-filename=rendered-manual.html", "--max-warnings=0", ], _rendered_manual_html(), ), ( "CSS", [ _tool("stylelint"), "--stdin", "--stdin-filename=graph-browser.css", "--max-warnings=0", ], _embedded(html, "style"), ), ( "JavaScript", [ _tool("eslint"), "--stdin", "--stdin-filename=graph-browser.js", "--max-warnings=0", ], _embedded(html, "script"), ), ) results = [_run(label, command, content) for label, command, content in checks] return 0 if all(results) else 1 if __name__ == "__main__": sys.exit(main())