URI: 
       Revert the breaking change from 0.146.0 with dots in content filenames - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 496730840e388d7187149503f6e6e4648a8f65c1
   DIR parent 6d69dc88a46a002eda3f5b56ac73d29c6b9d0bc3
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Mon, 21 Apr 2025 16:15:21 +0200
       
       Revert the breaking change from 0.146.0 with dots in content filenames
       
       Closes #13632
       
       Diffstat:
         M common/paths/pathparser.go          |      96 +++++++++++++++++++------------
         M common/paths/pathparser_test.go     |      93 +++++++++++++++++++++++++++----
         M common/paths/paths_integration_tes… |      23 +++++++++++++++++++++++
         M hugolib/collections_test.go         |       2 ++
         M hugolib/content_render_hooks_test.… |       2 --
         M hugolib/site_test.go                |       2 +-
         M tpl/tplimpl/templatestore.go        |      24 ++++++++----------------
         M tpl/tplimpl/templatestore_integrat… |       2 +-
       
       8 files changed, 175 insertions(+), 69 deletions(-)
       ---
   DIR diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
       @@ -124,14 +124,15 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
                if p.posContainerHigh != -1 {
                        return
                }
       -        mayHaveLang := pp.LanguageIndex != nil
       +        mayHaveLang := p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
                mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
                mayHaveOutputFormat := component == files.ComponentFolderLayouts
       -        mayHaveKind := mayHaveOutputFormat
       +        mayHaveKind := p.posIdentifierKind == -1 && mayHaveOutputFormat
       +        mayHaveLayout := component == files.ComponentFolderLayouts
        
                var found bool
                var high int
       -        if len(p.identifiers) > 0 {
       +        if len(p.identifiersKnown) > 0 {
                        high = lastDot
                } else {
                        high = len(p.s)
       @@ -139,9 +140,9 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
                id := types.LowHigh[string]{Low: i + 1, High: high}
                sid := p.s[id.Low:id.High]
        
       -        if len(p.identifiers) == 0 {
       +        if len(p.identifiersKnown) == 0 {
                        // The first is always the extension.
       -                p.identifiers = append(p.identifiers, id)
       +                p.identifiersKnown = append(p.identifiersKnown, id)
                        found = true
        
                        // May also be the output format.
       @@ -164,8 +165,8 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
                                }
                                found = langFound
                                if langFound {
       -                                p.identifiers = append(p.identifiers, id)
       -                                p.posIdentifierLanguage = len(p.identifiers) - 1
       +                                p.identifiersKnown = append(p.identifiersKnown, id)
       +                                p.posIdentifierLanguage = len(p.identifiersKnown) - 1
        
                                }
                        }
       @@ -177,28 +178,33 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
                                // false positives on the form css.html.
                                if pp.IsOutputFormat(sid, p.Ext()) {
                                        found = true
       -                                p.identifiers = append(p.identifiers, id)
       -                                p.posIdentifierOutputFormat = len(p.identifiers) - 1
       +                                p.identifiersKnown = append(p.identifiersKnown, id)
       +                                p.posIdentifierOutputFormat = len(p.identifiersKnown) - 1
                                }
                        }
        
                        if !found && mayHaveKind {
                                if kinds.GetKindMain(sid) != "" {
                                        found = true
       -                                p.identifiers = append(p.identifiers, id)
       -                                p.posIdentifierKind = len(p.identifiers) - 1
       +                                p.identifiersKnown = append(p.identifiersKnown, id)
       +                                p.posIdentifierKind = len(p.identifiersKnown) - 1
                                }
                        }
        
                        if !found && sid == identifierBaseof {
                                found = true
       -                        p.identifiers = append(p.identifiers, id)
       -                        p.posIdentifierBaseof = len(p.identifiers) - 1
       +                        p.identifiersKnown = append(p.identifiersKnown, id)
       +                        p.posIdentifierBaseof = len(p.identifiersKnown) - 1
       +                }
       +
       +                if !found && mayHaveLayout {
       +                        p.identifiersKnown = append(p.identifiersKnown, id)
       +                        p.posIdentifierLayout = len(p.identifiersKnown) - 1
       +                        found = true
                        }
        
                        if !found {
       -                        p.identifiers = append(p.identifiers, id)
       -                        p.identifiersUnknown = append(p.identifiersUnknown, len(p.identifiers)-1)
       +                        p.identifiersUnknown = append(p.identifiersUnknown, id)
                        }
        
                }
       @@ -252,13 +258,13 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
                        }
                }
        
       -        if len(p.identifiers) > 0 {
       +        if len(p.identifiersKnown) > 0 {
                        isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
                        isContent := isContentComponent && pp.IsContentExt(p.Ext())
       -                id := p.identifiers[len(p.identifiers)-1]
       +                id := p.identifiersKnown[len(p.identifiersKnown)-1]
        
       -                if id.High > p.posContainerHigh {
       -                        b := p.s[p.posContainerHigh:id.High]
       +                if id.Low > p.posContainerHigh {
       +                        b := p.s[p.posContainerHigh : id.Low-1]
                                if isContent {
                                        switch b {
                                        case "index":
       @@ -294,6 +300,16 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
                        }
                }
        
       +        if p.pathType == TypeShortcode && p.posIdentifierLayout != -1 {
       +                // myshortcode or myshortcode.html, no layout.
       +                if len(p.identifiersKnown) <= 2 {
       +                        p.posIdentifierLayout = -1
       +                } else {
       +                        // First is always the name.
       +                        p.posIdentifierLayout--
       +                }
       +        }
       +
                return p, nil
        }
        
       @@ -350,13 +366,14 @@ type Path struct {
                component string
                pathType  Type
        
       -        identifiers []types.LowHigh[string]
       +        identifiersKnown   []types.LowHigh[string]
       +        identifiersUnknown []types.LowHigh[string]
        
                posIdentifierLanguage     int
                posIdentifierOutputFormat int
                posIdentifierKind         int
       +        posIdentifierLayout       int
                posIdentifierBaseof       int
       -        identifiersUnknown        []int
                disabled                  bool
        
                trimLeadingSlash bool
       @@ -388,10 +405,11 @@ func (p *Path) reset() {
                p.posSectionHigh = -1
                p.component = ""
                p.pathType = 0
       -        p.identifiers = p.identifiers[:0]
       +        p.identifiersKnown = p.identifiersKnown[:0]
                p.posIdentifierLanguage = -1
                p.posIdentifierOutputFormat = -1
                p.posIdentifierKind = -1
       +        p.posIdentifierLayout = -1
                p.posIdentifierBaseof = -1
                p.disabled = false
                p.trimLeadingSlash = false
       @@ -479,7 +497,7 @@ func (p *Path) Name() string {
        // Name returns the last element of path without any extension.
        func (p *Path) NameNoExt() string {
                if i := p.identifierIndex(0); i != -1 {
       -                return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
       +                return p.s[p.posContainerHigh : p.identifiersKnown[i].Low-1]
                }
                return p.s[p.posContainerHigh:]
        }
       @@ -491,7 +509,7 @@ func (p *Path) NameNoLang() string {
                        return p.Name()
                }
        
       -        return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
       +        return p.s[p.posContainerHigh:p.identifiersKnown[i].Low-1] + p.s[p.identifiersKnown[i].High:]
        }
        
        // BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
       @@ -510,15 +528,15 @@ func (p *Path) NameNoIdentifier() string {
        }
        
        func (p *Path) nameLowHigh() types.LowHigh[string] {
       -        if len(p.identifiers) > 0 {
       -                lastID := p.identifiers[len(p.identifiers)-1]
       +        if len(p.identifiersKnown) > 0 {
       +                lastID := p.identifiersKnown[len(p.identifiersKnown)-1]
                        if p.posContainerHigh == lastID.Low {
                                // The last identifier is the name.
                                return lastID
                        }
                        return types.LowHigh[string]{
                                Low:  p.posContainerHigh,
       -                        High: p.identifiers[len(p.identifiers)-1].Low - 1,
       +                        High: p.identifiersKnown[len(p.identifiersKnown)-1].Low - 1,
                        }
                }
                return types.LowHigh[string]{
       @@ -566,7 +584,7 @@ func (p *Path) PathNoIdentifier() string {
        
        // PathBeforeLangAndOutputFormatAndExt returns the path up to the first identifier that is not a language or output format.
        func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
       -        if len(p.identifiers) == 0 {
       +        if len(p.identifiersKnown) == 0 {
                        return p.norm(p.s)
                }
                i := p.identifierIndex(0)
       @@ -582,7 +600,7 @@ func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
                        return p.norm(p.s)
                }
        
       -        id := p.identifiers[i]
       +        id := p.identifiersKnown[i]
                return p.norm(p.s[:id.Low-1])
        }
        
       @@ -633,11 +651,11 @@ func (p *Path) BaseNoLeadingSlash() string {
        }
        
        func (p *Path) base(preserveExt, isBundle bool) string {
       -        if len(p.identifiers) == 0 {
       +        if len(p.identifiersKnown) == 0 {
                        return p.norm(p.s)
                }
        
       -        if preserveExt && len(p.identifiers) == 1 {
       +        if preserveExt && len(p.identifiersKnown) == 1 {
                        // Preserve extension.
                        return p.norm(p.s)
                }
       @@ -659,7 +677,7 @@ func (p *Path) base(preserveExt, isBundle bool) string {
                }
        
                // For txt files etc. we want to preserve the extension.
       -        id := p.identifiers[0]
       +        id := p.identifiersKnown[0]
        
                return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
        }
       @@ -676,6 +694,10 @@ func (p *Path) Kind() string {
                return p.identifierAsString(p.posIdentifierKind)
        }
        
       +func (p *Path) Layout() string {
       +        return p.identifierAsString(p.posIdentifierLayout)
       +}
       +
        func (p *Path) Lang() string {
                return p.identifierAsString(p.posIdentifierLanguage)
        }
       @@ -689,8 +711,8 @@ func (p *Path) Disabled() bool {
        }
        
        func (p *Path) Identifiers() []string {
       -        ids := make([]string, len(p.identifiers))
       -        for i, id := range p.identifiers {
       +        ids := make([]string, len(p.identifiersKnown))
       +        for i, id := range p.identifiersKnown {
                        ids[i] = p.s[id.Low:id.High]
                }
                return ids
       @@ -699,7 +721,7 @@ func (p *Path) Identifiers() []string {
        func (p *Path) IdentifiersUnknown() []string {
                ids := make([]string, len(p.identifiersUnknown))
                for i, id := range p.identifiersUnknown {
       -                ids[i] = p.s[p.identifiers[id].Low:p.identifiers[id].High]
       +                ids[i] = p.s[id.Low:id.High]
                }
                return ids
        }
       @@ -735,12 +757,12 @@ func (p *Path) identifierAsString(i int) string {
                        return ""
                }
        
       -        id := p.identifiers[i]
       +        id := p.identifiersKnown[i]
                return p.s[id.Low:id.High]
        }
        
        func (p *Path) identifierIndex(i int) int {
       -        if i < 0 || i >= len(p.identifiers) {
       +        if i < 0 || i >= len(p.identifiersKnown) {
                        return -1
                }
                return i
   DIR diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
       @@ -171,22 +171,25 @@ func TestParse(t *testing.T) {
                                "/a/b.a.b.no.txt",
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
       -                                c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
       +                                c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
                                        c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
       -                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "b", "a", "b"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
                                        c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
       -                                c.Assert(p.Base(), qt.Equals, "/a/b.txt")
       -                                c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.txt")
       +                                c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
       +                                c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
                                        c.Assert(p.Path(), qt.Equals, "/a/b.a.b.no.txt")
       -                                c.Assert(p.PathNoLang(), qt.Equals, "/a/b.txt")
       +                                c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
                                        c.Assert(p.Ext(), qt.Equals, "txt")
       -                                c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b")
       +                                c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
                                },
                        },
                        {
                                "Home branch cundle",
                                "/_index.md",
                                func(c *qt.C, p *Path) {
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
       +                                c.Assert(p.IsBranchBundle(), qt.IsTrue)
       +                                c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.Base(), qt.Equals, "/")
                                        c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo")
                                        c.Assert(p.Path(), qt.Equals, "/_index.md")
       @@ -206,7 +209,8 @@ func TestParse(t *testing.T) {
                                        c.Assert(p.ContainerDir(), qt.Equals, "")
                                        c.Assert(p.Dir(), qt.Equals, "/a")
                                        c.Assert(p.Ext(), qt.Equals, "md")
       -                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "index"})
       +                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"index"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
                                        c.Assert(p.IsBranchBundle(), qt.IsFalse)
                                        c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsTrue)
       @@ -228,7 +232,7 @@ func TestParse(t *testing.T) {
                                        c.Assert(p.ContainerDir(), qt.Equals, "/a")
                                        c.Assert(p.Dir(), qt.Equals, "/a/b")
                                        c.Assert(p.Ext(), qt.Equals, "md")
       -                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "index"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
                                        c.Assert(p.IsBranchBundle(), qt.IsFalse)
                                        c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsTrue)
       @@ -250,7 +254,7 @@ func TestParse(t *testing.T) {
                                        c.Assert(p.Container(), qt.Equals, "b")
                                        c.Assert(p.ContainerDir(), qt.Equals, "/a")
                                        c.Assert(p.Ext(), qt.Equals, "md")
       -                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "_index"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
                                        c.Assert(p.IsBranchBundle(), qt.IsTrue)
                                        c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsFalse)
       @@ -289,7 +293,7 @@ func TestParse(t *testing.T) {
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Base(), qt.Equals, "/a/b/index.txt")
                                        c.Assert(p.Ext(), qt.Equals, "txt")
       -                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "index"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
                                        c.Assert(p.IsLeafBundle(), qt.IsFalse)
                                        c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
                                },
       @@ -372,7 +376,7 @@ func TestParse(t *testing.T) {
                }
                for _, test := range tests {
                        c.Run(test.name, func(c *qt.C) {
       -                        if test.name != "Basic Markdown file" {
       +                        if test.name != "Home branch cundle" {
                                        // return
                                }
                                test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
       @@ -401,11 +405,59 @@ func TestParseLayouts(t *testing.T) {
                                "/list.no.html",
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "list"})
       +                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
                                        c.Assert(p.Base(), qt.Equals, "/list.html")
                                        c.Assert(p.Lang(), qt.Equals, "no")
                                },
                        },
                        {
       +                        "Kind",
       +                        "/section.no.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section"})
       +                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
       +                                c.Assert(p.Base(), qt.Equals, "/section.html")
       +                                c.Assert(p.Lang(), qt.Equals, "no")
       +                        },
       +                },
       +                {
       +                        "Layout",
       +                        "/list.section.no.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Layout(), qt.Equals, "list")
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list"})
       +                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
       +                                c.Assert(p.Base(), qt.Equals, "/list.html")
       +                                c.Assert(p.Lang(), qt.Equals, "no")
       +                        },
       +                },
       +                {
       +                        "Layout multiple",
       +                        "/maylayout.list.section.no.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Layout(), qt.Equals, "maylayout")
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list", "maylayout"})
       +                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
       +                                c.Assert(p.Base(), qt.Equals, "/maylayout.html")
       +                                c.Assert(p.Lang(), qt.Equals, "no")
       +                        },
       +                },
       +                {
       +                        "Layout shortcode",
       +                        "/_shortcodes/myshort.list.no.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Layout(), qt.Equals, "list")
       +                        },
       +                },
       +                {
       +                        "Layout baseof",
       +                        "/baseof.list.no.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Layout(), qt.Equals, "list")
       +                        },
       +                },
       +                {
                                "Lang and output format",
                                "/list.no.amp.not.html",
                                func(c *qt.C, p *Path) {
       @@ -430,6 +482,20 @@ func TestParseLayouts(t *testing.T) {
                                },
                        },
                        {
       +                        "Shortcode with layout",
       +                        "/_shortcodes/myshortcode.list.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Base(), qt.Equals, "/_shortcodes/myshortcode.html")
       +                                c.Assert(p.Type(), qt.Equals, TypeShortcode)
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list", "myshortcode"})
       +                                c.Assert(p.PathNoIdentifier(), qt.Equals, "/_shortcodes/myshortcode")
       +                                c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/_shortcodes/myshortcode.list")
       +                                c.Assert(p.Lang(), qt.Equals, "")
       +                                c.Assert(p.Kind(), qt.Equals, "")
       +                                c.Assert(p.OutputFormat(), qt.Equals, "html")
       +                        },
       +                },
       +                {
                                "Sub dir",
                                "/pages/home.html",
                                func(c *qt.C, p *Path) {
       @@ -445,7 +511,7 @@ func TestParseLayouts(t *testing.T) {
                                "/pages/baseof.list.section.fr.amp.html",
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "amp", "fr", "section", "list", "baseof"})
       -                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"list"})
       +                                c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
                                        c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
                                        c.Assert(p.Lang(), qt.Equals, "fr")
                                        c.Assert(p.OutputFormat(), qt.Equals, "amp")
       @@ -501,6 +567,9 @@ func TestParseLayouts(t *testing.T) {
        
                for _, test := range tests {
                        c.Run(test.name, func(c *qt.C) {
       +                        if test.name != "Baseof" {
       +                                // return
       +                        }
                                test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
                        })
                }
   DIR diff --git a/common/paths/paths_integration_test.go b/common/paths/paths_integration_test.go
       @@ -78,3 +78,26 @@ disablePathToLower = true
                b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
                b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
        }
       +
       +func TestIssue13596(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableKinds = ['home','rss','section','sitemap','taxonomy','term']
       +-- content/p1/index.md --
       +---
       +title: p1
       +---
       +-- content/p1/a.1.txt --
       +-- content/p1/a.2.txt --
       +-- layouts/all.html --
       +{{ range .Resources.Match "*" }}{{ .Name }}|{{ end }}
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p1/index.html", "a.1.txt|a.2.txt|")
       +        b.AssertFileExists("public/p1/a.1.txt", true)
       +        b.AssertFileExists("public/p1/a.2.txt", true) // fails
       +}
   DIR diff --git a/hugolib/collections_test.go b/hugolib/collections_test.go
       @@ -39,6 +39,8 @@ title: "Page"
        `)
                b.CreateSites().Build(BuildCfg{})
        
       +        // b.H.TemplateStore.PrintDebug("", tplimpl.CategoryLayout, os.Stdout)
       +
                c.Assert(len(b.H.Sites), qt.Equals, 1)
                c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 2)
        
   DIR diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
       @@ -464,8 +464,6 @@ title: "Home"
        `
                b := Test(t, files)
        
       -        // b.DebugPrint("", tplimpl.CategoryShortcode)
       -
                b.AssertFileContentExact("public/index.xml", "My shortcode XML.")
                b.AssertFileContentExact("public/index.html", "My shortcode HTML.")
                s := b.H.Sites[0]
   DIR diff --git a/hugolib/site_test.go b/hugolib/site_test.go
       @@ -978,7 +978,7 @@ func TestRefLinking(t *testing.T) {
                        {".", "", true, "/level2/level3/"},
                        {"./", "", true, "/level2/level3/"},
        
       -                {"embedded.dot.md", "", true, "/level2/level3/embedded/"},
       +                {"embedded.dot.md", "", true, "/level2/level3/embedded.dot/"},
        
                        // test empty link, as well as fragment only link
                        {"", "", true, ""},
   DIR diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
       @@ -654,7 +654,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.LayoutFromTemplate, s)
       +                ts := fmt.Sprintf("kind: %q layout: %q lang: %q content: %.30s", vv.D.Kind, vv.D.LayoutFromTemplate, vv.D.Lang, 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) {
       @@ -1126,7 +1126,7 @@ func (s *TemplateStore) insertTemplate2(
        
                if !replace {
                        if v, found := m[nk]; found {
       -                        if len(pi.IdentifiersUnknown()) >= len(v.PathInfo.IdentifiersUnknown()) {
       +                        if len(pi.Identifiers()) >= len(v.PathInfo.Identifiers()) {
                                        // e.g. /pages/home.foo.html and  /pages/home.html where foo may be a valid language name in another site.
                                        return nil, nil
                                }
       @@ -1261,7 +1261,10 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
                                )
        
                                base := piOrig.PathBeforeLangAndOutputFormatAndExt()
       -                        identifiers := pi.IdentifiersUnknown()
       +                        identifiers := []string{}
       +                        if pi.Layout() != "" {
       +                                identifiers = append(identifiers, pi.Layout())
       +                        }
                                if pi.Kind() != "" {
                                        identifiers = append(identifiers, pi.Kind())
                                }
       @@ -1576,24 +1579,12 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
                outputFormat, mediaType := s.resolveOutputFormatAndOrMediaType(p.OutputFormat(), p.Ext())
                nameNoIdentifier := p.NameNoIdentifier()
        
       -        var layout string
       -        unknownids := p.IdentifiersUnknown()
       -        if p.Type() == paths.TypeShortcode {
       -                if len(unknownids) > 1 {
       -                        // The name is the last identifier.
       -                        layout = unknownids[len(unknownids)-2]
       -                }
       -        } else if len(unknownids) > 0 {
       -                // Pick the last, closest to the base name.
       -                layout = unknownids[len(unknownids)-1]
       -        }
       -
                d := TemplateDescriptor{
                        Lang:               p.Lang(),
                        OutputFormat:       p.OutputFormat(),
                        MediaType:          mediaType.Type,
                        Kind:               p.Kind(),
       -                LayoutFromTemplate: layout,
       +                LayoutFromTemplate: p.Layout(),
                        IsPlainText:        outputFormat.IsPlainText,
                }
        
       @@ -1633,6 +1624,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
        
                if category == CategoryShortcode {
                        k1 = p.PathNoIdentifier()
       +
                        parts := strings.Split(k1, "/"+containerShortcodes+"/")
                        k1 = parts[0]
                        if len(parts) > 1 {
   DIR diff --git a/tpl/tplimpl/templatestore_integration_test.go b/tpl/tplimpl/templatestore_integration_test.go
       @@ -1056,7 +1056,7 @@ All.
        
                b := hugolib.Test(t, files, hugolib.TestOptWarn())
        
       -        b.AssertLogContains("Duplicate content path")
       +        b.AssertLogContains("! Duplicate content path")
        }
        
        // Issue #13577.