Omit missing years from upload destination paths
ci / module boundaries / boundaries (push) Successful in 7s
ci / db / db (push) Successful in 2m33s
ci / api / api (push) Successful in 1m10s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 3m45s
ci / ts / ts (packages + web) (push) Successful in 6m29s

This commit is contained in:
mrleo1nid committed 2026-10-30 11:40:42 +03:00
1 parent 3309a79a98
commit db4548b115
10 files changed
+131 -5

No files matched your search

@@ -254,6 +254,65 @@ func (t Template) Render(in TemplateInput) (string, error) {
return finishPath(b.String())
}
// RenderWithoutMissingYear omits an unknown year and its punctuation for uploads.
// Other required metadata still uses strict rendering. Optional fragments retain
// their usual all-or-nothing semantics.
func (t Template) RenderWithoutMissingYear(in TemplateInput) (string, error) {
if fieldValue(in, tnode{field: "year"}) != "" {
return t.Render(in)
}
var src strings.Builder
for _, n := range t.nodes {
if n.isOpt {
if _, ok := renderOptional(in, n.opt); ok {
src.WriteString("[")
for _, o := range n.opt {
src.WriteString(templateNodeSource(o))
}
src.WriteString("]")
}
} else {
src.WriteString(templateNodeSource(n))
}
}
s := src.String()
for _, re := range missingYearParts {
s = re.ReplaceAllString(s, "")
}
// A year-only directory disappears as a whole.
var segments []string
for _, seg := range strings.Split(s, "/") {
if strings.TrimSpace(seg) != "" {
segments = append(segments, seg)
}
}
tpl, err := ParseTemplate(strings.Join(segments, "/"))
if err != nil {
return "", err
}
return tpl.Render(in)
}
var missingYearParts = []*regexp.Regexp{
regexp.MustCompile(`\(\s*\{year\}\s*\)`),
regexp.MustCompile(`\{year\}[ \t]*[-–—_][ \t]*`),
regexp.MustCompile(`[ \t]*[-–—_][ \t]*\{year\}`),
regexp.MustCompile(`\{year\}`),
}
func templateNodeSource(n tnode) string {
if n.field == "" {
return n.lit
}
if n.multi {
return "{" + n.field + ":1" + n.suffix + "}"
}
if n.pad > 0 {
return fmt.Sprintf("{%s:0%d}", n.field, n.pad)
}
return "{" + n.field + "}"
}
func renderOptional(in TemplateInput, nodes []tnode) (string, bool) {
var b strings.Builder
for _, n := range nodes {
@@ -7,6 +7,41 @@ import (
const docTemplate = "{albumartist}/{year} - {album}/{disc:1-}{track:02} - {title}.{ext}"
func TestRenderWithoutMissingYear(t *testing.T) {
for _, pattern := range []string{
"{artist}/{album} ({year})/{track:02} - {title}.{ext}",
"{artist}/{year} - {album}/{track:02} - {title}.{ext}",
"{artist}/{album} - {year}/{track:02} - {title}.{ext}",
"{artist}/{year}/{album}/{track:02} - {title}.{ext}",
"{artist}/[{year} - ]{album}/{track:02} - {title}.{ext}",
} {
t.Run(pattern, func(t *testing.T) {
tpl, err := ParseTemplate(pattern)
if err != nil {
t.Fatal(err)
}
input := in(Fields{Artist: "A", Album: "B", Title: "C", Track: "1"}, 1, "mp3")
got, err := tpl.RenderWithoutMissingYear(input)
if err != nil || got != "A/B/01 - C.mp3" {
t.Fatalf("got %q, %v", got, err)
}
input.Fields.Title = ""
if _, err := tpl.RenderWithoutMissingYear(input); err == nil {
t.Fatal("missing title must still fail")
}
input.Fields.Title, input.Fields.Year = "C", "2026"
want, err := tpl.Render(input)
if err != nil {
t.Fatal(err)
}
got, err = tpl.RenderWithoutMissingYear(input)
if err != nil || got != want {
t.Fatalf("known year: got %q, %v; want %q", got, err, want)
}
})
}
}
func in(f Fields, discs int, ext string) TemplateInput {
return TemplateInput{Fields: f, DiscCount: discs, Ext: ext}
}