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…
⎇ 534014c3ec69daea789a1d0523e1d00a69c6ed65
scripts/mudanza/formatos/apache.py
Historia
Quién tocó qué
crudo
traducir: familia «servicio» — systemd → tarjeta de arje, y el pivote pasa a ser uno POR FAMILIA
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