#!/usr/bin/env python3 from __future__ import annotations import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] TEMPLATES_DIR = ROOT / "app" / "templates" PAGES_DIR = TEMPLATES_DIR / "pages" BASE_FILE = TEMPLATES_DIR / "base.html" CSS_DIR = ROOT / "app" / "static" / "css" RULE_EXTENDS = "POL001" RULE_INLINE_STYLE = "POL002" RULE_EXTRA_ASSETS = "POL003" RULE_HEX_OUTSIDE_TOKENS = "POL004" RULE_PX_SPACING = "POL005" RULE_BASE_ASSETS = "POL006" EXTENDS_BASE_RE = re.compile(r"\{%-?\s*extends\s+\"base\.html\"\s*-?%\}") INLINE_STYLE_RE = re.compile(r"]+rel=\"stylesheet\"|]+src=\"", re.IGNORECASE) HEX_RE = re.compile(r"#[0-9a-fA-F]{3,8}") PX_SPACING_RE = re.compile( r"(?:margin|padding|gap|row-gap|column-gap)\s*:\s*[^;]*\d+px", flags=re.IGNORECASE, ) def err(errors: list[str], path: Path, line_no: int, rule: str, message: str) -> None: rel = path.relative_to(ROOT) errors.append(f"{rel}:{line_no}: {rule} {message}") def check_base_assets(errors: list[str]) -> None: if not BASE_FILE.exists(): errors.append(f"{BASE_FILE.relative_to(ROOT)}:1: {RULE_BASE_ASSETS} Missing base.html") return base_content = BASE_FILE.read_text(encoding="utf-8") css_hits = re.findall(r'/static/css/[^\"]+', base_content) js_hits = re.findall(r'/static/js/[^\"]+', base_content) expected_css = ["/static/css/app.css?v={{ asset_version }}"] expected_js = ["/static/js/app.js?v={{ asset_version }}"] if css_hits != expected_css: errors.append( f"{BASE_FILE.relative_to(ROOT)}:1: {RULE_BASE_ASSETS} expected CSS include {expected_css}, found {css_hits}" ) if js_hits != expected_js: errors.append( f"{BASE_FILE.relative_to(ROOT)}:1: {RULE_BASE_ASSETS} expected JS include {expected_js}, found {js_hits}" ) def check_pages_extend_base(errors: list[str]) -> None: page_files = sorted(PAGES_DIR.glob("*.html")) if not page_files: errors.append(f"{PAGES_DIR.relative_to(ROOT)}:1: {RULE_EXTENDS} No page templates found") return for path in page_files: content = path.read_text(encoding="utf-8") if not EXTENDS_BASE_RE.search(content): err( errors, path, 1, RULE_EXTENDS, "page template must contain {% extends \"base.html\" %}", ) def check_templates_inline_and_assets(errors: list[str]) -> None: for path in sorted(TEMPLATES_DIR.rglob("*.html")): for idx, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): if INLINE_STYLE_RE.search(line): err(errors, path, idx, RULE_INLINE_STYLE, "inline style or