refactor: 迁移至 bashly CLI 与功能域模块结构

- CLI 定义迁移至 src/bashly.yml(bashly 1.4 生成参数解析),入口收敛为 src/root_command.sh
- 源码按功能域重组:config/storage/integrate/media/pipeline,消除扁平散落
- 新增零依赖测试框架 tests/(123 用例)与 CI 流水线(lint + test + build + 产物一致性)
- build.sh 支持版本单一来源注入与 --check 产物校验;新增 lint.sh(bash -n + shellcheck 零容忍)
- 删除旧单文件 media_organizer.sh 与旧扁平模块
This commit is contained in:
Shuery committed 2026-08-14 22:41:28 +08:00
1 parent d978a13a83
commit 0df07ad0ae
48 files changed
+5813 -6812

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
# ============================================================================
# AI 批处理模块测试(src/lib/ai.sh)
# ============================================================================
setup() {
AI_BATCH_SIZE=50
AI_BATCH_KEYS_SEARCH=()
AI_BATCH_KEYS_SPECIAL=()
AI_BATCH_KEYS_ARTIST=()
AI_BATCH_KEYS_MATCH=()
PENDING_AI_SEARCH=()
PENDING_AI_SPECIAL=()
PENDING_AI_ARTIST=()
PENDING_AI_MATCH=()
}
# ai_extract_json:直接 JSON
test_ai_extract_direct_json() {
assert_eq '{"a": 1}' "$(ai_extract_json '{"a": 1}')"
}
# ai_extract_json:```json 围栏
test_ai_extract_fenced_json() {
local input=$'```json\n{"a": 1}\n```'
assert_eq '{"a": 1}' "$(ai_extract_json "$input")"
}
# ai_extract_json:异行 <thinking> 标签
test_ai_extract_thinking_block() {
local input=$'<thinking>分析中</thinking>\n{"a": 1}'
assert_eq '{"a": 1}' "$(ai_extract_json "$input")"
}
# ai_extract_json:同行 <thinking> 标签(awk 行级处理无法吞掉,需 sed 预处理)
test_ai_extract_thinking_inline() {
assert_eq '{"a": 1}' "$(ai_extract_json '<thinking>分析中</thinking>{"a": 1}')"
}
# ai_extract_json:「思考:」前缀
test_ai_extract_thinking_prefix() {
assert_eq '{"a": 1}' "$(ai_extract_json '思考:这是答案。{"a": 1}')"
}
# ai_extract_json:非 JSON 返回空
test_ai_extract_invalid_returns_empty() {
assert_eq "" "$(ai_extract_json 'not json at all')"
assert_eq "" "$(ai_extract_json '')"
}
# build_ai_input_json:三类条目构造正确的 JSON 结构
test_ai_input_json_structure() {
PENDING_AI_SEARCH["/m/电影 (2020).mkv"]="movie dir"
PENDING_AI_SPECIAL["123|Menu01"]="Show|01:Menu; 02:CM"
PENDING_AI_ARTIST["/m/song.flac"]="A; B|Album|" # 值格式:artists|album|rel_dir
local json rc=0
build_ai_input_json json
echo "$json" | jq -e '
(.search_entries | length) == 1 and
(.special_entries | length) == 1 and
(.artist_entries | length) == 1 and
(.match_entries | length) == 0
' >/dev/null || rc=$?
assert_eq '0' "$rc" "JSON 结构校验"
assert_eq "/m/电影 (2020).mkv" "$(echo "$json" | jq -r '.search_entries[0].file')"
assert_eq "123" "$(echo "$json" | jq -r '.special_entries[0].show_id')"
assert_eq "A; B" "$(echo "$json" | jq -r '.artist_entries[0].artists')"
# 副作用:keys 数组在调用方可见(修复子 shell 丢失 bug 后)
assert_eq '1' "${#AI_BATCH_KEYS_SEARCH[@]}"
assert_eq '1' "${#AI_BATCH_KEYS_SPECIAL[@]}"
assert_eq '1' "${#AI_BATCH_KEYS_ARTIST[@]}"
}
# build_ai_input_json:批大小上限
test_ai_input_json_batch_limit() {
AI_BATCH_SIZE=2
PENDING_AI_SEARCH["/a1.mkv"]="movie "
PENDING_AI_SEARCH["/a2.mkv"]="movie "
PENDING_AI_SEARCH["/a3.mkv"]="movie "
local json
build_ai_input_json json
assert_eq '2' "$(echo "$json" | jq '.search_entries | length')"
assert_eq '2' "${#AI_BATCH_KEYS_SEARCH[@]}"
}