from dataclasses import dataclass from importlib import import_module from types import ModuleType import typer from cli.context import EnvOption from cli.core import DEFAULT_CORE_SERVICES, CliContextFactory, CliExecutionService from cli.exit_codes import DEPENDENCY_ERROR from cli.output import CommandResult from cli.tui.copy import TUI_COPY @dataclass(frozen=True) class TuiDependencyResultFactory: """ TUI 依赖结果构建器。 该对象负责统一构建 Textual/Rich 缺失时的降级结果, 避免命令注册层继续直接拼接错误负载。 """ def build_missing_dependency_result(self) -> CommandResult: """ 构建 TUI 可选依赖缺失时的提示结果。 :return: 命令结果对象 """ return CommandResult( data={ 'ok': False, 'message': TUI_COPY.build_missing_dependency_message(), 'hint': TUI_COPY.build_missing_dependency_hint(), }, exit_code=DEPENDENCY_ERROR, ) @dataclass(frozen=True) class TuiAppModuleLoader: """ TUI 应用模块加载器。 该对象负责识别缺失的可选依赖,并在命令执行阶段按需导入 `cli.tui.app` 模块。 """ def is_missing_dependency_error(self, exc: ModuleNotFoundError) -> bool: """ 判断模块导入异常是否由 TUI 可选依赖缺失触发。 :param exc: 模块导入异常 :return: 是否为 TUI 依赖缺失 """ missing_module_name = (exc.name or '').split('.', 1)[0] return missing_module_name in {'textual', 'rich'} def load(self) -> ModuleType: """ 惰性导入 TUI 应用模块。 :return: `cli.tui.app` 模块对象 """ return import_module('cli.tui.app') @dataclass(frozen=True) class TuiCommandRegistration: """ TUI 命令注册器。 :param context_factory: CLI 上下文工厂 :param execution_service: CLI 执行服务 :param dependency_result_factory: TUI 缺失依赖结果构建器 :param module_loader: TUI 应用模块加载器 """ context_factory: CliContextFactory execution_service: CliExecutionService dependency_result_factory: TuiDependencyResultFactory module_loader: TuiAppModuleLoader def handle_tui_command(self, env: str) -> None: """ 执行 `shierfeng tui` 命令。 :param env: 当前命令运行环境 :return: None """ ctx = self.context_factory.build_readonly(env, 'text') try: tui_app_module = self.module_loader.load() except ModuleNotFoundError as exc: if self.module_loader.is_missing_dependency_error(exc): self.execution_service.complete_result( ctx, self.dependency_result_factory.build_missing_dependency_result(), ) return raise tui_app_module.TUI_APP_RUNNER.run(env) def build(self) -> typer.Typer: """ 构建 `tui` 子应用。 :return: `tui` 子应用 """ app = typer.Typer( help=TUI_COPY.build_tui_command_help(), no_args_is_help=False, invoke_without_command=True, context_settings={'help_option_names': ['-h', '--help']}, ) @app.callback() def tui( env: EnvOption = 'dev', ) -> None: """ 进入只读巡检工作台。 :param env: 当前命令运行环境 :return: None """ self.handle_tui_command(env) return app def register(self, root_cli: typer.Typer) -> None: """ 向根 Typer 应用注册 `tui` 命令。 :param root_cli: 根 Typer 应用 :return: None """ root_cli.add_typer(self.build(), name='tui') TUI_COMMAND_REGISTRATION = TuiCommandRegistration( context_factory=DEFAULT_CORE_SERVICES.context_factory, execution_service=DEFAULT_CORE_SERVICES.execution_service, dependency_result_factory=TuiDependencyResultFactory(), module_loader=TuiAppModuleLoader(), )