fix(core): BuildDir 只按模板里的分隔符分层

现象:默认模板 {title} 本该只出一层目录,但标题里带 | / : 的画廊
会凭空多出一层文件夹(6 个里 5 个中招)。

根因:把「非法文件名字符集」当成了「目录分隔符集」。两者只有 \ 和 /
重叠,剩下 : * ? " < > | 这 6 个字符被完全误当成目录分隔符。
更糟的是展开顺序——旧代码先把占位符全部替换完再切分,切分对象是
替换后的完整字符串,模板里的分隔符和值里的字符混在一起根本分不开。

EH 标题惯例是「原文 | 译名」,竖线极常见,所以中招率很高。

改动:改为先按模板文本切段(新增 reSep),再逐段展开占位符;
值里的非法字符一律交给 SanitizeSegment 清洗成 _,不再产生层级。
模板里自己写的 / 仍然正常分层,{artist}/{title} 行为不变。

顺带一提:值里的非法字符以前是「变成目录」,现在才是本该有的
「清洗成 _」——两处回归测试都锁住了。
This commit is contained in:
2026-10-11 11:41:00 +08:00
parent a078a00f45
commit 77a422b9bf
2 changed files with 90 additions and 8 deletions
+69
View File
@@ -66,6 +66,75 @@ func TestBuildDirDefaults(t *testing.T) {
}
}
// 占位符「值」里的非法字符只能清洗成 _,绝不能变成目录层级。
//
// 回归 2026-10 的线上问题:EH 标题惯例是「原文 | 译名」,
// 旧实现先替换占位符再按 [\\/:*?"<>|] 切分,于是标题里一个竖线
// 就凭空多出一层文件夹(6 个里 2 个中招)。
func TestBuildDirValueIllegalCharsDoNotNest(t *testing.T) {
sep := string(filepath.Separator)
tests := []struct {
name string
tpl string
title string
want string // 用 / 书写,断言前会换成本机分隔符
}{
{
name: "竖线(EH 标题惯例:原文 | 译名)",
tpl: "{title}",
title: "[Warashibe] Mesuana Hojiri Enkou | 牝穴開發援助交尾 [Chinese]",
want: "[Warashibe] Mesuana Hojiri Enkou _ 牝穴開發援助交尾 [Chinese]",
},
{
name: "斜杠",
tpl: "{title}",
title: "[Circle] AAA/BBB Collection",
want: "[Circle] AAA_BBB Collection",
},
{
name: "反斜杠",
tpl: "{title}",
title: `[Circle] AAA\BBB Collection`,
want: "[Circle] AAA_BBB Collection",
},
{
name: "冒号",
tpl: "{title}",
title: "Dai Ni Miyasaka Byouin 3 karte: Tsuyo Tanba",
want: "Dai Ni Miyasaka Byouin 3 karte_ Tsuyo Tanba",
},
{
name: "问号星号双引号尖括号",
tpl: "{title}",
title: `a?b*c"d<e>f|g`,
want: `a_b_c_d_e_f_g`,
},
{
name: "模板里的分隔符仍然生效(多级目录要保留)",
tpl: "{artist}/{title}",
title: "Enkou | 牝穴",
want: "Noxe/Enkou _ 牝穴",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := &eh.Gallery{GID: "3492157", Title: tt.title}
g.Meta.Artist = "Noxe"
got := BuildDir(tt.tpl, g)
want := strings.ReplaceAll(tt.want, "/", sep)
if got != want {
t.Errorf("\n got: %q\nwant: %q", got, want)
}
// 值里的非法字符不该产生额外层级:段数必须和模板段数一致
if n := strings.Count(got, sep); n != strings.Count(tt.tpl, "/") {
t.Errorf("层级数不对: got %q (%d 层)", got, n)
}
})
}
}
// 占位符兜底值
func TestBuildDirFallbacks(t *testing.T) {
g := &eh.Gallery{GID: "12345678", Title: "Plain Title"}