from __future__ import annotations import sys from dataclasses import dataclass from pathlib import Path from src.paths import get_layouts_dir from src.result_types import ErrorInfo, ErrorKind, Result @dataclass(frozen=True, slots=True) class LayoutFile: """A bundled Manual Slop layout asset (.ini). Stores raw INI text intended to be installed to cwd/manualslop_layout.ini by App._post_init when the user's INI is empty or missing. The raw text is opaque to this module; structure parsing (ImGui sections, [Window] entries) is the consumer's responsibility. [C: src/layouts.py:load_layouts_from_dir, src/gui_2.py:_install_default_layout_if_empty]""" name: str raw_text: str source_path: Path scope: str def load_layouts_from_file(path: Path, scope: str) -> dict[str, LayoutFile]: """Load ONE layout file and return a 1-entry dict. Public API matches themes' load_themes_from_toml shape (dict out). [C: src/layouts.py:load_layouts_from_dir]""" out: dict[str, LayoutFile] = {} if not path.exists() or not path.is_file(): return out try: raw = path.read_text(encoding="utf-8", errors="replace") except OSError as e: _layout_err = Result(data=None, errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"failed to read layout {path}: {e}", source="layouts.load_layouts_from_file", original=e)]) print(f"warning: failed to read {path}: {e}", file=sys.stderr) return out out[path.stem] = LayoutFile(name=path.stem, raw_text=raw, source_path=path, scope=scope) return out def load_layouts_from_dir(path: Path, scope: str) -> dict[str, LayoutFile]: """Load every .ini layout in `path`. Empty dict on missing dir or all-skip (per the 'delete to turn off' pattern in conductor/code_styleguides/feature_flags.md). Per-file errors are drained via Result + stderr warn, mirroring src/theme_models.py:load_themes_from_dir. [C: src/layouts.py:load_layouts_from_disk]""" out: dict[str, LayoutFile] = {} if not path.exists() or not path.is_dir(): return out for child in sorted(path.iterdir()): if not child.is_file(): continue if child.suffix.lower() != ".ini": continue try: raw = child.read_text(encoding="utf-8", errors="replace") except OSError as e: _layout_err = Result(data=None, errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"failed to read layout {child}: {e}", source="layouts.load_layouts_from_dir", original=e)]) print(f"warning: failed to read {child}: {e}", file=sys.stderr) continue layout = LayoutFile(name=child.stem, raw_text=raw, source_path=child, scope=scope) out[layout.name] = layout return out _LAYOUTS_CACHE: dict[str, LayoutFile] = {} def load_layouts_from_disk() -> dict[str, LayoutFile]: """Load all bundled layouts from the global layouts dir (resolved via src/paths.py:get_layouts_dir()). Cached at the module level; callers iterate the returned dict. The 'delete to turn off' guarantee: a missing or empty layouts/ dir returns {}. [C: src/gui_2.py:_install_default_layout_if_empty]""" global _LAYOUTS_CACHE layouts_dir = get_layouts_dir() _LAYOUTS_CACHE = load_layouts_from_dir(layouts_dir, scope="global") return _LAYOUTS_CACHE