]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
tpl: Make any layout set in front matter higher priority
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 12 Apr 2025 10:37:39 +0000 (12:37 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 12 Apr 2025 16:12:06 +0000 (18:12 +0200)
Fixes #13541

hugolib/alias.go
hugolib/content_map_test.go
hugolib/page.go
tpl/tplimpl/templatedescriptor.go
tpl/tplimpl/templatedescriptor_test.go
tpl/tplimpl/templatestore.go
tpl/tplimpl/templatestore_integration_test.go

index 3beee44dbb739d435817390a2e6bd0609594dc86..0bb3165c7333f37666e6e5c8e5cf81df82eeff11 100644 (file)
@@ -53,7 +53,7 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err
        if ps, ok := p.(*pageState); ok {
                base, templateDesc = ps.GetInternalTemplateBasePathAndDescriptor()
        }
-       templateDesc.Layout = ""
+       templateDesc.LayoutFromUser = ""
        templateDesc.Kind = ""
        templateDesc.OutputFormat = output.AliasHTMLFormat.Name
 
index aed2a7f139803bebd2f04acdb24a31abde57efc5..c24790495ea0fca2caf70ad3ec8624f7e6f1a6be 100644 (file)
@@ -538,6 +538,7 @@ title: p1
 -- content/p1/c.html --
 <p>c</p>
 -- layouts/_default/single.html --
+Path: {{ .Path }}|{{.Kind }}
 |{{ (.Resources.Get "a.html").RelPermalink -}}
 |{{ (.Resources.Get "b.html").RelPermalink -}}
 |{{ (.Resources.Get "c.html").Publish }}
index b36b9712ca9e471371e9349b8d281991b9772ceb..84d4d1ea883577f54b3ca9a5ad3e21f047898358 100644 (file)
@@ -482,12 +482,12 @@ func (po *pageOutput) GetInternalTemplateBasePathAndDescriptor() (string, tplimp
        f := po.f
        base := p.PathInfo().BaseReTyped(p.m.pageConfig.Type)
        return base, tplimpl.TemplateDescriptor{
-               Kind:         p.Kind(),
-               Lang:         p.Language().Lang,
-               Layout:       p.Layout(),
-               OutputFormat: f.Name,
-               MediaType:    f.MediaType.Type,
-               IsPlainText:  f.IsPlainText,
+               Kind:           p.Kind(),
+               Lang:           p.Language().Lang,
+               LayoutFromUser: p.Layout(),
+               OutputFormat:   f.Name,
+               MediaType:      f.MediaType.Type,
+               IsPlainText:    f.IsPlainText,
        }
 }
 
@@ -495,8 +495,8 @@ func (p *pageState) resolveTemplate(layouts ...string) (*tplimpl.TemplInfo, bool
        dir, d := p.GetInternalTemplateBasePathAndDescriptor()
 
        if len(layouts) > 0 {
-               d.Layout = layouts[0]
-               d.LayoutMustMatch = true
+               d.LayoutFromUser = layouts[0]
+               d.LayoutFromUserMustMatch = true
        }
 
        q := tplimpl.TemplateQuery{
index f93993092fd5984165fc27d238c8259b59ff576e..8e4390faebdf2f6bce1bb7fd2c5541166f548ef4 100644 (file)
@@ -22,8 +22,9 @@ const baseNameBaseof = "baseof"
 // This is used both as a key and in lookups.
 type TemplateDescriptor struct {
        // Group 1.
-       Kind   string // page, home, section, taxonomy, term (and only those)
-       Layout string // list, single, baseof, mycustomlayout.
+       Kind               string // page, home, section, taxonomy, term (and only those)
+       LayoutFromTemplate string // list, single, all,mycustomlayout
+       LayoutFromUser     string //  custom layout set in front matter, e.g. list, single, all, mycustomlayout
 
        // Group 2.
        OutputFormat string // rss, csv ...
@@ -34,23 +35,21 @@ type TemplateDescriptor struct {
        Variant2 string // contextual variant, e.g. "id" in render.
 
        // Misc.
-       LayoutMustMatch bool // If set, we only look for the exact layout.
-       IsPlainText     bool // Whether this is a plain text template.
+       LayoutFromUserMustMatch bool // If set, we only look for the exact layout.
+       IsPlainText             bool // Whether this is a plain text template.
 }
 
 func (d *TemplateDescriptor) normalizeFromFile() {
-       // fmt.Println("normalizeFromFile", "kind:", d.Kind, "layout:", d.Layout, "of:", d.OutputFormat)
-
-       if d.Layout == d.OutputFormat {
-               d.Layout = ""
+       if d.LayoutFromTemplate == d.OutputFormat {
+               d.LayoutFromTemplate = ""
        }
 
        if d.Kind == kinds.KindTemporary {
                d.Kind = ""
        }
 
-       if d.Layout == d.Kind {
-               d.Layout = ""
+       if d.LayoutFromTemplate == d.Kind {
+               d.LayoutFromTemplate = ""
        }
 }
 
@@ -61,7 +60,7 @@ type descriptorHandler struct {
 // Note that this in this setup is usually a descriptor constructed from a page,
 // so we want to find the best match for that page.
 func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool, this, other TemplateDescriptor) weight {
-       if this.LayoutMustMatch && this.Layout != other.Layout {
+       if this.LayoutFromUserMustMatch && this.LayoutFromUser != other.LayoutFromTemplate {
                return weightNoMatch
        }
 
@@ -94,20 +93,15 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
                return w
        }
 
-       if other.Layout != "" && other.Layout != layoutAll && other.Layout != this.Layout {
-               if isLayoutCustom(this.Layout) {
-                       if this.Kind == "" {
-                               this.Layout = ""
-                       } else if this.Kind == kinds.KindPage {
-                               this.Layout = layoutSingle
-                       } else {
-                               this.Layout = layoutList
+       if other.LayoutFromTemplate != "" && other.LayoutFromTemplate != layoutAll {
+               if this.LayoutFromUser == "" {
+                       if other.LayoutFromTemplate != this.LayoutFromTemplate {
+                               return w
+                       }
+               } else if isLayoutStandard(this.LayoutFromUser) {
+                       if other.LayoutFromTemplate != this.LayoutFromUser {
+                               return w
                        }
-               }
-
-               // Test again.
-               if other.Layout != this.Layout {
-                       return w
                }
        }
 
@@ -123,7 +117,11 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
                // We want e.g. home page in amp output format (media type text/html) to
                // find a template even if one isn't specified for that output format,
                // when one exist for the html output format (same media type).
-               if category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.Layout != other.Layout && other.Layout != layoutAll))) {
+               skip := category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.LayoutFromTemplate != other.LayoutFromTemplate && other.LayoutFromTemplate != layoutAll)))
+               if this.LayoutFromUser != "" {
+                       skip = skip && (this.LayoutFromUser != other.LayoutFromTemplate)
+               }
+               if skip {
                        return w
                }
 
@@ -148,14 +146,14 @@ 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)
-               weightLayout       = 2 // standard layouts (single,list,all)
-               weightOutputFormat = 2 // 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.
+               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)
+               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.
 
                // We will use the values for group 2 and 3
                // if the distance up to the template is shorter than
@@ -179,14 +177,16 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
                w.w2 = weight2Group1
        }
 
-       if other.Layout != "" && other.Layout == this.Layout || other.Layout == layoutAll {
-               if isLayoutCustom(this.Layout) {
-                       w.w1 += weightcustomLayout
-                       w.w2 = weight2Group2
-               } else {
-                       w.w1 += weightLayout
-                       w.w2 = weight2Group1
-               }
+       if this.LayoutFromUser == "" && other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate || other.LayoutFromTemplate == layoutAll) {
+               w.w1 += weightLayoutStandard
+               w.w2 = weight2Group1
+
+       }
+
+       // LayoutCustom is only set in this (usually from Page.Layout).
+       if this.LayoutFromUser != "" && this.LayoutFromUser == other.LayoutFromTemplate {
+               w.w1 += weightcustomLayout
+               w.w2 = weight2Group2
        }
 
        if other.Lang != "" && other.Lang == this.Lang {
index 6fb8c219515d8871c9ac5c0d35b35d7cabe20e13..20ab47fba94baa724ce439a5be3a58e1fe66a15f 100644 (file)
@@ -38,29 +38,29 @@ func TestTemplateDescriptorCompare(t *testing.T) {
        check(
 
                CategoryBaseof,
-               TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "404", MediaType: "text/html"},
-               TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "html", MediaType: "text/html"},
+               TemplateDescriptor{Kind: "", LayoutFromTemplate: "", Lang: "", OutputFormat: "404", MediaType: "text/html"},
+               TemplateDescriptor{Kind: "", LayoutFromTemplate: "", Lang: "", OutputFormat: "html", MediaType: "text/html"},
                false,
        )
 
        check(
                CategoryLayout,
                TemplateDescriptor{Kind: "", Lang: "en", OutputFormat: "404", MediaType: "text/html"},
-               TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "alias", MediaType: "text/html"},
+               TemplateDescriptor{Kind: "", LayoutFromTemplate: "", Lang: "", OutputFormat: "alias", MediaType: "text/html"},
                true,
        )
 
        less(
                CategoryLayout,
-               TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "html"},
-               TemplateDescriptor{Layout: "list", OutputFormat: "html"},
+               TemplateDescriptor{Kind: kinds.KindHome, LayoutFromTemplate: "list", OutputFormat: "html"},
+               TemplateDescriptor{LayoutFromTemplate: "list", OutputFormat: "html"},
                TemplateDescriptor{Kind: kinds.KindHome, OutputFormat: "html"},
        )
 
        check(
                CategoryLayout,
-               TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "html", MediaType: "text/html"},
-               TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "myformat", MediaType: "text/html"},
+               TemplateDescriptor{Kind: kinds.KindHome, LayoutFromTemplate: "list", OutputFormat: "html", MediaType: "text/html"},
+               TemplateDescriptor{Kind: kinds.KindHome, LayoutFromTemplate: "list", OutputFormat: "myformat", MediaType: "text/html"},
                false,
        )
 }
