51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
from tht.config import ConfigError
|
|
|
|
if TYPE_CHECKING:
|
|
from tht.config import Config
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResolvedPaths:
|
|
workspace: Path
|
|
sessions: Path
|
|
artifacts: Path
|
|
indexes: Path
|
|
corpus: Path
|
|
|
|
|
|
def _resolve_root(path: Path, workspace: Path, name: str) -> Path:
|
|
if path.is_absolute():
|
|
# Existing deployments commonly point at an external workspace checkout. Keep
|
|
# those paths working while `tht doctor` identifies them for migration.
|
|
return path
|
|
|
|
resolved = (workspace / path).resolve()
|
|
if not resolved.is_relative_to(workspace):
|
|
raise ConfigError(f"paths.{name} resolves outside workspace root")
|
|
return resolved
|
|
|
|
|
|
def resolve_workspace_paths(
|
|
config_path: Path, cfg: Config, data_root: Path
|
|
) -> ResolvedPaths:
|
|
"""Resolve portable paths under ``<data_root>/workspaces/<config stem>``.
|
|
|
|
Relative roots are sandboxed to the logical workspace. Absolute roots are a
|
|
compatibility bridge for existing installations and are never rewritten.
|
|
"""
|
|
workspace = (data_root / "workspaces" / config_path.stem).resolve()
|
|
roots = cfg.roots
|
|
return ResolvedPaths(
|
|
workspace=workspace,
|
|
sessions=_resolve_root(roots.sessions, workspace, "sessions"),
|
|
artifacts=_resolve_root(roots.artifacts, workspace, "artifacts"),
|
|
indexes=_resolve_root(roots.indexes, workspace, "indexes"),
|
|
corpus=(workspace / "corpus").resolve(),
|
|
)
|