82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
from types import ModuleType
|
|
|
|
from retrobuilder.model import BaseSpec, FeatureSpec, ProfileSpec
|
|
from retrobuilder.paths import base_dir, feature_dir, profile_file
|
|
|
|
|
|
def _load_module(path: Path, name: str) -> ModuleType:
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError(f'Cannot load module from {path}')
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def load_profile(root: Path, name: str) -> ProfileSpec:
|
|
path = profile_file(root, name)
|
|
module = _load_module(path, f'profile_{name}')
|
|
profile = getattr(module, 'PROFILE', None)
|
|
if not isinstance(profile, ProfileSpec):
|
|
raise TypeError(f'{path} must define PROFILE as ProfileSpec')
|
|
return profile
|
|
|
|
|
|
def load_base_module(root: Path, name: str) -> ModuleType:
|
|
return _load_module(base_dir(root, name) / 'entry.py', f'base_{name}')
|
|
|
|
|
|
def load_feature_module(root: Path, name: str) -> ModuleType:
|
|
return _load_module(feature_dir(root, name) / 'entry.py', f'feature_{name}')
|
|
|
|
|
|
def load_base_spec(root: Path, name: str) -> BaseSpec:
|
|
module = load_base_module(root, name)
|
|
spec = getattr(module, 'SPEC', None)
|
|
if not isinstance(spec, BaseSpec):
|
|
raise TypeError(f'bases/{name}/entry.py must define SPEC as BaseSpec')
|
|
return spec
|
|
|
|
|
|
def load_base_chain(root: Path, name: str) -> list[tuple[str, BaseSpec]]:
|
|
chain: list[tuple[str, BaseSpec]] = []
|
|
seen: set[str] = set()
|
|
current = name
|
|
while current:
|
|
if current in seen:
|
|
raise RuntimeError(f'Base inheritance loop detected at {current}')
|
|
seen.add(current)
|
|
spec = load_base_spec(root, current)
|
|
chain.append((current, spec))
|
|
current = spec.parent or ''
|
|
chain.reverse()
|
|
return chain
|
|
|
|
|
|
def load_feature_spec(root: Path, name: str) -> FeatureSpec:
|
|
module = load_feature_module(root, name)
|
|
spec = getattr(module, 'SPEC', None)
|
|
if not isinstance(spec, FeatureSpec):
|
|
raise TypeError(f'features/{name}/entry.py must define SPEC as FeatureSpec')
|
|
return spec
|
|
|
|
|
|
def load_base_entry(root: Path, name: str):
|
|
module = load_base_module(root, name)
|
|
entry_cls = getattr(module, 'Entry', None)
|
|
if entry_cls is None:
|
|
raise TypeError(f'bases/{name}/entry.py must define Entry')
|
|
return entry_cls
|
|
|
|
|
|
def load_feature_entry(root: Path, name: str):
|
|
module = load_feature_module(root, name)
|
|
entry_cls = getattr(module, 'Entry', None)
|
|
if entry_cls is None:
|
|
raise TypeError(f'features/{name}/entry.py must define Entry')
|
|
return entry_cls
|