"""Comprueba enlaces y anclas del HTML compilado, sin dependencias externas.""" from html.parser import HTMLParser from pathlib import Path from urllib.parse import unquote, urlsplit ROOT = Path(__file__).resolve().parents[1] / 'dist' class Document(HTMLParser): def __init__(self, source): super().__init__() self.ids = set() self.links = [] self.feed(source) def handle_starttag(self, tag, attrs): attrs = dict(attrs) if 'id' in attrs: self.ids.add(attrs['id']) if tag == 'a' and 'href' in attrs: self.links.append(attrs['href']) pages = {p: Document(p.read_text()) for p in ROOT.rglob('*.html')} if ROOT / 'index.html' not in pages: raise SystemExit('Primero ejecuta npm run build.') errors = set() count = 0 for page, document in pages.items(): for href in document.links: url = urlsplit(href) if url.scheme or url.netloc: continue dest = (ROOT / unquote(url.path.lstrip('/')) if url.path.startswith('/') else page.parent / unquote(url.path) if url.path else page) if dest.is_dir(): dest /= 'index.html' dest = dest.resolve() count += 1 if not dest.exists(): errors.add(f'{page.relative_to(ROOT)} → {href}: archivo ausente') elif url.fragment and dest in pages and unquote(url.fragment) not in pages[dest].ids: errors.add(f'{page.relative_to(ROOT)} → {href}: ancla ausente') expected = {'administrador', 'solicitante', 'comprador', 'autorizador', 'almacen', 'contabilidad', 'proveedor'} actual = {x.strip('/').split('/')[1] for x in pages[ROOT / 'index.html'].links if x.startswith('/roles/')} if actual != expected: errors.add(f'Los accesos de inicio no coinciden con los siete roles: {actual}') print(f'{len(pages)} páginas HTML; {count} enlaces internos; {len(errors)} errores.') for error in sorted(errors): print(error) raise SystemExit(bool(errors))