#!/usr/bin/env python3
"""Lightweight HTML audit for common a11y/markup issues."""
from __future__ import annotations
from html.parser import HTMLParser
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SKIP_DIRS = {"partials", ".git"}
SKIP_FILES = {
"test.html",
"test_jp.html",
"test_zh.html",
"startpage/test.html",
}
class AuditParser(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.ids: dict[str, int] = {}
self.duplicate_ids: set[str] = set()
self.missing_alt: list[str] = []
self.missing_iframe_title: list[str] = []
self.blank_rel: list[str] = []
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
attr_map = {k.lower(): (v or "") for k, v in attrs}
if "id" in attr_map:
ident = attr_map["id"]
if ident:
if ident in self.ids:
self.duplicate_ids.add(ident)
self.ids[ident] = self.ids.get(ident, 0) + 1
if tag == "img":
if "alt" not in attr_map:
src = attr_map.get("src", "")
self.missing_alt.append(src)
if tag == "iframe":
if not attr_map.get("title", ""):
src = attr_map.get("src", "")
self.missing_iframe_title.append(src)
if tag == "a":
if attr_map.get("target", "") == "_blank":
rel = attr_map.get("rel", "")
if "noopener" not in rel:
href = attr_map.get("href", "")
self.blank_rel.append(href)
def main() -> int:
issues = []
for html in ROOT.rglob("*.html"):
if any(part in SKIP_DIRS for part in html.parts):
continue
rel = html.relative_to(ROOT).as_posix()
if rel in SKIP_FILES:
continue
parser = AuditParser()
parser.feed(html.read_text(encoding="utf-8", errors="ignore"))
if parser.duplicate_ids:
issues.append((rel, "duplicate-ids", sorted(parser.duplicate_ids)))
if parser.missing_alt:
issues.append((rel, "img-missing-alt", parser.missing_alt))
if parser.missing_iframe_title:
issues.append((rel, "iframe-missing-title", parser.missing_iframe_title))
if parser.blank_rel:
issues.append((rel, "target-blank-missing-noopener", parser.blank_rel))
if not issues:
print("OK: no audit issues found")
return 0
print("HTML audit issues:")
for rel, kind, items in issues:
print(f"- {rel}: {kind}")
for item in items[:10]:
print(f" - {item}")
if len(items) > 10:
print(f" - ... ({len(items) - 10} more)")
return 1
if __name__ == "__main__":
raise SystemExit(main())