"""Registro de PLUGINS de formato (SDD 29). Un formato se agrega dejando un fichero en este directorio que decore sus funciones con `@lector` y `@escritor`. No hay que tocar el centro ni ningún otro plugin: ése es el punto de tener un pivote. @lector("nginx", familia="web") def leer(texto) -> Config: ... @escritor("caddy", familia="web") def escribir(cfg) -> str: ... ⚠ **LA FAMILIA NO ES DECORACIÓN.** El pivote es uno por familia: un unit de systemd no es un sitio web, y emparejar un lector de servicios con un escritor de web produciría basura *plausible* — el modo de fallo caro. El registro empareja SÓLO dentro de la familia y rechaza el cruce con un error, no con un intento. Es la misma regla del acta, aplicada un nivel más arriba: parecerse es peor que faltar. """ import importlib, pkgutil, pathlib LECTORES, ESCRITORES = {}, {} # nombre -> (funcion, familia) def lector(nombre, familia="web"): def deco(f): LECTORES[nombre] = (f, familia) return f return deco def escritor(nombre, familia="web"): def deco(f): ESCRITORES[nombre] = (f, familia) return f return deco def cargar(): """Importa todos los plugins del directorio. Descubrimiento, no lista cableada: una lista que hay que editar a mano se desincroniza y el formato nuevo 'no existe' sin que nada falle.""" for m in pkgutil.iter_modules([str(pathlib.Path(__file__).parent)]): if not m.name.startswith("_"): importlib.import_module(f"{__name__}.{m.name}") return LECTORES, ESCRITORES def familias(): """{familia: (lectores, escritores)} — para listar y para contar pares de verdad.""" d = {} for n, (_, fam) in LECTORES.items(): d.setdefault(fam, ([], []))[0].append(n) for n, (_, fam) in ESCRITORES.items(): d.setdefault(fam, ([], []))[1].append(n) return {k: (sorted(v[0]), sorted(v[1])) for k, v in sorted(d.items())}