from pathlib import Path from config import STORAGE_DIR, PROJECTS_DIR, PROJECT_ROOT THEMEN_DIR = STORAGE_DIR / "themen" def _safe(name: str) -> str: return name.replace("/", "_").replace("\x00", "") def topic_dir(topic: str) -> Path: return THEMEN_DIR / _safe(topic) def arbeit_dir(topic: str) -> Path: return topic_dir(topic) / "arbeit" def bausteine_path(topic: str) -> Path: return topic_dir(topic) / "bausteine.md" def subbausteine_path(topic: str) -> Path: """Sidecar: pro Baustein die Subbausteine mit Stufe (von allen Guides geteilt).""" return topic_dir(topic) / "subbausteine.json" def quelle_path(topic: str) -> Path: """Persistierte Quellen-Wahl pro Thema: {type, ort, spec}.""" return topic_dir(topic) / "quelle.json" def quelle_crawl_dir(topic: str) -> Path: """Zielordner für gecrawlte Link-Quellen (Seiten + PDF-.txt).""" return topic_dir(topic) / "quelle" def safe_ordner(ort: str) -> Path | None: """Ordnerpfad relativ zum Repo-Root, gesandboxt. None bei leer/Ausbruch (../, absolut außerhalb).""" if not ort or not ort.strip(): return None p = (PROJECT_ROOT / ort.strip()).resolve() try: p.relative_to(PROJECT_ROOT) except ValueError: return None return p def guide_content_path(topic: str, format_name: str) -> Path: return topic_dir(topic) / "guides" / f"{format_name}.json" def bausteine_topics() -> list[str]: """Themen, für die ein Themen-Ordner existiert.""" if not THEMEN_DIR.is_dir(): return [] return [d.name for d in THEMEN_DIR.iterdir() if d.is_dir()] def project_dir(name: str) -> Path: return PROJECTS_DIR / name