🔒 Repository is read-only – file editing is disabled.

PaganLinux/strip-recipes-footprints.py main

185 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
#!/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_<lang>` 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_<subpakiet>`, 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())