tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

⎇ main scripts/mudanza/formatos/apache.py Python · 106 líneas · 5.1 KB Historia Quién tocó qué crudo

traducir: familia «servicio» — systemd → tarjeta de arje, y el pivote pasa a ser uno POR FAMILIA fefa92ec5 · Sergio ·

1"""Plugin de formato: LECTOR de Apache httpd (SDD 29).
2
3Existe para PROBAR que el pivote paga: con este fichero, `apache → caddy` funciona **sin haber
4escrito un traductor apache→caddy**. El escritor de Caddy no se tocó. Ése es el argumento entero
5contra traducir de a pares — N×M contra N+M.
6"""
7import os, re, sys
8sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9from modelo import Config, Sitio, Ruta, Accion          # noqa: E402
10from formatos import lector                             # noqa: E402
11
12IRRELEVANTES = {"ServerRoot", "Listen", "LoadModule", "TypesConfig", "ErrorLog", "CustomLog",
13                "LogLevel", "Timeout", "KeepAlive", "User", "Group", "PidFile", "ServerAdmin"}
14
15
16@lector("apache", familia="web")
17def leer(texto, nombre=""):
18    cfg = Config(formato_origen="apache")
19    A = cfg.acta
20    sitio, ruta = None, None
21
22    for ln, linea in enumerate(texto.split("\n"), 1):
23        s = linea.split("#")[0].strip()
24        if not s:
25            continue
26        m = re.match(r'<(/?)(\w+)([^>]*)>', s)
27        if m:
28            cierra, tag, arg = m.group(1), m.group(2), m.group(3).strip()
29            if tag == "VirtualHost":
30                if cierra:
31                    if sitio:
32                        cfg.sitios.append(sitio); sitio = None
33                else:
34                    sitio = Sitio(origen=ln)
35                    puerto = arg.split(":")[-1].strip('"> ')
36                    if puerto.isdigit():
37                        sitio.puertos.append(puerto)
38                    A.anota(ln, "traducido", s)
39            elif tag in ("Directory", "Location", "LocationMatch", "DirectoryMatch"):
40                if cierra:
41                    if sitio and ruta:
42                        sitio.rutas.append(ruta); ruta = None
43                else:
44                    clase = "regex" if tag.endswith("Match") else "prefijo"
45                    ruta = Ruta(patron=arg.strip('"'), clase=clase, origen=ln)
46            elif tag in ("IfModule", "Files", "FilesMatch", "Limit"):
47                A.anota(ln, "SIN-TRADUCIR", s,
48                        f"`<{tag}>` no tiene traducción mecánica: su efecto depende de qué módulos "
49                        f"están cargados y del orden")
50            continue
51
52        p = s.split()
53        cab = p[0] if p else ""
54        destino = ruta if ruta is not None else sitio
55        if sitio is None:
56            if cab in IRRELEVANTES:
57                A.anota(ln, "no-aplica", s, "ajuste global del proceso httpd")
58            else:
59                A.anota(ln, "SIN-TRADUCIR", s, "directiva global no reconocida")
60            continue
61
62        if cab in ("ServerName", "ServerAlias"):
63            sitio.nombres += [x.strip('"') for x in p[1:]]
64            A.anota(ln, "traducido", s)
65        elif cab == "DocumentRoot":
66            sitio.raiz = p[1].strip('"') if len(p) > 1 else ""
67            A.anota(ln, "traducido", s)
68        elif cab == "DirectoryIndex":
69            sitio.indices = p[1:]
70            A.anota(ln, "traducido", s)
71        elif cab == "SSLCertificateFile":
72            sitio.tls["cert"] = p[1].strip('"') if len(p) > 1 else ""
73            A.anota(ln, "traducido", s)
74        elif cab == "SSLCertificateKeyFile":
75            sitio.tls["key"] = p[1].strip('"') if len(p) > 1 else ""
76            A.anota(ln, "traducido", s)
77        elif cab in ("ProxyPass", "ProxyPassReverse") and len(p) > 1:
78            # ⚠ `ProxyPass` tiene DOS formas y la diferencia importa: dentro de un `<Location>` lleva
79            # sólo la URL (`ProxyPass http://…`), fuera lleva ruta y URL (`ProxyPass /api http://…`).
80            # Mirar sólo la de tres tokens dejaba sin traducir justo la forma que más se usa, y el
81            # acta lo reportaba como «directiva no reconocida» — un falso negativo que manda a
82            # escribir a mano algo que el lector sí sabe hacer.
83            url = p[2].strip('"') if len(p) > 2 else p[1].strip('"')
84            if cab == "ProxyPassReverse":
85                A.anota(ln, "no-aplica", s, "el destino reescribe las cabeceras del proxy por su cuenta")
86            else:
87                if len(p) > 2 and ruta is None:
88                    destino.rutas.append(Ruta(patron=p[1].strip('"'), clase="prefijo", origen=ln))
89                    destino.rutas[-1].acciones.append(Accion("proxy", url))
90                else:
91                    destino.acciones.append(Accion("proxy", url))
92                A.anota(ln, "traducido", s)
93        elif cab == "Redirect" and len(p) > 1:
94            code = p[1] if p[1].isdigit() else "302"
95            dest = p[-1]
96            destino.acciones.append(Accion("redir", f"{code} {dest}"))
97            A.anota(ln, "traducido", s)
98        elif cab in ("RewriteEngine", "RewriteCond", "RewriteRule"):
99            A.anota(ln, "SIN-TRADUCIR", s,
100                    "mod_rewrite no tiene traducción mecánica: es un lenguaje propio con estado "
101                    "entre reglas")
102        elif cab in ("Options", "AllowOverride", "Require", "Order", "Allow", "Deny"):
103            A.anota(ln, "decision", s, "control de acceso o de listado: el destino lo expresa distinto")
104        else:
105            A.anota(ln, "SIN-TRADUCIR", s, "directiva no reconocida por este lector")
106    return cfg