gioser

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…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

⎇ main scripts/farm/harkaq-add-deps.py Python · 80 siq'ikuna · 3.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

harkaq: campaña desatendida — medir→declarar→verificar, sin regex y sin preguntas f783f0107 · sergio ·

1#!/usr/bin/env python3
2"""harkaq-add-deps — añade deps de build a una receta, sin romperla. (Cosecha desatendida.)
3
4    harkaq-add-deps.py recipes/zlib.toml make binutils
5
6Idempotente: si la dep ya está, no toca nada y sale 1 (nada que commitear).
7
8Corre DESATENDIDO y de noche, editando ficheros que son la fuente de verdad del catálogo. Por eso
9la regla es: **ante la duda, no tocar**. Un fichero mal editado a las 3am no da un error, da una
10receta corrupta que nadie mira hasta el lunes. Cada caso que no se entiende sale sin cambios y con
11motivo, que es la misma disciplina de `SinEvidencia`: no afirmar lo que no se sabe.
12"""
13import re
14import sys
15
16
17def anadir(path, nuevas):
18    with open(path) as f:
19        s = f.read()
20    orig = s
21
22    m = re.search(r'^\[deps\]\s*$(.*?)(?=^\[|\Z)', s, re.M | re.S)
23    if m:
24        bloque = m.group(1)
25        mb = re.search(r'^(\s*build\s*=\s*)\[([^\]]*)\]', bloque, re.M)
26        if mb:
27            ya = re.findall(r'"([^"]+)"', mb.group(2))
28            falta = [d for d in nuevas if d not in ya]
29            if not falta:
30                return False, "ya estaban declaradas"
31            lista = ya + falta
32            nuevo = mb.group(1) + "[" + ", ".join(f'"{d}"' for d in lista) + "]"
33            s = s[:m.start(1)] + bloque[:mb.start()] + nuevo + bloque[mb.end():] + s[m.end(1):]
34        else:
35            # Hay [deps] pero sin `build = [...]` (p.ej. sólo `runtime`). Insertar la línea.
36            linea = "build = [" + ", ".join(f'"{d}"' for d in nuevas) + "]\n"
37            s = s[:m.start(1)] + "\n" + linea + bloque.lstrip("\n") + s[m.end(1):]
38    else:
39        # Sin [deps]: se añade al final. `[build.phases]` suele ser la última sección y sus strings
40        # multilínea (''' … ''') se comerían un bloque puesto después ⇒ va ANTES de la primera
41        # sección [build...] si existe, y si no, al final.
42        bloque = "\n[deps]\nbuild = [" + ", ".join(f'"{d}"' for d in nuevas) + "]\n"
43        mfase = re.search(r'^\[build\.phases\]', s, re.M)
44        if mfase:
45            s = s[:mfase.start()] + bloque.lstrip("\n") + "\n" + s[mfase.start():]
46        else:
47            s = s.rstrip("\n") + "\n" + bloque
48
49    if s == orig:
50        return False, "sin cambios"
51
52    # Guarda: sólo escribir si el TOML sigue pareciendo un TOML y no perdimos nada. Un parser de
53    # verdad sería mejor, pero tomllib no ESCRIBE y re-emitir con un writer reordenaría el fichero
54    # entero (y con él, comentarios que valen oro en este repo).
55    try:
56        import tomllib
57        tomllib.loads(s)
58    except Exception as e:
59        return False, f"el resultado no parsea como TOML ({e}) — NO se toca"
60    if len(s) < len(orig):
61        return False, "el resultado es más corto que el original — NO se toca"
62
63    with open(path, "w") as f:
64        f.write(s)
65    return True, "ok"
66
67
68def main():
69    if len(sys.argv) < 3:
70        print(__doc__.split("\n")[2].strip(), file=sys.stderr)
71        return 2
72    path, nuevas = sys.argv[1], sys.argv[2:]
73    ok, motivo = anadir(path, nuevas)
74    if not ok:
75        print(f"   ·· {path}: {motivo}", file=sys.stderr)
76    return 0 if ok else 1
77
78
79if __name__ == "__main__":
80    sys.exit(main())