Files
takana/scripts/mudanza/traducir.py
T
SergioandClaude Opus 5 3137d47f16 traducir: CENTRO de traducción con modelo pivote y plugins — N+M en vez de N×M
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
2026-09-11 18:27:32 +00:00

104 lines
5.3 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()