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…
⎇ 9b5861d815cfb08418773920067400bd11f9aa66
scripts/mudanza/formatos/nginx.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 nginx (SDD 29)."""
2import os, sys
3sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
4from modelo import Config, Sitio, Ruta, Accion # noqa: E402
5from formatos import lector # noqa: E402
6
7# Bloques cuya semántica depende del orden de evaluación o del contexto: no se traducen, y —esto es
8# lo importante— TODO lo de adentro tampoco. Un `return 403` sacado de su `if` deja de ser
9# condicional y pasa a aplicar siempre: no se pierde información, se CAMBIA el sentido.
10OPACOS = {"map", "if", "stream", "upstream", "geo", "split_clients"}
11
12# Ajustes del proceso nginx que no tienen ni necesitan equivalente.
13GLOBALES_IRRELEVANTES = {
14 "worker_processes", "worker_connections", "user", "pid", "include", "sendfile",
15 "keepalive_timeout", "gzip", "default_type", "log_format", "types_hash_max_size",
16 "tcp_nopush", "multi_accept", "server_tokens", "charset",
17}
18
19
20def _tokenizar(texto):
21 out, nivel, buf, ln_ini = [], 0, "", 0
22 for ln, linea in enumerate(texto.split("\n"), 1):
23 s = linea.split("#")[0].strip()
24 if not s:
25 continue
26 if not buf:
27 ln_ini = ln
28 for ch in s:
29 if ch == "{":
30 out.append((ln_ini, nivel, buf.strip(), "abre")); nivel += 1; buf = ""
31 elif ch == "}":
32 if buf.strip():
33 out.append((ln_ini, nivel, buf.strip(), "dir"))
34 nivel -= 1; out.append((ln_ini, nivel, "", "cierra")); buf = ""
35 elif ch == ";":
36 out.append((ln_ini, nivel, buf.strip(), "dir")); buf = ""
37 else:
38 buf += ch
39 if buf and not buf.endswith(" "):
40 buf += " "
41 return out
42
43
44@lector("nginx", familia="web")
45def leer(texto, nombre=""):
46 cfg = Config(formato_origen="nginx")
47 A = cfg.acta
48 pila, sitio, ruta, opaco = [], None, None, 0
49
50 for ln, _niv, txt, tipo in _tokenizar(texto):
51 cab = txt.split()[0] if txt else ""
52
53 if tipo == "abre":
54 pila.append(cab)
55 if cab in OPACOS:
56 opaco += 1
57 A.anota(ln, "SIN-TRADUCIR", txt + " {",
58 f"`{cab}` depende del orden de evaluación y del contexto; TODO lo de adentro "
59 f"queda sin traducir — sacarlo del bloque le cambiaría el sentido")
60 elif cab == "server" and not opaco:
61 sitio = Sitio(origen=ln)
62 elif cab == "location" and not opaco:
63 p = txt.split()
64 if len(p) > 2: # modificador: ~ ~* ^~ =
65 clase = {"~": "regex", "~*": "regex", "=": "exacto", "^~": "prefijo"}.get(p[1], "regex")
66 ruta = Ruta(patron=p[2], clase=clase, origen=ln)
67 else:
68 ruta = Ruta(patron=p[1] if len(p) > 1 else "/", clase="prefijo", origen=ln)
69 continue
70
71 if tipo == "cierra":
72 c = pila.pop() if pila else ""
73 if c in OPACOS and opaco:
74 opaco -= 1
75 elif c == "location" and sitio is not None and ruta is not None:
76 sitio.rutas.append(ruta); ruta = None
77 elif c == "server" and sitio is not None:
78 cfg.sitios.append(sitio); sitio = None
79 continue
80
81 if opaco:
82 A.anota(ln, "SIN-TRADUCIR", txt,
83 "está dentro de un bloque que no se pudo traducir: sacarla de ahí dejaría de ser "
84 "condicional")
85 continue
86
87 p = txt.split()
88 destino = ruta if ruta is not None else sitio
89 if sitio is None:
90 if cab in GLOBALES_IRRELEVANTES or cab.endswith("_log"):
91 A.anota(ln, "no-aplica", txt, "ajuste global del proceso nginx, sin equivalente necesario")
92 elif txt:
93 A.anota(ln, "SIN-TRADUCIR", txt, "directiva global no reconocida")
94 continue
95
96 if cab == "server_name":
97 sitio.nombres += [x for x in p[1:] if x != "_"]
98 A.anota(ln, "traducido", txt)
99 elif cab == "listen":
100 sitio.puertos.append(p[1].split(":")[-1] if len(p) > 1 else "")
101 A.anota(ln, "traducido", txt)
102 elif cab == "root":
103 if ruta is not None:
104 ruta.acciones.append(Accion("archivos", p[1] if len(p) > 1 else ""))
105 else:
106 sitio.raiz = p[1] if len(p) > 1 else ""
107 A.anota(ln, "traducido", txt)
108 elif cab == "index":
109 sitio.indices = p[1:]
110 A.anota(ln, "traducido", txt)
111 elif cab in ("ssl_certificate", "ssl_certificate_key"):
112 sitio.tls["cert" if cab == "ssl_certificate" else "key"] = p[1] if len(p) > 1 else ""
113 A.anota(ln, "traducido", txt)
114 elif cab == "proxy_pass":
115 destino.acciones.append(Accion("proxy", p[1] if len(p) > 1 else ""))
116 A.anota(ln, "traducido", txt)
117 elif cab == "try_files":
118 destino.acciones.append(Accion("intentar", " ".join(p[1:])))
119 A.anota(ln, "traducido", txt)
120 elif cab == "return":
121 destino.acciones.append(Accion("redir", " ".join(p[1:])))
122 A.anota(ln, "traducido", txt)
123 elif cab in ("proxy_set_header", "add_header"):
124 A.anota(ln, "decision", txt,
125 "las cabeceras de proxy habituales (X-Forwarded-*) las pone el destino por su "
126 "cuenta; si ésta no es de ésas, hay que escribirla")
127 elif cab in ("client_max_body_size", "expires", "autoindex", "access_log", "error_log"):
128 A.anota(ln, "decision", txt, "tiene equivalente pero con otra forma: revisalo")
129 else:
130 A.anota(ln, "SIN-TRADUCIR", txt, "directiva no reconocida por este lector")
131 return cfg