from pathlib import Path from typing import List, Optional import yaml from pydantic import BaseModel class _BlockDumper(yaml.Dumper): pass _BlockDumper.add_representer( str, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", data, style="|" if "\n" in data else None ), ) class PlaybookItem(BaseModel): id: str title: str goal: str instructions: str tags: List[str] = [] order: int = 0 class PlaybookFileStore: def __init__(self, directory: Path): self.directory = directory directory.mkdir(parents=True, exist_ok=True) def _path(self, id: str) -> Path: return self.directory / f"{id}.yaml" def _read(self, path: Path) -> Optional[PlaybookItem]: try: with open(path, encoding="utf-8") as f: data = yaml.safe_load(f) return PlaybookItem( id=data["id"], title=data["title"], goal=data.get("goal", ""), instructions=data.get("instructions", ""), tags=data.get("tags", []), order=data.get("order", 0), ) except Exception: return None @staticmethod def _clean(s: str) -> str: return "\n".join(line.rstrip() for line in s.split("\n")).strip() def _write(self, item: PlaybookItem): data = { "id": item.id, "title": item.title, "goal": item.goal, "tags": item.tags, "order": item.order, "instructions": self._clean(item.instructions), } with open(self._path(item.id), "w", encoding="utf-8") as f: yaml.dump(data, f, Dumper=_BlockDumper, allow_unicode=True, default_flow_style=False, sort_keys=False, width=4096) def all_playbooks(self) -> List[PlaybookItem]: items = [self._read(p) for p in self.directory.glob("*.yaml")] return sorted((i for i in items if i), key=lambda x: x.order) def get_playbook(self, id: str) -> Optional[PlaybookItem]: return self._read(self._path(id)) def add_playbook(self, item: PlaybookItem): self._write(item) def delete_playbook(self, id: str): p = self._path(id) if p.exists(): p.unlink() def reorder_playbooks(self, ordered_ids: List[str]): all_ids = {p.stem for p in self.directory.glob("*.yaml")} valid = [pid for pid in ordered_ids if pid in all_ids] missing = sorted(all_ids - set(valid)) for index, pid in enumerate(valid + missing): item = self.get_playbook(pid) if item: item.order = index self._write(item) from ..nexus_config import PLAYBOOK_DIR playbook_store = PlaybookFileStore(PLAYBOOK_DIR)