From 6d69dc88a46a002eda3f5b56ac73d29c6b9d0bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 21 Apr 2025 18:02:50 +0200 Subject: [PATCH] tpl: Fix indeterminate template lookup with templates with and without lang Close #13636 --- hugolib/alias.go | 1 + hugolib/sitemap_test.go | 2 +- tpl/tplimpl/templatedescriptor.go | 29 +++--- tpl/tplimpl/templatestore.go | 8 +- tpl/tplimpl/templatestore_integration_test.go | 97 +++++++++++++++++++ 5 files changed, 120 insertions(+), 17 deletions(-) diff --git a/hugolib/alias.go b/hugolib/alias.go index 0bb3165c7..7b252c613 100644 --- a/hugolib/alias.go +++ b/hugolib/alias.go @@ -56,6 +56,7 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err templateDesc.LayoutFromUser = "" templateDesc.Kind = "" templateDesc.OutputFormat = output.AliasHTMLFormat.Name + templateDesc.MediaType = output.AliasHTMLFormat.MediaType.Type q := tplimpl.TemplateQuery{ Path: base, diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index 1c2642468..922ecbc12 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -139,7 +139,7 @@ weight = 1 languageName = "English" [languages.nn] weight = 2 --- layouts/_default/list.xml -- +-- layouts/list.xml -- Site: {{ .Site.Title }}| -- layouts/home -- Home. diff --git a/tpl/tplimpl/templatedescriptor.go b/tpl/tplimpl/templatedescriptor.go index 8b50605ac..f65ad3943 100644 --- a/tpl/tplimpl/templatedescriptor.go +++ b/tpl/tplimpl/templatedescriptor.go @@ -64,7 +64,7 @@ func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool return weightNoMatch } - w := this.doCompare(category, isEmbedded, other) + w := this.doCompare(category, isEmbedded, s.opts.DefaultContentLanguage, other) if w.w1 <= 0 { if category == CategoryMarkup && (this.Variant1 == other.Variant1) && (this.Variant2 == other.Variant2 || this.Variant2 != "" && other.Variant2 == "") { @@ -82,7 +82,7 @@ func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool } //lint:ignore ST1006 this vs other makes it easier to reason about. -func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, other TemplateDescriptor) weight { +func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, defaultContentLanguage string, other TemplateDescriptor) weight { w := weightNoMatch // HTML in plain text is OK, but not the other way around. @@ -124,6 +124,10 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth // Continue. } + if other.MediaType != this.MediaType { + return w + } + // One example of variant1 and 2 is for render codeblocks: // variant1=codeblock, variant2=go (language). if other.Variant1 != "" && other.Variant1 != this.Variant1 { @@ -142,14 +146,15 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth } const ( - weightKind = 3 // page, home, section, taxonomy, term (and only those) - weightcustomLayout = 4 // custom layout (mylayout, set in e.g. front matter) - weightLayoutStandard = 2 // standard layouts (single,list,all) - weightOutputFormat = 2 // a configured output format (e.g. rss, html, json) + weightKind = 5 // page, home, section, taxonomy, term (and only those) + weightcustomLayout = 6 // custom layout (mylayout, set in e.g. front matter) + weightLayoutStandard = 4 // standard layouts (single,list) + weightLayoutAll = 2 // the "all" layout + weightOutputFormat = 4 // a configured output format (e.g. rss, html, json) weightMediaType = 1 // a configured media type (e.g. text/html, text/plain) weightLang = 1 // a configured language (e.g. en, nn, fr, ...) - weightVariant1 = 4 // currently used for render hooks, e.g. "link", "image" - weightVariant2 = 2 // currently used for render hooks, e.g. the language "go" in code blocks. + weightVariant1 = 6 // currently used for render hooks, e.g. "link", "image" + weightVariant2 = 4 // currently used for render hooks, e.g. the language "go" in code blocks. // We will use the values for group 2 and 3 // if the distance up to the template is shorter than @@ -173,10 +178,12 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth w.w2 = weight2Group1 } - if other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate || other.LayoutFromTemplate == layoutAll) { + if other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate) { w.w1 += weightLayoutStandard w.w2 = weight2Group1 - + } else if other.LayoutFromTemplate == layoutAll { + w.w1 += weightLayoutAll + w.w2 = weight2Group1 } // LayoutCustom is only set in this (usually from Page.Layout). @@ -185,7 +192,7 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth w.w2 = weight2Group2 } - if other.Lang != "" && other.Lang == this.Lang { + if (other.Lang != "" && other.Lang == this.Lang) || (other.Lang == "" && this.Lang == defaultContentLanguage) { w.w1 += weightLang w.w3 += weight3 } diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go index 7770d053b..5be65f874 100644 --- a/tpl/tplimpl/templatestore.go +++ b/tpl/tplimpl/templatestore.go @@ -1842,7 +1842,9 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool { // Anything is better than nothing. return true } + if w.w1 <= 0 { + if best.w.w1 <= 0 { return ti.PathInfo.Path() < best.templ.PathInfo.Path() } @@ -1885,11 +1887,7 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool { return true } - if ti.D.LayoutFromTemplate != "" && best.desc.LayoutFromTemplate != "" { - return ti.D.LayoutFromTemplate != layoutAll - } - - return w.distance < best.w.distance || ti.PathInfo.Path() < best.templ.PathInfo.Path() + return ti.PathInfo.Path() < best.templ.PathInfo.Path() } return true diff --git a/tpl/tplimpl/templatestore_integration_test.go b/tpl/tplimpl/templatestore_integration_test.go index 51b28754f..06cb00212 100644 --- a/tpl/tplimpl/templatestore_integration_test.go +++ b/tpl/tplimpl/templatestore_integration_test.go @@ -3,6 +3,7 @@ package tplimpl_test import ( "context" "io" + "strings" "testing" qt "github.com/frankban/quicktest" @@ -684,6 +685,102 @@ layout: mylayout b.AssertFileContent("public/en/foo/index.xml", "layouts/list.xml") } +func TestLookupOrderIssue13636(t *testing.T) { + t.Parallel() + + filesTemplate := ` +-- hugo.toml -- +defaultContentLanguage = "en" +defaultContentLanguageInSubdir = true +[languages] +[languages.en] +weight = 1 +[languages.nn] +weight = 2 +-- content/s1/p1.en.md -- +--- +outputs: ["html", "amp", "json"] +--- +-- content/s1/p1.nn.md -- +--- +outputs: ["html", "amp", "json"] +--- +-- layouts/L1 -- +L1 +-- layouts/L2 -- +L2 +-- layouts/L3 -- +L3 + +` + + tests := []struct { + Lang string + L1 string + L2 string + L3 string + ExpectHTML string + ExpectAmp string + ExpectJSON string + }{ + {"en", "all.en.html", "all.html", "single.html", "single.html", "single.html", ""}, + {"en", "all.amp.html", "all.html", "page.html", "page.html", "all.amp.html", ""}, + {"en", "all.amp.html", "all.html", "list.html", "all.html", "all.amp.html", ""}, + {"en", "all.en.html", "all.json", "single.html", "single.html", "single.html", "all.json"}, + {"en", "all.en.html", "single.json", "single.html", "single.html", "single.html", "single.json"}, + {"en", "all.en.html", "all.html", "list.html", "all.en.html", "all.en.html", ""}, + {"en", "list.en.html", "list.html", "list.en.html", "", "", ""}, + {"nn", "all.en.html", "all.html", "single.html", "single.html", "single.html", ""}, + {"nn", "all.en.html", "all.nn.html", "single.html", "single.html", "single.html", ""}, + {"nn", "all.en.html", "all.nn.html", "single.nn.html", "single.nn.html", "single.nn.html", ""}, + {"nn", "single.json", "single.nn.json", "all.json", "", "", "single.nn.json"}, + {"nn", "single.json", "single.en.json", "all.nn.json", "", "", "single.json"}, + } + + for i, test := range tests { + if i != 8 { + // continue + } + files := strings.ReplaceAll(filesTemplate, "L1", test.L1) + files = strings.ReplaceAll(files, "L2", test.L2) + files = strings.ReplaceAll(files, "L3", test.L3) + t.Logf("Test %d: %s %s %s %s", i, test.Lang, test.L1, test.L2, test.L3) + + for range 3 { + b := hugolib.Test(t, files) + b.Assert(len(b.H.Sites), qt.Equals, 2) + + var ( + pubhHTML = "public/LANG/s1/p1/index.html" + pubhAmp = "public/LANG/amp/s1/p1/index.html" + pubhJSON = "public/LANG/s1/p1/index.json" + ) + + pubhHTML = strings.ReplaceAll(pubhHTML, "LANG", test.Lang) + pubhAmp = strings.ReplaceAll(pubhAmp, "LANG", test.Lang) + pubhJSON = strings.ReplaceAll(pubhJSON, "LANG", test.Lang) + + if test.ExpectHTML != "" { + b.AssertFileContent(pubhHTML, test.ExpectHTML) + } else { + b.AssertFileExists(pubhHTML, false) + } + + if test.ExpectAmp != "" { + b.AssertFileContent(pubhAmp, test.ExpectAmp) + } else { + b.AssertFileExists(pubhAmp, false) + } + + if test.ExpectJSON != "" { + b.AssertFileContent(pubhJSON, test.ExpectJSON) + } else { + b.AssertFileExists(pubhJSON, false) + } + } + } +} + func TestLookupShortcodeDepth(t *testing.T) { t.Parallel()