from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING from cli.tui.actions.models import ( ActionCommandBuilder, ActionExecutionMode, ActionSummaryBuilder, ActionTextBuilder, TuiActionSpec, ) from cli.tui.copy import TUI_COPY from cli.utils import NESTED_CLI_SUPPORT, SHELL_TEXT_FORMATTER if TYPE_CHECKING: from cli.tui.adapters.models import BrowserRecordSnapshot @dataclass(frozen=True) class TuiActionSpecFactory: """ TUI 动作规格构建器。 该对象负责统一构建动作预览文本与 `TuiActionSpec`, 避免各动作函数继续手工拼接重复的预览和规格对象。 """ @staticmethod def parse_record_suffix(record: BrowserRecordSnapshot, prefix: str) -> str: """ 从记录键中提取指定前缀后的标识。 :param record: 浏览记录 :param prefix: 键名前缀 :return: 后缀标识 """ raw_key = str(record.key).strip() token_prefix = f'{prefix}:' if not raw_key.startswith(token_prefix): return '' return raw_key[len(token_prefix) :].strip() def build_command_preview_lines( self, *, env: str, summary_lines: list[str], command_args: tuple[str, ...], append_yes: bool = True, consequence_text: str = TUI_COPY.build_action_consequence_text('preview'), ) -> list[str]: """ 构建统一的动作预览文本。 :param env: 当前运行环境 :param summary_lines: 业务摘要文本 :param command_args: 对应 CLI 命令参数 :param consequence_text: 结果影响说明 :return: 预览文本 """ nested_cli_arguments = [ *command_args, f'--env={env}', '--output=json', ] if append_yes: nested_cli_arguments.append('--yes') command = SHELL_TEXT_FORMATTER.format_shell_command( NESTED_CLI_SUPPORT.build_nested_cli_command(*nested_cli_arguments) ) return [ TUI_COPY.build_action_preview_title('summary'), *summary_lines, '', TUI_COPY.build_action_preview_title('env'), TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('target_env'), env), '', TUI_COPY.build_action_preview_title('command'), command, '', TUI_COPY.build_action_preview_title('consequence'), consequence_text, ] def build_external_command_preview_lines( self, *, env: str, summary_lines: list[str], command_args: tuple[str, ...], append_yes: bool = True, consequence_text: str = TUI_COPY.build_action_consequence_text('wizard'), ) -> list[str]: """ 构建外部交互命令的预览文本。 :param env: 当前运行环境 :param summary_lines: 业务摘要文本 :param command_args: 对应 CLI 命令参数 :param consequence_text: 结果影响说明 :return: 预览文本 """ return self.build_command_preview_lines( env=env, summary_lines=summary_lines, command_args=command_args, append_yes=append_yes, consequence_text=consequence_text, ) def build_nested_json_action( self, *, action_id: str, label: str, command_args: tuple[str, ...], env: str, summary_lines: list[str], preview_title: str | None = None, consequence_text: str = TUI_COPY.build_action_consequence_text('preview'), append_yes: bool = True, refresh_view: bool = True, ) -> TuiActionSpec: """ 构建标准 nested JSON 动作。 :param action_id: 动作唯一标识 :param label: 动作显示名称 :param command_args: 对应 CLI 命令参数 :param env: 当前运行环境 :param summary_lines: 业务摘要文本 :param preview_title: 预览标题 :param consequence_text: 结果影响说明 :param refresh_view: 执行后是否刷新视图 :return: 动作定义 """ return TuiActionSpec( action_id=action_id, label=label, command_args=command_args, preview_title=preview_title or label, preview_lines=self.build_command_preview_lines( env=env, summary_lines=summary_lines, command_args=command_args, append_yes=append_yes, consequence_text=consequence_text, ), append_yes=append_yes, refresh_view=refresh_view, ) def build_external_action( self, *, action_id: str, label: str, command_args: tuple[str, ...], env: str, summary_lines: list[str], preview_title: str | None = None, consequence_text: str = TUI_COPY.build_action_consequence_text('wizard'), append_yes: bool = True, refresh_view: bool = True, ) -> TuiActionSpec: """ 构建标准外部交互动作。 :param action_id: 动作唯一标识 :param label: 动作显示名称 :param command_args: 对应 CLI 命令参数 :param env: 当前运行环境 :param summary_lines: 业务摘要文本 :param preview_title: 预览标题 :param consequence_text: 结果影响说明 :param refresh_view: 执行后是否刷新视图 :return: 动作定义 """ return TuiActionSpec( action_id=action_id, label=label, command_args=command_args, preview_title=preview_title or label, preview_lines=self.build_external_command_preview_lines( env=env, summary_lines=summary_lines, command_args=command_args, append_yes=append_yes, consequence_text=consequence_text, ), execution_mode='external', append_yes=append_yes, refresh_view=refresh_view, ) @dataclass(frozen=True) class TuiActionTemplate: """ TUI 动作模板。 该模板将命令参数构建、摘要构建与执行模式收敛为统一对象, 使动作注册层可以直接声明“某个槽位对应哪类动作”。 :param action_id: 动作唯一标识 :param label: 动作显示名称 :param command_builder: 命令参数构建器 :param summary_builder: 预览摘要构建器 :param action_id_builder: 动态动作标识构建器 :param label_builder: 动态动作标题构建器 :param preview_title: 预览标题 :param consequence_text: 结果影响说明 :param execution_mode: 执行模式 :param append_yes: 是否在命令执行与预览中自动追加 `--yes` :param refresh_view: 执行后是否刷新视图 :param preview_env_override: 预览中展示的环境名覆盖值 """ action_id: str label: str command_builder: ActionCommandBuilder summary_builder: ActionSummaryBuilder action_id_builder: ActionTextBuilder | None = None label_builder: ActionTextBuilder | None = None preview_title: str | None = None consequence_text: str = TUI_COPY.build_action_consequence_text('preview') execution_mode: ActionExecutionMode = 'nested_json' append_yes: bool = True refresh_view: bool = True preview_env_override: str | None = None def build( self, *, record: BrowserRecordSnapshot | None, env: str, spec_factory: TuiActionSpecFactory, ) -> TuiActionSpec | None: """ 根据当前页面上下文构建动作定义。 :param record: 当前选中记录 :param env: 当前运行环境 :param spec_factory: 动作规格构建器 :return: 动作定义 """ command_args = self.command_builder(record, env) if command_args is None: return None preview_env = self.preview_env_override or env summary_lines = self.summary_builder(record, env) action_id = self.action_id_builder(record, env) if self.action_id_builder is not None else self.action_id label = self.label_builder(record, env) if self.label_builder is not None else self.label if self.execution_mode == 'external': return spec_factory.build_external_action( action_id=action_id, label=label, command_args=command_args, env=preview_env, summary_lines=summary_lines, preview_title=self.preview_title, consequence_text=self.consequence_text, append_yes=self.append_yes, refresh_view=self.refresh_view, ) return spec_factory.build_nested_json_action( action_id=action_id, label=label, command_args=command_args, env=preview_env, summary_lines=summary_lines, preview_title=self.preview_title, consequence_text=self.consequence_text, append_yes=self.append_yes, refresh_view=self.refresh_view, ) @dataclass(frozen=True) class TuiActionTemplateSupport: """ TUI 动作模板共享构建支持。 该对象集中封装多个动作模板都会复用的记录解析与摘要拼装逻辑, 避免这些基础能力继续散落在模块级函数中。 :param spec_factory: 动作规格构建器 """ spec_factory: TuiActionSpecFactory def build_scope_purpose_summary(self, scope: str, purpose: str) -> list[str]: """ 构建通用的作用范围与目的摘要。 :param scope: 作用范围说明 :param purpose: 目的说明 :return: 预览摘要 """ return [ TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('scope'), scope), TUI_COPY.build_labeled_value_line(TUI_COPY.build_action_preview_field_label('purpose'), purpose), ] def extract_job_id(self, record: BrowserRecordSnapshot | None) -> str | None: """ 从任务记录中提取任务 ID。 :param record: 当前记录 :return: 任务 ID """ if record is None: return None job_id = self.spec_factory.parse_record_suffix(record, 'job') return job_id if job_id.isdigit() else None @staticmethod def require_record_title(record: BrowserRecordSnapshot | None) -> str | None: """ 提取记录标题作为命令目标。 :param record: 当前记录 :return: 标题文本 """ if record is None: return None title = str(record.title).strip() return title or None