@@ -78,20 +78,20 @@ func BenchmarkCompareDescriptors(b *testing.B) {
                d1, d2 TemplateDescriptor
        }{
                {
-                       TemplateDescriptor{Kind: "", Layout: "", OutputFormat: "404", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
-                       TemplateDescriptor{Kind: "", Layout: "", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "", LayoutFromTemplate: "", OutputFormat: "404", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "", LayoutFromTemplate: "", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
                },
                {
-                       TemplateDescriptor{Kind: "page", Layout: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
-                       TemplateDescriptor{Kind: "", Layout: "list", OutputFormat: "", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "page", LayoutFromTemplate: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "", LayoutFromTemplate: "list", OutputFormat: "", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
                },
                {
-                       TemplateDescriptor{Kind: "page", Layout: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
-                       TemplateDescriptor{Kind: "", Layout: "", OutputFormat: "alias", MediaType: "text/html", Lang: "", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "page", LayoutFromTemplate: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "", LayoutFromTemplate: "", OutputFormat: "alias", MediaType: "text/html", Lang: "", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
                },
                {
-                       TemplateDescriptor{Kind: "page", Layout: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
-                       TemplateDescriptor{Kind: "", Layout: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "nn", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "page", LayoutFromTemplate: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
+                       TemplateDescriptor{Kind: "", LayoutFromTemplate: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "nn", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
                },
        }
 
index e761e14d108f52cc1010a69c3dccc70f77fddbf8..8483b7df033094b0ac6a1bfc46676d4da0bb760b 100644 (file)
@@ -378,11 +378,11 @@ func (q *TemplateQuery) init() {
        } else if kinds.GetKindMain(q.Desc.Kind) == "" {
                q.Desc.Kind = ""
        }
-       if q.Desc.Layout == "" && q.Desc.Kind != "" {
+       if q.Desc.LayoutFromTemplate == "" && q.Desc.Kind != "" {
                if q.Desc.Kind == kinds.KindPage {
-                       q.Desc.Layout = layoutSingle
+                       q.Desc.LayoutFromTemplate = layoutSingle
                } else {
-                       q.Desc.Layout = layoutList
+                       q.Desc.LayoutFromTemplate = layoutList
                }
        }
 
@@ -447,7 +447,7 @@ func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc Te
                                continue
                        }
 
-                       if vv.D.isKindInLayout(desc.Layout) && s.dh.compareDescriptors(CategoryBaseof, false, descBaseof, vv.D).w1 > 0 {
+                       if vv.D.isKindInLayout(desc.LayoutFromTemplate) && s.dh.compareDescriptors(CategoryBaseof, false, descBaseof, vv.D).w1 > 0 {
                                result = append(result, keyTemplateInfo{Key: k, Info: vv})
                        }
                }
@@ -549,7 +549,7 @@ func (s *TemplateStore) LookupPartial(pth string) *TemplInfo {
        ti, _ := s.cacheLookupPartials.GetOrCreate(pth, func() (*TemplInfo, error) {
                d := s.templateDescriptorFromPath(pth)
                desc := d.Desc
-               if desc.Layout != "" {
+               if desc.LayoutFromTemplate != "" {
                        panic("shortcode template descriptor must not have a layout")
                }
                best := s.getBest()
@@ -610,7 +610,7 @@ func (s *TemplateStore) PrintDebug(prefix string, category Category, w io.Writer
                        return
                }
                s := strings.ReplaceAll(strings.TrimSpace(vv.content), "\n", " ")
-               ts := fmt.Sprintf("kind: %q layout: %q content: %.30s", vv.D.Kind, vv.D.Layout, s)
+               ts := fmt.Sprintf("kind: %q layout: %q content: %.30s", vv.D.Kind, vv.D.LayoutFromTemplate, s)
                fmt.Fprintf(w, "%s%s %s\n", strings.Repeat(" ", level), key, ts)
        }
        s.treeMain.WalkPrefix(prefix, func(key string, v map[nodeKey]*TemplInfo) (bool, error) {
@@ -1573,12 +1573,12 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
        }
 
        d := TemplateDescriptor{
-               Lang:         p.Lang(),
-               OutputFormat: p.OutputFormat(),
-               MediaType:    mediaType.Type,
-               Kind:         p.Kind(),
-               Layout:       layout,
-               IsPlainText:  outputFormat.IsPlainText,
+               Lang:               p.Lang(),
+               OutputFormat:       p.OutputFormat(),
+               MediaType:          mediaType.Type,
+               Kind:               p.Kind(),
+               LayoutFromTemplate: layout,
+               IsPlainText:        outputFormat.IsPlainText,
        }
 
        d.normalizeFromFile()
@@ -1611,7 +1611,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
        }
 
        if category == CategoryPartial {
-               d.Layout = ""
+               d.LayoutFromTemplate = ""
                k1 = p.PathNoIdentifier()
        }
 
@@ -1626,15 +1626,15 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
        }
 
        // Legacy layout for home page.
-       if d.Layout == "index" {
+       if d.LayoutFromTemplate == "index" {
                if d.Kind == "" {
                        d.Kind = kinds.KindHome
                }
-               d.Layout = ""
+               d.LayoutFromTemplate = ""
        }
 
-       if d.Layout == d.Kind {
-               d.Layout = ""
+       if d.LayoutFromTemplate == d.Kind {
+               d.LayoutFromTemplate = ""
        }
 
        k1 = strings.TrimPrefix(k1, "/_default")
@@ -1645,7 +1645,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
        if category == CategoryMarkup {
                // We store all template nodes for a given directory on the same level.
                k1 = strings.TrimSuffix(k1, "/_markup")
-               parts := strings.Split(d.Layout, "-")
+               parts := strings.Split(d.LayoutFromTemplate, "-")
                if len(parts) < 2 {
                        return "", "", 0, TemplateDescriptor{}, fmt.Errorf("unrecognized render hook template")
                }
@@ -1654,7 +1654,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
                if len(parts) > 2 {
                        d.Variant2 = parts[2]
                }
-               d.Layout = "" // This allows using page layout as part of the key for lookups.
+               d.LayoutFromTemplate = "" // This allows using page layout as part of the key for lookups.
        }
 
        return k1, k2, category, d, nil
@@ -1868,8 +1868,8 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
                        return true
                }
 
-               if ti.D.Layout != "" && best.desc.Layout != "" {
-                       return ti.D.Layout != layoutAll
+               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()
@@ -1920,17 +1920,6 @@ type weight struct {
        distance int
 }
 
-func (w weight) isEqualWeights(other weight) bool {
-       return w.w1 == other.w1 && w.w2 == other.w2 && w.w3 == other.w3
-}
-
-func isLayoutCustom(s string) bool {
-       if s == "" || isLayoutStandard(s) {
-               return false
-       }
-       return true
-}
-
 func isLayoutStandard(s string) bool {
        switch s {
        case layoutAll, layoutList, layoutSingle:
@@ -1940,6 +1929,10 @@ func isLayoutStandard(s string) bool {
        }
 }
 
+func (w weight) isEqualWeights(other weight) bool {
+       return w.w1 == other.w1 && w.w2 == other.w2 && w.w3 == other.w3
+}
+
 func configureSiteStorage(opts SiteOptions, watching bool) *storeSite {
        funcsv := make(map[string]reflect.Value)
 
index db7cb5084c8d026d09842d9646ba8422dfacf84f..4644c96395801cd13bebc75a41a555c17eca2c12 100644 (file)
@@ -510,7 +510,7 @@ baseof: {{ block "main" . }}{{ end }}
                q := tplimpl.TemplateQuery{
                        Path:     "/baz",
                        Category: tplimpl.CategoryLayout,
-                       Desc:     tplimpl.TemplateDescriptor{Kind: kinds.KindPage, Layout: "single", OutputFormat: "html"},
+                       Desc:     tplimpl.TemplateDescriptor{Kind: kinds.KindPage, LayoutFromTemplate: "single", OutputFormat: "html"},
                }
                for i := 0; i < b.N; i++ {
                        store.LookupPagesLayout(q)
@@ -521,7 +521,7 @@ baseof: {{ block "main" . }}{{ end }}
                q := tplimpl.TemplateQuery{
                        Path:     "/foo/bar",
                        Category: tplimpl.CategoryLayout,
-                       Desc:     tplimpl.TemplateDescriptor{Kind: kinds.KindPage, Layout: "single", OutputFormat: "html"},
+                       Desc:     tplimpl.TemplateDescriptor{Kind: kinds.KindPage, LayoutFromTemplate: "single", OutputFormat: "html"},
                }
                for i := 0; i < b.N; i++ {
                        store.LookupPagesLayout(q)
@@ -648,9 +648,6 @@ layout: mylayout
 
        b := hugolib.Test(t, files, hugolib.TestOptWarn())
 
-       // s := b.H.Sites[0].TemplateStore
-       // s.PrintDebug("", tplimpl.CategoryLayout, os.Stdout)
-
        b.AssertLogContains("! WARN")
 
        // Single pages.
@@ -1095,6 +1092,41 @@ s2.
        })
 }
 
+func TestStandardLayoutInFrontMatter13588(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+disableKinds = ['home','page','rss','sitemap','taxonomy','term']
+-- content/s1/_index.md --
+---
+title: s1
+---
+-- content/s2/_index.md --
+---
+title: s2
+layout: list
+---
+-- content/s3/_index.md --
+---
+title: s3
+layout: single
+---
+-- layouts/list.html --
+list.html
+-- layouts/section.html --
+section.html
+-- layouts/single.html --
+single.html
+`
+
+       b := hugolib.Test(t, files)
+
+       b.AssertFileContent("public/s1/index.html", "section.html")
+       b.AssertFileContent("public/s2/index.html", "list.html")   // fail
+       b.AssertFileContent("public/s3/index.html", "single.html") // fail
+}
+
 func TestSkipDotFiles(t *testing.T) {
        t.Parallel()