376 lines
13 KiB
Python
376 lines
13 KiB
Python
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)
|