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/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 (
"io"
"regexp"
"strconv"
"strings"
"sync/atomic"
"unicode"
"unicode/utf8"
@ -49,11 +51,6 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
}
pi := ti.PathInfo
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 (
templ tpl.Template
@ -62,12 +59,18 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
if ti.D.IsPlainText {
prototype := t.parseText
if prototype.Lookup(name) != nil {
name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
}
templ, err = prototype.New(name).Parse(ti.content)
if err != nil {
return err
}
} else {
prototype := t.parseHTML
if prototype.Lookup(name) != nil {
name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
}
templ, err = prototype.New(name).Parse(ti.content)
if err != nil {
return err
@ -296,6 +299,8 @@ type templateNamespace struct {
prototypeText *texttemplate.Template
prototypeHTML *htmltemplate.Template
nameCounter atomic.Uint64
standaloneText *texttemplate.Template
baseofTextClones []*texttemplate.Template

View file

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

View file

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