#!/usr/bin/env python3 """ ================================================================================ strip-recipes-footprints.py — usuwa pola `footprint*` z receptur PAGBUILD.yaml ================================================================================ PaganOS odchodzi od modelu CRUX/cards/NuTyX, w którym lista plików pakietu była zadeklarowana w recepturze (pliki `*.footprint` -> pola `footprint`, `footprint_devel`, `footprint_man`, `footprint_` w YAML). To był balast: * `pagbuild` NIGDY nie czytał tych pól — pakiet powstaje z katalogu DESTDIR (faza `package:`), a więc z faktycznie zainstalowanych plików; * `pag` sam wylicza SHA256 każdego pliku przy instalacji i zapisuje je w /var/lib/pag/files.db (model jak dpkg/pacman — lista plików jest pochodną pakietu, nie deklaracją w recepturze). Zostaje model „jak Arch/Debian”: jedna receptura -> jeden pakiet -> wszystkie pliki, a menedżer pakietów sam śledzi ich listę i sumy. USUNIĘCIE JEST IDEMPOTENTNE. Skrypt nie rusza żadnych innych pól ani formatowania poza wycięciem logicznych kluczy `footprint*` (wraz z ich wieloliniowymi wartościami). Obsługiwane typy skalarów YAML: * blokowe (`footprint: |`, `...: >-`) — wcięte linie treści, * w cudzysłowie (`footprint: "....\\n...."`) — z zawijaniem przez `\\` + newline, * w apostrofach (`footprint: '...'`), * proste jednolinijkowe. Użycie: python3 strip-recipes-footprints.py [--root recipes] [--apply] Bez `--apply` działa w trybie dry-run (nic nie zapisuje). """ from __future__ import annotations import argparse import glob import os import re import sys # Klucz na początku linii (kolumna 0): dokładnie `footprint` albo # `footprint_`, np. `footprint_devel`, `footprint_xfwm4_pl`. # Nazwa podpakietu może zawierać myślniki (`footprint_autoconf-archive_man`), # plusy (`footprint_crypto++_devel`), kropki itd. – dopuszczamy dowolny znak # poza dwukropkiem (separatorem) i białym znakiem. KEY_RE = re.compile(r"^footprint(?:_[^:\s]+)?:") def _is_indented(line: str) -> bool: return line[:1] in (" ", "\t") def _value_span(lines: list[str], i: int) -> int: """Zwraca liczbę linii (>=1) zajmowanych przez wartość klucza w linii `i`. Linia `i` jest linią klucza `footprint*:` w kolumnie 0. """ after = lines[i].split(":", 1)[1] stripped = after.lstrip() # ── Skalar blokowy (| lub >, opcjonalnie z chomping/indent) ── if stripped[:1] in ("|", ">"): j = 1 while i + j < len(lines): nxt = lines[i + j] if nxt.strip() == "" or _is_indented(nxt): j += 1 else: break return j # ── Skalar w cudzysłowie ── if stripped[:1] == '"': k = after.index('"') buf = after[k + 1:] j = 1 pos = 0 while True: closed = False while pos < len(buf): c = buf[pos] if c == "\\": # Znak po backslashu jest escape'owany; końcowy `\` przed # newline to kontynuacja linii – wtedy `pos += 2` przeskakuje # poza koniec `buf` i płynnie przechodzimy do następnej linii. pos += 2 continue if c == '"': closed = True break pos += 1 if closed or i + j >= len(lines): return j buf = lines[i + j] pos = 0 j += 1 # ── Skalar w apostrofach ('' = escaped apostrof) ── if stripped[:1] == "'": k = after.index("'") buf = after[k + 1:] j = 1 pos = 0 while True: closed = False while pos < len(buf): c = buf[pos] if c == "'": if pos + 1 < len(buf) and buf[pos + 1] == "'": pos += 2 continue closed = True break pos += 1 if closed or i + j >= len(lines): return j buf = lines[i + j] pos = 0 j += 1 # ── Skalar prosty (jedna linia) ── return 1 def strip_text(text: str) -> tuple[str, list[str]]: """Usuwa logiczne klucze `footprint*`. Zwraca (nowy_tekst, [usunięte_klucze]).""" lines = text.split("\n") out: list[str] = [] removed: list[str] = [] i = 0 n = len(lines) while i < n: line = lines[i] if not _is_indented(line) and KEY_RE.match(line): removed.append(line.split(":", 1)[0]) i += _value_span(lines, i) continue out.append(line) i += 1 return "\n".join(out), removed def main() -> int: ap = argparse.ArgumentParser(description="Usuwa pola footprint* z PAGBUILD.yaml") ap.add_argument("--root", default="recipes", help="katalog z recepturami (domyślnie: recipes)") ap.add_argument("--apply", action="store_true", help="zapisz zmiany (domyślnie dry-run)") args = ap.parse_args() pattern = os.path.join(args.root, "**", "PAGBUILD.yaml") files = sorted(glob.glob(pattern, recursive=True)) if not files: sys.exit(f"❌ Nie znaleziono receptur pod: {pattern}") changed_files = 0 removed_total = 0 for path in files: try: with open(path, encoding="utf-8") as f: orig = f.read() except OSError as e: print(f"⚠️ Pominięto {path}: {e}") continue new, removed = strip_text(orig) if not removed: continue changed_files += 1 removed_total += len(removed) if args.apply: with open(path, "w", encoding="utf-8") as f: f.write(new) if changed_files <= 5: shown = ", ".join(removed[:6]) + (" ..." if len(removed) > 6 else "") print(f" {path}: -{len(removed)} ({shown})") verb = "Zmieniono" if args.apply else "Do zmiany" print(f"\n{verb}: {changed_files} plików, usunięto {removed_total} pól footprint* " f"(z {len(files)} receptur).") if not args.apply and changed_files: print("Dry-run — uruchom z --apply, aby zapisać.") return 0 if __name__ == "__main__": raise SystemExit(main())