Traducir de a pares no escala: con nginx, apache, caddy, haproxy y lighttpd son 20 traductores, y cada formato nuevo agrega 2N. Con un modelo intermedio son N lectores + M escritores: un formato nuevo cuesta uno o dos plugins y estrena todos los pares de golpe. **Probado, no argumentado**: el lector de apache se agregó SIN TOCAR el escritor de Caddy, y con eso `apache → caddy` funciona sin que exista ningún traductor de ese par. Las dos salidas las valida el caddy del propio corpus: `Valid configuration` en las dos. Los plugins se DESCUBREN (`formatos/` + `@lector` / `@escritor`): una lista mantenida a mano se desincroniza y el formato nuevo «no existe» sin que nada falle. `--list` dice qué pares habilita. **El acta viaja DENTRO del modelo**, y es la decisión que hace que el pivote no mienta: si fuera un efecto de cada traductor, lo que el LECTOR no entendió se perdería antes de llegar al escritor. El escritor además puede agregarle — Caddy no tiene equivalente de `location ~`, así que en vez de emitir un `handle` de prefijo que SE PARECE, lo manda al acta. Parecerse es peor que faltar. Y un bug del lector de apache, encontrado probando: `ProxyPass` tiene dos formas y dentro de `<Location>` lleva sólo la URL. Mirar sólo la de tres tokens dejaba sin traducir la forma más común y el acta la reportaba como «no reconocida» — un falso negativo que manda a escribir a mano algo que el lector sí sabe hacer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
104 lines
5.3 KiB
Python
Executable File
104 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""traducir.py — CENTRO de traducción de configuraciones, por modelo pivote y plugins (SDD 29).
|
||
|
||
Mudar de un servicio a su equivalente no es cambiar un binario: es **reescribir la configuración**.
|
||
Esto hace la parte mecánica y —sobre todo— **dice con número de línea lo que NO pudo traducir**.
|
||
|
||
── POR QUÉ UN CENTRO Y NO TRADUCTORES DE A PARES ───────────────────────────────────────────────
|
||
De a pares el trabajo crece en **N×M**: con nginx, apache, caddy, haproxy y lighttpd son 20
|
||
traductores, y cada formato nuevo agrega 2N. Con un **modelo pivote** son **N lectores + M
|
||
escritores**: un formato nuevo cuesta uno o dos plugins y estrena todos los pares de golpe.
|
||
|
||
Probado, no argumentado: el lector de apache se agregó **sin tocar el escritor de Caddy**, y con eso
|
||
`apache → caddy` funciona sin que exista ningún traductor apache→caddy.
|
||
|
||
── LOS PLUGINS ────────────────────────────────────────────────────────────────────────────────
|
||
Un formato se agrega dejando un fichero en `formatos/` con `@lector("x")` y/o `@escritor("y")`. El
|
||
centro los DESCUBRE: no hay una lista que editar, porque una lista que hay que mantener a mano se
|
||
desincroniza y el formato nuevo «no existe» sin que nada falle.
|
||
|
||
── DOCTRINA HEREDADA DE `soltar`/`paskaq` (tawasuyu) ───────────────────────────────────────────
|
||
`soltar` desencadena datos presos en formatos cautivos. Otro dominio, mismos principios:
|
||
**elisión honesta** (lo no traducido se reporta con línea y motivo), **no adivinar en silencio**,
|
||
**procedencia total** y **determinismo**. Y una consecuencia de diseño propia: **el acta viaja DENTRO
|
||
del modelo**, no al costado — si fuera de cada traductor, lo que el LECTOR no entendió se perdería
|
||
antes de llegar al escritor, y el fichero que uno despliega no lo diría.
|
||
|
||
Uso:
|
||
scripts/mudanza/traducir.py --list
|
||
scripts/mudanza/traducir.py --from nginx --to caddy --in nginx.conf --out Caddyfile
|
||
scripts/mudanza/traducir.py --from apache --to caddy --in httpd.conf --out Caddyfile
|
||
"""
|
||
import argparse, os, sys
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
import formatos # noqa: E402
|
||
|
||
|
||
def imprimir_acta(acta):
|
||
por = acta.por_estado()
|
||
print("\n══ ACTA DE TRADUCCIÓN ══")
|
||
print(f" {len(acta.entradas)} directiva(s) leídas\n")
|
||
titulo = {"SIN-TRADUCIR": "⚠ NO SE PUDO TRADUCIR — hay que escribirlas a mano",
|
||
"decision": "◆ traducidas con una DECISIÓN — revisalas",
|
||
"traducido": "✓ traducidas",
|
||
"no-aplica": "· no aplican en el destino"}
|
||
for e in ("SIN-TRADUCIR", "decision", "traducido", "no-aplica"):
|
||
if e not in por:
|
||
continue
|
||
print(f"── {titulo[e]} ({len(por[e])}) " + "─" * 22)
|
||
detalle = e in ("SIN-TRADUCIR", "decision")
|
||
for a in (por[e] if detalle else por[e][:5]):
|
||
print(f" línea {a['linea']:>4} {a['directiva'][:64]}")
|
||
if detalle and a["motivo"]:
|
||
print(f" └─ {a['motivo'][:98]}")
|
||
if not detalle and len(por[e]) > 5:
|
||
print(f" … y {len(por[e])-5} más")
|
||
print()
|
||
n = len(acta.sin_traducir())
|
||
if n:
|
||
print(f" ⚠ {n} sin traducir. Desplegar sin resolverlas es desplegar un servidor INCOMPLETO")
|
||
print(" que además parece funcionar — el modo de fallo más caro.")
|
||
return n
|
||
|
||
|
||
def main():
|
||
lectores, escritores = formatos.cargar()
|
||
ap = argparse.ArgumentParser(description="Centro de traducción de configuraciones (pivote + plugins).")
|
||
ap.add_argument("--list", action="store_true", help="listar formatos disponibles y los pares que habilitan")
|
||
ap.add_argument("--from", dest="desde", choices=sorted(lectores))
|
||
ap.add_argument("--to", dest="hacia", choices=sorted(escritores))
|
||
ap.add_argument("--in", dest="entrada")
|
||
ap.add_argument("--out", dest="salida")
|
||
a = ap.parse_args()
|
||
|
||
if a.list:
|
||
print(f"\n══ formatos registrados ══")
|
||
print(f" lectores ({len(lectores)}): {', '.join(sorted(lectores))}")
|
||
print(f" escritores ({len(escritores)}): {', '.join(sorted(escritores))}")
|
||
n, m = len(lectores), len(escritores)
|
||
print(f"\n pares habilitados: {n}×{m} = {n*m}, con {n+m} plugin(s).")
|
||
print(f" De a pares harían falta {n*m} traductores, y cada formato nuevo agregaría {n+m}.")
|
||
for le in sorted(lectores):
|
||
for es in sorted(escritores):
|
||
if le != es:
|
||
print(f" {le} → {es}")
|
||
return
|
||
|
||
if not (a.desde and a.hacia and a.entrada):
|
||
ap.error("hacen falta --from, --to y --in (o --list)")
|
||
|
||
cfg = lectores[a.desde](open(a.entrada).read())
|
||
texto = escritores[a.hacia](cfg) # el escritor puede AÑADIR al acta: se imprime después
|
||
n = imprimir_acta(cfg.acta)
|
||
if a.salida:
|
||
open(a.salida, "w").write(texto)
|
||
print(f"\n {a.hacia} → {a.salida}")
|
||
else:
|
||
print(texto)
|
||
sys.exit(1 if n else 0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|