from collections.abc import Callable from dataclasses import dataclass, field from cli.completion.providers import COMPLETION_PROVIDER_GATEWAY, CompletionProviderGateway CompletionProviderCallable = Callable[[object, list[str] | None, str], list[str]] @dataclass(frozen=True) class PageFilterOption: """ 浏览页筛选项定义。 :param key: 筛选键 :param label: 展示名称 :param shortcut: 快捷键 """ key: str label: str shortcut: str @dataclass(frozen=True) class PageSearchContext: """ 浏览页搜索上下文定义。 :param placeholder: 搜索输入提示 :param query: 当前搜索词 :param suggestions: 候选建议列表 """ placeholder: str query: str = '' suggestions: list[str] = field(default_factory=list) @dataclass(frozen=True) class SearchSuggestionProviderSpec: """ 页面搜索建议提供者定义。 :param placeholder: 搜索输入提示 :param suggestion_provider: 候选建议提供函数 """ placeholder: str suggestion_provider: Callable[[str], list[str]] | None = None JOB_FILTER_OPTIONS: tuple[PageFilterOption, ...] = ( PageFilterOption('all', '全部', '1'), PageFilterOption('failed', '失败', '2'), PageFilterOption('paused', '暂停', '3'), PageFilterOption('ok', '正常', '4'), ) CONFIG_FILTER_OPTIONS: tuple[PageFilterOption, ...] = ( PageFilterOption('all', '全部', '1'), PageFilterOption('risky', '高风险', '2'), PageFilterOption('mismatch', '值不一致', '3'), PageFilterOption('cache-drift', '缓存异常', '4'), ) class TuiSearchSuggestionProviderRegistry: """ TUI 搜索建议提供者注册表。 该对象负责管理页面搜索提示文案及其候选建议提供函数,避免搜索服务 继续通过字符串反射隐式定位 completion provider。 :param providers: 页面搜索提供者映射 """ def __init__(self, providers: dict[str, SearchSuggestionProviderSpec]) -> None: """ 初始化搜索建议提供者注册表。 :param providers: 页面搜索提供者映射 :return: None """ self.providers = {self.normalize_view_key(key): value for key, value in providers.items()} @staticmethod def normalize_view_key(view_key: str) -> str: """ 规范化页面视图标识。 :param view_key: 原始页面视图标识 :return: 规范化后的页面视图标识 """ return str(view_key or '').strip().lower() def get_provider(self, view_key: str) -> SearchSuggestionProviderSpec | None: """ 获取指定页面的搜索建议提供者。 :param view_key: 页面视图标识 :return: 搜索建议提供者定义 """ return self.providers.get(self.normalize_view_key(view_key)) class CompletionSuggestionProviderFactory: """ 基于 completion 注册表构建 TUI 搜索建议提供函数。 :param completion_provider_gateway: completion 提供器对外网关 """ def __init__(self, completion_provider_gateway: CompletionProviderGateway) -> None: """ 初始化 completion 搜索建议工厂。 :param completion_provider_gateway: completion 提供器对外网关 :return: None """ self.completion_provider_gateway = completion_provider_gateway def build(self, provider: CompletionProviderCallable) -> Callable[[str], list[str]]: """ 根据显式 completion provider 构建建议提供函数。 :param provider: completion provider 可调用对象 :return: 搜索建议提供函数 """ def provide(query: str) -> list[str]: """ 调用 completion provider 生成候选建议。 :param query: 当前搜索词 :return: 候选建议列表 """ candidates = provider(None, [], str(query or '').strip()) return [str(candidate).strip() for candidate in candidates if str(candidate).strip()] return provide def build_default_search_provider_registry( completion_provider_gateway: CompletionProviderGateway, ) -> TuiSearchSuggestionProviderRegistry: """ 构建默认 TUI 搜索建议提供者注册表。 :param completion_provider_gateway: completion 提供器对外网关 :return: 搜索建议提供者注册表 """ provider_factory = CompletionSuggestionProviderFactory(completion_provider_gateway) return TuiSearchSuggestionProviderRegistry( providers={ 'jobs': SearchSuggestionProviderSpec( '按任务名搜索', provider_factory.build(completion_provider_gateway.complete_job_names), ), 'configs': SearchSuggestionProviderSpec( '按配置键搜索', provider_factory.build(completion_provider_gateway.complete_config_keys), ), 'cache': SearchSuggestionProviderSpec( '按缓存名搜索', provider_factory.build(completion_provider_gateway.complete_cache_names), ), 'gen': SearchSuggestionProviderSpec( '按业务表名搜索', provider_factory.build(completion_provider_gateway.complete_gen_table_names), ), 'database': SearchSuggestionProviderSpec( '按 revision 搜索', provider_factory.build(completion_provider_gateway.complete_alembic_revisions), ), 'app': SearchSuggestionProviderSpec('按分区或内容搜索'), 'ops': SearchSuggestionProviderSpec('按分区或内容搜索'), 'crypto': SearchSuggestionProviderSpec('按分区或内容搜索'), } ) class TuiSearchHighlighter: """ TUI 搜索高亮器。 该对象负责对终端文本做轻量关键字高亮。 """ @staticmethod def highlight(text: str, query: str, *, left_tag: str = '【', right_tag: str = '】') -> str: """ 对给定文本中的搜索词做轻量高亮。 :param text: 原始文本 :param query: 搜索词 :param left_tag: 左高亮标记 :param right_tag: 右高亮标记 :return: 高亮后的文本 """ raw_text = str(text or '') normalized_query = str(query or '').strip() if not raw_text or not normalized_query: return raw_text lower_text = raw_text.lower() lower_query = normalized_query.lower() result: list[str] = [] cursor = 0 query_length = len(normalized_query) while True: index = lower_text.find(lower_query, cursor) if index < 0: result.append(raw_text[cursor:]) break result.append(raw_text[cursor:index]) result.append(f'{left_tag}{raw_text[index : index + query_length]}{right_tag}') cursor = index + query_length return ''.join(result) class TuiSearchService: """ TUI 搜索服务。 该对象负责筛选项解析、筛选条文案构建、搜索上下文装配和详情分区过滤。 """ def __init__(self, provider_registry: TuiSearchSuggestionProviderRegistry) -> None: """ 初始化 TUI 搜索服务。 :param provider_registry: 页面搜索建议提供者注册表 :return: None """ self.provider_registry = provider_registry @staticmethod def resolve_filter_option( options: list[PageFilterOption] | tuple[PageFilterOption, ...], filter_key: str | None, ) -> PageFilterOption | None: """ 从筛选项列表中解析指定键对应的筛选定义。 :param options: 筛选项列表 :param filter_key: 筛选键 :return: 匹配到的筛选项 """ normalized_key = str(filter_key or '').strip().lower() if not normalized_key: return None for option in options: if option.key == normalized_key: return option return None def build_filter_bar_text( self, options: list[PageFilterOption] | tuple[PageFilterOption, ...], active_filter_key: str | None, *, search_query: str | None = None, search_placeholder: str | None = None, search_suggestions: list[str] | None = None, ) -> str: """ 构建浏览页顶部筛选条文本。 :param options: 筛选项列表 :param active_filter_key: 当前激活筛选键 :param search_query: 当前搜索词 :param search_placeholder: 搜索提示文本 :param search_suggestions: 搜索候选建议 :return: 筛选条文本 """ if not options and not search_placeholder: return '' lines: list[str] = [] normalized_active = str(active_filter_key or '').strip().lower() if options: rendered_items = [ f'[{option.shortcut}] {option.label}{" *" if option.key == normalized_active else ""}' for option in options ] active_option = self.resolve_filter_option(options, normalized_active) active_label = active_option.label if active_option is not None else '未指定' lines.extend( [ f'筛选器 · {" ".join(rendered_items)}', f'当前筛选 · {active_label}', ] ) if search_placeholder: normalized_query = str(search_query or '').strip() lines.append(f'搜索器 · [/] {search_placeholder}') lines.append(f'当前搜索 · {normalized_query or "未指定"} | [Backspace] 清空') if search_suggestions: lines.append(f'候选建议 · {" ".join(search_suggestions[:4])}') return '\n'.join(lines) def resolve_search_context( self, view_key: str, query: str = '', *, default_suggestions: list[str] | None = None, ) -> PageSearchContext | None: """ 根据页面视图解析搜索上下文,并复用 completion provider 生成候选建议。 :param view_key: 当前页面视图标识 :param query: 当前搜索词 :param default_suggestions: 缺省候选建议 :return: 搜索上下文 """ normalized_view = str(view_key).strip().lower() provider_spec = self.provider_registry.get_provider(normalized_view) if provider_spec is None: return None suggestions = self.resolve_search_suggestions(provider_spec, query) if not suggestions and default_suggestions: normalized_query = str(query or '').strip().lower() suggestions = [ suggestion for suggestion in default_suggestions if suggestion and (not normalized_query or normalized_query in suggestion.lower()) ] return PageSearchContext( placeholder=provider_spec.placeholder, query=str(query or '').strip(), suggestions=suggestions[:6], ) @staticmethod def resolve_search_suggestions(provider_spec: SearchSuggestionProviderSpec, query: str) -> list[str]: """ 调用搜索建议提供函数生成当前搜索词的候选建议。 :param provider_spec: 搜索建议提供者定义 :param query: 当前搜索词 :return: 候选建议列表 """ normalized_query = str(query or '').strip() if not normalized_query or not callable(provider_spec.suggestion_provider): return [] try: candidates = provider_spec.suggestion_provider(normalized_query) except Exception: return [] return [str(candidate).strip() for candidate in candidates if str(candidate).strip()] @staticmethod def filter_detail_sections(sections: list[object], query: str) -> list[object]: """ 按搜索词过滤详情页分区列表。 :param sections: 原始分区列表 :param query: 当前搜索词 :return: 过滤后的分区列表 """ normalized_query = str(query or '').strip().lower() if not normalized_query: return sections filtered_sections: list[object] = [] for section in sections: title = str(getattr(section, 'title', '') or '').strip().lower() lines = getattr(section, 'lines', []) joined_lines = ' '.join(str(line).strip().lower() for line in lines if str(line).strip()) if normalized_query in title or normalized_query in joined_lines: filtered_sections.append(section) return filtered_sections TUI_SEARCH_HIGHLIGHTER = TuiSearchHighlighter() TUI_SEARCH_PROVIDER_REGISTRY = build_default_search_provider_registry(COMPLETION_PROVIDER_GATEWAY) TUI_SEARCH_SERVICE = TuiSearchService(TUI_SEARCH_PROVIDER_REGISTRY)