tpl: Fix legacy section mappings

Fixes #13584
This commit is contained in:
Bjørn Erik Pedersen
2025-04-11 11:20:03 +02:00
parent c19f1f2363
commit 1074e01152
4 changed files with 97 additions and 25 deletions

View File

@@ -214,3 +214,18 @@ All.
b.AssertFileContent("public/index.html", "All.") b.AssertFileContent("public/index.html", "All.")
b.AssertFileContent("public/amp/index.html", "All.") b.AssertFileContent("public/amp/index.html", "All.")
} }
// Issue #13584.
func TestLegacySectionSection(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- content/mysection/_index.md --
-- layouts/section/section.html --
layouts/section/section.html
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/mysection/index.html", "layouts/section/section.html")
}

View File

@@ -3,7 +3,9 @@ package tplimpl
import ( import (
"io" "io"
"regexp" "regexp"
"strconv"
"strings" "strings"
"sync/atomic"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@@ -49,11 +51,6 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
} }
pi := ti.PathInfo pi := ti.PathInfo
name := pi.PathNoLeadingSlash() name := pi.PathNoLeadingSlash()
if ti.isLegacyMapped {
// When mapping the old taxonomy structure to the new one, we may map the same path to multiple templates per kind.
// Append the kind here to make the name unique.
name += ("-" + ti.D.Kind)
}
var ( var (
templ tpl.Template templ tpl.Template
@@ -62,12 +59,18 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
if ti.D.IsPlainText { if ti.D.IsPlainText {
prototype := t.parseText prototype := t.parseText
if prototype.Lookup(name) != nil {
name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
}
templ, err = prototype.New(name).Parse(ti.content) templ, err = prototype.New(name).Parse(ti.content)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
prototype := t.parseHTML prototype := t.parseHTML
if prototype.Lookup(name) != nil {
name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
}
templ, err = prototype.New(name).Parse(ti.content) templ, err = prototype.New(name).Parse(ti.content)
if err != nil { if err != nil {
return err return err
@@ -296,6 +299,8 @@ type templateNamespace struct {
prototypeText *texttemplate.Template prototypeText *texttemplate.Template
prototypeHTML *htmltemplate.Template prototypeHTML *htmltemplate.Template
nameCounter atomic.Uint64
standaloneText *texttemplate.Template standaloneText *texttemplate.Template
baseofTextClones []*texttemplate.Template baseofTextClones []*texttemplate.Template

View File

@@ -690,7 +690,7 @@ func (t *TemplateStore) UnusedTemplates() []*TemplInfo {
var unused []*TemplInfo var unused []*TemplInfo
for vv := range t.templates() { for vv := range t.templates() {
if vv.subCategory != SubCategoryMain { if vv.subCategory != SubCategoryMain || vv.isLegacyMapped {
// Skip inline partials and internal templates. // Skip inline partials and internal templates.
continue continue
} }
@@ -1169,8 +1169,8 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
case containerPartials, containerShortcodes, containerMarkup: case containerPartials, containerShortcodes, containerMarkup:
// OK. // OK.
default: default:
applyLegacyMapping = true
pi = fromLegacyPath(pi) pi = fromLegacyPath(pi)
applyLegacyMapping = strings.Count(pi.Path(), "/") <= 2
} }
if applyLegacyMapping { if applyLegacyMapping {
@@ -1183,6 +1183,7 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
ext: pi.Ext(), ext: pi.Ext(),
outputFormat: pi.OutputFormat(), outputFormat: pi.OutputFormat(),
} }
if m2, ok := legacyOrdinalMappings[key]; ok { if m2, ok := legacyOrdinalMappings[key]; ok {
if m1.ordinal < m2.m.ordinal { if m1.ordinal < m2.m.ordinal {
// Higher up == better match. // Higher up == better match.
@@ -1208,28 +1209,76 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
base := piOrig.PathBeforeLangAndOutputFormatAndExt() base := piOrig.PathBeforeLangAndOutputFormatAndExt()
identifiers := pi.IdentifiersUnknown() identifiers := pi.IdentifiersUnknown()
if pi.Kind() != "" {
identifiers = append(identifiers, pi.Kind())
}
shouldIncludeSection := func(section string) bool {
switch section {
case containerShortcodes, containerPartials, containerMarkup:
return false
case "taxonomy", "":
return false
default:
for k, v := range s.opts.TaxonomySingularPlural {
if k == section || v == section {
return false
}
}
return true
}
}
if shouldIncludeSection(pi.Section()) {
identifiers = append(identifiers, pi.Section())
}
identifiers = helpers.UniqueStrings(identifiers)
// Tokens on e.g. form /SECTIONKIND/THESECTION // Tokens on e.g. form /SECTIONKIND/THESECTION
insertSectionTokens := func(section string, kindOnly bool) string { insertSectionTokens := func(section string) []string {
s := base kindOnly := isLayoutStandard(section)
var ss []string
s1 := base
if !kindOnly { if !kindOnly {
s = strings.Replace(s, section, sectionToken, 1) s1 = strings.ReplaceAll(s1, section, sectionToken)
} }
s = strings.Replace(s, kinds.KindSection, sectionKindToken, 1) s1 = strings.ReplaceAll(s1, kinds.KindSection, sectionKindToken)
return s if s1 != base {
ss = append(ss, s1)
}
s1 = strings.ReplaceAll(base, kinds.KindSection, sectionKindToken)
if !kindOnly {
s1 = strings.ReplaceAll(s1, section, sectionToken)
}
if s1 != base {
ss = append(ss, s1)
} }
for _, section := range identifiers { helpers.UniqueStringsReuse(ss)
if section == baseNameBaseof {
return ss
}
for _, id := range identifiers {
if id == "" {
continue continue
} }
kindOnly := isLayoutStandard(section)
p := insertSectionTokens(section, kindOnly) p := insertSectionTokens(id)
if m1, ok := s.opts.legacyMappingSection[p]; ok { for _, ss := range p {
m1.mapping.targetPath = strings.Replace(m1.mapping.targetPath, sectionToken, section, 1) if m1, ok := s.opts.legacyMappingSection[ss]; ok {
targetPath := m1.mapping.targetPath
if targetPath != "" {
targetPath = strings.ReplaceAll(targetPath, sectionToken, id)
targetPath = strings.ReplaceAll(targetPath, sectionKindToken, id)
targetPath = strings.ReplaceAll(targetPath, "//", "/")
}
m1.mapping.targetPath = targetPath
handleMapping(m1) handleMapping(m1)
} }
} }
}
} }

View File

@@ -441,7 +441,7 @@ title: "P1"
{{ define "main" }}FOO{{ end }} {{ define "main" }}FOO{{ end }}
-- layouts/_default/single.json -- -- layouts/_default/single.json --
-- layouts/_default/single.html -- -- layouts/_default/single.html --
{{ define "main" }}MAIN{{ end }} {{ define "main" }}MAIN /_default/single.html{{ end }}
-- layouts/post/single.html -- -- layouts/post/single.html --
{{ define "main" }}MAIN{{ end }} {{ define "main" }}MAIN{{ end }}
-- layouts/_partials/usedpartial.html -- -- layouts/_partials/usedpartial.html --
@@ -461,6 +461,8 @@ title: "P1"
) )
b.Build() b.Build()
b.AssertFileContent("public/p1/index.html", "MAIN /_default/single.html")
unused := b.H.GetTemplateStore().UnusedTemplates() unused := b.H.GetTemplateStore().UnusedTemplates()
var names []string var names []string
for _, tmpl := range unused { for _, tmpl := range unused {
@@ -468,8 +470,9 @@ title: "P1"
names = append(names, fi.Meta().PathInfo.PathNoLeadingSlash()) names = append(names, fi.Meta().PathInfo.PathNoLeadingSlash())
} }
} }
b.Assert(len(unused), qt.Equals, 5, qt.Commentf("%#v", names))
b.Assert(names, qt.DeepEquals, []string{"_partials/unusedpartial.html", "shortcodes/unusedshortcode.html", "baseof.json", "post/single.html", "_default/single.json"}) b.Assert(names, qt.DeepEquals, []string{"_partials/unusedpartial.html", "shortcodes/unusedshortcode.html", "baseof.json", "post/single.html", "_default/single.json"})
b.Assert(len(unused), qt.Equals, 5, qt.Commentf("%#v", names))
} }
func TestCreateManyTemplateStores(t *testing.T) { func TestCreateManyTemplateStores(t *testing.T) {