125 lines
5.0 KiB
Python
Executable File
125 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Revisa que el sitio cumpla «si no está en el sitio, no pasó».
|
|
|
|
Cuatro preguntas, todas las veces:
|
|
1. ¿Cada enlace de cada pendiente abre algo? (si no, alguien se queda esperando un documento)
|
|
2. ¿Cada enlace DENTRO de los documentos publicados abre algo?
|
|
3. ¿Hay documentos fechados en las carpetas NN- que nunca se publicaron?
|
|
4. ¿Hay destinos del MAP cuyo origen ya no existe, o publicados fuera del nav?
|
|
|
|
Uso: python3 scripts/revisar-enlaces.py [ruta-a-hbox-docs]
|
|
Sale con 1 si algo está roto, para poder colgarlo del despliegue o del agente diario.
|
|
Las rutas /api/… son del backend, no archivos: no cuentan como rotas.
|
|
"""
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from urllib.parse import unquote, urlparse
|
|
|
|
REPO = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(__file__), ".."))
|
|
PADRE = os.path.dirname(REPO)
|
|
DOCS = os.path.join(REPO, "docs")
|
|
SITE = os.path.join(REPO, "site")
|
|
LINK = re.compile(r"\[([^\]]{0,120})\]\(\s*<?([^)\s>]+)>?(?:\s+\"[^\"]*\")?\s*\)")
|
|
EXTERNO = ("http://", "https://", "mailto:", "tel:", "#", "data:")
|
|
BACKEND = ("/api/",)
|
|
|
|
|
|
def mapa():
|
|
txt = open(os.path.join(REPO, "scripts", "sync-docs.sh"), encoding="utf-8").read()
|
|
return [tuple(m.group(1).split("|", 1)) for m in re.finditer(r'^\s*"([^"]+\|[^"]+)"\s*$', txt, re.M)]
|
|
|
|
|
|
def docs_fechados():
|
|
out = []
|
|
for nn in sorted(d for d in os.listdir(PADRE) if re.match(r"^\d\d-", d)):
|
|
for raiz, dirs, files in os.walk(os.path.join(PADRE, nn)):
|
|
dirs[:] = [d for d in dirs if d not in (".git", "_to_delete", "img", "node_modules", "kicad")]
|
|
out += [os.path.relpath(os.path.join(raiz, fn), PADRE)
|
|
for fn in files if fn.endswith(".md") and re.match(r"^\d{4}-\d{2}-\d{2}_", fn)]
|
|
return sorted(out)
|
|
|
|
|
|
def resuelve(url, origen=None):
|
|
"""True / False / None (externo o del backend)."""
|
|
if url.startswith(BACKEND):
|
|
return None
|
|
p = urlparse(url)
|
|
if p.scheme in ("http", "https"):
|
|
if p.netloc not in ("drogheda.helgen.online", ""):
|
|
return None
|
|
elif p.scheme or url.startswith(EXTERNO):
|
|
return None
|
|
ruta = unquote(p.path or "")
|
|
if not ruta:
|
|
return None
|
|
if ruta.startswith("/"):
|
|
rel = ruta.lstrip("/")
|
|
cand = [os.path.join(SITE, rel, "index.html"), os.path.join(SITE, rel),
|
|
os.path.join(DOCS, rel), os.path.join(DOCS, rel.rstrip("/") + ".md"),
|
|
os.path.join(DOCS, rel, "index.md")]
|
|
elif origen:
|
|
base = os.path.dirname(origen)
|
|
cand = [os.path.join(base, ruta), os.path.join(base, ruta.rstrip("/") + ".md"),
|
|
os.path.join(base, ruta, "index.md")]
|
|
else:
|
|
return None
|
|
return any(os.path.exists(c) for c in cand)
|
|
|
|
|
|
def main():
|
|
malo = 0
|
|
print("Revisión de enlaces del sitio —", REPO)
|
|
|
|
f = os.path.join(REPO, "_estado", "tablero.json")
|
|
if os.path.exists(f):
|
|
d = json.load(open(f, encoding="utf-8"))
|
|
ts = d.get("tareas") or d.get("pendientes") or (d if isinstance(d, list) else [])
|
|
rotos = [(t.get("id"), t.get("responsable"), e.get("titulo"), e.get("url"))
|
|
for t in ts for e in (t.get("enlaces") or [])
|
|
if (e.get("url") or "").strip() and resuelve(e["url"]) is False]
|
|
print(f"1. enlaces de {len(ts)} pendientes: {len(rotos)} rotos")
|
|
for tid, resp, et, url in rotos:
|
|
print(f" ✕ {tid} ({resp}) «{et}» → {url}")
|
|
malo += len(rotos)
|
|
else:
|
|
print("1. (sin _estado/tablero.json: corre ./scripts/tablero.sh foto)")
|
|
|
|
rotos = []
|
|
for raiz, dirs, files in os.walk(DOCS):
|
|
dirs[:] = [x for x in dirs if x not in ("img", "fonts", "stylesheets", "js", "descargas")]
|
|
for fn in sorted(files):
|
|
if not fn.endswith(".md"):
|
|
continue
|
|
ruta = os.path.join(raiz, fn)
|
|
for m in LINK.finditer(open(ruta, encoding="utf-8", errors="replace").read()):
|
|
if resuelve(m.group(2), ruta) is False:
|
|
rotos.append((os.path.relpath(ruta, REPO), m.group(1)[:50], m.group(2)))
|
|
print(f"2. enlaces dentro de los documentos: {len(rotos)} rotos")
|
|
for d_, t_, u_ in rotos:
|
|
print(f" ✕ {d_}: «{t_}» → {u_}")
|
|
malo += len(rotos)
|
|
|
|
m = mapa()
|
|
origenes = {o for o, _ in m}
|
|
faltan = [d for d in docs_fechados() if d not in origenes]
|
|
print(f"3. documentos fechados sin publicar: {len(faltan)}")
|
|
for d_ in faltan:
|
|
print(" ✕", d_)
|
|
malo += len(faltan)
|
|
|
|
nav = open(os.path.join(REPO, "mkdocs.yml"), encoding="utf-8").read()
|
|
perdidos = [o for o, _ in m if not os.path.exists(os.path.join(PADRE, o))]
|
|
huerfanos = [d for o, d in m if d.endswith(".md") and d[len("docs/"):] not in nav]
|
|
print(f"4. orígenes perdidos: {len(perdidos)} · publicados fuera del nav: {len(huerfanos)}")
|
|
for x in perdidos + huerfanos:
|
|
print(" ✕", x)
|
|
malo += len(perdidos) + len(huerfanos)
|
|
|
|
print("TODO BIEN" if not malo else f"{malo} COSAS ROTAS")
|
|
return 1 if malo else 0
|
|
|
|
|
|
sys.exit(main())
|