from __future__ import annotations from argparse import ArgumentParser, Namespace from abc import ABC, abstractmethod from collections.abc import Iterable from typing import TypeVar, overload from cru import CruIterator, CruInternalError from cru.app import ApplicationPath, CruApplication _F = TypeVar("_F") class InternalAppException(CruInternalError): pass class AppFeatureProvider: def __init__(self, name: str, /, app: AppBase | None = None): super().__init__() self._name = name self._app = app if app else AppBase.get_instance() self._app_paths: list[ApplicationPath] = [] self.app.add_app_feature(self) @property def app(self) -> AppBase: return self._app @property def name(self) -> str: return self._name @property def app_paths(self) -> list[ApplicationPath]: return self._app_paths def add_app_path(self, subpath: str, is_dir: bool) -> ApplicationPath: p = ApplicationPath(self.app.app_dir, subpath, is_dir) self._app_paths.append(p) return p class AppCommandFeatureProvider(AppFeatureProvider, ABC): @abstractmethod def add_arg_parser(self, arg_parser: ArgumentParser) -> None: ... @abstractmethod def run_command(self, args: Namespace) -> None: ... class AppBase(CruApplication): _instance: AppBase | None = None @staticmethod def get_instance() -> AppBase: if AppBase._instance is None: raise CruInternalError("App instance not initialized") return AppBase._instance def __init__(self, name: str, app_dir: str): super().__init__(name) AppBase._instance = self self._app_dir = app_dir self._app_features: list[AppFeatureProvider] = [] @property def app_dir(self) -> str: return self._app_dir @property def app_features(self) -> list[AppFeatureProvider]: return self._app_features @property def app_paths(self) -> Iterable[ApplicationPath]: return ( CruIterator(self._app_features).transform(lambda x: x.app_paths).flatten(1) ) def add_app_feature(self, feature: AppFeatureProvider) -> None: self._app_features.append(feature) @overload def get_feature(self, feature: str) -> AppFeatureProvider: ... @overload def get_feature(self, feature: type[_F]) -> _F: ... def get_feature(self, feature: str | type[_F]) -> AppFeatureProvider | _F: if isinstance(feature, str): for f in self._app_features: if f.name == feature: return f elif isinstance(feature, type): for f in self._app_features: if isinstance(f, feature): return f else: raise InternalAppException( "Argument must be the name of feature or its class." ) raise InternalAppException(f"Feature {feature} not found.")