Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur, was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen, das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor. Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale Bezeichnung ersetzt; die Regeln selbst sind unverändert. Co-Authored-By: Claude Opus 5 <[email protected]>
99 lines
3.6 KiB
Python
99 lines
3.6 KiB
Python
"""Baut die schlanke Kopie eines Projektbaums fuer den Byte-Vergleich -- Ausschluesse nur als Pfade vom Wurzelordner.
|
|
|
|
Ersteller: Pascal Michel
|
|
Erstellt: 2026-09-17
|
|
Doku: .vault-tools/plan-kopie/LIESMICH.md
|
|
|
|
Aufruf: python kopie_bauen.py <projekt> <ziel> --ohne <relpfad> [<relpfad> ...]
|
|
|
|
Warum Pfade und keine Namen: Ein Ausschluss von "jedem Ordner namens data" hat am 2026-09-17 auch das Paket
|
|
trading/data getroffen. Nach dem Kopieren vergleicht das Skript deshalb die Python-Pakete (Ordner mit __init__.py)
|
|
beider Baeume und meldet jedes, das in der Kopie fehlt und nicht unter einem ausgeschlossenen Pfad liegt.
|
|
Das Ziel darf nicht existieren; Verknuepfungen und Junctions werden nicht verfolgt, sondern uebersprungen.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def _relativ(pfad: Path, wurzel: Path) -> str:
|
|
return pfad.relative_to(wurzel).as_posix()
|
|
|
|
|
|
def _pakete(wurzel: Path, ohne: set[str]) -> set[str]:
|
|
gefunden = set()
|
|
for ordner, unterordner, dateien in os.walk(wurzel):
|
|
rel = _relativ(Path(ordner), wurzel)
|
|
unterordner[:] = [
|
|
name for name in unterordner
|
|
if (f"{rel}/{name}" if rel != "." else name) not in ohne
|
|
and not Path(ordner, name).is_symlink()
|
|
and not Path(ordner, name).is_junction()
|
|
]
|
|
if "__init__.py" in dateien:
|
|
gefunden.add(rel)
|
|
return gefunden
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
|
parser.add_argument("projekt", type=Path)
|
|
parser.add_argument("ziel", type=Path)
|
|
parser.add_argument("--ohne", nargs="*", default=[], help="relative Pfade vom Wurzelordner, mit /")
|
|
args = parser.parse_args()
|
|
|
|
projekt = args.projekt.resolve()
|
|
ziel = args.ziel.resolve()
|
|
if not projekt.is_dir():
|
|
print(f"BEFUND Projekt {projekt} ist kein Ordner")
|
|
return 2
|
|
if ziel.exists():
|
|
print(f"BEFUND Ziel {ziel} existiert schon -- erst ansehen, dann selbst loeschen")
|
|
return 2
|
|
|
|
ohne = {eintrag.replace("\\", "/").strip("/") for eintrag in args.ohne}
|
|
befunde = []
|
|
for eintrag in sorted(ohne):
|
|
if "/" not in eintrag and not (projekt / eintrag).exists():
|
|
befunde.append(f"Ausschluss '{eintrag}' gibt es im Wurzelordner nicht -- ein Name statt eines Pfads?")
|
|
elif not (projekt / eintrag).exists():
|
|
befunde.append(f"Ausschluss '{eintrag}' gibt es nicht")
|
|
|
|
uebersprungen = []
|
|
|
|
def ignoriere(ordner: str, namen: list[str]) -> list[str]:
|
|
rel = _relativ(Path(ordner), projekt)
|
|
raus = []
|
|
for name in namen:
|
|
pfad = Path(ordner, name)
|
|
relpfad = name if rel == "." else f"{rel}/{name}"
|
|
if relpfad in ohne or name == "__pycache__":
|
|
raus.append(name)
|
|
elif pfad.is_symlink() or pfad.is_junction():
|
|
uebersprungen.append(relpfad)
|
|
raus.append(name)
|
|
return raus
|
|
|
|
shutil.copytree(projekt, ziel, ignore=ignoriere)
|
|
|
|
fehlend = sorted(_pakete(projekt, ohne) - _pakete(ziel, set()))
|
|
for paket in fehlend:
|
|
befunde.append(f"Paket '{paket}' fehlt in der Kopie")
|
|
|
|
dateien = sum(len(d) for _, _, d in os.walk(ziel))
|
|
print(f"{dateien} Dateien kopiert, {len(ohne)} Pfade ausgeschlossen, "
|
|
f"{len(uebersprungen)} Verknuepfungen uebersprungen, {len(_pakete(ziel, set()))} Pakete in der Kopie.")
|
|
for zeile in uebersprungen:
|
|
print(f"HINWEIS Verknuepfung uebersprungen: {zeile}")
|
|
for zeile in befunde:
|
|
print(f"BEFUND {zeile}")
|
|
print(f"{len(befunde)} Befund(e).")
|
|
return 1 if befunde else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|