"""Projekte = Ordner unter daten/. Kein Registry-File, das Listing ist die Liste.""" import shutil import config # Die Arbeitskopie einer laufenden Planung. Liegt im Projektordner und beginnt mit # einem Punkt — damit taucht sie im Listing nicht als eigenes Projekt auf. PLANUNG = ".planung" def _pruefe_name(name: str): if not name or not name.strip(): raise ValueError("Projektname darf nicht leer sein") if "/" in name or "\\" in name or name.startswith("."): raise ValueError("Ungültiger Projektname") def _ordner(name: str): _pruefe_name(name) return config.DATEN_DIR / name def liste() -> list[dict]: if not config.DATEN_DIR.exists(): return [] ordner = sorted( (p for p in config.DATEN_DIR.iterdir() if p.is_dir()), key=lambda p: p.name.lower(), ) return [{"name": p.name} for p in ordner] def anlegen(name: str): ziel = _ordner(name) if ziel.exists(): raise ValueError(f"Projekt „{name}“ existiert bereits") ziel.mkdir(parents=True) def umbenennen(alt: str, neu: str): quelle = _ordner(alt) ziel = _ordner(neu) if not quelle.is_dir(): raise FileNotFoundError(f"Projekt „{alt}“ nicht gefunden") if ziel.exists(): raise ValueError(f"Projekt „{neu}“ existiert bereits") quelle.rename(ziel) def loeschen(name: str): ordner = _ordner(name) if not ordner.is_dir(): raise FileNotFoundError(f"Projekt „{name}“ nicht gefunden") shutil.rmtree(ordner) def stand(name: str): """Der Projektordner selbst — was das Projekt heute kann. Nur wer den umgesetzten Stand meint, ruft das. Alles andere nimmt `wurzel()`. """ ordner = _ordner(name) if not ordner.is_dir(): raise FileNotFoundError(f"Projekt „{name}“ nicht gefunden") return ordner def wurzel(name: str): """Der Ordner, in dem gerade gearbeitet wird. Ohne offene Planung ist das der Projektordner. Läuft eine Planung, ist es ihre Arbeitskopie — und zwar für alles: Karten, Gespräch, Gedächtnis. Ein Schalter statt einer Fallunterscheidung in jeder Schreibfunktion. """ ordner = stand(name) kopie = ordner / PLANUNG return kopie if kopie.is_dir() else ordner