Files
MediaOrganizer/docs/adr/0006-user-custom-rules.md
Shuery c1ed1795c2
CI / lint + test + build (push) Canceled after 0s
docs: 重写 README、新增项目报告与开发工具配置
- README 现代化重写:徽章区、特性一览、统一 GitHub 提示块格式、修正版本号与 AI 默认值
- 新增 docs/PROJECT_REPORT.md 学术项目报告(含许可证合规分析与死链修复记录)
- 收录 23 份 ADR 架构决策记录与 CONTEXT.md 领域术语表
- 新增开发配置:.editorconfig / .shellcheckrc / .markdownlint-cli2.jsonc
- .gitignore 补充 mo_map/、检查报告、编辑器临时文件
- 新增 config.example.json 配置模板(不含真实密钥)
2026-08-14 22:41:40 +08:00

32 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用户自定义规则:命名模板与匹配规则
用户自定义能力增强:**命名模板**(`NAMING_*` 配置键)把命名格式化从硬编码外置为模板;**匹配规则**(`mo_config/match_rules.json`)提供确定性匹配——搜索别名与 ID 映射。两者共同把"用户的定制意图"从"改代码/依赖 AI 运气"变为"编辑配置文件"。
## 命名模板(NAMING_*)
六个配置键对应六处硬编码命名:`NAMING_MOVIE`(电影目录/文件名)、`NAMING_SHOW`(剧集目录名)、`NAMING_SEASON`(季目录名)、`NAMING_EPISODE`(剧集文件名)、`NAMING_SPECIAL`(未匹配特典文件名)、`NAMING_MUSIC`(音乐目录结构)。占位符语法三级:`{name}` 值插入、`{name:NN}` 数字补零、`{?name:text}` 条件段(name 非空才渲染 text,正文可含其他占位符,不支持嵌套条件段)。
关键实现决策:
1. **逐 token 扫描渲染**:`render_naming_template` 按 `{` 分界逐个解析占位符拼接输出,**不用** `${var//pat/repl}` 全局替换——bash 替换串中的 `&` 会被当作"匹配整体"展开(`MYTH & ROID` 这类乐队名会被损坏),逐 token 拼接天然免疫。
2. **条件段用花括号深度配对**:`{?year: ({year})}` 的正文含嵌套占位符,正文的结束 `}` 与条件段的结束 `}` 需区分——`match_conditional_close` 按 `{}` 深度计数找配对右花括号,正文取出后递归渲染(深度上限 10)。
3. **默认值集中**:`naming_template_default` 是六个默认模板的唯一定义点(config.sh 加载与 `render_naming` 兜底共用),保证"未配置"与"配置默认值"行为一致;默认模板与 v9.4 硬编码输出逐字节一致,升级零行为变化。
4. **加载期校验**:`validate_naming_templates` 提取全部占位符名(含条件段正文内)与允许集合比对,未知名打印警告(渲染为空)——拼写错误不静默。
5. **值内花括号剔除**:值中的 `{`/`}` 在赋值时剔除(防破坏模板解析);渲染结果整体 sanitize(电影/剧集目标要求目录名=文件名,模板不能引入路径分隔/非法字符)。
**考虑过的方案**:模板用 printf 风格 `%s` 占位(可读性差、无法表达条件段);条件段用单独配置键(如 `MOVIE_YEAR_TAG`,组合爆炸);正文禁止嵌套占位符(默认模板 `{?year: ({year})}` 即需要嵌套,否决);`sed` 正则替换(`&` 与转义问题同全局替换,且多字节文件名风险)。
## 匹配规则(match_rules.json)
用户手工维护的 JSON:`search_aliases`(搜索别名)与 `id_maps`(ID 映射,movie/tv 分表)。键 = 文件名清洗后的标题,`rule_key` 归一化(小写 + 空白折叠)后与 parse 产出的标题对齐。文件缺失时在用户确认下用 `MATCH_RULES_TEMPLATE` 常量创建空规则(自动化模式跳过创建、空规则继续);非法 JSON → 空规则不阻断。
优先级:**ID 映射(跳过搜索)> 常规搜索链(zh → en → 别名 → 目录名 → MAL)→ AI**。别名只在常规搜索无结果时介入;ID 映射先于一切搜索执行。规则全部确定性命中即不走 AI(省轮次、结果可预期)。
**考虑过的方案**:规则支持正则/glob 匹配(bash 正则性能与转义风险高,且命名清洗后的精确标题已覆盖主要场景);别名直接替换标题无条件生效(会绕过 TMDB 原语言搜索的命中,别名定位为"兜底"而非"替换");AI 学习写回 match_rules(用户显式维护是权威信号,与纠错学习同构——自动识别结果不学习)。
## 配套
- 匹配规则插入 `identify_movie`/`identify_tv_show` 的搜索链(ID 映射在搜索前,别名在 zh→en 之后、目录名兜底之前)。
- 命名模板接入 `build_movie_dest`/`build_tv_dest`/`special_s00_dest`/`fallback_naming`/音乐目标(识别路径与回退命名共用同一渲染,保证两套命名规则一致)。
- 新模块 `src/lib/rules.sh`(加载序在 maps.sh 之后);测试 `tests/cases/rules.sh`(渲染器 11 例)与 `tests/cases/match_rules.sh`(加载/查询 7 例)。