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

+90
View File
@@ -0,0 +1,90 @@
# ============================================================================
# 字符串工具模块测试(src/lib/strings.sh)
# ============================================================================
# 每个测试前重置依赖的全局状态
setup() {
COLOR_OUTPUT=false
TMDB_LANG="zh-CN"
SPECIAL_MAP=()
}
# sanitize:删除非法字符
test_sanitize_removes_illegal_chars() {
assert_eq "abcdefghij" "$(sanitize 'a/b\c:d*e?f"g<h>i|j')" "sanitize 应删除 \\ / : * ? \" < > |"
}
# sanitize:保留中文与空格
test_sanitize_keeps_cjk_and_spaces() {
assert_eq "某科学的超电磁炮T" "$(sanitize '某科学的超电磁炮T')"
assert_eq "刀剑神域 爱丽丝篇" "$(sanitize '刀剑神域 爱丽丝篇')"
}
# sanitize:空输入
test_sanitize_empty_input() {
assert_eq "" "$(sanitize "")"
assert_eq "" "$(sanitize "///")"
}
# clean_name:去方括号标记与首尾空白
test_clean_name_strips_brackets_and_spaces() {
assert_eq "Show Title" "$(clean_name '[VCB-Studio] Show Title [1080p]')"
assert_eq "Title" "$(clean_name ' [A] Title [B] ')"
}
# strip_season_suffix:多种季后缀
test_strip_season_suffix_variants() {
assert_eq "Show" "$(strip_season_suffix 'Show S01')"
assert_eq "Show" "$(strip_season_suffix 'Show 2nd Season')"
assert_eq "Show" "$(strip_season_suffix 'Show S12')"
# 行为契约:仅剥离 Season 前带数字的形式;"Show Season 2" 由 parse_media_filename 处理
assert_eq "Show Season 2" "$(strip_season_suffix 'Show Season 2')"
assert_eq "无需处理" "$(strip_season_suffix '无需处理')"
}
# norm_str:全角转半角 + 去空格/连字符 + 小写
test_norm_str_fullwidth_to_halfwidth() {
assert_eq "aliceization" "$(norm_str 'Aliceization')"
assert_eq "swordartonline" "$(norm_str 'Sword Art Online')"
assert_eq "iiiiiivv" "$(norm_str 'ⅡⅢⅣⅤ')"
assert_eq "menu01" "$(norm_str 'Menu 01')"
}
# safe_printf_int:补零与非数字默认值
test_safe_printf_int_padding() {
assert_eq "01" "$(safe_printf_int 1)"
assert_eq "23" "$(safe_printf_int 23)"
assert_eq "00" "$(safe_printf_int "")"
assert_eq "99" "$(safe_printf_int "abc" "99")"
assert_eq "07" "$(safe_printf_int "007")"
}
# normalize_abs:相对/绝对路径归一化
test_normalize_abs_paths() {
local d
d="$(mktemp -d)"
assert_eq "$d" "$(normalize_abs "$d")" "绝对路径原样返回"
assert_eq "$d/sub" "$(normalize_abs "$d/sub")" "不存在的子目录也应归一化"
assert_eq "" "$(normalize_abs "")" "空串原样返回"
rm -rf "$d"
}
# is_companion:伴随文件判断
test_is_companion_detection() {
assert_success is_companion "/path/Show.S01E01.mkv.ass" "/path/Show.S01E01.mkv"
assert_success is_companion "/path/Show.S01E01.zh-CN.ass" "/path/Show.S01E01"
assert_success is_companion "/path/Show.S01E01.flac" "/path/Show.S01E01"
assert_failure is_companion "/path/Other.mkv" "/path/Show.S01E01"
# 行为契约:视频自身去扩展名后等于 vbase → 也判为伴随(link_companions 调用前先跳过自身)
assert_success is_companion "/path/Show.S01E01.mkv" "/path/Show.S01E01"
}
# translate_fragment:查表、核心词回退、归一化
test_translate_fragment_lookup() {
SPECIAL_MAP["menu"]='["Menu", "菜单"]'
SPECIAL_MAP["cm01"]='["CM", "广告"]'
assert_eq "Menu" "$(translate_fragment "Menu01")" "去数字后查核心词"
assert_eq "CM" "$(translate_fragment "CM01")" "精确键优先"
assert_eq "Menu" "$(translate_fragment "Menu01")" "全角归一化"
assert_eq "PV-01" "$(translate_fragment "PV-01")" "无映射时原样返回"
}