Add profile-aware plugin TUI
This commit is contained in:
+110
-3
@@ -1,13 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tomllib
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
WINDOWS_INSTANCES_ROOT = Path("/home/pleb/Windows/Users/pleb/BSManager/BSInstances")
|
||||
LOCAL_INSTANCES_ROOT = Path.home() / ".local/share/BSManager/BSInstances"
|
||||
DEFAULT_INSTANCES_ROOTS = (WINDOWS_INSTANCES_ROOT, LOCAL_INSTANCES_ROOT)
|
||||
DEFAULT_INSTANCES_ROOT = WINDOWS_INSTANCES_ROOT
|
||||
LOCAL_CONFIG_NAME = "plugin-helper.local.toml"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Profile:
|
||||
id: str
|
||||
label: str
|
||||
instances_root: Path
|
||||
state_dir: Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeConfig:
|
||||
instances_roots: list[Path]
|
||||
state_root: Path
|
||||
profiles: tuple[Profile, ...]
|
||||
selected_profile: Profile | None
|
||||
config_path: Path
|
||||
config_loaded: bool
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def instances_root(value: str | None = None) -> Path:
|
||||
@@ -23,11 +49,92 @@ def instances_roots(value: str | None = None) -> list[Path]:
|
||||
|
||||
def state_root(value: str | None = None) -> Path:
|
||||
if value:
|
||||
return Path(value).expanduser()
|
||||
return _resolve_path(value, repo_root())
|
||||
xdg_state = os.environ.get("XDG_STATE_HOME")
|
||||
base = Path(xdg_state).expanduser() if xdg_state else Path.home() / ".local" / "state"
|
||||
return base / "plugin-helper"
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
def default_config_path(root: Path | None = None) -> Path:
|
||||
return (root or repo_root()) / LOCAL_CONFIG_NAME
|
||||
|
||||
|
||||
def _resolve_path(value: str | Path, base: Path) -> Path:
|
||||
path = Path(value).expanduser()
|
||||
if path.is_absolute():
|
||||
return path
|
||||
return (base / path).resolve()
|
||||
|
||||
|
||||
def load_profiles(config_path: str | Path | None = None, *, root: Path | None = None) -> tuple[tuple[Profile, ...], Path, bool]:
|
||||
repo = root or repo_root()
|
||||
path = _resolve_path(config_path, repo) if config_path else default_config_path(repo)
|
||||
if not path.exists():
|
||||
if config_path:
|
||||
raise FileNotFoundError(f"plugin-helper config not found: {path}")
|
||||
return (), path, False
|
||||
|
||||
with path.open("rb") as handle:
|
||||
data: dict[str, Any] = tomllib.load(handle)
|
||||
|
||||
profiles: list[Profile] = []
|
||||
seen: set[str] = set()
|
||||
base = path.parent
|
||||
for item in data.get("profiles", []):
|
||||
profile_id = item["id"]
|
||||
if profile_id in seen:
|
||||
raise ValueError(f"{path}: duplicate profile id: {profile_id}")
|
||||
seen.add(profile_id)
|
||||
profiles.append(
|
||||
Profile(
|
||||
id=profile_id,
|
||||
label=item.get("label", profile_id),
|
||||
instances_root=_resolve_path(item["instances_root"], base),
|
||||
state_dir=_resolve_path(item["state_dir"], base),
|
||||
)
|
||||
)
|
||||
return tuple(profiles), path, True
|
||||
|
||||
|
||||
def profile_by_id(profiles: tuple[Profile, ...], profile_id: str) -> Profile:
|
||||
for profile in profiles:
|
||||
if profile.id == profile_id:
|
||||
return profile
|
||||
available = ", ".join(profile.id for profile in profiles) or "(none)"
|
||||
raise KeyError(f"unknown profile: {profile_id}; available profiles: {available}")
|
||||
|
||||
|
||||
def resolve_runtime_config(
|
||||
*,
|
||||
instances_root_value: str | None = None,
|
||||
state_dir_value: str | None = None,
|
||||
profile_id: str | None = None,
|
||||
config_path: str | Path | None = None,
|
||||
root: Path | None = None,
|
||||
) -> RuntimeConfig:
|
||||
repo = root or repo_root()
|
||||
profiles, loaded_path, loaded = load_profiles(config_path, root=repo)
|
||||
selected = profile_by_id(profiles, profile_id) if profile_id else None
|
||||
|
||||
if instances_root_value:
|
||||
resolved_instances = instances_roots(instances_root_value)
|
||||
elif selected:
|
||||
resolved_instances = [selected.instances_root]
|
||||
else:
|
||||
resolved_instances = instances_roots(None)
|
||||
|
||||
if state_dir_value:
|
||||
resolved_state = _resolve_path(state_dir_value, repo)
|
||||
elif selected:
|
||||
resolved_state = selected.state_dir
|
||||
else:
|
||||
resolved_state = state_root(None)
|
||||
|
||||
return RuntimeConfig(
|
||||
instances_roots=resolved_instances,
|
||||
state_root=resolved_state,
|
||||
profiles=profiles,
|
||||
selected_profile=selected,
|
||||
config_path=loaded_path,
|
||||
config_loaded=loaded,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user