Reimplement and simplify Hugo's template system - hugo - [fork] hugo port for 9front
 (HTM) git clone git@git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 83cfdd78ca6469e6d7265323d9fad1448880e559
 (DIR) parent 812ea0b325b084eacee7492390b4f9d1aba5b6cf
 (HTM) Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Sun,  6 Apr 2025 19:55:35 +0200
       
       Reimplement and simplify Hugo's template system
       
       See #13541 for details.
       
       Fixes #13545
       Fixes #13515
       Closes #7964
       Closes #13365
       Closes #12988
       Closes #4891
       
       Diffstat:
         M commands/server.go                  |      16 ++++++++--------
         M common/constants/constants.go       |       1 +
         M common/hstrings/strings.go          |      10 +++++-----
         M common/maps/cache.go                |      19 +++++++++++++++++++
         M common/maps/ordered.go              |      12 +++++++++++-
         M common/paths/pathparser.go          |     350 +++++++++++++++++++++++--------
         M common/paths/pathparser_test.go     |     168 ++++++++++++++++++++++++++++---
         D common/paths/pathtype_string.go     |      27 ---------------------------
         A common/paths/type_string.go         |      32 +++++++++++++++++++++++++++++++
         M common/types/types.go               |      10 ++++++++++
         M config/allconfig/allconfig.go       |      19 ++++++++++++++++++-
         M create/content.go                   |       4 ++--
         M deps/deps.go                        |      47 +++++++++++++------------------
         M hugolib/alias.go                    |      39 +++++++++++++++++++------------
         M hugolib/alias_test.go               |      21 +++++++++++++++++----
         M hugolib/content_factory.go          |       4 ++--
         M hugolib/content_map.go              |       9 ++++-----
         M hugolib/content_map_page.go         |      18 ++++++++++--------
         M hugolib/content_map_test.go         |      23 ++++++++++++++++-------
         M hugolib/content_render_hooks_test.… |      93 +++++++++++++++++++++++++++++++
         M hugolib/doctree/simpletree.go       |     182 ++++++++++++++++++++++++++++---
         M hugolib/doctree/support.go          |      10 +++-------
         M hugolib/doctree/treeshifttree.go    |      18 ++++++++++++++----
         M hugolib/hugo_sites.go               |       2 --
         M hugolib/hugo_sites_build.go         |      56 ++++++++++++++++++++++---------
         M hugolib/hugo_smoke_test.go          |      13 +++++++------
         M hugolib/integrationtest_builder.go  |      18 +++++++++++++++---
         M hugolib/page.go                     |      81 ++++++++++++++-----------------
         M hugolib/page__common.go             |       4 ----
         M hugolib/page__content.go            |      25 +++++++++++--------------
         M hugolib/page__meta.go               |      11 +++++++----
         M hugolib/page__per_output.go         |     153 +++++++++++++++----------------
         M hugolib/pages_capture.go            |      17 ++++++++++-------
         M hugolib/pagesfromdata/pagesfromgot… |       8 ++++----
         M hugolib/pagesfromdata/pagesfromgot… |       3 ++-
         M hugolib/paginator_test.go           |      21 ++++++++++++++-------
         M hugolib/rebuild_test.go             |     147 +++++++++++++++++++++++++++----
         M hugolib/shortcode.go                |      94 +++++++++++++++++--------------
         M hugolib/shortcode_test.go           |      28 ++++++++++++++--------------
         M hugolib/site.go                     |      76 ++++++++++++++++++++++++-------
         M hugolib/site_output.go              |       3 ++-
         M hugolib/site_output_test.go         |       3 ++-
         M hugolib/site_render.go              |       8 ++++----
         M hugolib/site_test.go                |       3 +--
         M hugolib/taxonomy_test.go            |       2 ++
         M hugolib/template_test.go            |      57 ++++++++++++++++++-------------
         M identity/identity.go                |       4 ++++
         M internal/js/esbuild/batch.go        |       8 ++++----
         M langs/i18n/i18n_test.go             |       3 ---
         M markup/goldmark/codeblocks/codeblo… |       6 +++---
         M media/builtin.go                    |       3 +++
         M media/config.go                     |       5 ++++-
         M media/config_test.go                |       2 +-
         M media/mediaType.go                  |      10 +++++-----
         M output/docshelper.go                |      86 ++-----------------------------
         D output/layouts/layout.go            |     336 -------------------------------
         D output/layouts/layout_test.go       |     982 -------------------------------
         M output/outputFormat.go              |      26 ++++++++++++++++++++++++--
         M output/outputFormat_test.go         |       4 ++--
         M resources/kinds/kinds.go            |       1 +
         M resources/page/page.go              |       4 ++--
         M resources/page/page_paths.go        |       2 +-
         M resources/page/pages_related.go     |       2 +-
         M resources/page/testhelpers_test.go  |       2 +-
         M resources/resource_spec.go          |       3 ---
         M resources/resource_transformers/te… |      15 +++++++--------
         M testscripts/commands/hugo_printunu… |       2 +-
         M tpl/collections/apply.go            |       4 +---
         D tpl/collections/apply_test.go       |     104 -------------------------------
         M tpl/internal/go_templates/htmltemp… |      27 +++++++++++++++++++++++++++
         M tpl/internal/go_templates/htmltemp… |       2 +-
         M tpl/internal/go_templates/texttemp… |       2 +-
         M tpl/math/init.go                    |       2 +-
         M tpl/math/math.go                    |      16 +++++++++-------
         M tpl/math/math_test.go               |      48 ++++++++++++++++----------------
         M tpl/partials/partials.go            |      50 +++++++++++--------------------
         M tpl/partials/partials_integration_… |      10 +++++-----
         M tpl/template.go                     |     134 ++-----------------------------
         D tpl/template_info.go                |      57 -------------------------------
         M tpl/template_test.go                |      14 +-------------
         M tpl/templates/defer_integration_te… |      75 +++++++++++++++++++++++++++++++
         M tpl/templates/templates.go          |       4 ++--
         A tpl/tplimpl/category_string.go      |      30 ++++++++++++++++++++++++++++++
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/par… |       0 
         A tpl/tplimpl/embedded/templates/_pa… |      23 +++++++++++++++++++++++
         R tpl/tplimpl/embedded/templates/goo… |       0 
         R tpl/tplimpl/embedded/templates/ope… |       0 
         A tpl/tplimpl/embedded/templates/_pa… |     154 +++++++++++++++++++++++++++++++
         R tpl/tplimpl/embedded/templates/sch… |       0 
         R tpl/tplimpl/embedded/templates/twi… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         R tpl/tplimpl/embedded/templates/sho… |       0 
         D tpl/tplimpl/embedded/templates/dis… |      23 -----------------------
         D tpl/tplimpl/embedded/templates/pag… |     154 -------------------------------
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/_de… |       0 
         R tpl/tplimpl/embedded/templates/_de… |       0 
         A tpl/tplimpl/legacy.go               |     130 +++++++++++++++++++++++++++++++
         M tpl/tplimpl/render_hook_integratio… |       2 +-
         D tpl/tplimpl/shortcodes.go           |     153 -------------------------------
         D tpl/tplimpl/shortcodes_test.go      |      91 -------------------------------
         A tpl/tplimpl/subcategory_string.go   |      25 +++++++++++++++++++++++++
         D tpl/tplimpl/template.go             |    1235 -------------------------------
         D tpl/tplimpl/templateFuncster.go     |      14 --------------
         D tpl/tplimpl/templateProvider.go     |      51 -------------------------------
         D tpl/tplimpl/template_ast_transform… |     381 -------------------------------
         D tpl/tplimpl/template_ast_transform… |     161 -------------------------------
         D tpl/tplimpl/template_errors.go      |      64 -------------------------------
         M tpl/tplimpl/template_funcs.go       |     127 +------------------------------
         M tpl/tplimpl/template_funcs_test.go  |       2 +-
         A tpl/tplimpl/template_info.go        |      46 +++++++++++++++++++++++++++++++
         D tpl/tplimpl/template_test.go        |      40 -------------------------------
         A tpl/tplimpl/templatedescriptor.go   |     225 +++++++++++++++++++++++++++++++
         A tpl/tplimpl/templatedescriptor_tes… |     104 +++++++++++++++++++++++++++++++
         A tpl/tplimpl/templates.go            |     331 +++++++++++++++++++++++++++++++
         A tpl/tplimpl/templatestore.go        |    1854 +++++++++++++++++++++++++++++++
         A tpl/tplimpl/templatestore_integrat… |     842 +++++++++++++++++++++++++++++++
         A tpl/tplimpl/templatetransform.go    |     349 +++++++++++++++++++++++++++++++
         M tpl/tplimpl/tplimpl_integration_te… |      59 ++-----------------------------
         A tpl/tplimplinit/tplimplinit.go      |      96 +++++++++++++++++++++++++++++++
       
       142 files changed, 5866 insertions(+), 4920 deletions(-)
       ---
 (DIR) diff --git a/commands/server.go b/commands/server.go
       @@ -23,6 +23,7 @@ import (
                "errors"
                "fmt"
                "io"
       +        "maps"
                "net"
                "net/http"
                _ "net/http/pprof"
       @@ -48,6 +49,7 @@ import (
                "github.com/fsnotify/fsnotify"
                "github.com/gohugoio/hugo/common/herrors"
                "github.com/gohugoio/hugo/common/hugo"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
        
                "github.com/gohugoio/hugo/common/types"
                "github.com/gohugoio/hugo/common/urls"
       @@ -57,7 +59,6 @@ import (
                "github.com/gohugoio/hugo/hugolib"
                "github.com/gohugoio/hugo/hugolib/filesystems"
                "github.com/gohugoio/hugo/livereload"
       -        "github.com/gohugoio/hugo/tpl"
                "github.com/gohugoio/hugo/transform"
                "github.com/gohugoio/hugo/transform/livereloadinject"
                "github.com/spf13/afero"
       @@ -65,7 +66,6 @@ import (
                "github.com/spf13/fsync"
                "golang.org/x/sync/errgroup"
                "golang.org/x/sync/semaphore"
       -        "maps"
        )
        
        var (
       @@ -897,16 +897,16 @@ func (c *serverCommand) serve() error {
                // To allow the en user to change the error template while the server is running, we use
                // the freshest template we can provide.
                var (
       -                errTempl     tpl.Template
       -                templHandler tpl.TemplateHandler
       +                errTempl     *tplimpl.TemplInfo
       +                templHandler *tplimpl.TemplateStore
                )
       -        getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (tpl.Template, tpl.TemplateHandler) {
       +        getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (*tplimpl.TemplInfo, *tplimpl.TemplateStore) {
                        if h == nil {
                                return errTempl, templHandler
                        }
       -                templHandler := h.Tmpl()
       -                errTempl, found := templHandler.Lookup("_server/error.html")
       -                if !found {
       +                templHandler := h.GetTemplateStore()
       +                errTempl := templHandler.LookupByPath("/_server/error.html")
       +                if errTempl == nil {
                                panic("template server/error.html not found")
                        }
                        return errTempl, templHandler
 (DIR) diff --git a/common/constants/constants.go b/common/constants/constants.go
       @@ -23,6 +23,7 @@ const (
                WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
                WarnRenderShortcodesInHTML     = "warning-rendershortcodes-in-html"
                WarnGoldmarkRawHTML            = "warning-goldmark-raw-html"
       +        WarnPartialSuperfluousPrefix   = "warning-partial-superfluous-prefix"
        )
        
        // Field/method names with special meaning.
 (DIR) diff --git a/common/hstrings/strings.go b/common/hstrings/strings.go
       @@ -16,11 +16,11 @@ package hstrings
        import (
                "fmt"
                "regexp"
       +        "slices"
                "strings"
                "sync"
        
                "github.com/gohugoio/hugo/compare"
       -        "slices"
        )
        
        var _ compare.Eqer = StringEqualFold("")
       @@ -128,7 +128,7 @@ func ToString(v any) (string, bool) {
                return "", false
        }
        
       -type Tuple struct {
       -        First  string
       -        Second string
       -}
       +type (
       +        Strings2 [2]string
       +        Strings3 [3]string
       +)
 (DIR) diff --git a/common/maps/cache.go b/common/maps/cache.go
       @@ -69,6 +69,14 @@ func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
                return v, nil
        }
        
       +// Contains returns whether the given key exists in the cache.
       +func (c *Cache[K, T]) Contains(key K) bool {
       +        c.RLock()
       +        _, found := c.m[key]
       +        c.RUnlock()
       +        return found
       +}
       +
        // InitAndGet initializes the cache if not already done and returns the value for the given key.
        // The init state will be reset on Reset or Drain.
        func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
       @@ -108,6 +116,17 @@ func (c *Cache[K, T]) Set(key K, value T) {
                c.Unlock()
        }
        
       +// SetIfAbsent sets the given key to the given value if the key does not already exist in the cache.
       +func (c *Cache[K, T]) SetIfAbsent(key K, value T) {
       +        c.RLock()
       +        if _, found := c.get(key); !found {
       +                c.RUnlock()
       +                c.Set(key, value)
       +        } else {
       +                c.RUnlock()
       +        }
       +}
       +
        func (c *Cache[K, T]) set(key K, value T) {
                c.m[key] = value
        }
 (DIR) diff --git a/common/maps/ordered.go b/common/maps/ordered.go
       @@ -14,8 +14,9 @@
        package maps
        
        import (
       -        "github.com/gohugoio/hugo/common/hashing"
                "slices"
       +
       +        "github.com/gohugoio/hugo/common/hashing"
        )
        
        // Ordered is a map that can be iterated in the order of insertion.
       @@ -57,6 +58,15 @@ func (m *Ordered[K, T]) Get(key K) (T, bool) {
                return value, found
        }
        
       +// Has returns whether the given key exists in the map.
       +func (m *Ordered[K, T]) Has(key K) bool {
       +        if m == nil {
       +                return false
       +        }
       +        _, found := m.values[key]
       +        return found
       +}
       +
        // Delete deletes the value for the given key.
        func (m *Ordered[K, T]) Delete(key K) {
                if m == nil {
 (DIR) diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
       @@ -23,6 +23,11 @@ import (
                "github.com/gohugoio/hugo/common/types"
                "github.com/gohugoio/hugo/hugofs/files"
                "github.com/gohugoio/hugo/identity"
       +        "github.com/gohugoio/hugo/resources/kinds"
       +)
       +
       +const (
       +        identifierBaseof = "baseof"
        )
        
        // PathParser parses a path into a Path.
       @@ -33,6 +38,10 @@ type PathParser struct {
                // Reports whether the given language is disabled.
                IsLangDisabled func(string) bool
        
       +        // IsOutputFormat reports whether the given name is a valid output format.
       +        // The second argument is optional.
       +        IsOutputFormat func(name, ext string) bool
       +
                // Reports whether the given ext is a content file.
                IsContentExt func(string) bool
        }
       @@ -83,13 +92,10 @@ func (pp *PathParser) Parse(c, s string) *Path {
        }
        
        func (pp *PathParser) newPath(component string) *Path {
       -        return &Path{
       -                component:             component,
       -                posContainerLow:       -1,
       -                posContainerHigh:      -1,
       -                posSectionHigh:        -1,
       -                posIdentifierLanguage: -1,
       -        }
       +        p := &Path{}
       +        p.reset()
       +        p.component = component
       +        return p
        }
        
        func (pp *PathParser) parse(component, s string) (*Path, error) {
       @@ -114,10 +120,91 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
                return p, nil
        }
        
       -func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
       -        hasLang := pp.LanguageIndex != nil
       -        hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
       +func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot int) {
       +        if p.posContainerHigh != -1 {
       +                return
       +        }
       +        mayHaveLang := pp.LanguageIndex != nil
       +        mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
       +        mayHaveOutputFormat := component == files.ComponentFolderLayouts
       +        mayHaveKind := mayHaveOutputFormat
       +
       +        var found bool
       +        var high int
       +        if len(p.identifiers) > 0 {
       +                high = lastDot
       +        } else {
       +                high = len(p.s)
       +        }
       +        id := types.LowHigh[string]{Low: i + 1, High: high}
       +        sid := p.s[id.Low:id.High]
       +
       +        if len(p.identifiers) == 0 {
       +                // The first is always the extension.
       +                p.identifiers = append(p.identifiers, id)
       +                found = true
       +
       +                // May also be the output format.
       +                if mayHaveOutputFormat && pp.IsOutputFormat(sid, "") {
       +                        p.posIdentifierOutputFormat = 0
       +                }
       +        } else {
       +
       +                var langFound bool
       +
       +                if mayHaveLang {
       +                        var disabled bool
       +                        _, langFound = pp.LanguageIndex[sid]
       +                        if !langFound {
       +                                disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(sid)
       +                                if disabled {
       +                                        p.disabled = true
       +                                        langFound = true
       +                                }
       +                        }
       +                        found = langFound
       +                        if langFound {
       +                                p.identifiers = append(p.identifiers, id)
       +                                p.posIdentifierLanguage = len(p.identifiers) - 1
       +
       +                        }
       +                }
       +
       +                if !found && mayHaveOutputFormat {
       +                        // At this point we may already have resolved an output format,
       +                        // but we need to keep looking for a more specific one, e.g. amp before html.
       +                        // Use both name and extension to prevent
       +                        // 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
       +                        }
       +                }
       +
       +                if !found && mayHaveKind {
       +                        if kinds.GetKindMain(sid) != "" {
       +                                found = true
       +                                p.identifiers = append(p.identifiers, id)
       +                                p.posIdentifierKind = len(p.identifiers) - 1
       +                        }
       +                }
       +
       +                if !found && sid == identifierBaseof {
       +                        found = true
       +                        p.identifiers = append(p.identifiers, id)
       +                        p.posIdentifierBaseof = len(p.identifiers) - 1
       +                }
       +
       +                if !found {
       +                        p.identifiers = append(p.identifiers, id)
       +                        p.identifiersUnknown = append(p.identifiersUnknown, len(p.identifiers)-1)
       +                }
       +
       +        }
       +}
        
       +func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
                if runtime.GOOS == "windows" {
                        s = path.Clean(filepath.ToSlash(s))
                        if s == "." {
       @@ -140,46 +227,21 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
        
                p.s = s
                slashCount := 0
       +        lastDot := 0
        
                for i := len(s) - 1; i >= 0; i-- {
                        c := s[i]
        
                        switch c {
                        case '.':
       -                        if p.posContainerHigh == -1 {
       -                                var high int
       -                                if len(p.identifiers) > 0 {
       -                                        high = p.identifiers[len(p.identifiers)-1].Low - 1
       -                                } else {
       -                                        high = len(p.s)
       -                                }
       -                                id := types.LowHigh[string]{Low: i + 1, High: high}
       -                                if len(p.identifiers) == 0 {
       -                                        p.identifiers = append(p.identifiers, id)
       -                                } else if len(p.identifiers) == 1 {
       -                                        // Check for a valid language.
       -                                        s := p.s[id.Low:id.High]
       -
       -                                        if hasLang {
       -                                                var disabled bool
       -                                                _, langFound := pp.LanguageIndex[s]
       -                                                if !langFound {
       -                                                        disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(s)
       -                                                        if disabled {
       -                                                                p.disabled = true
       -                                                                langFound = true
       -                                                        }
       -                                                }
       -                                                if langFound {
       -                                                        p.posIdentifierLanguage = 1
       -                                                        p.identifiers = append(p.identifiers, id)
       -                                                }
       -                                        }
       -                                }
       -                        }
       +                        pp.parseIdentifier(component, s, p, i, lastDot)
       +                        lastDot = i
                        case '/':
                                slashCount++
                                if p.posContainerHigh == -1 {
       +                                if lastDot > 0 {
       +                                        pp.parseIdentifier(component, s, p, i, lastDot)
       +                                }
                                        p.posContainerHigh = i + 1
                                } else if p.posContainerLow == -1 {
                                        p.posContainerLow = i + 1
       @@ -194,22 +256,41 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
                        isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
                        isContent := isContentComponent && pp.IsContentExt(p.Ext())
                        id := p.identifiers[len(p.identifiers)-1]
       -                b := p.s[p.posContainerHigh : id.Low-1]
       -                if isContent {
       -                        switch b {
       -                        case "index":
       -                                p.bundleType = PathTypeLeaf
       -                        case "_index":
       -                                p.bundleType = PathTypeBranch
       -                        default:
       -                                p.bundleType = PathTypeContentSingle
       +
       +                if id.High > p.posContainerHigh {
       +                        b := p.s[p.posContainerHigh:id.High]
       +                        if isContent {
       +                                switch b {
       +                                case "index":
       +                                        p.pathType = TypeLeaf
       +                                case "_index":
       +                                        p.pathType = TypeBranch
       +                                default:
       +                                        p.pathType = TypeContentSingle
       +                                }
       +
       +                                if slashCount == 2 && p.IsLeafBundle() {
       +                                        p.posSectionHigh = 0
       +                                }
       +                        } else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
       +                                p.pathType = TypeContentData
                                }
       +                }
       +
       +        }
        
       -                        if slashCount == 2 && p.IsLeafBundle() {
       -                                p.posSectionHigh = 0
       +        if component == files.ComponentFolderLayouts {
       +                if p.posIdentifierBaseof != -1 {
       +                        p.pathType = TypeBaseof
       +                } else {
       +                        pth := p.Path()
       +                        if strings.Contains(pth, "/_shortcodes/") {
       +                                p.pathType = TypeShortcode
       +                        } else if strings.Contains(pth, "/_markup/") {
       +                                p.pathType = TypeMarkup
       +                        } else if strings.HasPrefix(pth, "/_partials/") {
       +                                p.pathType = TypePartial
                                }
       -                } else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
       -                        p.bundleType = PathTypeContentData
                        }
                }
        
       @@ -218,35 +299,44 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
        
        func ModifyPathBundleTypeResource(p *Path) {
                if p.IsContent() {
       -                p.bundleType = PathTypeContentResource
       +                p.pathType = TypeContentResource
                } else {
       -                p.bundleType = PathTypeFile
       +                p.pathType = TypeFile
                }
        }
        
       -type PathType int
       +//go:generate stringer -type Type
       +
       +type Type int
        
        const (
       +
                // A generic resource, e.g. a JSON file.
       -        PathTypeFile PathType = iota
       +        TypeFile Type = iota
        
                // All below are content files.
                // A resource of a content type with front matter.
       -        PathTypeContentResource
       +        TypeContentResource
        
                // E.g. /blog/my-post.md
       -        PathTypeContentSingle
       +        TypeContentSingle
        
                // All below are bundled content files.
        
                // Leaf bundles, e.g. /blog/my-post/index.md
       -        PathTypeLeaf
       +        TypeLeaf
        
                // Branch bundles, e.g. /blog/_index.md
       -        PathTypeBranch
       +        TypeBranch
        
                // Content data file, _content.gotmpl.
       -        PathTypeContentData
       +        TypeContentData
       +
       +        // Layout types.
       +        TypeMarkup
       +        TypeShortcode
       +        TypePartial
       +        TypeBaseof
        )
        
        type Path struct {
       @@ -257,13 +347,17 @@ type Path struct {
                posContainerHigh int
                posSectionHigh   int
        
       -        component  string
       -        bundleType PathType
       +        component string
       +        pathType  Type
        
                identifiers []types.LowHigh[string]
        
       -        posIdentifierLanguage int
       -        disabled              bool
       +        posIdentifierLanguage     int
       +        posIdentifierOutputFormat int
       +        posIdentifierKind         int
       +        posIdentifierBaseof       int
       +        identifiersUnknown        []int
       +        disabled                  bool
        
                trimLeadingSlash bool
        
       @@ -293,9 +387,12 @@ func (p *Path) reset() {
                p.posContainerHigh = -1
                p.posSectionHigh = -1
                p.component = ""
       -        p.bundleType = 0
       +        p.pathType = 0
                p.identifiers = p.identifiers[:0]
                p.posIdentifierLanguage = -1
       +        p.posIdentifierOutputFormat = -1
       +        p.posIdentifierKind = -1
       +        p.posIdentifierBaseof = -1
                p.disabled = false
                p.trimLeadingSlash = false
                p.unnormalized = nil
       @@ -316,6 +413,9 @@ func (p *Path) norm(s string) string {
        
        // IdentifierBase satisfies identity.Identity.
        func (p *Path) IdentifierBase() string {
       +        if p.Component() == files.ComponentFolderLayouts {
       +                return p.Path()
       +        }
                return p.Base()
        }
        
       @@ -332,6 +432,13 @@ func (p *Path) Container() string {
                return p.norm(p.s[p.posContainerLow : p.posContainerHigh-1])
        }
        
       +func (p *Path) String() string {
       +        if p == nil {
       +                return "<nil>"
       +        }
       +        return p.Path()
       +}
       +
        // ContainerDir returns the container directory for this path.
        // For content bundles this will be the parent directory.
        func (p *Path) ContainerDir() string {
       @@ -352,13 +459,13 @@ func (p *Path) Section() string {
        // IsContent returns true if the path is a content file (e.g. mypost.md).
        // Note that this will also return true for content files in a bundle.
        func (p *Path) IsContent() bool {
       -        return p.BundleType() >= PathTypeContentResource
       +        return p.Type() >= TypeContentResource && p.Type() <= TypeContentData
        }
        
        // isContentPage returns true if the path is a content file (e.g. mypost.md),
        // but nof if inside a leaf bundle.
        func (p *Path) isContentPage() bool {
       -        return p.BundleType() >= PathTypeContentSingle
       +        return p.Type() >= TypeContentSingle && p.Type() <= TypeContentData
        }
        
        // Name returns the last element of path.
       @@ -398,10 +505,26 @@ func (p *Path) BaseNameNoIdentifier() string {
        
        // NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
        func (p *Path) NameNoIdentifier() string {
       +        lowHigh := p.nameLowHigh()
       +        return p.s[lowHigh.Low:lowHigh.High]
       +}
       +
       +func (p *Path) nameLowHigh() types.LowHigh[string] {
                if len(p.identifiers) > 0 {
       -                return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
       +                lastID := p.identifiers[len(p.identifiers)-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,
       +                }
       +        }
       +        return types.LowHigh[string]{
       +                Low:  p.posContainerHigh,
       +                High: len(p.s),
                }
       -        return p.s[p.posContainerHigh:]
        }
        
        // Dir returns all but the last element of path, typically the path's directory.
       @@ -421,6 +544,11 @@ func (p *Path) Path() (d string) {
                return p.norm(p.s)
        }
        
       +// PathNoLeadingSlash returns the full path without the leading slash.
       +func (p *Path) PathNoLeadingSlash() string {
       +        return p.Path()[1:]
       +}
       +
        // Unnormalized returns the Path with the original case preserved.
        func (p *Path) Unnormalized() *Path {
                return p.unnormalized
       @@ -436,6 +564,28 @@ func (p *Path) PathNoIdentifier() string {
                return p.base(false, false)
        }
        
       +// 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 {
       +                return p.norm(p.s)
       +        }
       +        i := p.identifierIndex(0)
       +
       +        if j := p.posIdentifierOutputFormat; i == -1 || (j != -1 && j < i) {
       +                i = j
       +        }
       +        if j := p.posIdentifierLanguage; i == -1 || (j != -1 && j < i) {
       +                i = j
       +        }
       +
       +        if i == -1 {
       +                return p.norm(p.s)
       +        }
       +
       +        id := p.identifiers[i]
       +        return p.norm(p.s[:id.Low-1])
       +}
       +
        // PathRel returns the path relative to the given owner.
        func (p *Path) PathRel(owner *Path) string {
                ob := owner.Base()
       @@ -462,6 +612,21 @@ func (p *Path) Base() string {
                return p.base(!p.isContentPage(), p.IsBundle())
        }
        
       +// Used in template lookups.
       +// For pages with Type set, we treat that as the section.
       +func (p *Path) BaseReTyped(typ string) (d string) {
       +        base := p.Base()
       +        if typ == "" || p.Section() == typ {
       +                return base
       +        }
       +        d = "/" + typ
       +        if p.posSectionHigh != -1 {
       +                d += base[p.posSectionHigh:]
       +        }
       +        d = p.norm(d)
       +        return
       +}
       +
        // BaseNoLeadingSlash returns the base path without the leading slash.
        func (p *Path) BaseNoLeadingSlash() string {
                return p.Base()[1:]
       @@ -477,11 +642,12 @@ func (p *Path) base(preserveExt, isBundle bool) string {
                        return p.norm(p.s)
                }
        
       -        id := p.identifiers[len(p.identifiers)-1]
       -        high := id.Low - 1
       +        var high int
        
                if isBundle {
                        high = p.posContainerHigh - 1
       +        } else {
       +                high = p.nameLowHigh().High
                }
        
                if high == 0 {
       @@ -493,7 +659,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.identifiers[0]
        
                return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
        }
       @@ -502,8 +668,16 @@ func (p *Path) Ext() string {
                return p.identifierAsString(0)
        }
        
       +func (p *Path) OutputFormat() string {
       +        return p.identifierAsString(p.posIdentifierOutputFormat)
       +}
       +
       +func (p *Path) Kind() string {
       +        return p.identifierAsString(p.posIdentifierKind)
       +}
       +
        func (p *Path) Lang() string {
       -        return p.identifierAsString(1)
       +        return p.identifierAsString(p.posIdentifierLanguage)
        }
        
        func (p *Path) Identifier(i int) string {
       @@ -522,28 +696,36 @@ func (p *Path) Identifiers() []string {
                return ids
        }
        
       -func (p *Path) BundleType() PathType {
       -        return p.bundleType
       +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]
       +        }
       +        return ids
       +}
       +
       +func (p *Path) Type() Type {
       +        return p.pathType
        }
        
        func (p *Path) IsBundle() bool {
       -        return p.bundleType >= PathTypeLeaf
       +        return p.pathType >= TypeLeaf && p.pathType <= TypeContentData
        }
        
        func (p *Path) IsBranchBundle() bool {
       -        return p.bundleType == PathTypeBranch
       +        return p.pathType == TypeBranch
        }
        
        func (p *Path) IsLeafBundle() bool {
       -        return p.bundleType == PathTypeLeaf
       +        return p.pathType == TypeLeaf
        }
        
        func (p *Path) IsContentData() bool {
       -        return p.bundleType == PathTypeContentData
       +        return p.pathType == TypeContentData
        }
        
       -func (p Path) ForBundleType(t PathType) *Path {
       -        p.bundleType = t
       +func (p Path) ForBundleType(t Type) *Path {
       +        p.pathType = t
                return &p
        }
        
 (DIR) diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
       @@ -18,6 +18,7 @@ import (
                "testing"
        
                "github.com/gohugoio/hugo/hugofs/files"
       +        "github.com/gohugoio/hugo/resources/kinds"
        
                qt "github.com/frankban/quicktest"
        )
       @@ -26,10 +27,18 @@ var testParser = &PathParser{
                LanguageIndex: map[string]int{
                        "no": 0,
                        "en": 1,
       +                "fr": 2,
                },
                IsContentExt: func(ext string) bool {
                        return ext == "md"
                },
       +        IsOutputFormat: func(name, ext string) bool {
       +                switch name {
       +                case "html", "amp", "csv", "rss":
       +                        return true
       +                }
       +                return false
       +        },
        }
        
        func TestParse(t *testing.T) {
       @@ -105,17 +114,19 @@ func TestParse(t *testing.T) {
                                "Basic Markdown file",
                                "/a/b/c.md",
                                func(c *qt.C, p *Path) {
       +                                c.Assert(p.Ext(), qt.Equals, "md")
       +                                c.Assert(p.Type(), qt.Equals, TypeContentSingle)
                                        c.Assert(p.IsContent(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsFalse)
                                        c.Assert(p.Name(), qt.Equals, "c.md")
                                        c.Assert(p.Base(), qt.Equals, "/a/b/c")
       +                                c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b/c")
                                        c.Assert(p.Section(), qt.Equals, "a")
                                        c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "c")
                                        c.Assert(p.Path(), qt.Equals, "/a/b/c.md")
                                        c.Assert(p.Dir(), qt.Equals, "/a/b")
                                        c.Assert(p.Container(), qt.Equals, "b")
                                        c.Assert(p.ContainerDir(), qt.Equals, "/a/b")
       -                                c.Assert(p.Ext(), qt.Equals, "md")
                                },
                        },
                        {
       @@ -130,7 +141,7 @@ func TestParse(t *testing.T) {
        
                                        // Reclassify it as a content resource.
                                        ModifyPathBundleTypeResource(p)
       -                                c.Assert(p.BundleType(), qt.Equals, PathTypeContentResource)
       +                                c.Assert(p.Type(), qt.Equals, TypeContentResource)
                                        c.Assert(p.IsContent(), qt.IsTrue)
                                        c.Assert(p.Name(), qt.Equals, "b.md")
                                        c.Assert(p.Base(), qt.Equals, "/a/b.md")
       @@ -160,15 +171,16 @@ 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.a.b")
       +                                c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
                                        c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
       -                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
       -                                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.Identifiers(), qt.DeepEquals, []string{"txt", "no", "b", "a", "b"})
       +                                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.Path(), qt.Equals, "/a/b.a.b.no.txt")
       -                                c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
       +                                c.Assert(p.PathNoLang(), qt.Equals, "/a/b.txt")
                                        c.Assert(p.Ext(), qt.Equals, "txt")
       -                                c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
       +                                c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b")
                                },
                        },
                        {
       @@ -176,6 +188,7 @@ func TestParse(t *testing.T) {
                                "/_index.md",
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Base(), qt.Equals, "/")
       +                                c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo")
                                        c.Assert(p.Path(), qt.Equals, "/_index.md")
                                        c.Assert(p.Container(), qt.Equals, "")
                                        c.Assert(p.ContainerDir(), qt.Equals, "/")
       @@ -186,13 +199,14 @@ func TestParse(t *testing.T) {
                                "/a/index.md",
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Base(), qt.Equals, "/a")
       +                                c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/a")
                                        c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "a")
                                        c.Assert(p.Container(), qt.Equals, "a")
                                        c.Assert(p.Container(), qt.Equals, "a")
                                        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"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "index"})
                                        c.Assert(p.IsBranchBundle(), qt.IsFalse)
                                        c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsTrue)
       @@ -209,11 +223,12 @@ func TestParse(t *testing.T) {
                                func(c *qt.C, p *Path) {
                                        c.Assert(p.Base(), qt.Equals, "/a/b")
                                        c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
       +                                c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b")
                                        c.Assert(p.Container(), qt.Equals, "b")
                                        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"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "index"})
                                        c.Assert(p.IsBranchBundle(), qt.IsFalse)
                                        c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsTrue)
       @@ -235,7 +250,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"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "_index"})
                                        c.Assert(p.IsBranchBundle(), qt.IsTrue)
                                        c.Assert(p.IsBundle(), qt.IsTrue)
                                        c.Assert(p.IsLeafBundle(), qt.IsFalse)
       @@ -274,7 +289,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"})
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "index"})
                                        c.Assert(p.IsLeafBundle(), qt.IsFalse)
                                        c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
                                },
       @@ -357,11 +372,140 @@ func TestParse(t *testing.T) {
                }
                for _, test := range tests {
                        c.Run(test.name, func(c *qt.C) {
       +                        if test.name != "Basic Markdown file" {
       +                                // return
       +                        }
                                test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
                        })
                }
        }
        
       +func TestParseLayouts(t *testing.T) {
       +        c := qt.New(t)
       +
       +        tests := []struct {
       +                name   string
       +                path   string
       +                assert func(c *qt.C, p *Path)
       +        }{
       +                {
       +                        "Basic",
       +                        "/list.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Base(), qt.Equals, "/list.html")
       +                                c.Assert(p.OutputFormat(), qt.Equals, "html")
       +                        },
       +                },
       +                {
       +                        "Lang",
       +                        "/list.no.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "list"})
       +                                c.Assert(p.Base(), qt.Equals, "/list.html")
       +                                c.Assert(p.Lang(), qt.Equals, "no")
       +                        },
       +                },
       +                {
       +                        "Lang and output format",
       +                        "/list.no.amp.not.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "not", "amp", "no", "list"})
       +                                c.Assert(p.OutputFormat(), qt.Equals, "amp")
       +                                c.Assert(p.Ext(), qt.Equals, "html")
       +                                c.Assert(p.Lang(), qt.Equals, "no")
       +                                c.Assert(p.Base(), qt.Equals, "/list.html")
       +                        },
       +                },
       +                {
       +                        "Term",
       +                        "/term.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Base(), qt.Equals, "/term.html")
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "term"})
       +                                c.Assert(p.PathNoIdentifier(), qt.Equals, "/term")
       +                                c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/term")
       +                                c.Assert(p.Lang(), qt.Equals, "")
       +                                c.Assert(p.Kind(), qt.Equals, "term")
       +                                c.Assert(p.OutputFormat(), qt.Equals, "html")
       +                        },
       +                },
       +                {
       +                        "Sub dir",
       +                        "/pages/home.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "home"})
       +                                c.Assert(p.Lang(), qt.Equals, "")
       +                                c.Assert(p.Kind(), qt.Equals, "home")
       +                                c.Assert(p.OutputFormat(), qt.Equals, "html")
       +                                c.Assert(p.Dir(), qt.Equals, "/pages")
       +                        },
       +                },
       +                {
       +                        "Baseof",
       +                        "/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.Kind(), qt.Equals, kinds.KindSection)
       +                                c.Assert(p.Lang(), qt.Equals, "fr")
       +                                c.Assert(p.OutputFormat(), qt.Equals, "amp")
       +                                c.Assert(p.Dir(), qt.Equals, "/pages")
       +                                c.Assert(p.NameNoIdentifier(), qt.Equals, "baseof")
       +                                c.Assert(p.Type(), qt.Equals, TypeBaseof)
       +                                c.Assert(p.IdentifierBase(), qt.Equals, "/pages/baseof.list.section.fr.amp.html")
       +                        },
       +                },
       +                {
       +                        "Markup",
       +                        "/_markup/render-link.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Type(), qt.Equals, TypeMarkup)
       +                        },
       +                },
       +                {
       +                        "Markup nested",
       +                        "/foo/_markup/render-link.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Type(), qt.Equals, TypeMarkup)
       +                        },
       +                },
       +                {
       +                        "Shortcode",
       +                        "/_shortcodes/myshortcode.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Type(), qt.Equals, TypeShortcode)
       +                        },
       +                },
       +                {
       +                        "Shortcode nested",
       +                        "/foo/_shortcodes/myshortcode.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Type(), qt.Equals, TypeShortcode)
       +                        },
       +                },
       +                {
       +                        "Shortcode nested sub",
       +                        "/foo/_shortcodes/foo/myshortcode.html",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Type(), qt.Equals, TypeShortcode)
       +                        },
       +                },
       +                {
       +                        "Partials",
       +                        "/_partials/foo.bar",
       +                        func(c *qt.C, p *Path) {
       +                                c.Assert(p.Type(), qt.Equals, TypePartial)
       +                        },
       +                },
       +        }
       +
       +        for _, test := range tests {
       +                c.Run(test.name, func(c *qt.C) {
       +                        test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
       +                })
       +        }
       +}
       +
        func TestHasExt(t *testing.T) {
                c := qt.New(t)
        
 (DIR) diff --git a/common/paths/pathtype_string.go b/common/paths/pathtype_string.go
       @@ -1,27 +0,0 @@
       -// Code generated by "stringer -type=PathType"; DO NOT EDIT.
       -
       -package paths
       -
       -import "strconv"
       -
       -func _() {
       -        // An "invalid array index" compiler error signifies that the constant values have changed.
       -        // Re-run the stringer command to generate them again.
       -        var x [1]struct{}
       -        _ = x[PathTypeFile-0]
       -        _ = x[PathTypeContentResource-1]
       -        _ = x[PathTypeContentSingle-2]
       -        _ = x[PathTypeLeaf-3]
       -        _ = x[PathTypeBranch-4]
       -}
       -
       -const _PathType_name = "PathTypeFilePathTypeContentResourcePathTypeContentSinglePathTypeLeafPathTypeBranch"
       -
       -var _PathType_index = [...]uint8{0, 12, 35, 56, 68, 82}
       -
       -func (i PathType) String() string {
       -        if i < 0 || i >= PathType(len(_PathType_index)-1) {
       -                return "PathType(" + strconv.FormatInt(int64(i), 10) + ")"
       -        }
       -        return _PathType_name[_PathType_index[i]:_PathType_index[i+1]]
       -}
 (DIR) diff --git a/common/paths/type_string.go b/common/paths/type_string.go
       @@ -0,0 +1,32 @@
       +// Code generated by "stringer -type Type"; DO NOT EDIT.
       +
       +package paths
       +
       +import "strconv"
       +
       +func _() {
       +        // An "invalid array index" compiler error signifies that the constant values have changed.
       +        // Re-run the stringer command to generate them again.
       +        var x [1]struct{}
       +        _ = x[TypeFile-0]
       +        _ = x[TypeContentResource-1]
       +        _ = x[TypeContentSingle-2]
       +        _ = x[TypeLeaf-3]
       +        _ = x[TypeBranch-4]
       +        _ = x[TypeContentData-5]
       +        _ = x[TypeMarkup-6]
       +        _ = x[TypeShortcode-7]
       +        _ = x[TypePartial-8]
       +        _ = x[TypeBaseof-9]
       +}
       +
       +const _Type_name = "TypeFileTypeContentResourceTypeContentSingleTypeLeafTypeBranchTypeContentDataTypeMarkupTypeShortcodeTypePartialTypeBaseof"
       +
       +var _Type_index = [...]uint8{0, 8, 27, 44, 52, 62, 77, 87, 100, 111, 121}
       +
       +func (i Type) String() string {
       +        if i < 0 || i >= Type(len(_Type_index)-1) {
       +                return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
       +        }
       +        return _Type_name[_Type_index[i]:_Type_index[i+1]]
       +}
 (DIR) diff --git a/common/types/types.go b/common/types/types.go
       @@ -28,6 +28,16 @@ type RLocker interface {
                RUnlock()
        }
        
       +type Locker interface {
       +        Lock()
       +        Unlock()
       +}
       +
       +type RWLocker interface {
       +        RLocker
       +        Locker
       +}
       +
        // KeyValue is a interface{} tuple.
        type KeyValue struct {
                Key   any
 (DIR) diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
       @@ -849,7 +849,24 @@ func (c *Configs) Init() error {
                c.Languages = languages
                c.LanguagesDefaultFirst = languagesDefaultFirst
        
       -        c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.ContentTypes.Config.IsContentSuffix}
       +        c.ContentPathParser = &paths.PathParser{
       +                LanguageIndex:  languagesDefaultFirst.AsIndexSet(),
       +                IsLangDisabled: c.Base.IsLangDisabled,
       +                IsContentExt:   c.Base.ContentTypes.Config.IsContentSuffix,
       +                IsOutputFormat: func(name, ext string) bool {
       +                        if name == "" {
       +                                return false
       +                        }
       +
       +                        if of, ok := c.Base.OutputFormats.Config.GetByName(name); ok {
       +                                if ext != "" && !of.MediaType.HasSuffix(ext) {
       +                                        return false
       +                                }
       +                                return true
       +                        }
       +                        return false
       +                },
       +        }
        
                c.configLangs = make([]config.AllProvider, len(c.Languages))
                for i, l := range c.LanguagesDefaultFirst {
 (DIR) diff --git a/create/content.go b/create/content.go
       @@ -291,7 +291,7 @@ func (b *contentBuilder) applyArcheType(contentFilename string, archetypeFi hugo
        func (b *contentBuilder) mapArcheTypeDir() error {
                var m archetypeMap
        
       -        seen := map[hstrings.Tuple]bool{}
       +        seen := map[hstrings.Strings2]bool{}
        
                walkFn := func(path string, fim hugofs.FileMetaInfo) error {
                        if fim.IsDir() {
       @@ -301,7 +301,7 @@ func (b *contentBuilder) mapArcheTypeDir() error {
                        pi := fim.Meta().PathInfo
        
                        if pi.IsContent() {
       -                        pathLang := hstrings.Tuple{First: pi.PathNoIdentifier(), Second: fim.Meta().Lang}
       +                        pathLang := hstrings.Strings2{pi.PathBeforeLangAndOutputFormatAndExt(), fim.Meta().Lang}
                                if seen[pathLang] {
                                        // Duplicate content file, e.g. page.md and page.html.
                                        // In the regular build, we will filter out the duplicates, but
 (DIR) diff --git a/deps/deps.go b/deps/deps.go
       @@ -29,6 +29,7 @@ import (
                "github.com/gohugoio/hugo/media"
                "github.com/gohugoio/hugo/resources/page"
                "github.com/gohugoio/hugo/resources/postpub"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
        
                "github.com/gohugoio/hugo/metrics"
                "github.com/gohugoio/hugo/resources"
       @@ -46,12 +47,6 @@ type Deps struct {
        
                ExecHelper *hexec.Exec
        
       -        // The templates to use. This will usually implement the full tpl.TemplateManager.
       -        tmplHandlers *tpl.TemplateHandlers
       -
       -        // The template funcs.
       -        TmplFuncMap map[string]any
       -
                // The file systems to use.
                Fs *hugofs.Fs `json:"-"`
        
       @@ -79,7 +74,8 @@ type Deps struct {
                // The site building.
                Site page.Site
        
       -        TemplateProvider ResourceProvider
       +        TemplateStore *tplimpl.TemplateStore
       +
                // Used in tests
                OverloadedTemplateFuncs map[string]any
        
       @@ -102,6 +98,9 @@ type Deps struct {
                // This is common/global for all sites.
                BuildState *BuildState
        
       +        // Misc counters.
       +        Counters *Counters
       +
                // Holds RPC dispatchers for Katex etc.
                // TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
                WasmDispatchers *warpc.Dispatchers
       @@ -109,9 +108,6 @@ type Deps struct {
                // The JS batcher client.
                JSBatcherClient js.BatcherClient
        
       -        // The JS batcher client.
       -        // JSBatcherClient *esbuild.BatcherClient
       -
                isClosed bool
        
                *globalErrHandler
       @@ -130,8 +126,8 @@ func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
                return &d, nil
        }
        
       -func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
       -        d.tmplHandlers = t
       +func (d *Deps) GetTemplateStore() *tplimpl.TemplateStore {
       +        return d.TemplateStore
        }
        
        func (d *Deps) Init() error {
       @@ -153,10 +149,12 @@ func (d *Deps) Init() error {
                                logger: d.Log,
                        }
                }
       -
                if d.BuildState == nil {
                        d.BuildState = &BuildState{}
                }
       +        if d.Counters == nil {
       +                d.Counters = &Counters{}
       +        }
                if d.BuildState.DeferredExecutions == nil {
                        if d.BuildState.DeferredExecutionsGroupedByRenderingContext == nil {
                                d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
       @@ -263,22 +261,17 @@ func (d *Deps) Init() error {
                return nil
        }
        
       +// TODO(bep) rework this to get it in line with how we manage templates.
        func (d *Deps) Compile(prototype *Deps) error {
                var err error
                if prototype == nil {
       -                if err = d.TemplateProvider.NewResource(d); err != nil {
       -                        return err
       -                }
       +
                        if err = d.TranslationProvider.NewResource(d); err != nil {
                                return err
                        }
                        return nil
                }
        
       -        if err = d.TemplateProvider.CloneResource(d, prototype); err != nil {
       -                return err
       -        }
       -
                if err = d.TranslationProvider.CloneResource(d, prototype); err != nil {
                        return err
                }
       @@ -378,14 +371,6 @@ type ResourceProvider interface {
                CloneResource(dst, src *Deps) error
        }
        
       -func (d *Deps) Tmpl() tpl.TemplateHandler {
       -        return d.tmplHandlers.Tmpl
       -}
       -
       -func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
       -        return d.tmplHandlers.TxtTmpl
       -}
       -
        func (d *Deps) Close() error {
                if d.isClosed {
                        return nil
       @@ -454,6 +439,12 @@ type BuildState struct {
                DeferredExecutionsGroupedByRenderingContext map[tpl.RenderingContext]*DeferredExecutions
        }
        
       +// Misc counters.
       +type Counters struct {
       +        // Counter for the math.Counter function.
       +        MathCounter atomic.Uint64
       +}
       +
        type DeferredExecutions struct {
                // A set of filenames in /public that
                // contains a post-processing prefix.
 (DIR) diff --git a/hugolib/alias.go b/hugolib/alias.go
       @@ -29,16 +29,17 @@ import (
                "github.com/gohugoio/hugo/publisher"
                "github.com/gohugoio/hugo/resources/page"
                "github.com/gohugoio/hugo/tpl"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
        )
        
        type aliasHandler struct {
       -        t         tpl.TemplateHandler
       +        ts        *tplimpl.TemplateStore
                log       loggers.Logger
                allowRoot bool
        }
        
       -func newAliasHandler(t tpl.TemplateHandler, l loggers.Logger, allowRoot bool) aliasHandler {
       -        return aliasHandler{t, l, allowRoot}
       +func newAliasHandler(ts *tplimpl.TemplateStore, l loggers.Logger, allowRoot bool) aliasHandler {
       +        return aliasHandler{ts, l, allowRoot}
        }
        
        type aliasPage struct {
       @@ -47,16 +48,24 @@ type aliasPage struct {
        }
        
        func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, error) {
       -        var templ tpl.Template
       -        var found bool
       -
       -        templ, found = a.t.Lookup("alias.html")
       -        if !found {
       -                // TODO(bep) consolidate
       -                templ, found = a.t.Lookup("_internal/alias.html")
       -                if !found {
       -                        return nil, errors.New("no alias template found")
       -                }
       +        var templateDesc tplimpl.TemplateDescriptor
       +        var base string = ""
       +        if ps, ok := p.(*pageState); ok {
       +                base, templateDesc = ps.getTemplateBasePathAndDescriptor()
       +        }
       +        templateDesc.Layout = ""
       +        templateDesc.Kind = ""
       +        templateDesc.OutputFormat = output.AliasHTMLFormat.Name
       +
       +        q := tplimpl.TemplateQuery{
       +                Path:     base,
       +                Category: tplimpl.CategoryLayout,
       +                Desc:     templateDesc,
       +        }
       +
       +        t := a.ts.LookupPagesLayout(q)
       +        if t == nil {
       +                return nil, errors.New("no alias template found")
                }
        
                data := aliasPage{
       @@ -67,7 +76,7 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err
                ctx := tpl.Context.Page.Set(context.Background(), p)
        
                buffer := new(bytes.Buffer)
       -        err := a.t.ExecuteWithContext(ctx, templ, buffer, data)
       +        err := a.ts.ExecuteWithContext(ctx, t, buffer, data)
                if err != nil {
                        return nil, err
                }
       @@ -79,7 +88,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format
        }
        
        func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
       -        handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot)
       +        handler := newAliasHandler(s.GetTemplateStore(), s.Log, allowRoot)
        
                targetPath, err := handler.targetPathAlias(path)
                if err != nil {
 (DIR) diff --git a/hugolib/alias_test.go b/hugolib/alias_test.go
       @@ -107,13 +107,26 @@ func TestAliasMultipleOutputFormats(t *testing.T) {
        func TestAliasTemplate(t *testing.T) {
                t.Parallel()
        
       -        b := newTestSitesBuilder(t)
       -        b.WithSimpleConfigFile().WithContent("page.md", pageWithAlias).WithTemplatesAdded("alias.html", aliasTemplate)
       +        files := `
       +-- hugo.toml --
       +baseURL = "http://example.com"
       +-- layouts/single.html --
       +Single.
       +-- layouts/home.html --
       +Home.
       +-- layouts/alias.html --
       +ALIASTEMPLATE
       +-- content/page.md --
       +---
       +title: "Page"
       +aliases: ["/foo/bar/"]
       +---
       +`
        
       -        b.CreateSites().Build(BuildCfg{})
       +        b := Test(t, files)
        
                // the real page
       -        b.AssertFileContent("public/page/index.html", "For some moments the old man")
       +        b.AssertFileContent("public/page/index.html", "Single.")
                // the alias redirector
                b.AssertFileContent("public/foo/bar/index.html", "ALIASTEMPLATE")
        }
 (DIR) diff --git a/hugolib/content_factory.go b/hugolib/content_factory.go
       @@ -72,12 +72,12 @@ func (f ContentFactory) ApplyArchetypeTemplate(w io.Writer, p page.Page, archety
        
                templateSource = f.shortcodeReplacerPre.Replace(templateSource)
        
       -        templ, err := ps.s.TextTmpl().Parse("archetype.md", string(templateSource))
       +        templ, err := ps.s.TemplateStore.TextParse("archetype.md", templateSource)
                if err != nil {
                        return fmt.Errorf("failed to parse archetype template: %s: %w", err, err)
                }
        
       -        result, err := executeToString(context.Background(), ps.s.Tmpl(), templ, d)
       +        result, err := executeToString(context.Background(), ps.s.GetTemplateStore(), templ, d)
                if err != nil {
                        return fmt.Errorf("failed to execute archetype template: %s: %w", err, err)
                }
 (DIR) diff --git a/hugolib/content_map.go b/hugolib/content_map.go
       @@ -264,8 +264,8 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCoun
                meta := fi.Meta()
                pi := meta.PathInfo
        
       -        switch pi.BundleType() {
       -        case paths.PathTypeFile, paths.PathTypeContentResource:
       +        switch pi.Type() {
       +        case paths.TypeFile, paths.TypeContentResource:
                        m.s.Log.Trace(logg.StringFunc(
                                func() string {
                                        return fmt.Sprintf("insert resource: %q", fi.Meta().Filename)
       @@ -275,7 +275,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCoun
                                addErr = err
                                return
                        }
       -        case paths.PathTypeContentData:
       +        case paths.TypeContentData:
                        pc, rc, err := m.addPagesFromGoTmplFi(fi, buildConfig)
                        pageCount += pc
                        resourceCount += rc
       @@ -349,8 +349,7 @@ func (m *pageMap) addPagesFromGoTmplFi(fi hugofs.FileMetaInfo, buildConfig *Buil
                                        DepsFromSite: func(s page.Site) pagesfromdata.PagesFromTemplateDeps {
                                                ss := s.(*Site)
                                                return pagesfromdata.PagesFromTemplateDeps{
       -                                                TmplFinder: ss.TextTmpl(),
       -                                                TmplExec:   ss.Tmpl(),
       +                                                TemplateStore: ss.GetTemplateStore(),
                                                }
                                        },
                                        DependencyManager: s.Conf.NewIdentityManager("pagesfromdata"),
 (DIR) diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
       @@ -180,7 +180,7 @@ func (t *pageTrees) collectAndMarkStaleIdentities(p *paths.Path) []identity.Iden
        
                if p.Component() == files.ComponentFolderContent {
                        // It may also be a bundled content resource.
       -                key := p.ForBundleType(paths.PathTypeContentResource).Base()
       +                key := p.ForBundleType(paths.TypeContentResource).Base()
                        tree = t.treeResources
                        nCount = 0
                        tree.ForEeachInDimension(key, doctree.DimensionLanguage.Index(),
       @@ -1304,14 +1304,14 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context, 
                        checkedCounter atomic.Int64
                )
        
       -        resetPo := func(po *pageOutput, r identity.FinderResult) {
       -                if po.pco != nil {
       +        resetPo := func(po *pageOutput, rebuildContent bool, r identity.FinderResult) {
       +                if rebuildContent && po.pco != nil {
                                po.pco.Reset() // Will invalidate content cache.
                        }
        
                        po.renderState = 0
                        po.p.resourcesPublishInit = &sync.Once{}
       -                if r == identity.FinderFoundOneOfMany || po.f.Name == output.HTTPStatusHTMLFormat.Name {
       +                if r == identity.FinderFoundOneOfMany || po.f.Name == output.HTTPStatus404HTMLFormat.Name {
                                // Will force a re-render even in fast render mode.
                                po.renderOnce = false
                        }
       @@ -1323,7 +1323,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context, 
                }
        
                // This can be a relativeley expensive operations, so we do it in parallel.
       -        g := rungroup.Run[*pageState](ctx, rungroup.Config[*pageState]{
       +        g := rungroup.Run(ctx, rungroup.Config[*pageState]{
                        NumWorkers: h.numWorkers,
                        Handle: func(ctx context.Context, p *pageState) error {
                                if !p.isRenderedAny() {
       @@ -1335,7 +1335,8 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context, 
                                        checkedCounter.Add(1)
                                        if r := depsFinder.Contains(id, p.dependencyManager, 2); r > identity.FinderFoundOneOfManyRepetition {
                                                for _, po := range p.pageOutputs {
       -                                                resetPo(po, r)
       +                                                // Note that p.dependencyManager is used when rendering content, so reset that.
       +                                                resetPo(po, true, r)
                                                }
                                                // Done.
                                                return nil
       @@ -1351,7 +1352,8 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context, 
                                        for _, id := range changes {
                                                checkedCounter.Add(1)
                                                if r := depsFinder.Contains(id, po.dependencyManagerOutput, 50); r > identity.FinderFoundOneOfManyRepetition {
       -                                                resetPo(po, r)
       +                                                // Note that dependencyManagerOutput is not used when rendering content, so don't reset that.
       +                                                resetPo(po, false, r)
                                                        continue OUTPUTS
                                                }
                                        }
       @@ -1954,7 +1956,7 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
                        tree.InsertIntoValuesDimension(key, p)
                }
        
       -        addStandalone("/404", kinds.KindStatus404, output.HTTPStatusHTMLFormat)
       +        addStandalone("/404", kinds.KindStatus404, output.HTTPStatus404HTMLFormat)
        
                if s.conf.EnableRobotsTXT {
                        if m.i == 0 || s.Conf.IsMultihost() {
 (DIR) diff --git a/hugolib/content_map_test.go b/hugolib/content_map_test.go
       @@ -242,8 +242,13 @@ Data en
        }
        
        func TestBundleMultipleContentPageWithSamePath(t *testing.T) {
       +        t.Parallel()
       +
                files := `
        -- hugo.toml --
       +printPathWarnings = true
       +-- layouts/all.html --
       +All.
        -- content/bundle/index.md --
        ---
        title: "Bundle md"
       @@ -273,14 +278,18 @@ Bundle: {{ $bundle.Title }}|{{ $bundle.Params.foo }}|{{ $bundle.File.Filename }}
        P1: {{ $p1.Title }}|{{ $p1.Params.foo }}|{{ $p1.File.Filename }}|
        `
        
       -        b := Test(t, files)
       +        for range 3 {
       +                b := Test(t, files, TestOptWarn())
        
       -        // There's multiple content files sharing the same logical path and language.
       -        // This is a little arbitrary, but we have to pick one and prefer the Markdown version.
       -        b.AssertFileContent("public/index.html",
       -                filepath.FromSlash("Bundle: Bundle md|md|/content/bundle/index.md|"),
       -                filepath.FromSlash("P1: P1 md|md|/content/p1.md|"),
       -        )
       +                b.AssertLogContains("WARN  Duplicate content path: \"/p1\"")
       +
       +                // There's multiple content files sharing the same logical path and language.
       +                // This is a little arbitrary, but we have to pick one and prefer the Markdown version.
       +                b.AssertFileContent("public/index.html",
       +                        filepath.FromSlash("Bundle: Bundle md|md|/content/bundle/index.md|"),
       +                        filepath.FromSlash("P1: P1 md|md|/content/p1.md|"),
       +                )
       +        }
        }
        
        // Issue #11944
 (DIR) diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
       @@ -17,6 +17,8 @@ import (
                "fmt"
                "strings"
                "testing"
       +
       +        qt "github.com/frankban/quicktest"
        )
        
        func TestRenderHooksRSS(t *testing.T) {
       @@ -129,6 +131,7 @@ P1: <p>P1. <a href="https://www.gohugo.io">I&rsquo;m an inline-style link</a></p
        <h1 id="heading-in-p1">Heading in p1</h1>
        <h1 id="heading-in-p2">Heading in p2</h1>
        `)
       +
                b.AssertFileContent("public/index.xml", `
        P2: <p>P2. xml-link: https://www.bep.is|</p>
        P3: <p>P3. xml-link: https://www.example.org|</p>
       @@ -378,3 +381,93 @@ Content: {{ .Content}}|
                        "|Text: First line.\nSecond line.||\n",
                )
        }
       +
       +func TestContentOutputReuseRenderHooksAndShortcodesHTMLOnly(t *testing.T) {
       +        files := `
       +-- hugo.toml --
       +-- layouts/index.html --
       +HTML: {{ .Title }}|{{ .Content }}|
       +-- layouts/index.xml --
       +XML: {{ .Title }}|{{ .Content }}|
       +-- layouts/_markup/render-heading.html --
       +Render heading.
       +-- layouts/shortcodes/myshortcode.html --
       +My shortcode.
       +-- content/_index.md --
       +---
       +title: "Home"
       +---
       +## Heading
       +
       +{{< myshortcode >}}
       +`
       +        b := Test(t, files)
       +
       +        s := b.H.Sites[0]
       +        b.Assert(s.home.pageOutputTemplateVariationsState.Load(), qt.Equals, uint32(1))
       +        b.AssertFileContent("public/index.html", "HTML: Home|Render heading.\nMy shortcode.\n|")
       +        b.AssertFileContent("public/index.xml", "XML: Home|Render heading.\nMy shortcode.\n|")
       +}
       +
       +func TestContentOutputNoReuseRenderHooksInBothHTMLAnXML(t *testing.T) {
       +        files := `
       +-- hugo.toml --
       +disableKinds = ["taxonomy", "term"]
       +-- layouts/index.html --
       +HTML: {{ .Title }}|{{ .Content }}|
       +-- layouts/index.xml --
       +XML: {{ .Title }}|{{ .Content }}|
       +-- layouts/_markup/render-heading.html --
       +Render heading.
       +-- layouts/_markup/render-heading.xml --
       +Render heading XML.
       +-- content/_index.md --
       +---
       +title: "Home"
       +---
       +## Heading
       +
       +
       +`
       +        b := Test(t, files)
       +
       +        s := b.H.Sites[0]
       +        b.Assert(s.home.pageOutputTemplateVariationsState.Load() > 1, qt.IsTrue)
       +        b.AssertFileContentExact("public/index.xml", "XML: Home|Render heading XML.|")
       +        b.AssertFileContentExact("public/index.html", "HTML: Home|Render heading.|")
       +}
       +
       +func TestContentOutputNoReuseShortcodesInBothHTMLAnXML(t *testing.T) {
       +        files := `
       +-- hugo.toml --
       +disableKinds = ["taxonomy", "term"]
       +-- layouts/index.html --
       +HTML: {{ .Title }}|{{ .Content }}|
       +-- layouts/index.xml --
       +XML: {{ .Title }}|{{ .Content }}|
       +-- layouts/_markup/render-heading.html --
       +Render heading.
       +
       +-- layouts/shortcodes/myshortcode.html --
       +My shortcode HTML.
       +-- layouts/shortcodes/myshortcode.xml --
       +My shortcode XML.
       +-- content/_index.md --
       +---
       +title: "Home"
       +---
       +## Heading
       +
       +{{< myshortcode >}}
       +
       +
       +`
       +        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]
       +        b.Assert(s.home.pageOutputTemplateVariationsState.Load() > 1, qt.IsTrue)
       +}
 (DIR) diff --git a/hugolib/doctree/simpletree.go b/hugolib/doctree/simpletree.go
       @@ -14,35 +14,46 @@
        package doctree
        
        import (
       +        "iter"
                "sync"
        
                radix "github.com/armon/go-radix"
        )
        
       -// Tree is a radix tree that holds T.
       +// Tree is a non thread safe radix tree that holds T.
        type Tree[T any] interface {
       +        TreeCommon[T]
       +        WalkPrefix(s string, f func(s string, v T) (bool, error)) error
       +        WalkPath(s string, f func(s string, v T) (bool, error)) error
       +        All() iter.Seq2[string, T]
       +}
       +
       +// TreeThreadSafe is a thread safe radix tree that holds T.
       +type TreeThreadSafe[T any] interface {
       +        TreeCommon[T]
       +        WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error
       +        WalkPath(lockType LockType, s string, f func(s string, v T) (bool, error)) error
       +        All(lockType LockType) iter.Seq2[string, T]
       +}
       +
       +type TreeCommon[T any] interface {
                Get(s string) T
                LongestPrefix(s string) (string, T)
                Insert(s string, v T) T
       -        WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error
        }
        
       -// NewSimpleTree creates a new SimpleTree.
       -func NewSimpleTree[T comparable]() *SimpleTree[T] {
       +func NewSimpleTree[T any]() *SimpleTree[T] {
                return &SimpleTree[T]{tree: radix.New()}
        }
        
       -// SimpleTree is a thread safe radix tree that holds T.
       -type SimpleTree[T comparable] struct {
       -        mu   sync.RWMutex
       +// SimpleTree is a radix tree that holds T.
       +// This tree is not thread safe.
       +type SimpleTree[T any] struct {
                tree *radix.Tree
                zero T
        }
        
        func (tree *SimpleTree[T]) Get(s string) T {
       -        tree.mu.RLock()
       -        defer tree.mu.RUnlock()
       -
                if v, ok := tree.tree.Get(s); ok {
                        return v.(T)
                }
       @@ -50,9 +61,6 @@ func (tree *SimpleTree[T]) Get(s string) T {
        }
        
        func (tree *SimpleTree[T]) LongestPrefix(s string) (string, T) {
       -        tree.mu.RLock()
       -        defer tree.mu.RUnlock()
       -
                if s, v, ok := tree.tree.LongestPrefix(s); ok {
                        return s, v.(T)
                }
       @@ -60,17 +68,121 @@ func (tree *SimpleTree[T]) LongestPrefix(s string) (string, T) {
        }
        
        func (tree *SimpleTree[T]) Insert(s string, v T) T {
       +        tree.tree.Insert(s, v)
       +        return v
       +}
       +
       +func (tree *SimpleTree[T]) Walk(f func(s string, v T) (bool, error)) error {
       +        var err error
       +        tree.tree.Walk(func(s string, v any) bool {
       +                var b bool
       +                b, err = f(s, v.(T))
       +                if err != nil {
       +                        return true
       +                }
       +                return b
       +        })
       +        return err
       +}
       +
       +func (tree *SimpleTree[T]) WalkPrefix(s string, f func(s string, v T) (bool, error)) error {
       +        var err error
       +        tree.tree.WalkPrefix(s, func(s string, v any) bool {
       +                var b bool
       +                b, err = f(s, v.(T))
       +                if err != nil {
       +                        return true
       +                }
       +                return b
       +        })
       +
       +        return err
       +}
       +
       +func (tree *SimpleTree[T]) WalkPath(s string, f func(s string, v T) (bool, error)) error {
       +        var err error
       +        tree.tree.WalkPath(s, func(s string, v any) bool {
       +                var b bool
       +                b, err = f(s, v.(T))
       +                if err != nil {
       +                        return true
       +                }
       +                return b
       +        })
       +        return err
       +}
       +
       +func (tree *SimpleTree[T]) All() iter.Seq2[string, T] {
       +        return func(yield func(s string, v T) bool) {
       +                tree.tree.Walk(func(s string, v any) bool {
       +                        return !yield(s, v.(T))
       +                })
       +        }
       +}
       +
       +// NewSimpleThreadSafeTree creates a new SimpleTree.
       +func NewSimpleThreadSafeTree[T any]() *SimpleThreadSafeTree[T] {
       +        return &SimpleThreadSafeTree[T]{tree: radix.New(), mu: new(sync.RWMutex)}
       +}
       +
       +// SimpleThreadSafeTree is a thread safe radix tree that holds T.
       +type SimpleThreadSafeTree[T any] struct {
       +        mu     *sync.RWMutex
       +        noLock bool
       +        tree   *radix.Tree
       +        zero   T
       +}
       +
       +var noopFunc = func() {}
       +
       +func (tree *SimpleThreadSafeTree[T]) readLock() func() {
       +        if tree.noLock {
       +                return noopFunc
       +        }
       +        tree.mu.RLock()
       +        return tree.mu.RUnlock
       +}
       +
       +func (tree *SimpleThreadSafeTree[T]) writeLock() func() {
       +        if tree.noLock {
       +                return noopFunc
       +        }
                tree.mu.Lock()
       -        defer tree.mu.Unlock()
       +        return tree.mu.Unlock
       +}
       +
       +func (tree *SimpleThreadSafeTree[T]) Get(s string) T {
       +        unlock := tree.readLock()
       +        defer unlock()
       +
       +        if v, ok := tree.tree.Get(s); ok {
       +                return v.(T)
       +        }
       +        return tree.zero
       +}
       +
       +func (tree *SimpleThreadSafeTree[T]) LongestPrefix(s string) (string, T) {
       +        unlock := tree.readLock()
       +        defer unlock()
       +
       +        if s, v, ok := tree.tree.LongestPrefix(s); ok {
       +                return s, v.(T)
       +        }
       +        return "", tree.zero
       +}
       +
       +func (tree *SimpleThreadSafeTree[T]) Insert(s string, v T) T {
       +        unlock := tree.writeLock()
       +        defer unlock()
        
                tree.tree.Insert(s, v)
                return v
        }
        
       -func (tree *SimpleTree[T]) Lock(lockType LockType) func() {
       +func (tree *SimpleThreadSafeTree[T]) Lock(lockType LockType) func() {
                switch lockType {
                case LockTypeNone:
       -                return func() {}
       +                return noopFunc
                case LockTypeRead:
                        tree.mu.RLock()
                        return tree.mu.RUnlock
       @@ -78,10 +190,16 @@ func (tree *SimpleTree[T]) Lock(lockType LockType) func() {
                        tree.mu.Lock()
                        return tree.mu.Unlock
                }
       -        return func() {}
       +        return noopFunc
        }
        
       -func (tree *SimpleTree[T]) WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
       +func (tree SimpleThreadSafeTree[T]) LockTree(lockType LockType) (TreeThreadSafe[T], func()) {
       +        unlock := tree.Lock(lockType)
       +        tree.noLock = true
       +        return &tree, unlock // create a copy of tree with the noLock flag set to true.
       +}
       +
       +func (tree *SimpleThreadSafeTree[T]) WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
                commit := tree.Lock(lockType)
                defer commit()
                var err error
       @@ -96,3 +214,31 @@ func (tree *SimpleTree[T]) WalkPrefix(lockType LockType, s string, f func(s stri
        
                return err
        }
       +
       +func (tree *SimpleThreadSafeTree[T]) WalkPath(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
       +        commit := tree.Lock(lockType)
       +        defer commit()
       +        var err error
       +        tree.tree.WalkPath(s, func(s string, v any) bool {
       +                var b bool
       +                b, err = f(s, v.(T))
       +                if err != nil {
       +                        return true
       +                }
       +                return b
       +        })
       +
       +        return err
       +}
       +
       +func (tree *SimpleThreadSafeTree[T]) All(lockType LockType) iter.Seq2[string, T] {
       +        commit := tree.Lock(lockType)
       +        defer commit()
       +        return func(yield func(s string, v T) bool) {
       +                tree.tree.Walk(func(s string, v any) bool {
       +                        return !yield(s, v.(T))
       +                })
       +        }
       +}
       +
       +// iter.Seq[*TemplWithBaseApplied]
 (DIR) diff --git a/hugolib/doctree/support.go b/hugolib/doctree/support.go
       @@ -17,8 +17,6 @@ import (
                "fmt"
                "strings"
                "sync"
       -
       -        radix "github.com/armon/go-radix"
        )
        
        var _ MutableTrees = MutableTrees{}
       @@ -60,11 +58,9 @@ func (ctx *WalkContext[T]) AddPostHook(handler func() error) {
                ctx.HooksPost = append(ctx.HooksPost, handler)
        }
        
       -func (ctx *WalkContext[T]) Data() *SimpleTree[any] {
       +func (ctx *WalkContext[T]) Data() *SimpleThreadSafeTree[any] {
                ctx.dataInit.Do(func() {
       -                ctx.data = &SimpleTree[any]{
       -                        tree: radix.New(),
       -                }
       +                ctx.data = NewSimpleThreadSafeTree[any]()
                })
                return ctx.data
        }
       @@ -191,7 +187,7 @@ func (t MutableTrees) CanLock() bool {
        
        // WalkContext is passed to the Walk callback.
        type WalkContext[T any] struct {
       -        data          *SimpleTree[any]
       +        data          *SimpleThreadSafeTree[any]
                dataInit      sync.Once
                eventHandlers eventHandlers[T]
                events        []*Event[T]
 (DIR) diff --git a/hugolib/doctree/treeshifttree.go b/hugolib/doctree/treeshifttree.go
       @@ -13,7 +13,9 @@
        
        package doctree
        
       -var _ Tree[string] = (*TreeShiftTree[string])(nil)
       +import "iter"
       +
       +var _ TreeThreadSafe[string] = (*TreeShiftTree[string])(nil)
        
        type TreeShiftTree[T comparable] struct {
                // This tree is shiftable in one dimension.
       @@ -26,16 +28,16 @@ type TreeShiftTree[T comparable] struct {
                zero T
        
                // Will be of length equal to the length of the dimension.
       -        trees []*SimpleTree[T]
       +        trees []*SimpleThreadSafeTree[T]
        }
        
        func NewTreeShiftTree[T comparable](d, length int) *TreeShiftTree[T] {
                if length <= 0 {
                        panic("length must be > 0")
                }
       -        trees := make([]*SimpleTree[T], length)
       +        trees := make([]*SimpleThreadSafeTree[T], length)
                for i := range length {
       -                trees[i] = NewSimpleTree[T]()
       +                trees[i] = NewSimpleThreadSafeTree[T]()
                }
                return &TreeShiftTree[T]{d: d, trees: trees}
        }
       @@ -91,6 +93,14 @@ func (t *TreeShiftTree[T]) WalkPrefixRaw(lockType LockType, s string, f func(s s
                return nil
        }
        
       +func (t *TreeShiftTree[T]) WalkPath(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
       +        return t.trees[t.v].WalkPath(lockType, s, f)
       +}
       +
       +func (t *TreeShiftTree[T]) All(lockType LockType) iter.Seq2[string, T] {
       +        return t.trees[t.v].All(lockType)
       +}
       +
        func (t *TreeShiftTree[T]) LenRaw() int {
                var count int
                for _, tt := range t.trees {
 (DIR) diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
       @@ -134,7 +134,6 @@ func (h *HugoSites) resolveSite(lang string) *Site {
                return nil
        }
        
       -// Only used in tests.
        type buildCounters struct {
                contentRenderCounter atomic.Uint64
                pageRenderCounter    atomic.Uint64
       @@ -557,7 +556,6 @@ func (h *HugoSites) handleDataFile(r *source.File) error {
                higherPrecedentData := current[r.BaseFileName()]
        
                switch data.(type) {
       -        case nil:
                case map[string]any:
        
                        switch higherPrecedentData.(type) {
 (DIR) diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
       @@ -494,17 +494,17 @@ func (s *Site) executeDeferredTemplates(de *deps.DeferredExecutions) error {
                                        defer deferred.Mu.Unlock()
        
                                        if !deferred.Executed {
       -                                        tmpl := s.Deps.Tmpl()
       -                                        templ, found := tmpl.Lookup(deferred.TemplateName)
       -                                        if !found {
       -                                                panic(fmt.Sprintf("template %q not found", deferred.TemplateName))
       +                                        tmpl := s.Deps.GetTemplateStore()
       +                                        ti := s.TemplateStore.LookupByPath(deferred.TemplatePath)
       +                                        if ti == nil {
       +                                                panic(fmt.Sprintf("template %q not found", deferred.TemplatePath))
                                                }
        
                                                if err := func() error {
                                                        buf := bufferpool.GetBuffer()
                                                        defer bufferpool.PutBuffer(buf)
        
       -                                                err = tmpl.ExecuteWithContext(deferred.Ctx, templ, buf, deferred.Data)
       +                                                err = tmpl.ExecuteWithContext(deferred.Ctx, ti, buf, deferred.Data)
                                                        if err != nil {
                                                                return err
                                                        }
       @@ -577,9 +577,13 @@ func (h *HugoSites) printUnusedTemplatesOnce() error {
                h.printUnusedTemplatesInit.Do(func() {
                        conf := h.Configs.Base
                        if conf.PrintUnusedTemplates {
       -                        unusedTemplates := h.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
       +                        unusedTemplates := h.GetTemplateStore().UnusedTemplates()
                                for _, unusedTemplate := range unusedTemplates {
       -                                h.Log.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
       +                                if unusedTemplate.Fi != nil {
       +                                        h.Log.Warnf("Template %s is unused, source %q", unusedTemplate.PathInfo.Path(), unusedTemplate.Fi.Meta().Filename)
       +                                } else {
       +                                        h.Log.Warnf("Template %s is unused", unusedTemplate.PathInfo.Path())
       +                                }
                                }
                        }
                })
       @@ -954,7 +958,7 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
                        case files.ComponentFolderLayouts:
                                tmplChanged = true
                                templatePath := pathInfo.Unnormalized().TrimLeadingSlash().PathNoLang()
       -                        if !h.Tmpl().HasTemplate(templatePath) {
       +                        if !h.GetTemplateStore().HasTemplate(templatePath) {
                                        tmplAdded = true
                                }
        
       @@ -974,8 +978,9 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
                                        }
                                } else {
                                        logger.Println("Template changed", pathInfo.Path())
       -                                if templ, found := h.Tmpl().GetIdentity(templatePath); found {
       -                                        changes = append(changes, templ)
       +                                id := h.GetTemplateStore().GetIdentity(pathInfo.Path())
       +                                if id != nil {
       +                                        changes = append(changes, id)
                                        } else {
                                                changes = append(changes, pathInfo)
                                        }
       @@ -1084,7 +1089,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
        
                changed := &WhatChanged{
                        needsPagesAssembly: needsPagesAssemble,
       -                identitySet:        make(identity.Identities),
                }
                changed.Add(changes...)
        
       @@ -1106,17 +1110,39 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
                        }
                }
        
       -        h.Deps.OnChangeListeners.Notify(changed.Changes()...)
       +        changes2 := changed.Changes()
       +        h.Deps.OnChangeListeners.Notify(changes2...)
        
                if err := h.resolveAndClearStateForIdentities(ctx, l, cacheBusterOr, changed.Drain()); err != nil {
                        return err
                }
        
       -        if tmplChanged || i18nChanged {
       +        if tmplChanged {
                        if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
       -                        // TODO(bep) this could probably be optimized to somehow
       -                        // only load the changed templates and its dependencies, but that is non-trivial.
       +                        depsFinder := identity.NewFinder(identity.FinderConfig{})
                                ll := l.WithField("substep", "rebuild templates")
       +                        s := h.Sites[0]
       +                        if err := s.Deps.TemplateStore.RefreshFiles(func(fi hugofs.FileMetaInfo) bool {
       +                                pi := fi.Meta().PathInfo
       +                                for _, id := range changes2 {
       +                                        if depsFinder.Contains(pi, id, -1) > 0 {
       +                                                return true
       +                                        }
       +                                }
       +                                return false
       +                        }); err != nil {
       +                                return ll, err
       +                        }
       +
       +                        return ll, nil
       +                }); err != nil {
       +                        return err
       +                }
       +        }
       +
       +        if i18nChanged {
       +                if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
       +                        ll := l.WithField("substep", "rebuild i18n")
                                var prototype *deps.Deps
                                for i, s := range h.Sites {
                                        if err := s.Deps.Compile(prototype); err != nil {
 (DIR) diff --git a/hugolib/hugo_smoke_test.go b/hugolib/hugo_smoke_test.go
       @@ -76,12 +76,13 @@ Single: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {
        
        `
        
       -        b := Test(t, files)
       -
       -        b.AssertFileContent("public/index.html", `List: |/|html: /|rss: /index.xml|$`)
       -        b.AssertFileContent("public/index.xml", `List xml: |/|html: /|rss: /index.xml|$`)
       -        b.AssertFileContent("public/p1/index.html", `Single: Page|/p1/|html: /p1/|$`)
       -        b.AssertFileExists("public/p1/index.xml", false)
       +        for i := 0; i < 2; i++ {
       +                b := Test(t, files)
       +                b.AssertFileContent("public/index.html", `List: |/|html: /|rss: /index.xml|$`)
       +                b.AssertFileContent("public/index.xml", `List xml: |/|html: /|rss: /index.xml|$`)
       +                b.AssertFileContent("public/p1/index.html", `Single: Page|/p1/|html: /p1/|$`)
       +                b.AssertFileExists("public/p1/index.xml", false)
       +        }
        }
        
        func TestSmoke(t *testing.T) {
 (DIR) diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
       @@ -219,19 +219,31 @@ type IntegrationTestBuilder struct {
        
        type lockingBuffer struct {
                sync.Mutex
       -        bytes.Buffer
       +        buf bytes.Buffer
       +}
       +
       +func (b *lockingBuffer) String() string {
       +        b.Lock()
       +        defer b.Unlock()
       +        return b.buf.String()
       +}
       +
       +func (b *lockingBuffer) Reset() {
       +        b.Lock()
       +        defer b.Unlock()
       +        b.buf.Reset()
        }
        
        func (b *lockingBuffer) ReadFrom(r io.Reader) (n int64, err error) {
                b.Lock()
       -        n, err = b.Buffer.ReadFrom(r)
       +        n, err = b.buf.ReadFrom(r)
                b.Unlock()
                return
        }
        
        func (b *lockingBuffer) Write(p []byte) (n int, err error) {
                b.Lock()
       -        n, err = b.Buffer.Write(p)
       +        n, err = b.buf.Write(p)
                b.Unlock()
                return
        }
 (DIR) diff --git a/hugolib/page.go b/hugolib/page.go
       @@ -28,15 +28,13 @@ import (
                "github.com/gohugoio/hugo/identity"
                "github.com/gohugoio/hugo/media"
                "github.com/gohugoio/hugo/output"
       -        "github.com/gohugoio/hugo/output/layouts"
                "github.com/gohugoio/hugo/related"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
                "github.com/spf13/afero"
        
                "github.com/gohugoio/hugo/markup/converter"
                "github.com/gohugoio/hugo/markup/tableofcontents"
        
       -        "github.com/gohugoio/hugo/tpl"
       -
                "github.com/gohugoio/hugo/common/herrors"
                "github.com/gohugoio/hugo/common/types"
        
       @@ -116,6 +114,14 @@ type pageState struct {
                resourcesPublishInit *sync.Once
        }
        
       +func (p *pageState) incrPageOutputTemplateVariation() {
       +        p.pageOutputTemplateVariationsState.Add(1)
       +}
       +
       +func (p *pageState) canReusePageOutputContent() bool {
       +        return p.pageOutputTemplateVariationsState.Load() == 1
       +}
       +
        func (p *pageState) IdentifierBase() string {
                return p.Path()
        }
       @@ -169,10 +175,6 @@ func (p *pageState) resetBuildState() {
                // Nothing to do for now.
        }
        
       -func (p *pageState) reusePageOutputContent() bool {
       -        return p.pageOutputTemplateVariationsState.Load() == 1
       -}
       -
        func (p *pageState) skipRender() bool {
                b := p.s.conf.C.SegmentFilter.ShouldExcludeFine(
                        segments.SegmentMatcherFields{
       @@ -474,49 +476,40 @@ func (ps *pageState) initCommonProviders(pp pagePaths) error {
                return nil
        }
        
       -func (p *pageState) getLayoutDescriptor() layouts.LayoutDescriptor {
       -        p.layoutDescriptorInit.Do(func() {
       -                var section string
       -                sections := p.SectionsEntries()
       -
       -                switch p.Kind() {
       -                case kinds.KindSection:
       -                        if len(sections) > 0 {
       -                                section = sections[0]
       -                        }
       -                case kinds.KindTaxonomy, kinds.KindTerm:
       -
       -                        if p.m.singular != "" {
       -                                section = p.m.singular
       -                        } else if len(sections) > 0 {
       -                                section = sections[0]
       -                        }
       -                default:
       -                }
       -
       -                p.layoutDescriptor = layouts.LayoutDescriptor{
       -                        Kind:    p.Kind(),
       -                        Type:    p.Type(),
       -                        Lang:    p.Language().Lang,
       -                        Layout:  p.Layout(),
       -                        Section: section,
       -                }
       -        })
       -
       -        return p.layoutDescriptor
       +func (po *pageOutput) getTemplateBasePathAndDescriptor() (string, tplimpl.TemplateDescriptor) {
       +        p := po.p
       +        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,
       +        }
        }
        
       -func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) {
       -        f := p.outputFormat()
       -
       -        d := p.getLayoutDescriptor()
       +func (p *pageState) resolveTemplate(layouts ...string) (*tplimpl.TemplInfo, bool, error) {
       +        dir, d := p.getTemplateBasePathAndDescriptor()
        
                if len(layouts) > 0 {
                        d.Layout = layouts[0]
       -                d.LayoutOverride = true
       +                d.LayoutMustMatch = true
       +        }
       +
       +        q := tplimpl.TemplateQuery{
       +                Path:     dir,
       +                Category: tplimpl.CategoryLayout,
       +                Desc:     d,
       +        }
       +
       +        tinfo := p.s.TemplateStore.LookupPagesLayout(q)
       +        if tinfo == nil {
       +                return nil, false, nil
                }
        
       -        return p.s.Tmpl().LookupLayout(d, f)
       +        return tinfo, true, nil
        }
        
        // Must be run after the site section tree etc. is built and ready.
       @@ -705,7 +698,7 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
        
                if isRenderingSite {
                        cp := p.pageOutput.pco
       -                if cp == nil && p.reusePageOutputContent() {
       +                if cp == nil && p.canReusePageOutputContent() {
                                // Look for content to reuse.
                                for i := range p.pageOutputs {
                                        if i == idx {
 (DIR) diff --git a/hugolib/page__common.go b/hugolib/page__common.go
       @@ -21,7 +21,6 @@ import (
                "github.com/gohugoio/hugo/lazy"
                "github.com/gohugoio/hugo/markup/converter"
                "github.com/gohugoio/hugo/navigation"
       -        "github.com/gohugoio/hugo/output/layouts"
                "github.com/gohugoio/hugo/resources/page"
                "github.com/gohugoio/hugo/resources/resource"
                "github.com/gohugoio/hugo/source"
       @@ -86,9 +85,6 @@ type pageCommon struct {
                // should look like.
                targetPathDescriptor page.TargetPathDescriptor
        
       -        layoutDescriptor     layouts.LayoutDescriptor
       -        layoutDescriptorInit sync.Once
       -
                // Set if feature enabled and this is in a Git repo.
                gitInfo    source.GitInfo
                codeowners []string
 (DIR) diff --git a/hugolib/page__content.go b/hugolib/page__content.go
       @@ -24,6 +24,8 @@ import (
                "strings"
                "unicode/utf8"
        
       +        maps0 "maps"
       +
                "github.com/bep/logg"
                "github.com/gohugoio/hugo/common/hcontext"
                "github.com/gohugoio/hugo/common/herrors"
       @@ -32,7 +34,6 @@ import (
                "github.com/gohugoio/hugo/common/maps"
                "github.com/gohugoio/hugo/common/types/hstring"
                "github.com/gohugoio/hugo/helpers"
       -        "github.com/gohugoio/hugo/identity"
                "github.com/gohugoio/hugo/markup"
                "github.com/gohugoio/hugo/markup/converter"
                "github.com/gohugoio/hugo/markup/goldmark/hugocontext"
       @@ -45,7 +46,6 @@ import (
                "github.com/gohugoio/hugo/tpl"
                "github.com/mitchellh/mapstructure"
                "github.com/spf13/cast"
       -        maps0 "maps"
        )
        
        const (
       @@ -600,7 +600,7 @@ func (c *cachedContentScope) contentRendered(ctx context.Context) (contentSummar
                                        return nil, err
                                }
                                if hasShortcodeVariants {
       -                                cp.po.p.pageOutputTemplateVariationsState.Add(1)
       +                                cp.po.p.incrPageOutputTemplateVariation()
                                }
        
                                var result contentSummary
       @@ -684,10 +684,9 @@ func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfCont
                        if err := cp.initRenderHooks(); err != nil {
                                return nil, err
                        }
       -                f := cp.po.f
                        po := cp.po
                        p := po.p
       -                ct.contentPlaceholders, err = c.shortcodeState.prepareShortcodesForPage(ctx, p, f, false)
       +                ct.contentPlaceholders, err = c.shortcodeState.prepareShortcodesForPage(ctx, po, false)
                        if err != nil {
                                return nil, err
                        }
       @@ -701,16 +700,14 @@ func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfCont
        
                                if p.s.conf.Internal.Watch {
                                        for _, s := range cp2.po.p.m.content.shortcodeState.shortcodes {
       -                                        for _, templ := range s.templs {
       -                                                cp.trackDependency(templ.(identity.IdentityProvider))
       -                                        }
       +                                        cp.trackDependency(s.templ)
                                        }
                                }
        
                                // Transfer shortcode names so HasShortcode works for shortcodes from included pages.
                                cp.po.p.m.content.shortcodeState.transferNames(cp2.po.p.m.content.shortcodeState)
                                if cp2.po.p.pageOutputTemplateVariationsState.Load() > 0 {
       -                                cp.po.p.pageOutputTemplateVariationsState.Add(1)
       +                                cp.po.p.incrPageOutputTemplateVariation()
                                }
                        }
        
       @@ -723,7 +720,7 @@ func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfCont
                        }
        
                        if hasVariants {
       -                        p.pageOutputTemplateVariationsState.Add(1)
       +                        p.incrPageOutputTemplateVariation()
                        }
        
                        isHTML := cp.po.p.m.pageConfig.ContentMediaType.IsHTML()
       @@ -980,7 +977,7 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
                                return "", err
                        }
        
       -                placeholders, err := s.prepareShortcodesForPage(ctx, pco.po.p, pco.po.f, true)
       +                placeholders, err := s.prepareShortcodesForPage(ctx, pco.po, true)
                        if err != nil {
                                return "", err
                        }
       @@ -990,7 +987,7 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
                                return "", err
                        }
                        if hasVariants {
       -                        pco.po.p.pageOutputTemplateVariationsState.Add(1)
       +                        pco.po.p.incrPageOutputTemplateVariation()
                        }
                        b, err := pco.renderContentWithConverter(ctx, conv, contentToRender, false)
                        if err != nil {
       @@ -1028,7 +1025,7 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
                                        return "", err
                                }
                                if hasShortcodeVariants {
       -                                pco.po.p.pageOutputTemplateVariationsState.Add(1)
       +                                pco.po.p.incrPageOutputTemplateVariation()
                                }
                        }
        
       @@ -1110,7 +1107,7 @@ func (c *cachedContentScope) RenderShortcodes(ctx context.Context) (template.HTM
                }
        
                if hasVariants {
       -                pco.po.p.pageOutputTemplateVariationsState.Add(1)
       +                pco.po.p.incrPageOutputTemplateVariation()
                }
        
                if cb != nil {
 (DIR) diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
       @@ -72,8 +72,11 @@ type pageMeta struct {
        
        // Prepare for a rebuild of the data passed in from front matter.
        func (m *pageMeta) setMetaPostPrepareRebuild() {
       -        params := xmaps.Clone[map[string]any](m.paramsOriginal)
       +        params := xmaps.Clone(m.paramsOriginal)
                m.pageMetaParams.pageConfig = &pagemeta.PageConfig{
       +                Kind:   m.pageConfig.Kind,
       +                Lang:   m.pageConfig.Lang,
       +                Path:   m.pageConfig.Path,
                        Params: params,
                }
                m.pageMetaFrontMatter = pageMetaFrontMatter{}
       @@ -108,10 +111,10 @@ func (p *pageMeta) Aliases() []string {
        }
        
        func (p *pageMeta) BundleType() string {
       -        switch p.pathInfo.BundleType() {
       -        case paths.PathTypeLeaf:
       +        switch p.pathInfo.Type() {
       +        case paths.TypeLeaf:
                        return "leaf"
       -        case paths.PathTypeBranch:
       +        case paths.TypeBranch:
                        return "branch"
                default:
                        return ""
 (DIR) diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
       @@ -19,23 +19,21 @@ import (
                "errors"
                "fmt"
                "html/template"
       -        "strings"
                "sync"
                "sync/atomic"
        
                "github.com/gohugoio/hugo/common/maps"
                "github.com/gohugoio/hugo/common/text"
                "github.com/gohugoio/hugo/identity"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
                "github.com/spf13/cast"
        
                "github.com/gohugoio/hugo/markup/converter/hooks"
       -        "github.com/gohugoio/hugo/markup/highlight/chromalexers"
                "github.com/gohugoio/hugo/markup/tableofcontents"
        
                "github.com/gohugoio/hugo/markup/converter"
        
                bp "github.com/gohugoio/hugo/bufferpool"
       -        "github.com/gohugoio/hugo/tpl"
        
                "github.com/gohugoio/hugo/output"
                "github.com/gohugoio/hugo/resources/page"
       @@ -120,9 +118,9 @@ func (pco *pageContentOutput) Render(ctx context.Context, layout ...string) (tem
                }
        
                // Make sure to send the *pageState and not the *pageContentOutput to the template.
       -        res, err := executeToString(ctx, pco.po.p.s.Tmpl(), templ, pco.po.p)
       +        res, err := executeToString(ctx, pco.po.p.s.GetTemplateStore(), templ, pco.po.p)
                if err != nil {
       -                return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Name(), err))
       +                return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Template.Name(), err))
                }
                return template.HTML(res), nil
        }
       @@ -274,103 +272,100 @@ func (pco *pageContentOutput) initRenderHooks() error {
                                        return r
                                }
        
       -                        layoutDescriptor := pco.po.p.getLayoutDescriptor()
       -                        layoutDescriptor.RenderingHook = true
       -                        layoutDescriptor.LayoutOverride = false
       -                        layoutDescriptor.Layout = ""
       +                        // Inherit the descriptor from the page/current output format.
       +                        // This allows for fine-grained control of the template used for
       +                        // rendering of e.g. links.
       +                        base, layoutDescriptor := pco.po.p.getTemplateBasePathAndDescriptor()
        
                                switch tp {
                                case hooks.LinkRendererType:
       -                                layoutDescriptor.Kind = "render-link"
       +                                layoutDescriptor.Variant1 = "link"
                                case hooks.ImageRendererType:
       -                                layoutDescriptor.Kind = "render-image"
       +                                layoutDescriptor.Variant1 = "image"
                                case hooks.HeadingRendererType:
       -                                layoutDescriptor.Kind = "render-heading"
       +                                layoutDescriptor.Variant1 = "heading"
                                case hooks.PassthroughRendererType:
       -                                layoutDescriptor.Kind = "render-passthrough"
       +                                layoutDescriptor.Variant1 = "passthrough"
                                        if id != nil {
       -                                        layoutDescriptor.KindVariants = id.(string)
       +                                        layoutDescriptor.Variant2 = id.(string)
                                        }
                                case hooks.BlockquoteRendererType:
       -                                layoutDescriptor.Kind = "render-blockquote"
       +                                layoutDescriptor.Variant1 = "blockquote"
                                        if id != nil {
       -                                        layoutDescriptor.KindVariants = id.(string)
       +                                        layoutDescriptor.Variant2 = id.(string)
                                        }
                                case hooks.TableRendererType:
       -                                layoutDescriptor.Kind = "render-table"
       +                                layoutDescriptor.Variant1 = "table"
                                case hooks.CodeBlockRendererType:
       -                                layoutDescriptor.Kind = "render-codeblock"
       +                                layoutDescriptor.Variant1 = "codeblock"
                                        if id != nil {
       -                                        lang := id.(string)
       -                                        lexer := chromalexers.Get(lang)
       -                                        if lexer != nil {
       -                                                layoutDescriptor.KindVariants = strings.Join(lexer.Config().Aliases, ",")
       -                                        } else {
       -                                                layoutDescriptor.KindVariants = lang
       -                                        }
       +                                        layoutDescriptor.Variant2 = id.(string)
                                        }
                                }
        
       -                        getHookTemplate := func(f output.Format) (tpl.Template, bool) {
       -                                templ, found, err := pco.po.p.s.Tmpl().LookupLayout(layoutDescriptor, f)
       -                                if err != nil {
       -                                        panic(err)
       +                        renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
       +                        var ignoreInternal bool
       +                        switch layoutDescriptor.Variant1 {
       +                        case "link":
       +                                ignoreInternal = !renderHookConfig.Link.IsEnableDefault()
       +                        case "image":
       +                                ignoreInternal = !renderHookConfig.Image.IsEnableDefault()
       +                        }
       +
       +                        candidates := pco.po.p.s.renderFormats
       +                        var numCandidatesFound int
       +                        consider := func(candidate *tplimpl.TemplInfo) bool {
       +                                if layoutDescriptor.Variant1 != candidate.D.Variant1 {
       +                                        return false
                                        }
       -                                if found {
       -                                        if isitp, ok := templ.(tpl.IsInternalTemplateProvider); ok && isitp.IsInternalTemplate() {
       -
       -                                                renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
       -
       -                                                switch templ.Name() {
       -                                                case "_default/_markup/render-link.html":
       -                                                        if !renderHookConfig.Link.IsEnableDefault() {
       -                                                                return nil, false
       -                                                        }
       -                                                case "_default/_markup/render-image.html":
       -                                                        if !renderHookConfig.Image.IsEnableDefault() {
       -                                                                return nil, false
       -                                                        }
       -                                                }
       -                                        }
       +
       +                                if layoutDescriptor.Variant2 != "" && candidate.D.Variant2 != "" && layoutDescriptor.Variant2 != candidate.D.Variant2 {
       +                                        return false
                                        }
       -                                return templ, found
       -                        }
        
       -                        templ, found1 := getHookTemplate(pco.po.f)
       -                        if !found1 || pco.po.p.reusePageOutputContent() {
       -                                defaultOutputFormat := pco.po.p.s.conf.C.DefaultOutputFormat
       +                                if ignoreInternal && candidate.SubCategory == tplimpl.SubCategoryEmbedded {
       +                                        // Don't consider the internal hook templates.
       +                                        return false
       +                                }
        
       -                                candidates := pco.po.p.s.renderFormats
       +                                if pco.po.p.pageOutputTemplateVariationsState.Load() > 1 {
       +                                        return true
       +                                }
        
       -                                // Some hooks may only be available in HTML, and if
       -                                // this site is configured to not have HTML output, we need to
       -                                // make sure we have a fallback. This should be very rare.
       -                                if pco.po.f.MediaType.FirstSuffix.Suffix != "html" {
       -                                        if _, found := candidates.GetBySuffix("html"); !found {
       -                                                candidates = append(candidates, output.HTMLFormat)
       -                                        }
       +                                if candidate.D.OutputFormat == "" {
       +                                        numCandidatesFound++
       +                                } else if _, found := candidates.GetByName(candidate.D.OutputFormat); found {
       +                                        numCandidatesFound++
                                        }
        
       -                                // Check if some of the other output formats would give a different template.
       -                                for _, f := range candidates {
       -                                        if f.Name == pco.po.f.Name {
       -                                                continue
       -                                        }
       -                                        templ2, found2 := getHookTemplate(f)
       -
       -                                        if found2 {
       -                                                if !found1 && f.Name == defaultOutputFormat.Name {
       -                                                        templ = templ2
       -                                                        found1 = true
       -                                                        break
       -                                                }
       -
       -                                                if templ != templ2 {
       -                                                        pco.po.p.pageOutputTemplateVariationsState.Add(1)
       -                                                        break
       -                                                }
       -                                        }
       +                                return true
       +                        }
       +
       +                        getHookTemplate := func() (*tplimpl.TemplInfo, bool) {
       +                                q := tplimpl.TemplateQuery{
       +                                        Path:     base,
       +                                        Category: tplimpl.CategoryMarkup,
       +                                        Desc:     layoutDescriptor,
       +                                        Consider: consider,
                                        }
       +
       +                                v := pco.po.p.s.TemplateStore.LookupPagesLayout(q)
       +                                return v, v != nil
       +                        }
       +
       +                        templ, found1 := getHookTemplate()
       +                        if found1 && templ == nil {
       +                                panic("found1 is true, but templ is nil")
       +                        }
       +
       +                        if !found1 && layoutDescriptor.OutputFormat == pco.po.p.s.conf.DefaultOutputFormat {
       +                                numCandidatesFound++
       +                        }
       +
       +                        if numCandidatesFound > 1 {
       +                                // More than one output format candidate found for this hook temoplate,
       +                                // so we cannot reuse the same rendered content.
       +                                pco.po.p.incrPageOutputTemplateVariation()
                                }
        
                                if !found1 {
       @@ -384,7 +379,7 @@ func (pco *pageContentOutput) initRenderHooks() error {
                                }
        
                                r := hookRendererTemplate{
       -                                templateHandler: pco.po.p.s.Tmpl(),
       +                                templateHandler: pco.po.p.s.GetTemplateStore(),
                                        templ:           templ,
                                        resolvePosition: resolvePosition,
                                }
       @@ -488,7 +483,7 @@ func (t targetPathsHolder) targetPaths() page.TargetPaths {
                return t.paths
        }
        
       -func executeToString(ctx context.Context, h tpl.TemplateHandler, templ tpl.Template, data any) (string, error) {
       +func executeToString(ctx context.Context, h *tplimpl.TemplateStore, templ *tplimpl.TemplInfo, data any) (string, error) {
                b := bp.GetBuffer()
                defer bp.PutBuffer(b)
                if err := h.ExecuteWithContext(ctx, templ, b, data); err != nil {
 (DIR) diff --git a/hugolib/pages_capture.go b/hugolib/pages_capture.go
       @@ -195,7 +195,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
                                                        return id.p.Dir() == fim.Meta().PathInfo.Dir()
                                                }
        
       -                                        if fim.Meta().PathInfo.IsLeafBundle() && id.p.BundleType() == paths.PathTypeContentSingle {
       +                                        if fim.Meta().PathInfo.IsLeafBundle() && id.p.Type() == paths.TypeContentSingle {
                                                        return id.p.Dir() == fim.Meta().PathInfo.Dir()
                                                }
        
       @@ -314,7 +314,7 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
                                return nil, filepath.SkipDir
                        }
        
       -                seen := map[hstrings.Tuple]bool{}
       +                seen := map[hstrings.Strings2]hugofs.FileMetaInfo{}
                        for _, fi := range readdir {
                                if fi.IsDir() {
                                        continue
       @@ -327,11 +327,14 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
                                // These would eventually have been filtered out as duplicates when
                                // inserting them into the document store,
                                // but doing it here will preserve a consistent ordering.
       -                        baseLang := hstrings.Tuple{First: pi.Base(), Second: meta.Lang}
       -                        if seen[baseLang] {
       +                        baseLang := hstrings.Strings2{pi.Base(), meta.Lang}
       +                        if fi2, ok := seen[baseLang]; ok {
       +                                if c.h.Configs.Base.PrintPathWarnings && !c.h.isRebuild() {
       +                                        c.logger.Warnf("Duplicate content path: %q file: %q file: %q", pi.Base(), fi2.Meta().Filename, meta.Filename)
       +                                }
                                        continue
                                }
       -                        seen[baseLang] = true
       +                        seen[baseLang] = fi
        
                                if pi == nil {
                                        panic(fmt.Sprintf("no path info for %q", meta.Filename))
       @@ -374,7 +377,7 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
        
        func (c *pagesCollector) handleBundleLeaf(dir, bundle hugofs.FileMetaInfo, inPath string, readdir []hugofs.FileMetaInfo) error {
                bundlePi := bundle.Meta().PathInfo
       -        seen := map[hstrings.Tuple]bool{}
       +        seen := map[hstrings.Strings2]bool{}
        
                walk := func(path string, info hugofs.FileMetaInfo) error {
                        if info.IsDir() {
       @@ -396,7 +399,7 @@ func (c *pagesCollector) handleBundleLeaf(dir, bundle hugofs.FileMetaInfo, inPat
                        // These would eventually have been filtered out as duplicates when
                        // inserting them into the document store,
                        // but doing it here will preserve a consistent ordering.
       -                baseLang := hstrings.Tuple{First: pi.Base(), Second: info.Meta().Lang}
       +                baseLang := hstrings.Strings2{pi.Base(), info.Meta().Lang}
                        if seen[baseLang] {
                                return nil
                        }
 (DIR) diff --git a/hugolib/pagesfromdata/pagesfromgotmpl.go b/hugolib/pagesfromdata/pagesfromgotmpl.go
       @@ -29,6 +29,7 @@ import (
                "github.com/gohugoio/hugo/resources/page/pagemeta"
                "github.com/gohugoio/hugo/resources/resource"
                "github.com/gohugoio/hugo/tpl"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
                "github.com/mitchellh/mapstructure"
                "github.com/spf13/cast"
        )
       @@ -167,8 +168,7 @@ type PagesFromTemplateOptions struct {
        }
        
        type PagesFromTemplateDeps struct {
       -        TmplFinder tpl.TemplateParseFinder
       -        TmplExec   tpl.TemplateExecutor
       +        TemplateStore *tplimpl.TemplateStore
        }
        
        var _ resource.Staler = (*PagesFromTemplate)(nil)
       @@ -303,7 +303,7 @@ func (p *PagesFromTemplate) Execute(ctx context.Context) (BuildInfo, error) {
                }
                defer f.Close()
        
       -        tmpl, err := p.TmplFinder.Parse(filepath.ToSlash(p.GoTmplFi.Meta().Filename), helpers.ReaderToString(f))
       +        tmpl, err := p.TemplateStore.TextParse(filepath.ToSlash(p.GoTmplFi.Meta().Filename), helpers.ReaderToString(f))
                if err != nil {
                        return BuildInfo{}, err
                }
       @@ -314,7 +314,7 @@ func (p *PagesFromTemplate) Execute(ctx context.Context) (BuildInfo, error) {
        
                ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, p)
        
       -        if err := p.TmplExec.ExecuteWithContext(ctx, tmpl, io.Discard, data); err != nil {
       +        if err := p.TemplateStore.ExecuteWithContext(ctx, tmpl, io.Discard, data); err != nil {
                        return BuildInfo{}, err
                }
        
 (DIR) diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
       @@ -98,7 +98,8 @@ ADD_MORE_PLACEHOLDER
        
        func TestPagesFromGoTmplMisc(t *testing.T) {
                t.Parallel()
       -        b := hugolib.Test(t, filesPagesFromDataTempleBasic)
       +        b := hugolib.Test(t, filesPagesFromDataTempleBasic, hugolib.TestOptWarn())
       +        b.AssertLogContains("! WARN")
                b.AssertPublishDir(`
        docs/p1/mytext.txt
        docs/p1/sub/mytex2.tx
 (DIR) diff --git a/hugolib/paginator_test.go b/hugolib/paginator_test.go
       @@ -15,7 +15,6 @@ package hugolib
        
        import (
                "fmt"
       -        "path/filepath"
                "testing"
        
                qt "github.com/frankban/quicktest"
       @@ -102,10 +101,18 @@ URL: {{ $pag.URL }}
        
        // Issue 6023
        func TestPaginateWithSort(t *testing.T) {
       -        b := newTestSitesBuilder(t).WithSimpleConfigFile()
       -        b.WithTemplatesAdded("index.html", `{{ range (.Paginate (sort .Site.RegularPages ".File.Filename" "desc")).Pages }}|{{ .File.Filename }}{{ end }}`)
       -        b.Build(BuildCfg{}).AssertFileContent("public/index.html",
       -                filepath.FromSlash("|content/sect/doc1.nn.md|content/sect/doc1.nb.md|content/sect/doc1.fr.md|content/sect/doc1.en.md"))
       +        files := `
       +-- hugo.toml --
       +-- content/a/a.md --
       +-- content/z/b.md --
       +-- content/x/b.md --
       +-- content/x/a.md --
       +-- layouts/home.html --
       +Paginate: {{ range (.Paginate (sort .Site.RegularPages ".File.Filename" "desc")).Pages }}|{{ .Path }}{{ end }}
       +`
       +        b := Test(t, files)
       +
       +        b.AssertFileContent("public/index.html", "Paginate: |/z/b|/x/b|/x/a|/a/a")
        }
        
        // https://github.com/gohugoio/hugo/issues/6797
       @@ -176,12 +183,12 @@ Paginator: {{ .Paginator }}
        
        func TestNilPointerErrorMessage(t *testing.T) {
                files := `
       --- hugo.toml --
       +-- hugo.toml --        
        -- content/p1.md --
        -- layouts/_default/single.html --
        Home Filename: {{ site.Home.File.Filename }}
        `
                b, err := TestE(t, files)
                b.Assert(err, qt.IsNotNil)
       -        b.Assert(err.Error(), qt.Contains, `_default/single.html:1:22: executing "_default/single.html" – File is nil; wrap it in if or with: {{ with site.Home.File }}{{ .Filename }}{{ end }}`)
       +        b.Assert(err.Error(), qt.Contains, `single.html:1:22: executing "single.html" – File is nil; wrap it in if or with: {{ with site.Home.File }}{{ .Filename }}{{ end }}`)
        }
 (DIR) diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go
       @@ -51,6 +51,7 @@ My Section Bundle Content Content.
        title: "My Section"
        ---
        -- content/mysection/mysectiontext.txt --
       +Content.
        -- content/_index.md --
        ---
        title: "Home"
       @@ -99,15 +100,17 @@ My Other Text: {{ $r.Content }}|{{ $r.Permalink }}|
        `
        
        func TestRebuildEditLeafBundleHeaderOnly(t *testing.T) {
       -        b := TestRunning(t, rebuildFilesSimple)
       -        b.AssertFileContent("public/mysection/mysectionbundle/index.html",
       -                "My Section Bundle Content Content.")
       -
       -        b.EditFileReplaceAll("content/mysection/mysectionbundle/index.md", "My Section Bundle Content.", "My Section Bundle Content Edited.").Build()
       -        b.AssertFileContent("public/mysection/mysectionbundle/index.html",
       -                "My Section Bundle Content Edited.")
       -        b.AssertRenderCountPage(2) // home (rss) + bundle.
       -        b.AssertRenderCountContent(1)
       +        t.Parallel()
       +        for i := 0; i < 3; i++ {
       +                b := TestRunning(t, rebuildFilesSimple)
       +                b.AssertFileContent("public/mysection/mysectionbundle/index.html",
       +                        "My Section Bundle Content Content.")
       +                b.EditFileReplaceAll("content/mysection/mysectionbundle/index.md", "My Section Bundle Content.", "My Section Bundle Content Edited.").Build()
       +                b.AssertFileContent("public/mysection/mysectionbundle/index.html",
       +                        "My Section Bundle Content Edited.")
       +                b.AssertRenderCountPage(2) // home (rss) + bundle.
       +                b.AssertRenderCountContent(1)
       +        }
        }
        
        func TestRebuildEditTextFileInLeafBundle(t *testing.T) {
       @@ -119,7 +122,7 @@ func TestRebuildEditTextFileInLeafBundle(t *testing.T) {
                b.AssertFileContent("public/mysection/mysectionbundle/index.html",
                        "Text 2 Content Edited")
                b.AssertRenderCountPage(1)
       -        b.AssertRenderCountContent(1)
       +        b.AssertRenderCountContent(0)
        }
        
        func TestRebuildEditTextFileInShortcode(t *testing.T) {
       @@ -180,17 +183,17 @@ func TestRebuildEditTextFileInHomeBundle(t *testing.T) {
                b.AssertFileContent("public/index.html", "Home Content.")
                b.AssertFileContent("public/index.html", "Home Text Content Edited.")
                b.AssertRenderCountPage(1)
       -        b.AssertRenderCountContent(1)
       +        b.AssertRenderCountContent(0)
        }
        
        func TestRebuildEditTextFileInBranchBundle(t *testing.T) {
                b := TestRunning(t, rebuildFilesSimple)
       -        b.AssertFileContent("public/mysection/index.html", "My Section")
       +        b.AssertFileContent("public/mysection/index.html", "My Section", "0:/mysection/mysectiontext.txt|Content.|")
        
                b.EditFileReplaceAll("content/mysection/mysectiontext.txt", "Content.", "Content Edited.").Build()
       -        b.AssertFileContent("public/mysection/index.html", "My Section")
       +        b.AssertFileContent("public/mysection/index.html", "My Section", "0:/mysection/mysectiontext.txt|Content Edited.|")
                b.AssertRenderCountPage(1)
       -        b.AssertRenderCountContent(1)
       +        b.AssertRenderCountContent(0)
        }
        
        func testRebuildBothWatchingAndRunning(t *testing.T, files string, withB func(b *IntegrationTestBuilder)) {
       @@ -484,7 +487,43 @@ Home: {{ .Title }}|{{ .Content }}|
                })
        }
        
       -func TestRebuildSingleWithBaseof(t *testing.T) {
       +func TestRebuildSingle(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +title = "Hugo Site"
       +baseURL = "https://example.com"
       +disableKinds = ["term", "taxonomy", "sitemap", "robotstxt", "404"]
       +disableLiveReload = true
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +P1 Content.
       +-- layouts/index.html --
       +Home.
       +-- layouts/single.html --
       +Single: {{ .Title }}|{{ .Content }}|
       +{{ with (templates.Defer (dict "key" "global")) }}
       +Defer.
       +{{ end }}
       +`
       +        b := Test(t, files, TestOptRunning())
       +        b.AssertFileContent("public/p1/index.html", "Single: P1|", "Defer.")
       +        b.AssertRenderCountPage(3)
       +        b.AssertRenderCountContent(1)
       +        b.EditFileReplaceFunc("layouts/single.html", func(s string) string {
       +                s = strings.Replace(s, "Single", "Single Edited", 1)
       +                s = strings.Replace(s, "Defer.", "Defer Edited.", 1)
       +                return s
       +        }).Build()
       +        b.AssertFileContent("public/p1/index.html", "Single Edited: P1|", "Defer Edited.")
       +        b.AssertRenderCountPage(1)
       +        b.AssertRenderCountContent(0)
       +}
       +
       +func TestRebuildSingleWithBaseofEditSingle(t *testing.T) {
                t.Parallel()
        
                files := `
       @@ -498,9 +537,13 @@ disableLiveReload = true
        title: "P1"
        ---
        P1 Content.
       +[foo](/foo)
        -- layouts/_default/baseof.html --
        Baseof: {{ .Title }}|
        {{ block "main" . }}default{{ end }}
       +{{ with (templates.Defer (dict "foo" "bar")) }}
       +Defer.
       +{{ end }}
        -- layouts/index.html --
        Home.
        -- layouts/_default/single.html --
       @@ -509,11 +552,81 @@ Single: {{ .Title }}|{{ .Content }}|
        {{ end }}
        `
                b := Test(t, files, TestOptRunning())
       -        b.AssertFileContent("public/p1/index.html", "Baseof: P1|\n\nSingle: P1|<p>P1 Content.</p>\n|")
       +        b.AssertFileContent("public/p1/index.html", "Single: P1|")
                b.EditFileReplaceFunc("layouts/_default/single.html", func(s string) string {
                        return strings.Replace(s, "Single", "Single Edited", 1)
                }).Build()
       -        b.AssertFileContent("public/p1/index.html", "Baseof: P1|\n\nSingle Edited: P1|<p>P1 Content.</p>\n|")
       +        b.AssertFileContent("public/p1/index.html", "Single Edited")
       +}
       +
       +func TestRebuildSingleWithBaseofEditBaseof(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +title = "Hugo Site"
       +baseURL = "https://example.com"
       +disableKinds = ["term", "taxonomy"]
       +disableLiveReload = true
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +P1 Content.
       +[foo](/foo)
       +-- layouts/_default/baseof.html --
       +Baseof: {{ .Title }}|
       +{{ block "main" . }}default{{ end }}
       +{{ with (templates.Defer (dict "foo" "bar")) }}
       +Defer.
       +{{ end }}
       +-- layouts/index.html --
       +Home.
       +-- layouts/_default/single.html --
       +{{ define "main" }}
       +Single: {{ .Title }}|{{ .Content }}|
       +{{ end }}
       +`
       +        b := Test(t, files, TestOptRunning())
       +        b.AssertFileContent("public/p1/index.html", "Single: P1|")
       +        fmt.Println("===============")
       +        b.EditFileReplaceAll("layouts/_default/baseof.html", "Baseof", "Baseof Edited").Build()
       +        b.AssertFileContent("public/p1/index.html", "Baseof Edited")
       +}
       +
       +func TestRebuildWithDeferEditRenderHook(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +title = "Hugo Site"
       +baseURL = "https://example.com"
       +disableKinds = ["term", "taxonomy"]
       +disableLiveReload = true
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +P1 Content.
       +[foo](/foo)
       +-- layouts/_default/baseof.html --
       +Baseof: {{ .Title }}|
       +{{ block "main" . }}default{{ end }}
       + {{ with (templates.Defer (dict "foo" "bar")) }}
       +Defer.
       +{{ end }}
       +-- layouts/single.html --
       +{{ define "main" }}
       +Single: {{ .Title }}|{{ .Content }}|
       +{{ end }}
       +-- layouts/_default/_markup/render-link.html --
       +Render Link.
       +`
       +        b := Test(t, files, TestOptRunning())
       +        // Edit render hook.
       +        b.EditFileReplaceAll("layouts/_default/_markup/render-link.html", "Render Link", "Render Link Edited").Build()
       +
       +        b.AssertFileContent("public/p1/index.html", "Render Link Edited")
        }
        
        func TestRebuildFromString(t *testing.T) {
 (DIR) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
       @@ -1,4 +1,4 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       +// Copyright 2025 The Hugo Authors. All rights reserved.
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
       @@ -29,6 +29,7 @@ import (
        
                "github.com/gohugoio/hugo/common/herrors"
                "github.com/gohugoio/hugo/common/types"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
        
                "github.com/gohugoio/hugo/parser/pageparser"
                "github.com/gohugoio/hugo/resources/page"
       @@ -36,7 +37,6 @@ import (
                "github.com/gohugoio/hugo/common/maps"
                "github.com/gohugoio/hugo/common/text"
                "github.com/gohugoio/hugo/common/urls"
       -        "github.com/gohugoio/hugo/output"
        
                bp "github.com/gohugoio/hugo/bufferpool"
                "github.com/gohugoio/hugo/tpl"
       @@ -205,8 +205,7 @@ type shortcode struct {
        
                indentation string // indentation from source.
        
       -        info   tpl.Info       // One of the output formats (arbitrary)
       -        templs []tpl.Template // All output formats
       +        templ *tplimpl.TemplInfo
        
                // If set, the rendered shortcode is sent as part of the surrounding content
                // to Goldmark and similar.
       @@ -230,16 +229,15 @@ func (s shortcode) insertPlaceholder() bool {
        }
        
        func (s shortcode) needsInner() bool {
       -        return s.info != nil && s.info.ParseInfo().IsInner
       +        return s.templ != nil && s.templ.ParseInfo.IsInner
        }
        
        func (s shortcode) configVersion() int {
       -        if s.info == nil {
       +        if s.templ == nil {
                        // Not set for inline shortcodes.
                        return 2
                }
       -
       -        return s.info.ParseInfo().Config.Version
       +        return s.templ.ParseInfo.Config.Version
        }
        
        func (s shortcode) innerString() string {
       @@ -315,12 +313,12 @@ func prepareShortcode(
                ctx context.Context,
                level int,
                s *Site,
       -        tplVariants tpl.TemplateVariants,
                sc *shortcode,
                parent *ShortcodeWithPage,
       -        p *pageState,
       +        po *pageOutput,
                isRenderString bool,
        ) (shortcodeRenderer, error) {
       +        p := po.p
                toParseErr := func(err error) error {
                        source := p.m.content.mustSource()
                        return p.parseError(fmt.Errorf("failed to render shortcode %q: %w", sc.name, err), source, sc.pos)
       @@ -333,7 +331,7 @@ func prepareShortcode(
                                // parsed and rendered by Goldmark.
                                ctx = tpl.Context.IsInGoldmark.Set(ctx, true)
                        }
       -                r, err := doRenderShortcode(ctx, level, s, tplVariants, sc, parent, p, isRenderString)
       +                r, err := doRenderShortcode(ctx, level, s, sc, parent, po, isRenderString)
                        if err != nil {
                                return nil, false, toParseErr(err)
                        }
       @@ -352,30 +350,29 @@ func doRenderShortcode(
                ctx context.Context,
                level int,
                s *Site,
       -        tplVariants tpl.TemplateVariants,
                sc *shortcode,
                parent *ShortcodeWithPage,
       -        p *pageState,
       +        po *pageOutput,
                isRenderString bool,
        ) (shortcodeRenderer, error) {
       -        var tmpl tpl.Template
       +        var tmpl *tplimpl.TemplInfo
       +        p := po.p
        
                // Tracks whether this shortcode or any of its children has template variations
                // in other languages or output formats. We are currently only interested in
       -        // the output formats, so we may get some false positives -- we
       -        // should improve on that.
       +        // the output formats.
                var hasVariants bool
        
                if sc.isInline {
                        if !p.s.ExecHelper.Sec().EnableInlineShortcodes {
                                return zeroShortcode, nil
                        }
       -                templName := path.Join("_inline_shortcode", p.Path(), sc.name)
       +                templatePath := path.Join("_inline_shortcode", p.Path(), sc.name)
                        if sc.isClosing {
                                templStr := sc.innerString()
        
                                var err error
       -                        tmpl, err = s.TextTmpl().Parse(templName, templStr)
       +                        tmpl, err = s.TemplateStore.TextParse(templatePath, templStr)
                                if err != nil {
                                        if isRenderString {
                                                return zeroShortcode, p.wrapError(err)
       @@ -389,21 +386,32 @@ func doRenderShortcode(
        
                        } else {
                                // Re-use of shortcode defined earlier in the same page.
       -                        var found bool
       -                        tmpl, found = s.TextTmpl().Lookup(templName)
       -                        if !found {
       +                        tmpl = s.TemplateStore.TextLookup(templatePath)
       +                        if tmpl == nil {
                                        return zeroShortcode, fmt.Errorf("no earlier definition of shortcode %q found", sc.name)
                                }
                        }
       -                tmpl = tpl.AddIdentity(tmpl)
                } else {
       -                var found, more bool
       -                tmpl, found, more = s.Tmpl().LookupVariant(sc.name, tplVariants)
       -                if !found {
       +                ofCount := map[string]int{}
       +                include := func(match *tplimpl.TemplInfo) bool {
       +                        ofCount[match.D.OutputFormat]++
       +                        return true
       +                }
       +                base, layoutDescriptor := po.getTemplateBasePathAndDescriptor()
       +                q := tplimpl.TemplateQuery{
       +                        Path:     base,
       +                        Name:     sc.name,
       +                        Category: tplimpl.CategoryShortcode,
       +                        Desc:     layoutDescriptor,
       +                        Consider: include,
       +                }
       +                v := s.TemplateStore.LookupShortcode(q)
       +                if v == nil {
                                s.Log.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path())
                                return zeroShortcode, nil
                        }
       -                hasVariants = hasVariants || more
       +                tmpl = v
       +                hasVariants = hasVariants || len(ofCount) > 1
                }
        
                data := &ShortcodeWithPage{
       @@ -427,7 +435,7 @@ func doRenderShortcode(
                                case string:
                                        inner += innerData
                                case *shortcode:
       -                                s, err := prepareShortcode(ctx, level+1, s, tplVariants, innerData, data, p, isRenderString)
       +                                s, err := prepareShortcode(ctx, level+1, s, innerData, data, po, isRenderString)
                                        if err != nil {
                                                return zeroShortcode, err
                                        }
       @@ -484,7 +492,7 @@ func doRenderShortcode(
        
                }
        
       -        result, err := renderShortcodeWithPage(ctx, s.Tmpl(), tmpl, data)
       +        result, err := renderShortcodeWithPage(ctx, s.GetTemplateStore(), tmpl, data)
        
                if err != nil && sc.isInline {
                        fe := herrors.NewFileErrorFromName(err, p.File().Filename())
       @@ -534,16 +542,11 @@ func (s *shortcodeHandler) hasName(name string) bool {
                return ok
        }
        
       -func (s *shortcodeHandler) prepareShortcodesForPage(ctx context.Context, p *pageState, f output.Format, isRenderString bool) (map[string]shortcodeRenderer, error) {
       +func (s *shortcodeHandler) prepareShortcodesForPage(ctx context.Context, po *pageOutput, isRenderString bool) (map[string]shortcodeRenderer, error) {
                rendered := make(map[string]shortcodeRenderer)
        
       -        tplVariants := tpl.TemplateVariants{
       -                Language:     p.Language().Lang,
       -                OutputFormat: f,
       -        }
       -
                for _, v := range s.shortcodes {
       -                s, err := prepareShortcode(ctx, 0, s.s, tplVariants, v, nil, p, isRenderString)
       +                s, err := prepareShortcode(ctx, 0, s.s, v, nil, po, isRenderString)
                        if err != nil {
                                return nil, err
                        }
       @@ -636,7 +639,7 @@ Loop:
                                // we trust the template on this:
                                // if there's no inner, we're done
                                if !sc.isInline {
       -                                if !sc.info.ParseInfo().IsInner {
       +                                if !sc.templ.ParseInfo.IsInner {
                                                return sc, nil
                                        }
                                }
       @@ -672,14 +675,19 @@ Loop:
        
                                sc.name = currItem.ValStr(source)
        
       -                        // Used to check if the template expects inner content.
       -                        templs := s.s.Tmpl().LookupVariants(sc.name)
       -                        if templs == nil {
       +                        // Used to check if the template expects inner content,
       +                        // so just pick one arbitrarily with the same name.
       +                        q := tplimpl.TemplateQuery{
       +                                Path:     "",
       +                                Name:     sc.name,
       +                                Category: tplimpl.CategoryShortcode,
       +                                Consider: nil,
       +                        }
       +                        templ := s.s.TemplateStore.LookupShortcode(q)
       +                        if templ == nil {
                                        return nil, fmt.Errorf("%s: template for shortcode %q not found", errorPrefix, sc.name)
                                }
       -
       -                        sc.info = templs[0].(tpl.Info)
       -                        sc.templs = templs
       +                        sc.templ = templ
                        case currItem.IsInlineShortcodeName():
                                sc.name = currItem.ValStr(source)
                                sc.isInline = true
       @@ -778,7 +786,7 @@ func expandShortcodeTokens(
                return source, nil
        }
        
       -func renderShortcodeWithPage(ctx context.Context, h tpl.TemplateHandler, tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
       +func renderShortcodeWithPage(ctx context.Context, h *tplimpl.TemplateStore, tmpl *tplimpl.TemplInfo, data *ShortcodeWithPage) (string, error) {
                buffer := bp.GetBuffer()
                defer bp.PutBuffer(buffer)
        
 (DIR) diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
       @@ -33,14 +33,14 @@ func TestExtractShortcodes(t *testing.T) {
                b := newTestSitesBuilder(t).WithSimpleConfigFile()
        
                b.WithTemplates(
       -                "default/single.html", `EMPTY`,
       -                "_internal/shortcodes/tag.html", `tag`,
       -                "_internal/shortcodes/legacytag.html", `{{ $_hugo_config := "{ \"version\": 1 }" }}tag`,
       -                "_internal/shortcodes/sc1.html", `sc1`,
       -                "_internal/shortcodes/sc2.html", `sc2`,
       -                "_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`,
       -                "_internal/shortcodes/inner2.html", `{{.Inner}}`,
       -                "_internal/shortcodes/inner3.html", `{{.Inner}}`,
       +                "pages/single.html", `EMPTY`,
       +                "shortcodes/tag.html", `tag`,
       +                "shortcodes/legacytag.html", `{{ $_hugo_config := "{ \"version\": 1 }" }}tag`,
       +                "shortcodes/sc1.html", `sc1`,
       +                "shortcodes/sc2.html", `sc2`,
       +                "shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`,
       +                "shortcodes/inner2.html", `{{.Inner}}`,
       +                "shortcodes/inner3.html", `{{.Inner}}`,
                ).WithContent("page.md", `---
        title: "Shortcodes Galore!"
        ---
       @@ -57,10 +57,9 @@ title: "Shortcodes Galore!"
                        if s == nil {
                                return "<nil>"
                        }
       -
                        var version int
       -                if s.info != nil {
       -                        version = s.info.ParseInfo().Config.Version
       +                if s.templ != nil {
       +                        version = s.templ.ParseInfo.Config.Version
                        }
                        return strReplacer.Replace(fmt.Sprintf("%s;inline:%t;closing:%t;inner:%v;params:%v;ordinal:%d;markup:%t;version:%d;pos:%d",
                                s.name, s.isInline, s.isClosing, s.inner, s.params, s.ordinal, s.doMarkup, version, s.pos))
       @@ -69,7 +68,7 @@ title: "Shortcodes Galore!"
                regexpCheck := func(re string) func(c *qt.C, shortcode *shortcode, err error) {
                        return func(c *qt.C, shortcode *shortcode, err error) {
                                c.Assert(err, qt.IsNil)
       -                        c.Assert(str(shortcode), qt.Matches, ".*"+re+".*")
       +                        c.Assert(str(shortcode), qt.Matches, ".*"+re+".*", qt.Commentf("%s", shortcode.name))
                        }
                }
        
       @@ -888,6 +887,7 @@ outputs: ["html", "css", "csv", "json"]
                                "_default/single.json", "{{ .Content }}",
                                "shortcodes/myshort.html", `Short-HTML`,
                                "shortcodes/myshort.csv", `Short-CSV`,
       +                        "shortcodes/myshort.txt", `Short-TXT`,
                        )
        
                        b.Build(BuildCfg{})
       @@ -897,12 +897,12 @@ outputs: ["html", "css", "csv", "json"]
                        for i := range numPages {
                                b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i), "Short-HTML")
                                b.AssertFileContent(fmt.Sprintf("public/page%d/index.csv", i), "Short-CSV")
       -                        b.AssertFileContent(fmt.Sprintf("public/page%d/index.json", i), "Short-HTML")
       +                        b.AssertFileContent(fmt.Sprintf("public/page%d/index.json", i), "Short-CSV")
        
                        }
        
                        for i := range numPages {
       -                        b.AssertFileContent(fmt.Sprintf("public/page%d/styles.css", i), "Short-HTML")
       +                        b.AssertFileContent(fmt.Sprintf("public/page%d/styles.css", i), "Short-CSV")
                        }
        
                }
 (DIR) diff --git a/hugolib/site.go b/hugolib/site.go
       @@ -47,7 +47,13 @@ import (
                "github.com/gohugoio/hugo/langs/i18n"
                "github.com/gohugoio/hugo/modules"
                "github.com/gohugoio/hugo/resources"
       +
                "github.com/gohugoio/hugo/tpl/tplimpl"
       +        "github.com/gohugoio/hugo/tpl/tplimplinit"
       +        xmaps "golang.org/x/exp/maps"
       +
       +        // Loads the template funcs namespaces.
       +
                "golang.org/x/text/unicode/norm"
        
                "github.com/gohugoio/hugo/common/paths"
       @@ -188,8 +194,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                        BuildState: &deps.BuildState{
                                OnSignalRebuild: onSignalRebuild,
                        },
       +                Counters:            &deps.Counters{},
                        MemCache:            memCache,
       -                TemplateProvider:    tplimpl.DefaultTemplateProvider,
                        TranslationProvider: i18n.NewTranslationProvider(),
                        WasmDispatchers: warpc.AllDispatchers(
                                warpc.Options{
       @@ -385,6 +391,34 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
                var prototype *deps.Deps
                for i, s := range sites {
                        s.h = h
       +                // The template store needs to be initialized after the h container is set on s.
       +                if i == 0 {
       +                        templateStore, err := tplimpl.NewStore(
       +                                tplimpl.StoreOptions{
       +                                        Fs:                     s.BaseFs.Layouts.Fs,
       +                                        DefaultContentLanguage: s.Conf.DefaultContentLanguage(),
       +                                        Watching:               s.Conf.Watching(),
       +                                        PathParser:             s.Conf.PathParser(),
       +                                        Metrics:                d.Metrics,
       +                                        OutputFormats:          s.conf.OutputFormats.Config,
       +                                        MediaTypes:             s.conf.MediaTypes.Config,
       +                                        DefaultOutputFormat:    s.conf.DefaultOutputFormat,
       +                                        TaxonomySingularPlural: s.conf.Taxonomies,
       +                                }, tplimpl.SiteOptions{
       +                                        Site:          s,
       +                                        TemplateFuncs: tplimplinit.CreateFuncMap(s.Deps),
       +                                })
       +                        if err != nil {
       +                                return nil, err
       +                        }
       +                        s.Deps.TemplateStore = templateStore
       +                } else {
       +                        s.Deps.TemplateStore = prototype.TemplateStore.WithSiteOpts(
       +                                tplimpl.SiteOptions{
       +                                        Site:          s,
       +                                        TemplateFuncs: tplimplinit.CreateFuncMap(s.Deps),
       +                                })
       +                }
                        if err := s.Deps.Compile(prototype); err != nil {
                                return nil, err
                        }
       @@ -464,7 +498,10 @@ func (s *Site) MainSections() []string {
        
        // Returns a struct with some information about the build.
        func (s *Site) Hugo() hugo.HugoInfo {
       -        if s.h == nil || s.h.hugoInfo.Environment == "" {
       +        if s.h == nil {
       +                panic("site: hugo: h not initialized")
       +        }
       +        if s.h.hugoInfo.Environment == "" {
                        panic("site: hugo: hugoInfo not initialized")
                }
                return s.h.hugoInfo
       @@ -797,7 +834,7 @@ func (s *Site) initRenderFormats() {
                s.renderFormats = formats
        }
        
       -func (s *Site) GetRelatedDocsHandler() *page.RelatedDocsHandler {
       +func (s *Site) GetInternalRelatedDocsHandler() *page.RelatedDocsHandler {
                return s.relatedDocsHandler
        }
        
       @@ -923,19 +960,24 @@ type WhatChanged struct {
                mu sync.Mutex
        
                needsPagesAssembly bool
       -        identitySet        identity.Identities
       +
       +        ids map[identity.Identity]bool
       +}
       +
       +func (w *WhatChanged) init() {
       +        if w.ids == nil {
       +                w.ids = make(map[identity.Identity]bool)
       +        }
        }
        
        func (w *WhatChanged) Add(ids ...identity.Identity) {
                w.mu.Lock()
                defer w.mu.Unlock()
        
       -        if w.identitySet == nil {
       -                w.identitySet = make(identity.Identities)
       -        }
       +        w.init()
        
                for _, id := range ids {
       -                w.identitySet[id] = true
       +                w.ids[id] = true
                }
        }
        
       @@ -946,20 +988,20 @@ func (w *WhatChanged) Clear() {
        }
        
        func (w *WhatChanged) clear() {
       -        w.identitySet = identity.Identities{}
       +        w.ids = nil
        }
        
        func (w *WhatChanged) Changes() []identity.Identity {
       -        if w == nil || w.identitySet == nil {
       +        if w == nil || w.ids == nil {
                        return nil
                }
       -        return w.identitySet.AsSlice()
       +        return xmaps.Keys(w.ids)
        }
        
        func (w *WhatChanged) Drain() []identity.Identity {
                w.mu.Lock()
                defer w.mu.Unlock()
       -        ids := w.identitySet.AsSlice()
       +        ids := w.Changes()
                w.clear()
                return ids
        }
       @@ -1394,7 +1436,7 @@ const (
                pageDependencyScopeGlobal
        )
        
       -func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, d any, templ tpl.Template) error {
       +func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, d any, templ *tplimpl.TemplInfo) error {
                s.h.buildCounters.pageRenderCounter.Add(1)
                renderBuffer := bp.GetBuffer()
                defer bp.PutBuffer(renderBuffer)
       @@ -1453,8 +1495,8 @@ var infoOnMissingLayout = map[string]bool{
        // hookRendererTemplate is the canonical implementation of all hooks.ITEMRenderer,
        // where ITEM is the thing being hooked.
        type hookRendererTemplate struct {
       -        templateHandler tpl.TemplateHandler
       -        templ           tpl.Template
       +        templateHandler *tplimpl.TemplateStore
       +        templ           *tplimpl.TemplInfo
                resolvePosition func(ctx any) text.Position
        }
        
       @@ -1490,7 +1532,7 @@ func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool {
                return false
        }
        
       -func (s *Site) renderForTemplate(ctx context.Context, name, outputFormat string, d any, w io.Writer, templ tpl.Template) (err error) {
       +func (s *Site) renderForTemplate(ctx context.Context, name, outputFormat string, d any, w io.Writer, templ *tplimpl.TemplInfo) (err error) {
                if templ == nil {
                        s.logMissingLayout(name, "", "", outputFormat)
                        return nil
       @@ -1500,7 +1542,7 @@ func (s *Site) renderForTemplate(ctx context.Context, name, outputFormat string,
                        panic("nil context")
                }
        
       -        if err = s.Tmpl().ExecuteWithContext(ctx, templ, w, d); err != nil {
       +        if err = s.GetTemplateStore().ExecuteWithContext(ctx, templ, w, d); err != nil {
                        filename := name
                        if p, ok := d.(*pageState); ok {
                                filename = p.String()
 (DIR) diff --git a/hugolib/site_output.go b/hugolib/site_output.go
       @@ -27,6 +27,7 @@ func createDefaultOutputFormats(allFormats output.Formats) map[string]output.For
                htmlOut, _ := allFormats.GetByName(output.HTMLFormat.Name)
                robotsOut, _ := allFormats.GetByName(output.RobotsTxtFormat.Name)
                sitemapOut, _ := allFormats.GetByName(output.SitemapFormat.Name)
       +        httpStatus404Out, _ := allFormats.GetByName(output.HTTPStatus404HTMLFormat.Name)
        
                defaultListTypes := output.Formats{htmlOut}
                if rssFound {
       @@ -42,7 +43,7 @@ func createDefaultOutputFormats(allFormats output.Formats) map[string]output.For
                        // Below are for consistency. They are currently not used during rendering.
                        kinds.KindSitemap:   {sitemapOut},
                        kinds.KindRobotsTXT: {robotsOut},
       -                kinds.KindStatus404: {htmlOut},
       +                kinds.KindStatus404: {httpStatus404Out},
                }
        
                // May be disabled
 (DIR) diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
       @@ -387,7 +387,7 @@ func TestCreateSiteOutputFormats(t *testing.T) {
                        c.Assert(outputs[kinds.KindRSS], deepEqualsOutputFormats, output.Formats{output.RSSFormat})
                        c.Assert(outputs[kinds.KindSitemap], deepEqualsOutputFormats, output.Formats{output.SitemapFormat})
                        c.Assert(outputs[kinds.KindRobotsTXT], deepEqualsOutputFormats, output.Formats{output.RobotsTxtFormat})
       -                c.Assert(outputs[kinds.KindStatus404], deepEqualsOutputFormats, output.Formats{output.HTMLFormat})
       +                c.Assert(outputs[kinds.KindStatus404], deepEqualsOutputFormats, output.Formats{output.HTTPStatus404HTMLFormat})
                })
        
                // Issue #4528
       @@ -481,6 +481,7 @@ permalinkable = true
        [outputFormats.nobase]
        mediaType = "application/json"
        permalinkable = true
       +isPlainText = true
        
        `
        
 (DIR) diff --git a/hugolib/site_render.go b/hugolib/site_render.go
       @@ -23,9 +23,9 @@ import (
                "github.com/bep/logg"
                "github.com/gohugoio/hugo/common/herrors"
                "github.com/gohugoio/hugo/hugolib/doctree"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
        
                "github.com/gohugoio/hugo/config"
       -        "github.com/gohugoio/hugo/tpl"
        
                "github.com/gohugoio/hugo/resources/kinds"
                "github.com/gohugoio/hugo/resources/page"
       @@ -57,7 +57,7 @@ func (s siteRenderContext) shouldRenderStandalonePage(kind string) bool {
                        return s.outIdx == 0
                }
        
       -        if kind == kinds.KindStatus404 {
       +        if kind == kinds.KindTemporary || kind == kinds.KindStatus404 {
                        // 1 for all output formats
                        return s.outIdx == 0
                }
       @@ -168,7 +168,7 @@ func pageRenderer(
        
                        s.Log.Trace(
                                func() string {
       -                                return fmt.Sprintf("rendering outputFormat %q kind %q using layout %q to %q", p.pageOutput.f.Name, p.Kind(), templ.Name(), targetPath)
       +                                return fmt.Sprintf("rendering outputFormat %q kind %q using layout %q to %q", p.pageOutput.f.Name, p.Kind(), templ.Template.Name(), targetPath)
                                },
                        )
        
       @@ -225,7 +225,7 @@ func (s *Site) logMissingLayout(name, layout, kind, outputFormat string) {
        }
        
        // renderPaginator must be run after the owning Page has been rendered.
       -func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
       +func (s *Site) renderPaginator(p *pageState, templ *tplimpl.TemplInfo) error {
                paginatePath := s.Conf.Pagination().Path
        
                d := p.targetPathDescriptor
 (DIR) diff --git a/hugolib/site_test.go b/hugolib/site_test.go
       @@ -978,8 +978,7 @@ func TestRefLinking(t *testing.T) {
                        {".", "", true, "/level2/level3/"},
                        {"./", "", true, "/level2/level3/"},
        
       -                // try to confuse parsing
       -                {"embedded.dot.md", "", true, "/level2/level3/embedded.dot/"},
       +                {"embedded.dot.md", "", true, "/level2/level3/embedded/"},
        
                        // test empty link, as well as fragment only link
                        {"", "", true, ""},
 (DIR) diff --git a/hugolib/taxonomy_test.go b/hugolib/taxonomy_test.go
       @@ -76,6 +76,8 @@ func TestTaxonomiesWithAndWithoutContentFile(t *testing.T) {
        }
        
        func doTestTaxonomiesWithAndWithoutContentFile(t *testing.T, uglyURLs bool) {
       +        t.Helper()
       +
                siteConfig := `
        baseURL = "http://example.com/blog"
        titleCaseStyle = "firstupper"
 (DIR) diff --git a/hugolib/template_test.go b/hugolib/template_test.go
       @@ -26,6 +26,8 @@ import (
                "github.com/gohugoio/hugo/hugofs"
        )
        
       +// TODO(bep) keep this until we release v0.146.0 as a security against breaking changes, but it's rather messy and mostly duplicate of
       +// tests in the tplimpl package, so eventually just remove it.
        func TestTemplateLookupOrder(t *testing.T) {
                var (
                        fs      *hugofs.Fs
       @@ -185,6 +187,9 @@ func TestTemplateLookupOrder(t *testing.T) {
                } {
        
                        this := this
       +                if this.name != "Variant 1" {
       +                        continue
       +                }
                        t.Run(this.name, func(t *testing.T) {
                                // TODO(bep) there are some function vars need to pull down here to enable => t.Parallel()
                                cfg, fs = newTestCfg()
       @@ -200,7 +205,7 @@ Some content
                                }
        
                                buildSingleSite(t, deps.DepsCfg{Fs: fs, Configs: configs}, BuildCfg{})
       -                        // helpers.PrintFs(s.BaseFs.Layouts.Fs, "", os.Stdout)
       +                        // s.TemplateStore.PrintDebug("", 0, os.Stdout)
                                this.assert(t)
                        })
        
       @@ -270,11 +275,11 @@ func TestTemplateNoBasePlease(t *testing.T) {
                b := newTestSitesBuilder(t).WithSimpleConfigFile()
        
                b.WithTemplates("_default/list.html", `
       -        {{ define "main" }}
       -          Bonjour
       -        {{ end }}
       +{{ define "main" }}
       +  Bonjour
       +{{ end }}
        
       -        {{ printf "list" }}
       +{{ printf "list" }}
        
        
                `)
       @@ -344,33 +349,36 @@ title: %s
                        b.AssertFileContent("public/p1/index.html", `Single: P1`)
                })
        
       -        t.Run("baseof", func(t *testing.T) {
       -                t.Parallel()
       -                b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
       +        {
       +        }
       +}
        
       -                b.WithTemplatesAdded(
       -                        "index.html", `{{ define "main" }}Main Home En{{ end }}`,
       -                        "index.fr.html", `{{ define "main" }}Main Home Fr{{ end }}`,
       -                        "baseof.html", `Baseof en: {{ block "main" . }}main block{{ end }}`,
       -                        "baseof.fr.html", `Baseof fr: {{ block "main" . }}main block{{ end }}`,
       -                        "mysection/baseof.html", `Baseof mysection: {{ block "main" .  }}mysection block{{ end }}`,
       -                        "_default/single.html", `{{ define "main" }}Main Default Single{{ end }}`,
       -                        "_default/list.html", `{{ define "main" }}Main Default List{{ end }}`,
       -                )
       +func TestTemplateLookupSitBaseOf(t *testing.T) {
       +        t.Parallel()
       +        b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
       +
       +        b.WithTemplatesAdded(
       +                "index.html", `{{ define "main" }}Main Home En{{ end }}`,
       +                "index.fr.html", `{{ define "main" }}Main Home Fr{{ end }}`,
       +                "baseof.html", `Baseof en: {{ block "main" . }}main block{{ end }}`,
       +                "baseof.fr.html", `Baseof fr: {{ block "main" . }}main block{{ end }}`,
       +                "mysection/baseof.html", `Baseof mysection: {{ block "main" .  }}mysection block{{ end }}`,
       +                "_default/single.html", `{{ define "main" }}Main Default Single{{ end }}`,
       +                "_default/list.html", `{{ define "main" }}Main Default List{{ end }}`,
       +        )
        
       -                b.WithContent("mysection/p1.md", `---
       +        b.WithContent("mysection/p1.md", `---
        title: My Page
        ---
        
        `)
        
       -                b.CreateSites().Build(BuildCfg{})
       +        b.CreateSites().Build(BuildCfg{})
        
       -                b.AssertFileContent("public/en/index.html", `Baseof en: Main Home En`)
       -                b.AssertFileContent("public/fr/index.html", `Baseof fr: Main Home Fr`)
       -                b.AssertFileContent("public/en/mysection/index.html", `Baseof mysection: Main Default List`)
       -                b.AssertFileContent("public/en/mysection/p1/index.html", `Baseof mysection: Main Default Single`)
       -        })
       +        b.AssertFileContent("public/en/index.html", `Baseof en: Main Home En`)
       +        b.AssertFileContent("public/fr/index.html", `Baseof fr: Main Home Fr`)
       +        b.AssertFileContent("public/en/mysection/index.html", `Baseof mysection: Main Default List`)
       +        b.AssertFileContent("public/en/mysection/p1/index.html", `Baseof mysection: Main Default Single`)
        }
        
        func TestTemplateFuncs(t *testing.T) {
       @@ -707,6 +715,7 @@ a: {{ $a }}
                b.AssertFileContent("public/index.html", `a: [a b c]`)
        }
        
       +// Legacy behavior for internal templates.
        func TestOverrideInternalTemplate(t *testing.T) {
                files := `
        -- hugo.toml --
 (DIR) diff --git a/identity/identity.go b/identity/identity.go
       @@ -509,6 +509,10 @@ func probablyEq(a, b Identity) bool {
                        return true
                }
        
       +        if a2, ok := a.(compare.ProbablyEqer); ok && a2.ProbablyEq(b) {
       +                return true
       +        }
       +
                if a2, ok := a.(IsProbablyDependentProvider); ok {
                        return a2.IsProbablyDependent(b)
                }
 (DIR) diff --git a/internal/js/esbuild/batch.go b/internal/js/esbuild/batch.go
       @@ -43,7 +43,7 @@ import (
                "github.com/gohugoio/hugo/resources"
                "github.com/gohugoio/hugo/resources/resource"
                "github.com/gohugoio/hugo/resources/resource_factories/create"
       -        "github.com/gohugoio/hugo/tpl"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
                "github.com/mitchellh/mapstructure"
                "github.com/spf13/cast"
        )
       @@ -192,7 +192,7 @@ type BatcherClient struct {
                d *deps.Deps
        
                once           sync.Once
       -        runnerTemplate tpl.Template
       +        runnerTemplate *tplimpl.TemplInfo
        
                createClient *create.Client
                buildClient  *BuildClient
       @@ -208,7 +208,7 @@ func (c *BatcherClient) New(id string) (js.Batcher, error) {
                c.once.Do(func() {
                        // We should fix the initialization order here (or use the Go template package directly), but we need to wait
                        // for the Hugo templates to be ready.
       -                tmpl, err := c.d.TextTmpl().Parse("batch-esm-runner", runnerTemplateStr)
       +                tmpl, err := c.d.TemplateStore.TextParse("batch-esm-runner", runnerTemplateStr)
                        if err != nil {
                                initErr = err
                                return
       @@ -287,7 +287,7 @@ func (c *BatcherClient) Store() *maps.Cache[string, js.Batcher] {
        func (c *BatcherClient) buildBatchGroup(ctx context.Context, t *batchGroupTemplateContext) (resource.Resource, string, error) {
                var buf bytes.Buffer
        
       -        if err := c.d.Tmpl().ExecuteWithContext(ctx, c.runnerTemplate, &buf, t); err != nil {
       +        if err := c.d.GetTemplateStore().ExecuteWithContext(ctx, c.runnerTemplate, &buf, t); err != nil {
                        return nil, "", err
                }
        
 (DIR) diff --git a/langs/i18n/i18n_test.go b/langs/i18n/i18n_test.go
       @@ -23,8 +23,6 @@ import (
                "github.com/gohugoio/hugo/common/types"
                "github.com/gohugoio/hugo/config/testconfig"
        
       -        "github.com/gohugoio/hugo/tpl/tplimpl"
       -
                "github.com/gohugoio/hugo/resources/page"
                "github.com/spf13/afero"
        
       @@ -472,7 +470,6 @@ func prepareTranslationProvider(t testing.TB, test i18nTest, cfg config.Provider
        func prepareDeps(afs afero.Fs, cfg config.Provider) (*deps.Deps, *TranslationProvider) {
                d := testconfig.GetTestDeps(afs, cfg)
                translationProvider := NewTranslationProvider()
       -        d.TemplateProvider = tplimpl.DefaultTemplateProvider
                d.TranslationProvider = translationProvider
                d.Site = page.NewDummyHugoSite(d.Conf)
                if err := d.Compile(nil); err != nil {
 (DIR) diff --git a/markup/goldmark/codeblocks/codeblocks_integration_test.go b/markup/goldmark/codeblocks/codeblocks_integration_test.go
       @@ -69,7 +69,7 @@ fmt.Println("Hello, World!");
        
        ## Golang Code
        
       -§§§golang
       +§§§go
        fmt.Println("Hello, Golang!");
        §§§
        
       @@ -97,14 +97,14 @@ Go Language: go|
        Go Code: fmt.Println("Hello, World!");
        
        Go Code: fmt.Println("Hello, Golang!");
       -Go Language: golang|
       +Go Language: go|
        
        
                `,
                        "Goat SVG:<svg class='diagram' xmlns='http://www.w3.org/2000/svg' version='1.1' height='25' width='40'",
                        "Goat Attribute: 600|",
                        "<h2 id=\"go-code\">Go Code</h2>\nGo Code: fmt.Println(\"Hello, World!\");\n|\nGo Language: go|",
       -                "<h2 id=\"golang-code\">Golang Code</h2>\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: golang|",
       +                "<h2 id=\"golang-code\">Golang Code</h2>\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: go|",
                        "<h2 id=\"bash-code\">Bash Code</h2>\n<div class=\"highlight blue\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"ln\">32</span><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">&#34;l1&#34;</span><span class=\"p\">;</span>\n</span></span><span class=\"line hl\"><span class=\"ln\">33</span>",
                )
        }
 (DIR) diff --git a/media/builtin.go b/media/builtin.go
       @@ -5,6 +5,7 @@ type BuiltinTypes struct {
                CSSType        Type
                SCSSType       Type
                SASSType       Type
       +        GotmplType     Type
                CSVType        Type
                HTMLType       Type
                JavascriptType Type
       @@ -60,6 +61,7 @@ var Builtin = BuiltinTypes{
                CSSType:        Type{Type: "text/css"},
                SCSSType:       Type{Type: "text/x-scss"},
                SASSType:       Type{Type: "text/x-sass"},
       +        GotmplType:     Type{Type: "text/x-gotmpl"},
                CSVType:        Type{Type: "text/csv"},
                HTMLType:       Type{Type: "text/html"},
                JavascriptType: Type{Type: "text/javascript"},
       @@ -121,6 +123,7 @@ var defaultMediaTypesConfig = map[string]any{
                "text/typescript": map[string]any{"suffixes": []string{"ts"}},
                "text/tsx":        map[string]any{"suffixes": []string{"tsx"}},
                "text/jsx":        map[string]any{"suffixes": []string{"jsx"}},
       +        "text/x-gotmpl":   map[string]any{"suffixes": []string{"gotmpl"}},
        
                "application/json":          map[string]any{"suffixes": []string{"json"}},
                "application/manifest+json": map[string]any{"suffixes": []string{"webmanifest"}},
 (DIR) diff --git a/media/config.go b/media/config.go
       @@ -17,6 +17,7 @@ import (
                "fmt"
                "path/filepath"
                "reflect"
       +        "slices"
                "sort"
                "strings"
        
       @@ -26,7 +27,6 @@ import (
        
                "github.com/mitchellh/mapstructure"
                "github.com/spf13/cast"
       -        "slices"
        )
        
        // DefaultTypes is the default media types supported by Hugo.
       @@ -271,4 +271,7 @@ var DefaultPathParser = &paths.PathParser{
                IsContentExt: func(ext string) bool {
                        panic("not supported")
                },
       +        IsOutputFormat: func(name, ext string) bool {
       +                panic("DefaultPathParser: not supported")
       +        },
        }
 (DIR) diff --git a/media/config_test.go b/media/config_test.go
       @@ -151,5 +151,5 @@ func TestDefaultTypes(t *testing.T) {
        
                }
        
       -        c.Assert(len(DefaultTypes), qt.Equals, 40)
       +        c.Assert(len(DefaultTypes), qt.Equals, 41)
        }
 (DIR) diff --git a/media/mediaType.go b/media/mediaType.go
       @@ -282,7 +282,7 @@ func (t Types) BySuffix(suffix string) []Type {
                suffix = t.normalizeSuffix(suffix)
                var types []Type
                for _, tt := range t {
       -                if tt.hasSuffix(suffix) {
       +                if tt.HasSuffix(suffix) {
                                types = append(types, tt)
                        }
                }
       @@ -293,7 +293,7 @@ func (t Types) BySuffix(suffix string) []Type {
        func (t Types) GetFirstBySuffix(suffix string) (Type, SuffixInfo, bool) {
                suffix = t.normalizeSuffix(suffix)
                for _, tt := range t {
       -                if tt.hasSuffix(suffix) {
       +                if tt.HasSuffix(suffix) {
                                return tt, SuffixInfo{
                                        FullSuffix: tt.Delimiter + suffix,
                                        Suffix:     suffix,
       @@ -310,7 +310,7 @@ func (t Types) GetFirstBySuffix(suffix string) (Type, SuffixInfo, bool) {
        func (t Types) GetBySuffix(suffix string) (tp Type, si SuffixInfo, found bool) {
                suffix = t.normalizeSuffix(suffix)
                for _, tt := range t {
       -                if tt.hasSuffix(suffix) {
       +                if tt.HasSuffix(suffix) {
                                if found {
                                        // ambiguous
                                        found = false
       @@ -330,14 +330,14 @@ func (t Types) GetBySuffix(suffix string) (tp Type, si SuffixInfo, found bool) {
        func (t Types) IsTextSuffix(suffix string) bool {
                suffix = t.normalizeSuffix(suffix)
                for _, tt := range t {
       -                if tt.hasSuffix(suffix) {
       +                if tt.HasSuffix(suffix) {
                                return tt.IsText()
                        }
                }
                return false
        }
        
       -func (m Type) hasSuffix(suffix string) bool {
       +func (m Type) HasSuffix(suffix string) bool {
                return strings.Contains(","+m.SuffixesCSV+",", ","+suffix+",")
        }
        
 (DIR) diff --git a/output/docshelper.go b/output/docshelper.go
       @@ -1,12 +1,10 @@
        package output
        
        import (
       -        "strings"
        
                //        "fmt"
        
                "github.com/gohugoio/hugo/docshelper"
       -        "github.com/gohugoio/hugo/output/layouts"
        )
        
        // This is is just some helpers used to create some JSON used in the Hugo docs.
       @@ -14,90 +12,12 @@ func init() {
                docsProvider := func() docshelper.DocProvider {
                        return docshelper.DocProvider{
                                "output": map[string]any{
       -                                "layouts": createLayoutExamples(),
       +                                // TODO(bep), maybe revisit this later, but I hope this isn't needed.
       +                                // "layouts": createLayoutExamples(),
       +                                "layouts": map[string]any{},
                                },
                        }
                }
        
                docshelper.AddDocProviderFunc(docsProvider)
        }
       -
       -func createLayoutExamples() any {
       -        type Example struct {
       -                Example      string
       -                Kind         string
       -                OutputFormat string
       -                Suffix       string
       -                Layouts      []string `json:"Template Lookup Order"`
       -        }
       -
       -        var (
       -                basicExamples []Example
       -                demoLayout    = "demolayout"
       -                demoType      = "demotype"
       -        )
       -
       -        for _, example := range []struct {
       -                name string
       -                d    layouts.LayoutDescriptor
       -        }{
       -                // Taxonomy layouts.LayoutDescriptor={categories category taxonomy en  false Type Section
       -                {"Single page in \"posts\" section", layouts.LayoutDescriptor{Kind: "page", Type: "posts", OutputFormatName: "html", Suffix: "html"}},
       -                {"Base template for single page in \"posts\" section", layouts.LayoutDescriptor{Baseof: true, Kind: "page", Type: "posts", OutputFormatName: "html", Suffix: "html"}},
       -                {"Single page in \"posts\" section with layout set to \"demolayout\"", layouts.LayoutDescriptor{Kind: "page", Type: "posts", Layout: demoLayout, OutputFormatName: "html", Suffix: "html"}},
       -                {"Base template for single page in \"posts\" section with layout set to \"demolayout\"", layouts.LayoutDescriptor{Baseof: true, Kind: "page", Type: "posts", Layout: demoLayout, OutputFormatName: "html", Suffix: "html"}},
       -                {"AMP single page in \"posts\" section", layouts.LayoutDescriptor{Kind: "page", Type: "posts", OutputFormatName: "amp", Suffix: "html"}},
       -                {"AMP single page in \"posts\" section, French language", layouts.LayoutDescriptor{Kind: "page", Type: "posts", Lang: "fr", OutputFormatName: "amp", Suffix: "html"}},
       -                // Typeless pages get "page" as type
       -                {"Home page", layouts.LayoutDescriptor{Kind: "home", Type: "page", OutputFormatName: "html", Suffix: "html"}},
       -                {"Base template for home page", layouts.LayoutDescriptor{Baseof: true, Kind: "home", Type: "page", OutputFormatName: "html", Suffix: "html"}},
       -                {"Home page with type set to \"demotype\"", layouts.LayoutDescriptor{Kind: "home", Type: demoType, OutputFormatName: "html", Suffix: "html"}},
       -                {"Base template for home page with type set to \"demotype\"", layouts.LayoutDescriptor{Baseof: true, Kind: "home", Type: demoType, OutputFormatName: "html", Suffix: "html"}},
       -                {"Home page with layout set to \"demolayout\"", layouts.LayoutDescriptor{Kind: "home", Type: "page", Layout: demoLayout, OutputFormatName: "html", Suffix: "html"}},
       -                {"AMP home, French language", layouts.LayoutDescriptor{Kind: "home", Type: "page", Lang: "fr", OutputFormatName: "amp", Suffix: "html"}},
       -                {"JSON home", layouts.LayoutDescriptor{Kind: "home", Type: "page", OutputFormatName: "json", Suffix: "json"}},
       -                {"RSS home", layouts.LayoutDescriptor{Kind: "home", Type: "page", OutputFormatName: "rss", Suffix: "xml"}},
       -
       -                {"Section list for \"posts\"", layouts.LayoutDescriptor{Kind: "section", Type: "posts", Section: "posts", OutputFormatName: "html", Suffix: "html"}},
       -                {"Section list for \"posts\" with type set to \"blog\"", layouts.LayoutDescriptor{Kind: "section", Type: "blog", Section: "posts", OutputFormatName: "html", Suffix: "html"}},
       -                {"Section list for \"posts\" with layout set to \"demolayout\"", layouts.LayoutDescriptor{Kind: "section", Layout: demoLayout, Section: "posts", OutputFormatName: "html", Suffix: "html"}},
       -                {"Section list for \"posts\"", layouts.LayoutDescriptor{Kind: "section", Type: "posts", OutputFormatName: "rss", Suffix: "xml"}},
       -
       -                {"Taxonomy list for \"categories\"", layouts.LayoutDescriptor{Kind: "taxonomy", Type: "categories", Section: "category", OutputFormatName: "html", Suffix: "html"}},
       -                {"Taxonomy list for \"categories\"", layouts.LayoutDescriptor{Kind: "taxonomy", Type: "categories", Section: "category", OutputFormatName: "rss", Suffix: "xml"}},
       -
       -                {"Term list for \"categories\"", layouts.LayoutDescriptor{Kind: "term", Type: "categories", Section: "category", OutputFormatName: "html", Suffix: "html"}},
       -                {"Term list for \"categories\"", layouts.LayoutDescriptor{Kind: "term", Type: "categories", Section: "category", OutputFormatName: "rss", Suffix: "xml"}},
       -        } {
       -
       -                l := layouts.NewLayoutHandler()
       -                layouts, _ := l.For(example.d)
       -
       -                basicExamples = append(basicExamples, Example{
       -                        Example:      example.name,
       -                        Kind:         example.d.Kind,
       -                        OutputFormat: example.d.OutputFormatName,
       -                        Suffix:       example.d.Suffix,
       -                        Layouts:      makeLayoutsPresentable(layouts),
       -                })
       -        }
       -
       -        return basicExamples
       -}
       -
       -func makeLayoutsPresentable(l []string) []string {
       -        var filtered []string
       -        for _, ll := range l {
       -                if strings.Contains(ll, "page/") {
       -                        // This is a valid lookup, but it's more confusing than useful.
       -                        continue
       -                }
       -                ll = "layouts/" + strings.TrimPrefix(ll, "_text/")
       -
       -                if !strings.Contains(ll, "indexes") {
       -                        filtered = append(filtered, ll)
       -                }
       -        }
       -
       -        return filtered
       -}
 (DIR) diff --git a/output/layouts/layout.go b/output/layouts/layout.go
       @@ -1,336 +0,0 @@
       -// Copyright 2024 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package layouts
       -
       -import (
       -        "strings"
       -        "sync"
       -)
       -
       -// These may be used as content sections with potential conflicts. Avoid that.
       -var reservedSections = map[string]bool{
       -        "shortcodes": true,
       -        "partials":   true,
       -}
       -
       -// LayoutDescriptor describes how a layout should be chosen. This is
       -// typically built from a Page.
       -type LayoutDescriptor struct {
       -        Type    string
       -        Section string
       -
       -        // E.g. "page", but also used for the _markup render kinds, e.g. "render-image".
       -        Kind string
       -
       -        // Comma-separated list of kind variants, e.g. "go,json" as variants which would find "render-codeblock-go.html"
       -        KindVariants string
       -
       -        Lang   string
       -        Layout string
       -        // LayoutOverride indicates what we should only look for the above layout.
       -        LayoutOverride bool
       -
       -        // From OutputFormat and MediaType.
       -        OutputFormatName string
       -        Suffix           string
       -
       -        RenderingHook bool
       -        Baseof        bool
       -}
       -
       -func (d LayoutDescriptor) isList() bool {
       -        return !d.RenderingHook && d.Kind != "page" && d.Kind != "404" && d.Kind != "sitemap" && d.Kind != "sitemapindex"
       -}
       -
       -// LayoutHandler calculates the layout template to use to render a given output type.
       -type LayoutHandler struct {
       -        mu    sync.RWMutex
       -        cache map[LayoutDescriptor][]string
       -}
       -
       -// NewLayoutHandler creates a new LayoutHandler.
       -func NewLayoutHandler() *LayoutHandler {
       -        return &LayoutHandler{cache: make(map[LayoutDescriptor][]string)}
       -}
       -
       -// For returns a layout for the given LayoutDescriptor and options.
       -// Layouts are rendered and cached internally.
       -func (l *LayoutHandler) For(d LayoutDescriptor) ([]string, error) {
       -        // We will get lots of requests for the same layouts, so avoid recalculations.
       -        l.mu.RLock()
       -        if cacheVal, found := l.cache[d]; found {
       -                l.mu.RUnlock()
       -                return cacheVal, nil
       -        }
       -        l.mu.RUnlock()
       -
       -        layouts := resolvePageTemplate(d)
       -
       -        layouts = uniqueStringsReuse(layouts)
       -
       -        l.mu.Lock()
       -        l.cache[d] = layouts
       -        l.mu.Unlock()
       -
       -        return layouts, nil
       -}
       -
       -type layoutBuilder struct {
       -        layoutVariations []string
       -        typeVariations   []string
       -        d                LayoutDescriptor
       -        // f                Format
       -}
       -
       -func (l *layoutBuilder) addLayoutVariations(vars ...string) {
       -        for _, layoutVar := range vars {
       -                if l.d.Baseof && layoutVar != "baseof" {
       -                        l.layoutVariations = append(l.layoutVariations, layoutVar+"-baseof")
       -                        continue
       -                }
       -                if !l.d.RenderingHook && !l.d.Baseof && l.d.LayoutOverride && layoutVar != l.d.Layout {
       -                        continue
       -                }
       -                l.layoutVariations = append(l.layoutVariations, layoutVar)
       -        }
       -}
       -
       -func (l *layoutBuilder) addTypeVariations(vars ...string) {
       -        for _, typeVar := range vars {
       -                if !reservedSections[typeVar] {
       -                        if l.d.RenderingHook {
       -                                typeVar = typeVar + renderingHookRoot
       -                        }
       -                        l.typeVariations = append(l.typeVariations, typeVar)
       -                }
       -        }
       -}
       -
       -func (l *layoutBuilder) addSectionType() {
       -        if l.d.Section != "" {
       -                l.addTypeVariations(l.d.Section)
       -        }
       -}
       -
       -func (l *layoutBuilder) addKind() {
       -        l.addLayoutVariations(l.d.Kind)
       -        l.addTypeVariations(l.d.Kind)
       -}
       -
       -const renderingHookRoot = "/_markup"
       -
       -func resolvePageTemplate(d LayoutDescriptor) []string {
       -        b := &layoutBuilder{d: d}
       -
       -        if !d.RenderingHook && d.Layout != "" {
       -                b.addLayoutVariations(d.Layout)
       -        }
       -        if d.Type != "" {
       -                b.addTypeVariations(d.Type)
       -        }
       -
       -        if d.RenderingHook {
       -                if d.KindVariants != "" {
       -                        // Add the more specific variants first.
       -                        for _, variant := range strings.Split(d.KindVariants, ",") {
       -                                b.addLayoutVariations(d.Kind + "-" + variant)
       -                        }
       -                }
       -                b.addLayoutVariations(d.Kind)
       -                b.addSectionType()
       -        }
       -
       -        switch d.Kind {
       -        case "page":
       -                b.addLayoutVariations("single")
       -                b.addSectionType()
       -        case "home":
       -                b.addLayoutVariations("index", "home")
       -                // Also look in the root
       -                b.addTypeVariations("")
       -        case "section":
       -                if d.Section != "" {
       -                        b.addLayoutVariations(d.Section)
       -                }
       -                b.addSectionType()
       -                b.addKind()
       -        case "term":
       -                b.addKind()
       -                if d.Section != "" {
       -                        b.addLayoutVariations(d.Section)
       -                }
       -                b.addLayoutVariations("taxonomy")
       -                b.addTypeVariations("taxonomy")
       -                b.addSectionType()
       -        case "taxonomy":
       -                if d.Section != "" {
       -                        b.addLayoutVariations(d.Section + ".terms")
       -                }
       -                b.addSectionType()
       -                b.addLayoutVariations("terms")
       -                // For legacy reasons this is deliberately put last.
       -                b.addKind()
       -        case "404":
       -                b.addLayoutVariations("404")
       -                b.addTypeVariations("")
       -        case "robotstxt":
       -                b.addLayoutVariations("robots")
       -                b.addTypeVariations("")
       -        case "sitemap":
       -                b.addLayoutVariations("sitemap")
       -                b.addTypeVariations("")
       -        case "sitemapindex":
       -                b.addLayoutVariations("sitemapindex")
       -                b.addTypeVariations("")
       -        }
       -
       -        isRSS := d.OutputFormatName == "rss"
       -        if !d.RenderingHook && !d.Baseof && isRSS {
       -                // The historic and common rss.xml case
       -                b.addLayoutVariations("")
       -        }
       -
       -        if d.Baseof || d.Kind != "404" {
       -                // Most have _default in their lookup path
       -                b.addTypeVariations("_default")
       -        }
       -
       -        if d.isList() {
       -                // Add the common list type
       -                b.addLayoutVariations("list")
       -        }
       -
       -        if d.Baseof {
       -                b.addLayoutVariations("baseof")
       -        }
       -
       -        layouts := b.resolveVariations()
       -
       -        if !d.RenderingHook && !d.Baseof && isRSS {
       -                layouts = append(layouts, "_internal/_default/rss.xml")
       -        }
       -
       -        switch d.Kind {
       -        case "robotstxt":
       -                layouts = append(layouts, "_internal/_default/robots.txt")
       -        case "sitemap":
       -                layouts = append(layouts, "_internal/_default/sitemap.xml")
       -        case "sitemapindex":
       -                layouts = append(layouts, "_internal/_default/sitemapindex.xml")
       -        }
       -
       -        return layouts
       -}
       -
       -func (l *layoutBuilder) resolveVariations() []string {
       -        var layouts []string
       -
       -        var variations []string
       -        name := strings.ToLower(l.d.OutputFormatName)
       -
       -        if l.d.Lang != "" {
       -                // We prefer the most specific type before language.
       -                variations = append(variations, []string{l.d.Lang + "." + name, name, l.d.Lang}...)
       -        } else {
       -                variations = append(variations, name)
       -        }
       -
       -        variations = append(variations, "")
       -
       -        for _, typeVar := range l.typeVariations {
       -                for _, variation := range variations {
       -                        for _, layoutVar := range l.layoutVariations {
       -                                if variation == "" && layoutVar == "" {
       -                                        continue
       -                                }
       -
       -                                s := constructLayoutPath(typeVar, layoutVar, variation, l.d.Suffix)
       -                                if s != "" {
       -                                        layouts = append(layouts, s)
       -                                }
       -                        }
       -                }
       -        }
       -
       -        return layouts
       -}
       -
       -// constructLayoutPath constructs a layout path given a type, layout,
       -// variations, and extension.  The path constructed follows the pattern of
       -// type/layout.variations.extension.  If any value is empty, it will be left out
       -// of the path construction.
       -//
       -// Path construction requires at least 2 of 3 out of layout, variations, and extension.
       -// If more than one of those is empty, an empty string is returned.
       -func constructLayoutPath(typ, layout, variations, extension string) string {
       -        // we already know that layout and variations are not both empty because of
       -        // checks in resolveVariants().
       -        if extension == "" && (layout == "" || variations == "") {
       -                return ""
       -        }
       -
       -        // Commence valid path construction...
       -
       -        var (
       -                p       strings.Builder
       -                needDot bool
       -        )
       -
       -        if typ != "" {
       -                p.WriteString(typ)
       -                p.WriteString("/")
       -        }
       -
       -        if layout != "" {
       -                p.WriteString(layout)
       -                needDot = true
       -        }
       -
       -        if variations != "" {
       -                if needDot {
       -                        p.WriteString(".")
       -                }
       -                p.WriteString(variations)
       -                needDot = true
       -        }
       -
       -        if extension != "" {
       -                if needDot {
       -                        p.WriteString(".")
       -                }
       -                p.WriteString(extension)
       -        }
       -
       -        return p.String()
       -}
       -
       -// Inline this here so we can use tinygo to compile a wasm binary of this package.
       -func uniqueStringsReuse(s []string) []string {
       -        result := s[:0]
       -        for i, val := range s {
       -                var seen bool
       -
       -                for j := range i {
       -                        if s[j] == val {
       -                                seen = true
       -                                break
       -                        }
       -                }
       -
       -                if !seen {
       -                        result = append(result, val)
       -                }
       -        }
       -        return result
       -}
 (DIR) diff --git a/output/layouts/layout_test.go b/output/layouts/layout_test.go
       @@ -1,982 +0,0 @@
       -// Copyright 2017-present The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package layouts
       -
       -import (
       -        "fmt"
       -        "reflect"
       -        "strings"
       -        "testing"
       -
       -        qt "github.com/frankban/quicktest"
       -        "github.com/kylelemons/godebug/diff"
       -)
       -
       -func TestLayout(t *testing.T) {
       -        c := qt.New(t)
       -
       -        for _, this := range []struct {
       -                name             string
       -                layoutDescriptor LayoutDescriptor
       -                layoutOverride   string
       -                expect           []string
       -        }{
       -                {
       -                        "Home",
       -                        LayoutDescriptor{Kind: "home", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "index.amp.html",
       -                                "home.amp.html",
       -                                "list.amp.html",
       -                                "index.html",
       -                                "home.html",
       -                                "list.html",
       -                                "_default/index.amp.html",
       -                                "_default/home.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/index.html",
       -                                "_default/home.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Home baseof",
       -                        LayoutDescriptor{Kind: "home", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "index-baseof.amp.html",
       -                                "home-baseof.amp.html",
       -                                "list-baseof.amp.html",
       -                                "baseof.amp.html",
       -                                "index-baseof.html",
       -                                "home-baseof.html",
       -                                "list-baseof.html",
       -                                "baseof.html",
       -                                "_default/index-baseof.amp.html",
       -                                "_default/home-baseof.amp.html",
       -                                "_default/list-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/index-baseof.html",
       -                                "_default/home-baseof.html",
       -                                "_default/list-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Home, HTML",
       -                        LayoutDescriptor{Kind: "home", OutputFormatName: "html", Suffix: "html"},
       -                        "",
       -                        // We will eventually get to index.html. This looks stuttery, but makes the lookup logic easy to understand.
       -                        []string{
       -                                "index.html.html",
       -                                "home.html.html",
       -                                "list.html.html",
       -                                "index.html",
       -                                "home.html",
       -                                "list.html",
       -                                "_default/index.html.html",
       -                                "_default/home.html.html",
       -                                "_default/list.html.html",
       -                                "_default/index.html",
       -                                "_default/home.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Home, HTML, baseof",
       -                        LayoutDescriptor{Kind: "home", Baseof: true, OutputFormatName: "html", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "index-baseof.html.html",
       -                                "home-baseof.html.html",
       -                                "list-baseof.html.html",
       -                                "baseof.html.html",
       -                                "index-baseof.html",
       -                                "home-baseof.html",
       -                                "list-baseof.html",
       -                                "baseof.html",
       -                                "_default/index-baseof.html.html",
       -                                "_default/home-baseof.html.html",
       -                                "_default/list-baseof.html.html",
       -                                "_default/baseof.html.html",
       -                                "_default/index-baseof.html",
       -                                "_default/home-baseof.html",
       -                                "_default/list-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Home, french language",
       -                        LayoutDescriptor{Kind: "home", Lang: "fr", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "index.fr.amp.html",
       -                                "home.fr.amp.html",
       -                                "list.fr.amp.html",
       -                                "index.amp.html",
       -                                "home.amp.html",
       -                                "list.amp.html",
       -                                "index.fr.html",
       -                                "home.fr.html",
       -                                "list.fr.html",
       -                                "index.html",
       -                                "home.html",
       -                                "list.html",
       -                                "_default/index.fr.amp.html",
       -                                "_default/home.fr.amp.html",
       -                                "_default/list.fr.amp.html",
       -                                "_default/index.amp.html",
       -                                "_default/home.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/index.fr.html",
       -                                "_default/home.fr.html",
       -                                "_default/list.fr.html",
       -                                "_default/index.html",
       -                                "_default/home.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Home, no ext or delim",
       -                        LayoutDescriptor{Kind: "home", OutputFormatName: "nem", Suffix: ""},
       -                        "",
       -                        []string{
       -                                "index.nem",
       -                                "home.nem",
       -                                "list.nem",
       -                                "_default/index.nem",
       -                                "_default/home.nem",
       -                                "_default/list.nem",
       -                        },
       -                },
       -                {
       -                        "Home, no ext",
       -                        LayoutDescriptor{Kind: "home", OutputFormatName: "nex", Suffix: ""},
       -                        "",
       -                        []string{
       -                                "index.nex",
       -                                "home.nex",
       -                                "list.nex",
       -                                "_default/index.nex",
       -                                "_default/home.nex",
       -                                "_default/list.nex",
       -                        },
       -                },
       -                {
       -                        "Page, no ext or delim",
       -                        LayoutDescriptor{Kind: "page", OutputFormatName: "nem", Suffix: ""},
       -                        "",
       -                        []string{"_default/single.nem"},
       -                },
       -                {
       -                        "Section",
       -                        LayoutDescriptor{Kind: "section", Section: "sect1", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "sect1/sect1.amp.html",
       -                                "sect1/section.amp.html",
       -                                "sect1/list.amp.html",
       -                                "sect1/sect1.html",
       -                                "sect1/section.html",
       -                                "sect1/list.html",
       -                                "section/sect1.amp.html",
       -                                "section/section.amp.html",
       -                                "section/list.amp.html",
       -                                "section/sect1.html",
       -                                "section/section.html",
       -                                "section/list.html",
       -                                "_default/sect1.amp.html",
       -                                "_default/section.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/sect1.html",
       -                                "_default/section.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Section, baseof",
       -                        LayoutDescriptor{Kind: "section", Section: "sect1", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "sect1/sect1-baseof.amp.html",
       -                                "sect1/section-baseof.amp.html",
       -                                "sect1/list-baseof.amp.html",
       -                                "sect1/baseof.amp.html",
       -                                "sect1/sect1-baseof.html",
       -                                "sect1/section-baseof.html",
       -                                "sect1/list-baseof.html",
       -                                "sect1/baseof.html",
       -                                "section/sect1-baseof.amp.html",
       -                                "section/section-baseof.amp.html",
       -                                "section/list-baseof.amp.html",
       -                                "section/baseof.amp.html",
       -                                "section/sect1-baseof.html",
       -                                "section/section-baseof.html",
       -                                "section/list-baseof.html",
       -                                "section/baseof.html",
       -                                "_default/sect1-baseof.amp.html",
       -                                "_default/section-baseof.amp.html",
       -                                "_default/list-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/sect1-baseof.html",
       -                                "_default/section-baseof.html",
       -                                "_default/list-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Section, baseof, French, AMP",
       -                        LayoutDescriptor{Kind: "section", Section: "sect1", Lang: "fr", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "sect1/sect1-baseof.fr.amp.html",
       -                                "sect1/section-baseof.fr.amp.html",
       -                                "sect1/list-baseof.fr.amp.html",
       -                                "sect1/baseof.fr.amp.html",
       -                                "sect1/sect1-baseof.amp.html",
       -                                "sect1/section-baseof.amp.html",
       -                                "sect1/list-baseof.amp.html",
       -                                "sect1/baseof.amp.html",
       -                                "sect1/sect1-baseof.fr.html",
       -                                "sect1/section-baseof.fr.html",
       -                                "sect1/list-baseof.fr.html",
       -                                "sect1/baseof.fr.html",
       -                                "sect1/sect1-baseof.html",
       -                                "sect1/section-baseof.html",
       -                                "sect1/list-baseof.html",
       -                                "sect1/baseof.html",
       -                                "section/sect1-baseof.fr.amp.html",
       -                                "section/section-baseof.fr.amp.html",
       -                                "section/list-baseof.fr.amp.html",
       -                                "section/baseof.fr.amp.html",
       -                                "section/sect1-baseof.amp.html",
       -                                "section/section-baseof.amp.html",
       -                                "section/list-baseof.amp.html",
       -                                "section/baseof.amp.html",
       -                                "section/sect1-baseof.fr.html",
       -                                "section/section-baseof.fr.html",
       -                                "section/list-baseof.fr.html",
       -                                "section/baseof.fr.html",
       -                                "section/sect1-baseof.html",
       -                                "section/section-baseof.html",
       -                                "section/list-baseof.html",
       -                                "section/baseof.html",
       -                                "_default/sect1-baseof.fr.amp.html",
       -                                "_default/section-baseof.fr.amp.html",
       -                                "_default/list-baseof.fr.amp.html",
       -                                "_default/baseof.fr.amp.html",
       -                                "_default/sect1-baseof.amp.html",
       -                                "_default/section-baseof.amp.html",
       -                                "_default/list-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/sect1-baseof.fr.html",
       -                                "_default/section-baseof.fr.html",
       -                                "_default/list-baseof.fr.html",
       -                                "_default/baseof.fr.html",
       -                                "_default/sect1-baseof.html",
       -                                "_default/section-baseof.html",
       -                                "_default/list-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Section with layout",
       -                        LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "sect1/mylayout.amp.html",
       -                                "sect1/sect1.amp.html",
       -                                "sect1/section.amp.html",
       -                                "sect1/list.amp.html",
       -                                "sect1/mylayout.html",
       -                                "sect1/sect1.html",
       -                                "sect1/section.html",
       -                                "sect1/list.html",
       -                                "section/mylayout.amp.html",
       -                                "section/sect1.amp.html",
       -                                "section/section.amp.html",
       -                                "section/list.amp.html",
       -                                "section/mylayout.html",
       -                                "section/sect1.html",
       -                                "section/section.html",
       -                                "section/list.html",
       -                                "_default/mylayout.amp.html",
       -                                "_default/sect1.amp.html",
       -                                "_default/section.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/mylayout.html",
       -                                "_default/sect1.html",
       -                                "_default/section.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Term, French, AMP",
       -                        LayoutDescriptor{Kind: "term", Section: "tags", Lang: "fr", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "term/term.fr.amp.html",
       -                                "term/tags.fr.amp.html",
       -                                "term/taxonomy.fr.amp.html",
       -                                "term/list.fr.amp.html",
       -                                "term/term.amp.html",
       -                                "term/tags.amp.html",
       -                                "term/taxonomy.amp.html",
       -                                "term/list.amp.html",
       -                                "term/term.fr.html",
       -                                "term/tags.fr.html",
       -                                "term/taxonomy.fr.html",
       -                                "term/list.fr.html",
       -                                "term/term.html",
       -                                "term/tags.html",
       -                                "term/taxonomy.html",
       -                                "term/list.html",
       -                                "taxonomy/term.fr.amp.html",
       -                                "taxonomy/tags.fr.amp.html",
       -                                "taxonomy/taxonomy.fr.amp.html",
       -                                "taxonomy/list.fr.amp.html",
       -                                "taxonomy/term.amp.html",
       -                                "taxonomy/tags.amp.html",
       -                                "taxonomy/taxonomy.amp.html",
       -                                "taxonomy/list.amp.html",
       -                                "taxonomy/term.fr.html",
       -                                "taxonomy/tags.fr.html",
       -                                "taxonomy/taxonomy.fr.html",
       -                                "taxonomy/list.fr.html",
       -                                "taxonomy/term.html",
       -                                "taxonomy/tags.html",
       -                                "taxonomy/taxonomy.html",
       -                                "taxonomy/list.html",
       -                                "tags/term.fr.amp.html",
       -                                "tags/tags.fr.amp.html",
       -                                "tags/taxonomy.fr.amp.html",
       -                                "tags/list.fr.amp.html",
       -                                "tags/term.amp.html",
       -                                "tags/tags.amp.html",
       -                                "tags/taxonomy.amp.html",
       -                                "tags/list.amp.html",
       -                                "tags/term.fr.html",
       -                                "tags/tags.fr.html",
       -                                "tags/taxonomy.fr.html",
       -                                "tags/list.fr.html",
       -                                "tags/term.html",
       -                                "tags/tags.html",
       -                                "tags/taxonomy.html",
       -                                "tags/list.html",
       -                                "_default/term.fr.amp.html",
       -                                "_default/tags.fr.amp.html",
       -                                "_default/taxonomy.fr.amp.html",
       -                                "_default/list.fr.amp.html",
       -                                "_default/term.amp.html",
       -                                "_default/tags.amp.html",
       -                                "_default/taxonomy.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/term.fr.html",
       -                                "_default/tags.fr.html",
       -                                "_default/taxonomy.fr.html",
       -                                "_default/list.fr.html",
       -                                "_default/term.html",
       -                                "_default/tags.html",
       -                                "_default/taxonomy.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Term, baseof, French, AMP",
       -                        LayoutDescriptor{Kind: "term", Section: "tags", Lang: "fr", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "term/term-baseof.fr.amp.html",
       -                                "term/tags-baseof.fr.amp.html",
       -                                "term/taxonomy-baseof.fr.amp.html",
       -                                "term/list-baseof.fr.amp.html",
       -                                "term/baseof.fr.amp.html",
       -                                "term/term-baseof.amp.html",
       -                                "term/tags-baseof.amp.html",
       -                                "term/taxonomy-baseof.amp.html",
       -                                "term/list-baseof.amp.html",
       -                                "term/baseof.amp.html",
       -                                "term/term-baseof.fr.html",
       -                                "term/tags-baseof.fr.html",
       -                                "term/taxonomy-baseof.fr.html",
       -                                "term/list-baseof.fr.html",
       -                                "term/baseof.fr.html",
       -                                "term/term-baseof.html",
       -                                "term/tags-baseof.html",
       -                                "term/taxonomy-baseof.html",
       -                                "term/list-baseof.html",
       -                                "term/baseof.html",
       -                                "taxonomy/term-baseof.fr.amp.html",
       -                                "taxonomy/tags-baseof.fr.amp.html",
       -                                "taxonomy/taxonomy-baseof.fr.amp.html",
       -                                "taxonomy/list-baseof.fr.amp.html",
       -                                "taxonomy/baseof.fr.amp.html",
       -                                "taxonomy/term-baseof.amp.html",
       -                                "taxonomy/tags-baseof.amp.html",
       -                                "taxonomy/taxonomy-baseof.amp.html",
       -                                "taxonomy/list-baseof.amp.html",
       -                                "taxonomy/baseof.amp.html",
       -                                "taxonomy/term-baseof.fr.html",
       -                                "taxonomy/tags-baseof.fr.html",
       -                                "taxonomy/taxonomy-baseof.fr.html",
       -                                "taxonomy/list-baseof.fr.html",
       -                                "taxonomy/baseof.fr.html",
       -                                "taxonomy/term-baseof.html",
       -                                "taxonomy/tags-baseof.html",
       -                                "taxonomy/taxonomy-baseof.html",
       -                                "taxonomy/list-baseof.html",
       -                                "taxonomy/baseof.html",
       -                                "tags/term-baseof.fr.amp.html",
       -                                "tags/tags-baseof.fr.amp.html",
       -                                "tags/taxonomy-baseof.fr.amp.html",
       -                                "tags/list-baseof.fr.amp.html",
       -                                "tags/baseof.fr.amp.html",
       -                                "tags/term-baseof.amp.html",
       -                                "tags/tags-baseof.amp.html",
       -                                "tags/taxonomy-baseof.amp.html",
       -                                "tags/list-baseof.amp.html",
       -                                "tags/baseof.amp.html",
       -                                "tags/term-baseof.fr.html",
       -                                "tags/tags-baseof.fr.html",
       -                                "tags/taxonomy-baseof.fr.html",
       -                                "tags/list-baseof.fr.html",
       -                                "tags/baseof.fr.html",
       -                                "tags/term-baseof.html",
       -                                "tags/tags-baseof.html",
       -                                "tags/taxonomy-baseof.html",
       -                                "tags/list-baseof.html",
       -                                "tags/baseof.html",
       -                                "_default/term-baseof.fr.amp.html",
       -                                "_default/tags-baseof.fr.amp.html",
       -                                "_default/taxonomy-baseof.fr.amp.html",
       -                                "_default/list-baseof.fr.amp.html",
       -                                "_default/baseof.fr.amp.html",
       -                                "_default/term-baseof.amp.html",
       -                                "_default/tags-baseof.amp.html",
       -                                "_default/taxonomy-baseof.amp.html",
       -                                "_default/list-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/term-baseof.fr.html",
       -                                "_default/tags-baseof.fr.html",
       -                                "_default/taxonomy-baseof.fr.html",
       -                                "_default/list-baseof.fr.html",
       -                                "_default/baseof.fr.html",
       -                                "_default/term-baseof.html",
       -                                "_default/tags-baseof.html",
       -                                "_default/taxonomy-baseof.html",
       -                                "_default/list-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Term",
       -                        LayoutDescriptor{Kind: "term", Section: "tags", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "term/term.amp.html",
       -                                "term/tags.amp.html",
       -                                "term/taxonomy.amp.html",
       -                                "term/list.amp.html",
       -                                "term/term.html",
       -                                "term/tags.html",
       -                                "term/taxonomy.html",
       -                                "term/list.html",
       -                                "taxonomy/term.amp.html",
       -                                "taxonomy/tags.amp.html",
       -                                "taxonomy/taxonomy.amp.html",
       -                                "taxonomy/list.amp.html",
       -                                "taxonomy/term.html",
       -                                "taxonomy/tags.html",
       -                                "taxonomy/taxonomy.html",
       -                                "taxonomy/list.html",
       -                                "tags/term.amp.html",
       -                                "tags/tags.amp.html",
       -                                "tags/taxonomy.amp.html",
       -                                "tags/list.amp.html",
       -                                "tags/term.html",
       -                                "tags/tags.html",
       -                                "tags/taxonomy.html",
       -                                "tags/list.html",
       -                                "_default/term.amp.html",
       -                                "_default/tags.amp.html",
       -                                "_default/taxonomy.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/term.html",
       -                                "_default/tags.html",
       -                                "_default/taxonomy.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Taxonomy",
       -                        LayoutDescriptor{Kind: "taxonomy", Section: "categories", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "categories/categories.terms.amp.html",
       -                                "categories/terms.amp.html",
       -                                "categories/taxonomy.amp.html",
       -                                "categories/list.amp.html",
       -                                "categories/categories.terms.html",
       -                                "categories/terms.html",
       -                                "categories/taxonomy.html",
       -                                "categories/list.html",
       -                                "taxonomy/categories.terms.amp.html",
       -                                "taxonomy/terms.amp.html",
       -                                "taxonomy/taxonomy.amp.html",
       -                                "taxonomy/list.amp.html",
       -                                "taxonomy/categories.terms.html",
       -                                "taxonomy/terms.html",
       -                                "taxonomy/taxonomy.html",
       -                                "taxonomy/list.html",
       -                                "_default/categories.terms.amp.html",
       -                                "_default/terms.amp.html",
       -                                "_default/taxonomy.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/categories.terms.html",
       -                                "_default/terms.html",
       -                                "_default/taxonomy.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Page",
       -                        LayoutDescriptor{Kind: "page", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "_default/single.amp.html",
       -                                "_default/single.html",
       -                        },
       -                },
       -                {
       -                        "Page, baseof",
       -                        LayoutDescriptor{Kind: "page", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "_default/single-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/single-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Page with layout",
       -                        LayoutDescriptor{Kind: "page", Layout: "mylayout", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "_default/mylayout.amp.html",
       -                                "_default/single.amp.html",
       -                                "_default/mylayout.html",
       -                                "_default/single.html",
       -                        },
       -                },
       -                {
       -                        "Page with layout, baseof",
       -                        LayoutDescriptor{Kind: "page", Layout: "mylayout", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "_default/mylayout-baseof.amp.html",
       -                                "_default/single-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/mylayout-baseof.html",
       -                                "_default/single-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Page with layout and type",
       -                        LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "myttype/mylayout.amp.html",
       -                                "myttype/single.amp.html",
       -                                "myttype/mylayout.html",
       -                                "myttype/single.html",
       -                                "_default/mylayout.amp.html",
       -                                "_default/single.amp.html",
       -                                "_default/mylayout.html",
       -                                "_default/single.html",
       -                        },
       -                },
       -                {
       -                        "Page baseof with layout and type",
       -                        LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "myttype/mylayout-baseof.amp.html",
       -                                "myttype/single-baseof.amp.html",
       -                                "myttype/baseof.amp.html",
       -                                "myttype/mylayout-baseof.html",
       -                                "myttype/single-baseof.html",
       -                                "myttype/baseof.html",
       -                                "_default/mylayout-baseof.amp.html",
       -                                "_default/single-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/mylayout-baseof.html",
       -                                "_default/single-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Page baseof with layout and type in French",
       -                        LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", Lang: "fr", Baseof: true, OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "myttype/mylayout-baseof.fr.amp.html",
       -                                "myttype/single-baseof.fr.amp.html",
       -                                "myttype/baseof.fr.amp.html",
       -                                "myttype/mylayout-baseof.amp.html",
       -                                "myttype/single-baseof.amp.html",
       -                                "myttype/baseof.amp.html",
       -                                "myttype/mylayout-baseof.fr.html",
       -                                "myttype/single-baseof.fr.html",
       -                                "myttype/baseof.fr.html",
       -                                "myttype/mylayout-baseof.html",
       -                                "myttype/single-baseof.html",
       -                                "myttype/baseof.html",
       -                                "_default/mylayout-baseof.fr.amp.html",
       -                                "_default/single-baseof.fr.amp.html",
       -                                "_default/baseof.fr.amp.html",
       -                                "_default/mylayout-baseof.amp.html",
       -                                "_default/single-baseof.amp.html",
       -                                "_default/baseof.amp.html",
       -                                "_default/mylayout-baseof.fr.html",
       -                                "_default/single-baseof.fr.html",
       -                                "_default/baseof.fr.html",
       -                                "_default/mylayout-baseof.html",
       -                                "_default/single-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Page with layout and type with subtype",
       -                        LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "myttype/mysubtype/mylayout.amp.html",
       -                                "myttype/mysubtype/single.amp.html",
       -                                "myttype/mysubtype/mylayout.html",
       -                                "myttype/mysubtype/single.html",
       -                                "_default/mylayout.amp.html",
       -                                "_default/single.amp.html",
       -                                "_default/mylayout.html",
       -                                "_default/single.html",
       -                        },
       -                },
       -                // RSS
       -                {
       -                        "RSS Home",
       -                        LayoutDescriptor{Kind: "home", OutputFormatName: "rss", Suffix: "xml"},
       -                        "",
       -                        []string{
       -                                "index.rss.xml",
       -                                "home.rss.xml",
       -                                "rss.xml",
       -                                "list.rss.xml",
       -                                "index.xml",
       -                                "home.xml",
       -                                "list.xml",
       -                                "_default/index.rss.xml",
       -                                "_default/home.rss.xml",
       -                                "_default/rss.xml",
       -                                "_default/list.rss.xml",
       -                                "_default/index.xml",
       -                                "_default/home.xml",
       -                                "_default/list.xml",
       -                                "_internal/_default/rss.xml",
       -                        },
       -                },
       -                {
       -                        "RSS Home, baseof",
       -                        LayoutDescriptor{Kind: "home", Baseof: true, OutputFormatName: "rss", Suffix: "xml"},
       -                        "",
       -                        []string{
       -                                "index-baseof.rss.xml",
       -                                "home-baseof.rss.xml",
       -                                "list-baseof.rss.xml",
       -                                "baseof.rss.xml",
       -                                "index-baseof.xml",
       -                                "home-baseof.xml",
       -                                "list-baseof.xml",
       -                                "baseof.xml",
       -                                "_default/index-baseof.rss.xml",
       -                                "_default/home-baseof.rss.xml",
       -                                "_default/list-baseof.rss.xml",
       -                                "_default/baseof.rss.xml",
       -                                "_default/index-baseof.xml",
       -                                "_default/home-baseof.xml",
       -                                "_default/list-baseof.xml",
       -                                "_default/baseof.xml",
       -                        },
       -                },
       -                {
       -                        "RSS Section",
       -                        LayoutDescriptor{Kind: "section", Section: "sect1", OutputFormatName: "rss", Suffix: "xml"},
       -                        "",
       -                        []string{
       -                                "sect1/sect1.rss.xml",
       -                                "sect1/section.rss.xml",
       -                                "sect1/rss.xml",
       -                                "sect1/list.rss.xml",
       -                                "sect1/sect1.xml",
       -                                "sect1/section.xml",
       -                                "sect1/list.xml",
       -                                "section/sect1.rss.xml",
       -                                "section/section.rss.xml",
       -                                "section/rss.xml",
       -                                "section/list.rss.xml",
       -                                "section/sect1.xml",
       -                                "section/section.xml",
       -                                "section/list.xml",
       -                                "_default/sect1.rss.xml",
       -                                "_default/section.rss.xml",
       -                                "_default/rss.xml",
       -                                "_default/list.rss.xml",
       -                                "_default/sect1.xml",
       -                                "_default/section.xml",
       -                                "_default/list.xml",
       -                                "_internal/_default/rss.xml",
       -                        },
       -                },
       -                {
       -                        "RSS Term",
       -                        LayoutDescriptor{Kind: "term", Section: "tag", OutputFormatName: "rss", Suffix: "xml"},
       -                        "",
       -                        []string{
       -                                "term/term.rss.xml",
       -                                "term/tag.rss.xml",
       -                                "term/taxonomy.rss.xml",
       -                                "term/rss.xml",
       -                                "term/list.rss.xml",
       -                                "term/term.xml",
       -                                "term/tag.xml",
       -                                "term/taxonomy.xml",
       -                                "term/list.xml",
       -                                "taxonomy/term.rss.xml",
       -                                "taxonomy/tag.rss.xml",
       -                                "taxonomy/taxonomy.rss.xml",
       -                                "taxonomy/rss.xml",
       -                                "taxonomy/list.rss.xml",
       -                                "taxonomy/term.xml",
       -                                "taxonomy/tag.xml",
       -                                "taxonomy/taxonomy.xml",
       -                                "taxonomy/list.xml",
       -                                "tag/term.rss.xml",
       -                                "tag/tag.rss.xml",
       -                                "tag/taxonomy.rss.xml",
       -                                "tag/rss.xml",
       -                                "tag/list.rss.xml",
       -                                "tag/term.xml",
       -                                "tag/tag.xml",
       -                                "tag/taxonomy.xml",
       -                                "tag/list.xml",
       -                                "_default/term.rss.xml",
       -                                "_default/tag.rss.xml",
       -                                "_default/taxonomy.rss.xml",
       -                                "_default/rss.xml",
       -                                "_default/list.rss.xml",
       -                                "_default/term.xml",
       -                                "_default/tag.xml",
       -                                "_default/taxonomy.xml",
       -                                "_default/list.xml",
       -                                "_internal/_default/rss.xml",
       -                        },
       -                },
       -                {
       -                        "RSS Taxonomy",
       -                        LayoutDescriptor{Kind: "taxonomy", Section: "tag", OutputFormatName: "rss", Suffix: "xml"},
       -                        "",
       -                        []string{
       -                                "tag/tag.terms.rss.xml",
       -                                "tag/terms.rss.xml",
       -                                "tag/taxonomy.rss.xml",
       -                                "tag/rss.xml",
       -                                "tag/list.rss.xml",
       -                                "tag/tag.terms.xml",
       -                                "tag/terms.xml",
       -                                "tag/taxonomy.xml",
       -                                "tag/list.xml",
       -                                "taxonomy/tag.terms.rss.xml",
       -                                "taxonomy/terms.rss.xml",
       -                                "taxonomy/taxonomy.rss.xml",
       -                                "taxonomy/rss.xml",
       -                                "taxonomy/list.rss.xml",
       -                                "taxonomy/tag.terms.xml",
       -                                "taxonomy/terms.xml",
       -                                "taxonomy/taxonomy.xml",
       -                                "taxonomy/list.xml",
       -                                "_default/tag.terms.rss.xml",
       -                                "_default/terms.rss.xml",
       -                                "_default/taxonomy.rss.xml",
       -                                "_default/rss.xml",
       -                                "_default/list.rss.xml",
       -                                "_default/tag.terms.xml",
       -                                "_default/terms.xml",
       -                                "_default/taxonomy.xml",
       -                                "_default/list.xml",
       -                                "_internal/_default/rss.xml",
       -                        },
       -                },
       -                {
       -                        "Home plain text",
       -                        LayoutDescriptor{Kind: "home", OutputFormatName: "json", Suffix: "json"},
       -                        "",
       -                        []string{
       -                                "index.json.json",
       -                                "home.json.json",
       -                                "list.json.json",
       -                                "index.json",
       -                                "home.json",
       -                                "list.json",
       -                                "_default/index.json.json",
       -                                "_default/home.json.json",
       -                                "_default/list.json.json",
       -                                "_default/index.json",
       -                                "_default/home.json",
       -                                "_default/list.json",
       -                        },
       -                },
       -                {
       -                        "Page plain text",
       -                        LayoutDescriptor{Kind: "page", OutputFormatName: "json", Suffix: "json"},
       -                        "",
       -                        []string{
       -                                "_default/single.json.json",
       -                                "_default/single.json",
       -                        },
       -                },
       -                {
       -                        "Reserved section, shortcodes",
       -                        LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "section/shortcodes.amp.html",
       -                                "section/section.amp.html",
       -                                "section/list.amp.html",
       -                                "section/shortcodes.html",
       -                                "section/section.html",
       -                                "section/list.html",
       -                                "_default/shortcodes.amp.html",
       -                                "_default/section.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/shortcodes.html",
       -                                "_default/section.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                {
       -                        "Reserved section, partials",
       -                        LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "section/partials.amp.html",
       -                                "section/section.amp.html",
       -                                "section/list.amp.html",
       -                                "section/partials.html",
       -                                "section/section.html",
       -                                "section/list.html",
       -                                "_default/partials.amp.html",
       -                                "_default/section.amp.html",
       -                                "_default/list.amp.html",
       -                                "_default/partials.html",
       -                                "_default/section.html",
       -                                "_default/list.html",
       -                        },
       -                },
       -                // This is currently always HTML only
       -                {
       -                        "404, HTML",
       -                        LayoutDescriptor{Kind: "404", OutputFormatName: "html", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "404.html.html",
       -                                "404.html",
       -                        },
       -                },
       -                {
       -                        "404, HTML baseof",
       -                        LayoutDescriptor{Kind: "404", Baseof: true, OutputFormatName: "html", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "404-baseof.html.html",
       -                                "baseof.html.html",
       -                                "404-baseof.html",
       -                                "baseof.html",
       -                                "_default/404-baseof.html.html",
       -                                "_default/baseof.html.html",
       -                                "_default/404-baseof.html",
       -                                "_default/baseof.html",
       -                        },
       -                },
       -                {
       -                        "Content hook",
       -                        LayoutDescriptor{Kind: "render-link", RenderingHook: true, Layout: "mylayout", Section: "blog", OutputFormatName: "amp", Suffix: "html"},
       -                        "",
       -                        []string{
       -                                "blog/_markup/render-link.amp.html",
       -                                "blog/_markup/render-link.html",
       -                                "_default/_markup/render-link.amp.html",
       -                                "_default/_markup/render-link.html",
       -                        },
       -                },
       -        } {
       -                c.Run(this.name, func(c *qt.C) {
       -                        l := NewLayoutHandler()
       -
       -                        layouts, err := l.For(this.layoutDescriptor)
       -
       -                        c.Assert(err, qt.IsNil)
       -                        c.Assert(layouts, qt.Not(qt.IsNil), qt.Commentf(this.layoutDescriptor.Kind))
       -
       -                        if !reflect.DeepEqual(layouts, this.expect) {
       -                                r := strings.NewReplacer(
       -                                        "[", "\t\"",
       -                                        "]", "\",",
       -                                        " ", "\",\n\t\"",
       -                                )
       -                                fmtGot := r.Replace(fmt.Sprintf("%v", layouts))
       -                                fmtExp := r.Replace(fmt.Sprintf("%v", this.expect))
       -
       -                                c.Fatalf("got %d items, expected %d:\nGot:\n\t%v\nExpected:\n\t%v\nDiff:\n%s", len(layouts), len(this.expect), layouts, this.expect, diff.Diff(fmtExp, fmtGot))
       -
       -                        }
       -                })
       -        }
       -}
       -
       -/*
       -func BenchmarkLayout(b *testing.B) {
       -        descriptor := LayoutDescriptor{Kind: "taxonomy", Section: "categories"}
       -        l := NewLayoutHandler()
       -
       -        for i := 0; i < b.N; i++ {
       -                _, err := l.For(descriptor, HTMLFormat)
       -                if err != nil {
       -                        panic(err)
       -                }
       -        }
       -}
       -
       -func BenchmarkLayoutUncached(b *testing.B) {
       -        for i := 0; i < b.N; i++ {
       -                descriptor := LayoutDescriptor{Kind: "taxonomy", Section: "categories"}
       -                l := NewLayoutHandler()
       -
       -                _, err := l.For(descriptor, HTMLFormat)
       -                if err != nil {
       -                        panic(err)
       -                }
       -        }
       -}
       -*/
 (DIR) diff --git a/output/outputFormat.go b/output/outputFormat.go
       @@ -133,6 +133,15 @@ var (
                        Weight: 10,
                }
        
       +        // Alias is the output format used for alias redirects.
       +        AliasHTMLFormat = Format{
       +                Name:          "alias",
       +                MediaType:     media.Builtin.HTMLType,
       +                IsHTML:        true,
       +                Ugly:          true,
       +                Permalinkable: false,
       +        }
       +
                MarkdownFormat = Format{
                        Name:        "markdown",
                        MediaType:   media.Builtin.MarkdownType,
       @@ -192,8 +201,17 @@ var (
                        Rel:       "sitemap",
                }
        
       -        HTTPStatusHTMLFormat = Format{
       -                Name:           "httpstatus",
       +        GotmplFormat = Format{
       +                Name:           "gotmpl",
       +                MediaType:      media.Builtin.GotmplType,
       +                IsPlainText:    true,
       +                NotAlternative: true,
       +        }
       +
       +        // I'm not sure having a 404 format is a good idea,
       +        // for one, we would want to have multiple formats for this.
       +        HTTPStatus404HTMLFormat = Format{
       +                Name:           "404",
                        MediaType:      media.Builtin.HTMLType,
                        NotAlternative: true,
                        Ugly:           true,
       @@ -209,12 +227,16 @@ var DefaultFormats = Formats{
                CSSFormat,
                CSVFormat,
                HTMLFormat,
       +        GotmplFormat,
       +        HTTPStatus404HTMLFormat,
       +        AliasHTMLFormat,
                JSONFormat,
                MarkdownFormat,
                WebAppManifestFormat,
                RobotsTxtFormat,
                RSSFormat,
                SitemapFormat,
       +        SitemapIndexFormat,
        }
        
        func init() {
 (DIR) diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go
       @@ -68,7 +68,7 @@ func TestDefaultTypes(t *testing.T) {
                c.Assert(RSSFormat.NoUgly, qt.Equals, true)
                c.Assert(CalendarFormat.IsHTML, qt.Equals, false)
        
       -        c.Assert(len(DefaultFormats), qt.Equals, 11)
       +        c.Assert(len(DefaultFormats), qt.Equals, 15)
        }
        
        func TestGetFormatByName(t *testing.T) {
       @@ -140,7 +140,7 @@ func TestGetFormatByFilename(t *testing.T) {
        func TestSort(t *testing.T) {
                c := qt.New(t)
                c.Assert(DefaultFormats[0].Name, qt.Equals, "html")
       -        c.Assert(DefaultFormats[1].Name, qt.Equals, "amp")
       +        c.Assert(DefaultFormats[1].Name, qt.Equals, "404")
        
                json := JSONFormat
                json.Weight = 1
 (DIR) diff --git a/resources/kinds/kinds.go b/resources/kinds/kinds.go
       @@ -34,6 +34,7 @@ const (
        
                // The following are (currently) temporary nodes,
                // i.e. nodes we create just to render in isolation.
       +        KindTemporary    = "temporary"
                KindRSS          = "rss"
                KindSitemap      = "sitemap"
                KindSitemapIndex = "sitemapindex"
 (DIR) diff --git a/resources/page/page.go b/resources/page/page.go
       @@ -150,8 +150,8 @@ type InSectionPositioner interface {
        
        // InternalDependencies is considered an internal interface.
        type InternalDependencies interface {
       -        // GetRelatedDocsHandler is for internal use only.
       -        GetRelatedDocsHandler() *RelatedDocsHandler
       +        // GetInternalRelatedDocsHandler is for internal use only.
       +        GetInternalRelatedDocsHandler() *RelatedDocsHandler
        }
        
        // OutputFormatsProvider provides the OutputFormats of a Page.
 (DIR) diff --git a/resources/page/page_paths.go b/resources/page/page_paths.go
       @@ -145,7 +145,7 @@ func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) {
                        pb.isUgly = true
                }
        
       -        if d.Type == output.HTTPStatusHTMLFormat || d.Type == output.SitemapFormat || d.Type == output.RobotsTxtFormat {
       +        if d.Type == output.HTTPStatus404HTMLFormat || d.Type == output.SitemapFormat || d.Type == output.RobotsTxtFormat {
                        pb.noSubResources = true
                } else if d.Kind != kinds.KindPage && d.URL == "" && d.Section.Base() != "/" {
                        if d.ExpandedPermalink != "" {
 (DIR) diff --git a/resources/page/pages_related.go b/resources/page/pages_related.go
       @@ -129,7 +129,7 @@ func (p Pages) withInvertedIndex(ctx context.Context, search func(idx *related.I
                        return nil, fmt.Errorf("invalid type %T in related search", p[0])
                }
        
       -        cache := d.GetRelatedDocsHandler()
       +        cache := d.GetInternalRelatedDocsHandler()
        
                searchIndex, err := cache.getOrCreateIndex(ctx, p)
                if err != nil {
 (DIR) diff --git a/resources/page/testhelpers_test.go b/resources/page/testhelpers_test.go
       @@ -221,7 +221,7 @@ func (p *testPage) GetTerms(taxonomy string) Pages {
                panic("testpage: not implemented")
        }
        
       -func (p *testPage) GetRelatedDocsHandler() *RelatedDocsHandler {
       +func (p *testPage) GetInternalRelatedDocsHandler() *RelatedDocsHandler {
                return relatedDocsHandler
        }
        
 (DIR) diff --git a/resources/resource_spec.go b/resources/resource_spec.go
       @@ -42,7 +42,6 @@ import (
                "github.com/gohugoio/hugo/resources/images"
                "github.com/gohugoio/hugo/resources/page"
                "github.com/gohugoio/hugo/resources/resource"
       -        "github.com/gohugoio/hugo/tpl"
        )
        
        func NewSpec(
       @@ -123,8 +122,6 @@ type Spec struct {
                BuildClosers types.CloseAdder
                Rebuilder    identity.SignalRebuilder
        
       -        TextTemplates tpl.TemplateParseFinder
       -
                Permalinks page.PermalinkExpander
        
                ImageCache *ImageCache
 (DIR) diff --git a/resources/resource_transformers/templates/execute_as_template.go b/resources/resource_transformers/templates/execute_as_template.go
       @@ -23,17 +23,17 @@ import (
                "github.com/gohugoio/hugo/resources"
                "github.com/gohugoio/hugo/resources/internal"
                "github.com/gohugoio/hugo/resources/resource"
       -        "github.com/gohugoio/hugo/tpl"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
        )
        
        // Client contains methods to perform template processing of Resource objects.
        type Client struct {
                rs *resources.Spec
       -        t  tpl.TemplatesProvider
       +        t  tplimpl.TemplateStoreProvider
        }
        
        // New creates a new Client with the given specification.
       -func New(rs *resources.Spec, t tpl.TemplatesProvider) *Client {
       +func New(rs *resources.Spec, t tplimpl.TemplateStoreProvider) *Client {
                if rs == nil {
                        panic("must provide a resource Spec")
                }
       @@ -45,7 +45,7 @@ func New(rs *resources.Spec, t tpl.TemplatesProvider) *Client {
        
        type executeAsTemplateTransform struct {
                rs         *resources.Spec
       -        t          tpl.TemplatesProvider
       +        t          tplimpl.TemplateStoreProvider
                targetPath string
                data       any
        }
       @@ -56,14 +56,13 @@ func (t *executeAsTemplateTransform) Key() internal.ResourceTransformationKey {
        
        func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransformationCtx) error {
                tplStr := helpers.ReaderToString(ctx.From)
       -        templ, err := t.t.TextTmpl().Parse(ctx.InPath, tplStr)
       +        th := t.t.GetTemplateStore()
       +        ti, err := th.TextParse(ctx.InPath, tplStr)
                if err != nil {
                        return fmt.Errorf("failed to parse Resource %q as Template:: %w", ctx.InPath, err)
                }
       -
                ctx.OutPath = t.targetPath
       -
       -        return t.t.Tmpl().ExecuteWithContext(ctx.Ctx, templ, ctx.To, t.data)
       +        return th.ExecuteWithContext(ctx.Ctx, ti, ctx.To, t.data)
        }
        
        func (c *Client) ExecuteAsTemplate(ctx context.Context, res resources.ResourceTransformer, targetPath string, data any) (resource.Resource, error) {
 (DIR) diff --git a/testscripts/commands/hugo_printunusedtemplates.txt b/testscripts/commands/hugo_printunusedtemplates.txt
       @@ -1,6 +1,6 @@
        hugo  --printUnusedTemplates
        
       -stderr 'Template _default/list.html is unused'
       +stderr 'Template /list.html is unused'
        
        -- hugo.toml --
        disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section", "page"]
 (DIR) diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go
       @@ -21,7 +21,6 @@ import (
                "strings"
        
                "github.com/gohugoio/hugo/common/hreflect"
       -        "github.com/gohugoio/hugo/tpl"
        )
        
        // Apply takes an array or slice c and returns a new slice with the function fname applied over it.
       @@ -109,8 +108,7 @@ func applyFnToThis(ctx context.Context, fn, this reflect.Value, args ...any) (re
        func (ns *Namespace) lookupFunc(ctx context.Context, fname string) (reflect.Value, bool) {
                namespace, methodName, ok := strings.Cut(fname, ".")
                if !ok {
       -                templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter)
       -                return templ.GetFunc(fname)
       +                return ns.deps.GetTemplateStore().GetFunc(fname)
                }
        
                // Namespace
 (DIR) diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go
       @@ -1,104 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package collections
       -
       -import (
       -        "context"
       -        "fmt"
       -        "io"
       -        "reflect"
       -        "testing"
       -
       -        qt "github.com/frankban/quicktest"
       -        "github.com/gohugoio/hugo/config/testconfig"
       -        "github.com/gohugoio/hugo/identity"
       -        "github.com/gohugoio/hugo/output"
       -        "github.com/gohugoio/hugo/output/layouts"
       -        "github.com/gohugoio/hugo/tpl"
       -)
       -
       -type templateFinder int
       -
       -func (templateFinder) GetIdentity(string) (identity.Identity, bool) {
       -        return identity.StringIdentity("test"), true
       -}
       -
       -func (templateFinder) Lookup(name string) (tpl.Template, bool) {
       -        return nil, false
       -}
       -
       -func (templateFinder) HasTemplate(name string) bool {
       -        return false
       -}
       -
       -func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
       -        return nil, false, false
       -}
       -
       -func (templateFinder) LookupVariants(name string) []tpl.Template {
       -        return nil
       -}
       -
       -func (templateFinder) LookupLayout(d layouts.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
       -        return nil, false, nil
       -}
       -
       -func (templateFinder) Execute(t tpl.Template, wr io.Writer, data any) error {
       -        return nil
       -}
       -
       -func (templateFinder) ExecuteWithContext(ctx context.Context, t tpl.Template, wr io.Writer, data any) error {
       -        return nil
       -}
       -
       -func (templateFinder) GetFunc(name string) (reflect.Value, bool) {
       -        if name == "dobedobedo" {
       -                return reflect.Value{}, false
       -        }
       -
       -        return reflect.ValueOf(fmt.Sprint), true
       -}
       -
       -func TestApply(t *testing.T) {
       -        t.Parallel()
       -        c := qt.New(t)
       -        d := testconfig.GetTestDeps(nil, nil)
       -        d.SetTempl(&tpl.TemplateHandlers{
       -                Tmpl: new(templateFinder),
       -        })
       -        ns := New(d)
       -
       -        strings := []any{"a\n", "b\n"}
       -
       -        ctx := context.Background()
       -
       -        result, err := ns.Apply(ctx, strings, "print", "a", "b", "c")
       -        c.Assert(err, qt.IsNil)
       -        c.Assert(result, qt.DeepEquals, []any{"abc", "abc"})
       -
       -        _, err = ns.Apply(ctx, strings, "apply", ".")
       -        c.Assert(err, qt.Not(qt.IsNil))
       -
       -        var nilErr *error
       -        _, err = ns.Apply(ctx, nilErr, "chomp", ".")
       -        c.Assert(err, qt.Not(qt.IsNil))
       -
       -        _, err = ns.Apply(ctx, strings, "dobedobedo", ".")
       -        c.Assert(err, qt.Not(qt.IsNil))
       -
       -        _, err = ns.Apply(ctx, strings, "foo.Chomp", "c\n")
       -        if err == nil {
       -                t.Errorf("apply with unknown func should fail")
       -        }
       -}
 (DIR) diff --git a/tpl/internal/go_templates/htmltemplate/hugo_template.go b/tpl/internal/go_templates/htmltemplate/hugo_template.go
       @@ -14,6 +14,8 @@
        package template
        
        import (
       +        "fmt"
       +
                "github.com/gohugoio/hugo/common/types"
                template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
        )
       @@ -51,3 +53,28 @@ func indirect(a any) any {
        
                return in
        }
       +
       +// CloneShallow creates a shallow copy of the template. It does not clone  or copy the nested templates.
       +func (t *Template) CloneShallow() (*Template, error) {
       +        t.nameSpace.mu.Lock()
       +        defer t.nameSpace.mu.Unlock()
       +        if t.escapeErr != nil {
       +                return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
       +        }
       +        textClone, err := t.text.Clone()
       +        if err != nil {
       +                return nil, err
       +        }
       +        ns := &nameSpace{set: make(map[string]*Template)}
       +        ns.esc = makeEscaper(ns)
       +        ret := &Template{
       +                nil,
       +                textClone,
       +                textClone.Tree,
       +                ns,
       +        }
       +        ret.set[ret.Name()] = ret
       +
       +        // Return the template associated with the name of this template.
       +        return ret.set[ret.Name()], nil
       +}
 (DIR) diff --git a/tpl/internal/go_templates/htmltemplate/template.go b/tpl/internal/go_templates/htmltemplate/template.go
       @@ -267,7 +267,7 @@ func (t *Template) Clone() (*Template, error) {
                        name := x.Name()
                        src := t.set[name]
                        if src == nil || src.escapeErr != nil {
       -                        return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
       +                        return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed, %q not found", t.Name(), name)
                        }
                        x.Tree = x.Tree.Copy()
                        ret.set[name] = &Template{
 (DIR) diff --git a/tpl/internal/go_templates/texttemplate/example_test.go b/tpl/internal/go_templates/texttemplate/example_test.go
       @@ -35,7 +35,7 @@ Josie
                        Name, Gift string
                        Attended   bool
                }
       -        var recipients = []Recipient{
       +        recipients := []Recipient{
                        {"Aunt Mildred", "bone china tea set", true},
                        {"Uncle John", "moleskin pants", false},
                        {"Cousin Rodney", "", false},
 (DIR) diff --git a/tpl/math/init.go b/tpl/math/init.go
       @@ -24,7 +24,7 @@ const name = "math"
        
        func init() {
                f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
       -                ctx := New()
       +                ctx := New(d)
        
                        ns := &internal.TemplateFuncsNamespace{
                                Name:    name,
 (DIR) diff --git a/tpl/math/math.go b/tpl/math/math.go
       @@ -20,9 +20,9 @@ import (
                "math"
                "math/rand"
                "reflect"
       -        "sync/atomic"
        
                _math "github.com/gohugoio/hugo/common/math"
       +        "github.com/gohugoio/hugo/deps"
                "github.com/spf13/cast"
        )
        
       @@ -32,12 +32,16 @@ var (
        )
        
        // New returns a new instance of the math-namespaced template functions.
       -func New() *Namespace {
       -        return &Namespace{}
       +func New(d *deps.Deps) *Namespace {
       +        return &Namespace{
       +                d: d,
       +        }
        }
        
        // Namespace provides template functions for the "math" namespace.
       -type Namespace struct{}
       +type Namespace struct {
       +        d *deps.Deps
       +}
        
        // Abs returns the absolute value of n.
        func (ns *Namespace) Abs(n any) (float64, error) {
       @@ -345,8 +349,6 @@ func (ns *Namespace) doArithmetic(inputs []any, operation rune) (value any, err 
                return
        }
        
       -var counter uint64
       -
        // Counter increments and returns a global counter.
        // This was originally added to be used in tests where now.UnixNano did not
        // have the needed precision (especially on Windows).
       @@ -354,5 +356,5 @@ var counter uint64
        // and the counter will reset on new builds.
        // <docsmeta>{"identifiers": ["now.UnixNano"] }</docsmeta>
        func (ns *Namespace) Counter() uint64 {
       -        return atomic.AddUint64(&counter, uint64(1))
       +        return ns.d.Counters.MathCounter.Add(1)
        }
 (DIR) diff --git a/tpl/math/math_test.go b/tpl/math/math_test.go
       @@ -24,7 +24,7 @@ func TestBasicNSArithmetic(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                type TestCase struct {
                        fn     func(inputs ...any) (any, error)
       @@ -66,7 +66,7 @@ func TestBasicNSArithmetic(t *testing.T) {
        func TestAbs(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -93,7 +93,7 @@ func TestAbs(t *testing.T) {
        func TestCeil(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -126,7 +126,7 @@ func TestFloor(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -159,7 +159,7 @@ func TestLog(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -200,7 +200,7 @@ func TestSqrt(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -239,7 +239,7 @@ func TestMod(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -279,7 +279,7 @@ func TestModBool(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -325,7 +325,7 @@ func TestRound(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -358,7 +358,7 @@ func TestPow(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -398,7 +398,7 @@ func TestMax(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                type TestCase struct {
                        values []any
       @@ -452,7 +452,7 @@ func TestMin(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                type TestCase struct {
                        values []any
       @@ -507,7 +507,7 @@ func TestSum(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                mustSum := func(values ...any) any {
                        result, err := ns.Sum(values...)
       @@ -530,7 +530,7 @@ func TestProduct(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                mustProduct := func(values ...any) any {
                        result, err := ns.Product(values...)
       @@ -554,7 +554,7 @@ func TestPi(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                expect := 3.1415
                result := ns.Pi()
       @@ -570,7 +570,7 @@ func TestSin(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -604,7 +604,7 @@ func TestCos(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -638,7 +638,7 @@ func TestTan(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
        
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        a      any
       @@ -680,7 +680,7 @@ func TestTan(t *testing.T) {
        func TestAsin(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -715,7 +715,7 @@ func TestAsin(t *testing.T) {
        func TestAcos(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -751,7 +751,7 @@ func TestAcos(t *testing.T) {
        func TestAtan(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -782,7 +782,7 @@ func TestAtan(t *testing.T) {
        func TestAtan2(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -821,7 +821,7 @@ func TestAtan2(t *testing.T) {
        func TestToDegrees(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
       @@ -852,7 +852,7 @@ func TestToDegrees(t *testing.T) {
        func TestToRadians(t *testing.T) {
                t.Parallel()
                c := qt.New(t)
       -        ns := New()
       +        ns := New(nil)
        
                for _, test := range []struct {
                        x      any
 (DIR) diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
       @@ -25,12 +25,12 @@ import (
        
                "github.com/bep/lazycache"
        
       +        "github.com/gohugoio/hugo/common/constants"
                "github.com/gohugoio/hugo/common/hashing"
                "github.com/gohugoio/hugo/identity"
        
       -        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       -
                "github.com/gohugoio/hugo/tpl"
       +        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
        
                bp "github.com/gohugoio/hugo/bufferpool"
                "github.com/gohugoio/hugo/deps"
       @@ -54,13 +54,6 @@ func (k partialCacheKey) Key() string {
                return hashing.HashString(append([]any{k.Name}, k.Variants...)...)
        }
        
       -func (k partialCacheKey) templateName() string {
       -        if !strings.HasPrefix(k.Name, "partials/") {
       -                return "partials/" + k.Name
       -        }
       -        return k.Name
       -}
       -
        // partialCache represents a LRU cache of partials.
        type partialCache struct {
                cache *lazycache.Cache[string, includeResult]
       @@ -129,6 +122,11 @@ func (ns *Namespace) Include(ctx context.Context, name string, contextList ...an
        }
        
        func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataList ...any) includeResult {
       +        if strings.HasPrefix(name, "partials/") {
       +                // This is most likely not what the user intended.
       +                // This worked before Hugo 0.146.0.
       +                ns.deps.Log.Warnidf(constants.WarnPartialSuperfluousPrefix, "Partial name %q starting with 'partials/' (as in {{ partial \"%s\"}}) is most likely not what you want. Before 0.146.0 we did a double lookup in this situation.", name, name)
       +        }
                // Create a new context with a timeout not connected to the incoming context.
                timeoutCtx, cancel := context.WithTimeout(context.Background(), ns.deps.Conf.Timeout())
                defer cancel()
       @@ -159,28 +157,14 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) 
                if len(dataList) > 0 {
                        data = dataList[0]
                }
       -
       -        var n string
       -        if strings.HasPrefix(name, "partials/") {
       -                n = name
       -        } else {
       -                n = "partials/" + name
       -        }
       -
       -        templ, found := ns.deps.Tmpl().Lookup(n)
       -        if !found {
       -                // For legacy reasons.
       -                templ, found = ns.deps.Tmpl().Lookup(n + ".html")
       -        }
       -
       -        if !found {
       +        name, desc := ns.deps.TemplateStore.TemplateDescriptorFromPath(name)
       +        v := ns.deps.TemplateStore.LookupPartial(name, desc)
       +        if v == nil {
                        return includeResult{err: fmt.Errorf("partial %q not found", name)}
                }
        
       -        var info tpl.ParseInfo
       -        if ip, ok := templ.(tpl.Info); ok {
       -                info = ip.ParseInfo()
       -        }
       +        templ := v
       +        info := v.ParseInfo
        
                var w io.Writer
        
       @@ -200,7 +184,7 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) 
                        w = b
                }
        
       -        if err := ns.deps.Tmpl().ExecuteWithContext(ctx, templ, w, data); err != nil {
       +        if err := ns.deps.GetTemplateStore().ExecuteWithContext(ctx, templ, w, data); err != nil {
                        return includeResult{err: err}
                }
        
       @@ -208,14 +192,14 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) 
        
                if ctx, ok := data.(*contextWrapper); ok {
                        result = ctx.Result
       -        } else if _, ok := templ.(*texttemplate.Template); ok {
       +        } else if _, ok := templ.Template.(*texttemplate.Template); ok {
                        result = w.(fmt.Stringer).String()
                } else {
                        result = template.HTML(w.(fmt.Stringer).String())
                }
        
                return includeResult{
       -                name:   templ.Name(),
       +                name:   templ.Template.Name(),
                        result: result,
                }
        }
       @@ -253,9 +237,9 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
                                // The templates that gets executed is measured in Execute.
                                // We need to track the time spent in the cache to
                                // get the totals correct.
       -                        ns.deps.Metrics.MeasureSince(key.templateName(), start)
       +                        ns.deps.Metrics.MeasureSince(r.name, start)
                        }
       -                ns.deps.Metrics.TrackValue(key.templateName(), r.result, found)
       +                ns.deps.Metrics.TrackValue(r.name, r.result, found)
                }
        
                if r.mangager != nil && depsManagerIn != nil {
 (DIR) diff --git a/tpl/partials/partials_integration_test.go b/tpl/partials/partials_integration_test.go
       @@ -170,7 +170,7 @@ D1
                got := buf.String()
        
                // Get rid of all the durations, they are never the same.
       -        durationRe := regexp.MustCompile(`\b[\.\d]*(ms|µs|s)\b`)
       +        durationRe := regexp.MustCompile(`\b[\.\d]*(ms|ns|µs|s)\b`)
        
                normalize := func(s string) string {
                        s = durationRe.ReplaceAllString(s, "")
       @@ -193,10 +193,10 @@ D1
        
                expect := `
                0        0       0      1  index.html
       -        100        0       0      1  partials/static2.html
       -        100       50       1      2  partials/static1.html
       -        25       50       2      4  partials/dynamic1.html
       -        66       33       1      3  partials/halfdynamic1.html
       +        100        0       0      1  _partials/static2.html
       +        100       50       1      2  _partials/static1.html
       +        25       50       2      4  _partials/dynamic1.html
       +        66       33       1      3  _partials/halfdynamic1.html
                `
        
                b.Assert(got, hqt.IsSameString, expect)
 (DIR) diff --git a/tpl/template.go b/tpl/template.go
       @@ -1,4 +1,4 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       +// Copyright 2025 The Hugo Authors. All rights reserved.
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
       @@ -16,9 +16,6 @@ package tpl
        
        import (
                "context"
       -        "io"
       -        "reflect"
       -        "regexp"
                "strings"
                "sync"
                "unicode"
       @@ -27,140 +24,18 @@ import (
                "github.com/gohugoio/hugo/common/hcontext"
                "github.com/gohugoio/hugo/identity"
                "github.com/gohugoio/hugo/langs"
       -        "github.com/gohugoio/hugo/output/layouts"
       -
       -        "github.com/gohugoio/hugo/output"
        
                htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
                texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
        )
        
       -// TemplateManager manages the collection of templates.
       -type TemplateManager interface {
       -        TemplateHandler
       -        TemplateFuncGetter
       -        AddTemplate(name, tpl string) error
       -}
       -
       -// TemplateVariants describes the possible variants of a template.
       -// All of these may be empty.
       -type TemplateVariants struct {
       -        Language     string
       -        OutputFormat output.Format
       -}
       -
       -// TemplateFinder finds templates.
       -type TemplateFinder interface {
       -        TemplateLookup
       -        TemplateLookupVariant
       -}
       -
       -// UnusedTemplatesProvider lists unused templates if the build is configured to track those.
       -type UnusedTemplatesProvider interface {
       -        UnusedTemplates() []FileInfo
       -}
       -
       -// TemplateHandlers holds the templates needed by Hugo.
       -type TemplateHandlers struct {
       -        Tmpl    TemplateHandler
       -        TxtTmpl TemplateParseFinder
       -}
       -
       -type TemplateExecutor interface {
       -        ExecuteWithContext(ctx context.Context, t Template, wr io.Writer, data any) error
       -}
       -
       -// TemplateHandler finds and executes templates.
       -type TemplateHandler interface {
       -        TemplateFinder
       -        TemplateExecutor
       -        LookupLayout(d layouts.LayoutDescriptor, f output.Format) (Template, bool, error)
       -        HasTemplate(name string) bool
       -        GetIdentity(name string) (identity.Identity, bool)
       -}
       -
       -type TemplateLookup interface {
       -        Lookup(name string) (Template, bool)
       -}
       -
       -type TemplateLookupVariant interface {
       -        // TODO(bep) this currently only works for shortcodes.
       -        // We may unify and expand this variant pattern to the
       -        // other templates, but we need this now for the shortcodes to
       -        // quickly determine if a shortcode has a template for a given
       -        // output format.
       -        // It returns the template, if it was found or not and if there are
       -        // alternative representations (output format, language).
       -        // We are currently only interested in output formats, so we should improve
       -        // this for speed.
       -        LookupVariant(name string, variants TemplateVariants) (Template, bool, bool)
       -        LookupVariants(name string) []Template
       -}
       -
        // Template is the common interface between text/template and html/template.
        type Template interface {
                Name() string
                Prepare() (*texttemplate.Template, error)
        }
        
       -// AddIdentity checks if t is an identity.Identity and returns it if so.
       -// Else it wraps it in a templateIdentity using its name as the base.
       -func AddIdentity(t Template) Template {
       -        if _, ok := t.(identity.IdentityProvider); ok {
       -                return t
       -        }
       -        return templateIdentityProvider{
       -                Template: t,
       -                id:       identity.StringIdentity(t.Name()),
       -        }
       -}
       -
       -type templateIdentityProvider struct {
       -        Template
       -        id identity.Identity
       -}
       -
       -func (t templateIdentityProvider) GetIdentity() identity.Identity {
       -        return t.id
       -}
       -
       -// TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain.
       -type TemplateParser interface {
       -        Parse(name, tpl string) (Template, error)
       -}
       -
       -// TemplateParseFinder provides both parsing and finding.
       -type TemplateParseFinder interface {
       -        TemplateParser
       -        TemplateFinder
       -}
       -
       -// TemplateDebugger prints some debug info to stdout.
       -type TemplateDebugger interface {
       -        Debug()
       -}
       -
       -// TemplatesProvider as implemented by deps.Deps.
       -type TemplatesProvider interface {
       -        Tmpl() TemplateHandler
       -        TextTmpl() TemplateParseFinder
       -}
       -
       -var baseOfRe = regexp.MustCompile("template: (.*?):")
       -
       -func extractBaseOf(err string) string {
       -        m := baseOfRe.FindStringSubmatch(err)
       -        if len(m) == 2 {
       -                return m[1]
       -        }
       -        return ""
       -}
       -
       -// TemplateFuncGetter allows to find a template func by name.
       -type TemplateFuncGetter interface {
       -        GetFunc(name string) (reflect.Value, bool)
       -}
       -
       +// RenderingContext represents the currently rendered site/language.
        type RenderingContext struct {
                Site       site
                SiteOutIdx int
       @@ -201,7 +76,9 @@ type site interface {
        }
        
        const (
       +        // HugoDeferredTemplatePrefix is the prefix for placeholders for deferred templates.
                HugoDeferredTemplatePrefix = "__hdeferred/"
       +        // HugoDeferredTemplateSuffix is the suffix for placeholders for deferred templates.
                HugoDeferredTemplateSuffix = "__d="
        )
        
       @@ -243,10 +120,11 @@ func StripHTML(s string) string {
                return s
        }
        
       +// DeferredExecution holds the template and data for a deferred execution.
        type DeferredExecution struct {
                Mu           sync.Mutex
                Ctx          context.Context
       -        TemplateName string
       +        TemplatePath string
                Data         any
        
                Executed bool
 (DIR) diff --git a/tpl/template_info.go b/tpl/template_info.go
       @@ -1,57 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tpl
       -
       -// Increments on breaking changes.
       -const TemplateVersion = 2
       -
       -type Info interface {
       -        ParseInfo() ParseInfo
       -}
       -
       -type FileInfo interface {
       -        Name() string
       -        Filename() string
       -}
       -
       -type IsInternalTemplateProvider interface {
       -        IsInternalTemplate() bool
       -}
       -
       -type ParseInfo struct {
       -        // Set for shortcode templates with any {{ .Inner }}
       -        IsInner bool
       -
       -        // Set for partials with a return statement.
       -        HasReturn bool
       -
       -        // Config extracted from template.
       -        Config ParseConfig
       -}
       -
       -func (info ParseInfo) IsZero() bool {
       -        return info.Config.Version == 0
       -}
       -
       -type ParseConfig struct {
       -        Version int
       -}
       -
       -var DefaultParseConfig = ParseConfig{
       -        Version: TemplateVersion,
       -}
       -
       -var DefaultParseInfo = ParseInfo{
       -        Config: DefaultParseConfig,
       -}
 (DIR) diff --git a/tpl/template_test.go b/tpl/template_test.go
       @@ -1,4 +1,4 @@
       -// Copyright 2018 The Hugo Authors. All rights reserved.
       +// Copyright 2025 The Hugo Authors. All rights reserved.
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
       @@ -15,20 +15,8 @@ package tpl
        
        import (
                "testing"
       -
       -        qt "github.com/frankban/quicktest"
        )
        
       -func TestExtractBaseof(t *testing.T) {
       -        c := qt.New(t)
       -
       -        replaced := extractBaseOf(`failed: template: _default/baseof.html:37:11: executing "_default/baseof.html" at <.Parents>: can't evaluate field Parents in type *hugolib.PageOutput`)
       -
       -        c.Assert(replaced, qt.Equals, "_default/baseof.html")
       -        c.Assert(extractBaseOf("not baseof for you"), qt.Equals, "")
       -        c.Assert(extractBaseOf("template: blog/baseof.html:23:11:"), qt.Equals, "blog/baseof.html")
       -}
       -
        func TestStripHTML(t *testing.T) {
                type test struct {
                        input, expected string
 (DIR) diff --git a/tpl/templates/defer_integration_test.go b/tpl/templates/defer_integration_test.go
       @@ -71,6 +71,81 @@ AMP.
        
        `
        
       +func TestDeferNoBaseof(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/index.html --
       +Home.
       +{{ with (templates.Defer (dict "key" "foo")) }}
       + Defer
       +{{ end }}
       +-- content/_index.md --
       +---
       +title: "Home"
       +---
       +
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/index.html", "Home.\n\n Defer")
       +}
       +
       +func TestDeferBaseof(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/baseof.html --
       +{{ with (templates.Defer (dict "key" "foo")) }}
       +Defer
       +{{ end }}
       +Block:{{ block "main" . }}{{ end }}$
       +-- layouts/index.html --
       +{{ define "main" }}
       +Home.
       +{{ end }}
       +-- content/_index.md --
       +---
       +title: "Home"
       +---
       +
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/index.html", "Home.\n\n Defer")
       +}
       +
       +func TestDeferMain(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/baseof.html --
       +
       +Block:{{ block "main" . }}{{ end }}$
       +-- layouts/index.html --
       +{{ define "main" }}
       +Home.
       +{{ with (templates.Defer (dict "key" "foo")) }}
       +Defer
       +{{ end }}
       +{{ end }}
       +-- content/_index.md --
       +---
       +title: "Home"
       +---
       +
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/index.html", "Home.\n\n Defer")
       +}
       +
        func TestDeferBasic(t *testing.T) {
                t.Parallel()
        
 (DIR) diff --git a/tpl/templates/templates.go b/tpl/templates/templates.go
       @@ -44,7 +44,7 @@ type Namespace struct {
        // Note that this is the Unix-styled relative path including filename suffix,
        // e.g. partials/header.html
        func (ns *Namespace) Exists(name string) bool {
       -        return ns.deps.Tmpl().HasTemplate(name)
       +        return ns.deps.GetTemplateStore().HasTemplate(name)
        }
        
        // Defer defers the execution of a template block.
       @@ -93,7 +93,7 @@ func (ns *Namespace) DoDefer(ctx context.Context, id string, optsv any) string {
                _, _ = ns.deps.BuildState.DeferredExecutions.Executions.GetOrCreate(id,
                        func() (*tpl.DeferredExecution, error) {
                                return &tpl.DeferredExecution{
       -                                TemplateName: templateName,
       +                                TemplatePath: templateName,
                                        Ctx:          ctx,
                                        Data:         opts.Data,
                                        Executed:     false,
 (DIR) diff --git a/tpl/tplimpl/category_string.go b/tpl/tplimpl/category_string.go
       @@ -0,0 +1,30 @@
       +// Code generated by "stringer -type Category"; DO NOT EDIT.
       +
       +package tplimpl
       +
       +import "strconv"
       +
       +func _() {
       +        // An "invalid array index" compiler error signifies that the constant values have changed.
       +        // Re-run the stringer command to generate them again.
       +        var x [1]struct{}
       +        _ = x[CategoryLayout-1]
       +        _ = x[CategoryBaseof-2]
       +        _ = x[CategoryMarkup-3]
       +        _ = x[CategoryShortcode-4]
       +        _ = x[CategoryPartial-5]
       +        _ = x[CategoryServer-6]
       +        _ = x[CategoryHugo-7]
       +}
       +
       +const _Category_name = "CategoryLayoutCategoryBaseofCategoryMarkupCategoryShortcodeCategoryPartialCategoryServerCategoryHugo"
       +
       +var _Category_index = [...]uint8{0, 14, 28, 42, 59, 74, 88, 100}
       +
       +func (i Category) String() string {
       +        i -= 1
       +        if i < 0 || i >= Category(len(_Category_index)-1) {
       +                return "Category(" + strconv.FormatInt(int64(i+1), 10) + ")"
       +        }
       +        return _Category_name[_Category_index[i]:_Category_index[i+1]]
       +}
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-codeblock-goat.html b/tpl/tplimpl/embedded/templates/_markup/render-codeblock-goat.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html b/tpl/tplimpl/embedded/templates/_markup/render-image.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html b/tpl/tplimpl/embedded/templates/_markup/render-link.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-table.html b/tpl/tplimpl/embedded/templates/_markup/render-table.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/partials/_funcs/get-page-images.html b/tpl/tplimpl/embedded/templates/_partials/_funcs/get-page-images.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_partials/disqus.html b/tpl/tplimpl/embedded/templates/_partials/disqus.html
       @@ -0,0 +1,23 @@
       +{{- $pc := .Site.Config.Privacy.Disqus -}}
       +{{- if not $pc.Disable -}}
       +{{ if .Site.Config.Services.Disqus.Shortname }}<div id="disqus_thread"></div>
       +<script>
       +    window.disqus_config = function () {
       +    {{with .Params.disqus_identifier }}this.page.identifier = '{{ . }}';{{end}}
       +    {{with .Params.disqus_title }}this.page.title = '{{ . }}';{{end}}
       +    {{with .Params.disqus_url }}this.page.url = '{{ . |  transform.HTMLEscape | safeURL }}';{{end}}
       +    };
       +    (function() {
       +        if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
       +            document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
       +            return;
       +        }
       +        var d = document, s = d.createElement('script'); s.async = true;
       +        s.src = '//' + {{ .Site.Config.Services.Disqus.Shortname }} + '.disqus.com/embed.js';
       +        s.setAttribute('data-timestamp', +new Date());
       +        (d.head || d.body).appendChild(s);
       +    })();
       +</script>
       +<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
       +<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}
       +{{- end -}}
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/google_analytics.html b/tpl/tplimpl/embedded/templates/_partials/google_analytics.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/opengraph.html b/tpl/tplimpl/embedded/templates/_partials/opengraph.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_partials/pagination.html b/tpl/tplimpl/embedded/templates/_partials/pagination.html
       @@ -0,0 +1,154 @@
       +{{- $validFormats := slice "default" "terse" }}
       +
       +{{- $msg1 := "When passing a map to the internal pagination template, one of the elements must be named 'page', and it must be set to the context of the current page." }}
       +{{- $msg2 := "The 'format' specified in the map passed to the internal pagination template is invalid. Valid choices are: %s." }}
       +
       +{{- $page := . }}
       +{{- $format := "default" }}
       +
       +{{- if reflect.IsMap . }}
       +  {{- with .page }}
       +    {{- $page = . }}
       +  {{- else }}
       +    {{- errorf $msg1 }}
       +  {{- end }}
       +  {{- with .format }}
       +    {{- $format = lower . }}
       +  {{- end }}
       +{{- end }}
       +
       +{{- if in $validFormats $format }}
       +  {{- if gt $page.Paginator.TotalPages 1 }}
       +    <ul class="pagination pagination-{{ $format }}">
       +      {{- partial (printf "inline/pagination/%s" $format) $page }}
       +    </ul>
       +  {{- end }}
       +{{- else }}
       +  {{- errorf $msg2 (delimit $validFormats ", ") }}
       +{{- end -}}
       +
       +{{/* Format: default
       +{{/* --------------------------------------------------------------------- */}}
       +{{- define "partials/inline/pagination/default" }}
       +  {{- with .Paginator }}
       +    {{- $currentPageNumber := .PageNumber }}
       +
       +    {{- with .First }}
       +      {{- if ne $currentPageNumber .PageNumber }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="First" class="page-link" role="button"><span aria-hidden="true">&laquo;&laquo;</span></a>
       +      </li>
       +      {{- else }}
       +      <li class="page-item disabled">
       +        <a aria-disabled="true" aria-label="First" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&laquo;&laquo;</span></a>
       +      </li>
       +      {{- end }}
       +    {{- end }}
       +
       +    {{- with .Prev }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="Previous" class="page-link" role="button"><span aria-hidden="true">&laquo;</span></a>
       +      </li>
       +    {{- else }}
       +      <li class="page-item disabled">
       +        <a aria-disabled="true" aria-label="Previous" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&laquo;</span></a>
       +      </li>
       +    {{- end }}
       +
       +    {{- $slots := 5 }}
       +    {{- $start := math.Max 1 (sub .PageNumber (math.Floor (div $slots 2))) }}
       +    {{- $end := math.Min .TotalPages (sub (add $start $slots) 1) }}
       +    {{- if lt (add (sub $end $start) 1) $slots }}
       +      {{- $start = math.Max 1 (add (sub $end $slots) 1) }}
       +    {{- end }}
       +
       +    {{- range $k := seq $start $end }}
       +      {{- if eq $.Paginator.PageNumber $k }}
       +      <li class="page-item active">
       +        <a aria-current="page" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       +      </li>
       +      {{- else }}
       +      <li class="page-item">
       +        <a href="{{ (index $.Paginator.Pagers (sub $k 1)).URL }}" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       +      </li>
       +      {{- end }}
       +    {{- end }}
       +
       +    {{- with .Next }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="Next" class="page-link" role="button"><span aria-hidden="true">&raquo;</span></a>
       +      </li>
       +    {{- else }}
       +      <li class="page-item disabled">
       +        <a aria-disabled="true" aria-label="Next" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&raquo;</span></a>
       +      </li>
       +    {{- end }}
       +
       +    {{- with .Last }}
       +      {{- if ne $currentPageNumber .PageNumber }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="Last" class="page-link" role="button"><span aria-hidden="true">&raquo;&raquo;</span></a>
       +      </li>
       +      {{- else }}
       +      <li class="page-item disabled">
       +        <a aria-disabled="true" aria-label="Last" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&raquo;&raquo;</span></a>
       +      </li>
       +      {{- end }}
       +    {{- end }}
       +  {{- end }}
       +{{- end -}}
       +
       +{{/* Format: terse
       +{{/* --------------------------------------------------------------------- */}}
       +{{- define "partials/inline/pagination/terse" }}
       +  {{- with .Paginator }}
       +    {{- $currentPageNumber := .PageNumber }}
       +
       +    {{- with .First }}
       +      {{- if ne $currentPageNumber .PageNumber }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="First" class="page-link" role="button"><span aria-hidden="true">&laquo;&laquo;</span></a>
       +      </li>
       +      {{- end }}
       +    {{- end }}
       +
       +    {{- with .Prev }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="Previous" class="page-link" role="button"><span aria-hidden="true">&laquo;</span></a>
       +      </li>
       +    {{- end }}
       +
       +    {{- $slots := 3 }}
       +    {{- $start := math.Max 1 (sub .PageNumber (math.Floor (div $slots 2))) }}
       +    {{- $end := math.Min .TotalPages (sub (add $start $slots) 1) }}
       +    {{- if lt (add (sub $end $start) 1) $slots }}
       +      {{- $start = math.Max 1 (add (sub $end $slots) 1) }}
       +    {{- end }}
       +
       +    {{- range $k := seq $start $end }}
       +      {{- if eq $.Paginator.PageNumber $k }}
       +      <li class="page-item active">
       +        <a aria-current="page" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       +      </li>
       +      {{- else }}
       +      <li class="page-item">
       +        <a href="{{ (index $.Paginator.Pagers (sub $k 1)).URL }}" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       +      </li>
       +      {{- end }}
       +    {{- end }}
       +
       +    {{- with .Next }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="Next" class="page-link" role="button"><span aria-hidden="true">&raquo;</span></a>
       +      </li>
       +    {{- end }}
       +
       +    {{- with .Last }}
       +      {{- if ne $currentPageNumber .PageNumber }}
       +      <li class="page-item">
       +        <a href="{{ .URL }}" aria-label="Last" class="page-link" role="button"><span aria-hidden="true">&raquo;&raquo;</span></a>
       +      </li>
       +      {{- end }}
       +    {{- end }}
       +  {{- end }}
       +{{- end -}}
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/schema.html b/tpl/tplimpl/embedded/templates/_partials/schema.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/twitter_cards.html b/tpl/tplimpl/embedded/templates/_partials/twitter_cards.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/1__h_simple_assets.html b/tpl/tplimpl/embedded/templates/_shortcodes/1__h_simple_assets.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/comment.html b/tpl/tplimpl/embedded/templates/_shortcodes/comment.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/details.html b/tpl/tplimpl/embedded/templates/_shortcodes/details.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/figure.html b/tpl/tplimpl/embedded/templates/_shortcodes/figure.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/gist.html b/tpl/tplimpl/embedded/templates/_shortcodes/gist.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/highlight.html b/tpl/tplimpl/embedded/templates/_shortcodes/highlight.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/instagram.html b/tpl/tplimpl/embedded/templates/_shortcodes/instagram.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/instagram_simple.html b/tpl/tplimpl/embedded/templates/_shortcodes/instagram_simple.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/param.html b/tpl/tplimpl/embedded/templates/_shortcodes/param.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/qr.html b/tpl/tplimpl/embedded/templates/_shortcodes/qr.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/ref.html b/tpl/tplimpl/embedded/templates/_shortcodes/ref.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/relref.html b/tpl/tplimpl/embedded/templates/_shortcodes/relref.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/twitter.html b/tpl/tplimpl/embedded/templates/_shortcodes/twitter.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/twitter_simple.html b/tpl/tplimpl/embedded/templates/_shortcodes/twitter_simple.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/vimeo.html b/tpl/tplimpl/embedded/templates/_shortcodes/vimeo.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/vimeo_simple.html b/tpl/tplimpl/embedded/templates/_shortcodes/vimeo_simple.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/x.html b/tpl/tplimpl/embedded/templates/_shortcodes/x.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/x_simple.html b/tpl/tplimpl/embedded/templates/_shortcodes/x_simple.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/shortcodes/youtube.html b/tpl/tplimpl/embedded/templates/_shortcodes/youtube.html
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/disqus.html b/tpl/tplimpl/embedded/templates/disqus.html
       @@ -1,23 +0,0 @@
       -{{- $pc := .Site.Config.Privacy.Disqus -}}
       -{{- if not $pc.Disable -}}
       -{{ if .Site.Config.Services.Disqus.Shortname }}<div id="disqus_thread"></div>
       -<script>
       -    window.disqus_config = function () {
       -    {{with .Params.disqus_identifier }}this.page.identifier = '{{ . }}';{{end}}
       -    {{with .Params.disqus_title }}this.page.title = '{{ . }}';{{end}}
       -    {{with .Params.disqus_url }}this.page.url = '{{ . | html  }}';{{end}}
       -    };
       -    (function() {
       -        if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
       -            document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
       -            return;
       -        }
       -        var d = document, s = d.createElement('script'); s.async = true;
       -        s.src = '//' + {{ .Site.Config.Services.Disqus.Shortname }} + '.disqus.com/embed.js';
       -        s.setAttribute('data-timestamp', +new Date());
       -        (d.head || d.body).appendChild(s);
       -    })();
       -</script>
       -<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
       -<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}
       -{{- end -}}
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/pagination.html b/tpl/tplimpl/embedded/templates/pagination.html
       @@ -1,154 +0,0 @@
       -{{- $validFormats := slice "default" "terse" }}
       -
       -{{- $msg1 := "When passing a map to the internal pagination template, one of the elements must be named 'page', and it must be set to the context of the current page." }}
       -{{- $msg2 := "The 'format' specified in the map passed to the internal pagination template is invalid. Valid choices are: %s." }}
       -
       -{{- $page := . }}
       -{{- $format := "default" }}
       -
       -{{- if reflect.IsMap . }}
       -  {{- with .page }}
       -    {{- $page = . }}
       -  {{- else }}
       -    {{- errorf $msg1 }}
       -  {{- end }}
       -  {{- with .format }}
       -    {{- $format = lower . }}
       -  {{- end }}
       -{{- end }}
       -
       -{{- if in $validFormats $format }}
       -  {{- if gt $page.Paginator.TotalPages 1 }}
       -    <ul class="pagination pagination-{{ $format }}">
       -      {{- partial (printf "partials/inline/pagination/%s" $format) $page }}
       -    </ul>
       -  {{- end }}
       -{{- else }}
       -  {{- errorf $msg2 (delimit $validFormats ", ") }}
       -{{- end -}}
       -
       -{{/* Format: default
       -{{/* --------------------------------------------------------------------- */}}
       -{{- define "partials/inline/pagination/default" }}
       -  {{- with .Paginator }}
       -    {{- $currentPageNumber := .PageNumber }}
       -
       -    {{- with .First }}
       -      {{- if ne $currentPageNumber .PageNumber }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="First" class="page-link" role="button"><span aria-hidden="true">&laquo;&laquo;</span></a>
       -      </li>
       -      {{- else }}
       -      <li class="page-item disabled">
       -        <a aria-disabled="true" aria-label="First" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&laquo;&laquo;</span></a>
       -      </li>
       -      {{- end }}
       -    {{- end }}
       -
       -    {{- with .Prev }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="Previous" class="page-link" role="button"><span aria-hidden="true">&laquo;</span></a>
       -      </li>
       -    {{- else }}
       -      <li class="page-item disabled">
       -        <a aria-disabled="true" aria-label="Previous" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&laquo;</span></a>
       -      </li>
       -    {{- end }}
       -
       -    {{- $slots := 5 }}
       -    {{- $start := math.Max 1 (sub .PageNumber (math.Floor (div $slots 2))) }}
       -    {{- $end := math.Min .TotalPages (sub (add $start $slots) 1) }}
       -    {{- if lt (add (sub $end $start) 1) $slots }}
       -      {{- $start = math.Max 1 (add (sub $end $slots) 1) }}
       -    {{- end }}
       -
       -    {{- range $k := seq $start $end }}
       -      {{- if eq $.Paginator.PageNumber $k }}
       -      <li class="page-item active">
       -        <a aria-current="page" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       -      </li>
       -      {{- else }}
       -      <li class="page-item">
       -        <a href="{{ (index $.Paginator.Pagers (sub $k 1)).URL }}" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       -      </li>
       -      {{- end }}
       -    {{- end }}
       -
       -    {{- with .Next }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="Next" class="page-link" role="button"><span aria-hidden="true">&raquo;</span></a>
       -      </li>
       -    {{- else }}
       -      <li class="page-item disabled">
       -        <a aria-disabled="true" aria-label="Next" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&raquo;</span></a>
       -      </li>
       -    {{- end }}
       -
       -    {{- with .Last }}
       -      {{- if ne $currentPageNumber .PageNumber }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="Last" class="page-link" role="button"><span aria-hidden="true">&raquo;&raquo;</span></a>
       -      </li>
       -      {{- else }}
       -      <li class="page-item disabled">
       -        <a aria-disabled="true" aria-label="Last" class="page-link" role="button" tabindex="-1"><span aria-hidden="true">&raquo;&raquo;</span></a>
       -      </li>
       -      {{- end }}
       -    {{- end }}
       -  {{- end }}
       -{{- end -}}
       -
       -{{/* Format: terse
       -{{/* --------------------------------------------------------------------- */}}
       -{{- define "partials/inline/pagination/terse" }}
       -  {{- with .Paginator }}
       -    {{- $currentPageNumber := .PageNumber }}
       -
       -    {{- with .First }}
       -      {{- if ne $currentPageNumber .PageNumber }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="First" class="page-link" role="button"><span aria-hidden="true">&laquo;&laquo;</span></a>
       -      </li>
       -      {{- end }}
       -    {{- end }}
       -
       -    {{- with .Prev }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="Previous" class="page-link" role="button"><span aria-hidden="true">&laquo;</span></a>
       -      </li>
       -    {{- end }}
       -
       -    {{- $slots := 3 }}
       -    {{- $start := math.Max 1 (sub .PageNumber (math.Floor (div $slots 2))) }}
       -    {{- $end := math.Min .TotalPages (sub (add $start $slots) 1) }}
       -    {{- if lt (add (sub $end $start) 1) $slots }}
       -      {{- $start = math.Max 1 (add (sub $end $slots) 1) }}
       -    {{- end }}
       -
       -    {{- range $k := seq $start $end }}
       -      {{- if eq $.Paginator.PageNumber $k }}
       -      <li class="page-item active">
       -        <a aria-current="page" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       -      </li>
       -      {{- else }}
       -      <li class="page-item">
       -        <a href="{{ (index $.Paginator.Pagers (sub $k 1)).URL }}" aria-label="Page {{ $k }}" class="page-link" role="button">{{ $k }}</a>
       -      </li>
       -      {{- end }}
       -    {{- end }}
       -
       -    {{- with .Next }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="Next" class="page-link" role="button"><span aria-hidden="true">&raquo;</span></a>
       -      </li>
       -    {{- end }}
       -
       -    {{- with .Last }}
       -      {{- if ne $currentPageNumber .PageNumber }}
       -      <li class="page-item">
       -        <a href="{{ .URL }}" aria-label="Last" class="page-link" role="button"><span aria-hidden="true">&raquo;&raquo;</span></a>
       -      </li>
       -      {{- end }}
       -    {{- end }}
       -  {{- end }}
       -{{- end -}}
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/robots.txt b/tpl/tplimpl/embedded/templates/robots.txt
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/rss.xml b/tpl/tplimpl/embedded/templates/rss.xml
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/sitemap.xml b/tpl/tplimpl/embedded/templates/sitemap.xml
 (DIR) diff --git a/tpl/tplimpl/embedded/templates/_default/sitemapindex.xml b/tpl/tplimpl/embedded/templates/sitemapindex.xml
 (DIR) diff --git a/tpl/tplimpl/legacy.go b/tpl/tplimpl/legacy.go
       @@ -0,0 +1,130 @@
       +// Copyright 2025 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package tplimpl
       +
       +import (
       +        "github.com/gohugoio/hugo/hugofs"
       +        "github.com/gohugoio/hugo/resources/kinds"
       +)
       +
       +type layoutLegacyMapping struct {
       +        sourcePath string
       +        target     layoutLegacyMappingTarget
       +}
       +
       +type layoutLegacyMappingTarget struct {
       +        targetPath     string
       +        targetDesc     TemplateDescriptor
       +        targetCategory Category
       +}
       +
       +var (
       +        ltermPlural = layoutLegacyMappingTarget{
       +                targetPath:     "/PLURAL",
       +                targetDesc:     TemplateDescriptor{Kind: kinds.KindTerm},
       +                targetCategory: CategoryLayout,
       +        }
       +        ltermBase = layoutLegacyMappingTarget{
       +                targetPath:     "",
       +                targetDesc:     TemplateDescriptor{Kind: kinds.KindTerm},
       +                targetCategory: CategoryLayout,
       +        }
       +
       +        ltaxPlural = layoutLegacyMappingTarget{
       +                targetPath:     "/PLURAL",
       +                targetDesc:     TemplateDescriptor{Kind: kinds.KindTaxonomy},
       +                targetCategory: CategoryLayout,
       +        }
       +        ltaxBase = layoutLegacyMappingTarget{
       +                targetPath:     "",
       +                targetDesc:     TemplateDescriptor{Kind: kinds.KindTaxonomy},
       +                targetCategory: CategoryLayout,
       +        }
       +
       +        lsectBase = layoutLegacyMappingTarget{
       +                targetPath:     "",
       +                targetDesc:     TemplateDescriptor{Kind: kinds.KindSection},
       +                targetCategory: CategoryLayout,
       +        }
       +        lsectTheSection = layoutLegacyMappingTarget{
       +                targetPath:     "/THESECTION",
       +                targetDesc:     TemplateDescriptor{Kind: kinds.KindSection},
       +                targetCategory: CategoryLayout,
       +        }
       +)
       +
       +type legacyTargetPathIdentifiers struct {
       +        targetPath     string
       +        targetCategory Category
       +        kind           string
       +        lang           string
       +        outputFormat   string
       +        ext            string
       +}
       +
       +type legacyOrdinalMapping struct {
       +        ordinal int
       +        mapping layoutLegacyMappingTarget
       +}
       +
       +type legacyOrdinalMappingFi struct {
       +        m  legacyOrdinalMapping
       +        fi hugofs.FileMetaInfo
       +}
       +
       +var legacyTermMappings = []layoutLegacyMapping{
       +        {sourcePath: "/PLURAL/term", target: ltermPlural},
       +        {sourcePath: "/PLURAL/SINGULAR", target: ltermPlural},
       +        {sourcePath: "/term/term", target: ltermBase},
       +        {sourcePath: "/term/SINGULAR", target: ltermPlural},
       +        {sourcePath: "/term/taxonomy", target: ltermPlural},
       +        {sourcePath: "/term/list", target: ltermBase},
       +        {sourcePath: "/taxonomy/term", target: ltermBase},
       +        {sourcePath: "/taxonomy/SINGULAR", target: ltermPlural},
       +        {sourcePath: "/SINGULAR/term", target: ltermPlural},
       +        {sourcePath: "/SINGULAR/SINGULAR", target: ltermPlural},
       +        {sourcePath: "/_default/SINGULAR", target: ltermPlural},
       +        {sourcePath: "/_default/taxonomy", target: ltermBase},
       +}
       +
       +var legacyTaxonomyMappings = []layoutLegacyMapping{
       +        {sourcePath: "/PLURAL/SINGULAR.terms", target: ltaxPlural},
       +        {sourcePath: "/PLURAL/terms", target: ltaxPlural},
       +        {sourcePath: "/PLURAL/taxonomy", target: ltaxPlural},
       +        {sourcePath: "/PLURAL/list", target: ltaxPlural},
       +        {sourcePath: "/SINGULAR/SINGULAR.terms", target: ltaxPlural},
       +        {sourcePath: "/SINGULAR/terms", target: ltaxPlural},
       +        {sourcePath: "/SINGULAR/taxonomy", target: ltaxPlural},
       +        {sourcePath: "/SINGULAR/list", target: ltaxPlural},
       +        {sourcePath: "/taxonomy/SINGULAR.terms", target: ltaxPlural},
       +        {sourcePath: "/taxonomy/terms", target: ltaxBase},
       +        {sourcePath: "/taxonomy/taxonomy", target: ltaxBase},
       +        {sourcePath: "/taxonomy/list", target: ltaxBase},
       +        {sourcePath: "/_default/SINGULAR.terms", target: ltaxBase},
       +        {sourcePath: "/_default/terms", target: ltaxBase},
       +        {sourcePath: "/_default/taxonomy", target: ltaxBase},
       +}
       +
       +var legacySectionMappings = []layoutLegacyMapping{
       +        // E.g. /mysection/mysection.html
       +        {sourcePath: "/THESECTION/THESECTION", target: lsectTheSection},
       +        // E.g. /section/mysection.html
       +        {sourcePath: "/SECTIONKIND/THESECTION", target: lsectTheSection},
       +        // E.g. /section/section.html
       +        {sourcePath: "/SECTIONKIND/SECTIONKIND", target: lsectBase},
       +        // E.g. /section/list.html
       +        {sourcePath: "/SECTIONKIND/list", target: lsectBase},
       +        // E.g. /_default/mysection.html
       +        {sourcePath: "/_default/THESECTION", target: lsectTheSection},
       +}
 (DIR) diff --git a/tpl/tplimpl/render_hook_integration_test.go b/tpl/tplimpl/render_hook_integration_test.go
       @@ -1,4 +1,4 @@
       -// Copyright 2024 The Hugo Authors. All rights reserved.
       +// Copyright 2025 The Hugo Authors. All rights reserved.
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
 (DIR) diff --git a/tpl/tplimpl/shortcodes.go b/tpl/tplimpl/shortcodes.go
       @@ -1,153 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
       -
       -import (
       -        "strings"
       -
       -        "github.com/gohugoio/hugo/tpl"
       -)
       -
       -// Currently lang, outFormat, suffix
       -const numTemplateVariants = 3
       -
       -type shortcodeVariant struct {
       -        // The possible variants: lang, outFormat, suffix
       -        // gtag
       -        // gtag.html
       -        // gtag.no.html
       -        // gtag.no.amp.html
       -        // A slice of length numTemplateVariants.
       -        variants []string
       -
       -        ts *templateState
       -}
       -
       -type shortcodeTemplates struct {
       -        variants []shortcodeVariant
       -}
       -
       -func (s *shortcodeTemplates) indexOf(variants []string) int {
       -L:
       -        for i, v1 := range s.variants {
       -                for i, v2 := range v1.variants {
       -                        if v2 != variants[i] {
       -                                continue L
       -                        }
       -                }
       -                return i
       -        }
       -        return -1
       -}
       -
       -func (s *shortcodeTemplates) fromVariants(variants tpl.TemplateVariants) (shortcodeVariant, bool) {
       -        return s.fromVariantsSlice([]string{
       -                variants.Language,
       -                strings.ToLower(variants.OutputFormat.Name),
       -                variants.OutputFormat.MediaType.FirstSuffix.Suffix,
       -        })
       -}
       -
       -func (s *shortcodeTemplates) fromVariantsSlice(variants []string) (shortcodeVariant, bool) {
       -        var (
       -                bestMatch       shortcodeVariant
       -                bestMatchWeight int
       -        )
       -
       -        for _, variant := range s.variants {
       -                w := s.compareVariants(variants, variant.variants)
       -                if bestMatchWeight == 0 || w > bestMatchWeight {
       -                        bestMatch = variant
       -                        bestMatchWeight = w
       -                }
       -        }
       -
       -        return bestMatch, true
       -}
       -
       -// calculate a weight for two string slices of same length.
       -// higher value means "better match".
       -func (s *shortcodeTemplates) compareVariants(a, b []string) int {
       -        weight := 0
       -        k := len(a)
       -        for i, av := range a {
       -                bv := b[i]
       -                if av == bv {
       -                        // Add more weight to the left side (language...).
       -                        weight = weight + k - i
       -                } else {
       -                        weight--
       -                }
       -        }
       -        return weight
       -}
       -
       -func templateVariants(name string) []string {
       -        _, variants := templateNameAndVariants(name)
       -        return variants
       -}
       -
       -func templateNameAndVariants(name string) (string, []string) {
       -        variants := make([]string, numTemplateVariants)
       -
       -        parts := strings.Split(name, ".")
       -
       -        if len(parts) <= 1 {
       -                // No variants.
       -                return name, variants
       -        }
       -
       -        name = parts[0]
       -        parts = parts[1:]
       -        lp := len(parts)
       -        start := len(variants) - lp
       -
       -        for i, j := start, 0; i < len(variants); i, j = i+1, j+1 {
       -                variants[i] = parts[j]
       -        }
       -
       -        if lp > 1 && lp < len(variants) {
       -                for i := lp - 1; i > 0; i-- {
       -                        variants[i-1] = variants[i]
       -                }
       -        }
       -
       -        if lp == 1 {
       -                // Suffix only. Duplicate it into the output format field to
       -                // make HTML win over AMP.
       -                variants[len(variants)-2] = variants[len(variants)-1]
       -        }
       -
       -        return name, variants
       -}
       -
       -func resolveTemplateType(name string) templateType {
       -        if isShortcode(name) {
       -                return templateShortcode
       -        }
       -
       -        if strings.Contains(name, "partials/") {
       -                return templatePartial
       -        }
       -
       -        return templateUndefined
       -}
       -
       -func isShortcode(name string) bool {
       -        return strings.Contains(name, shortcodesPathPrefix)
       -}
       -
       -func isInternal(name string) bool {
       -        return strings.HasPrefix(name, internalPathPrefix)
       -}
 (DIR) diff --git a/tpl/tplimpl/shortcodes_test.go b/tpl/tplimpl/shortcodes_test.go
       @@ -1,91 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
       -
       -import (
       -        "testing"
       -
       -        qt "github.com/frankban/quicktest"
       -)
       -
       -func TestShortcodesTemplate(t *testing.T) {
       -        t.Run("isShortcode", func(t *testing.T) {
       -                c := qt.New(t)
       -                c.Assert(isShortcode("shortcodes/figures.html"), qt.Equals, true)
       -                c.Assert(isShortcode("_internal/shortcodes/figures.html"), qt.Equals, true)
       -                c.Assert(isShortcode("shortcodes\\figures.html"), qt.Equals, false)
       -                c.Assert(isShortcode("myshortcodes"), qt.Equals, false)
       -        })
       -
       -        t.Run("variantsFromName", func(t *testing.T) {
       -                c := qt.New(t)
       -                c.Assert(templateVariants("figure.html"), qt.DeepEquals, []string{"", "html", "html"})
       -                c.Assert(templateVariants("figure.no.html"), qt.DeepEquals, []string{"no", "no", "html"})
       -                c.Assert(templateVariants("figure.no.amp.html"), qt.DeepEquals, []string{"no", "amp", "html"})
       -                c.Assert(templateVariants("figure.amp.html"), qt.DeepEquals, []string{"amp", "amp", "html"})
       -
       -                name, variants := templateNameAndVariants("figure.html")
       -                c.Assert(name, qt.Equals, "figure")
       -                c.Assert(variants, qt.DeepEquals, []string{"", "html", "html"})
       -        })
       -
       -        t.Run("compareVariants", func(t *testing.T) {
       -                c := qt.New(t)
       -                var s *shortcodeTemplates
       -
       -                tests := []struct {
       -                        name     string
       -                        name1    string
       -                        name2    string
       -                        expected int
       -                }{
       -                        {"Same suffix", "figure.html", "figure.html", 6},
       -                        {"Same suffix and output format", "figure.html.html", "figure.html.html", 6},
       -                        {"Same suffix, output format and language", "figure.no.html.html", "figure.no.html.html", 6},
       -                        {"No suffix", "figure", "figure", 6},
       -                        {"Different output format", "figure.amp.html", "figure.html.html", -1},
       -                        {"One with output format, one without", "figure.amp.html", "figure.html", -1},
       -                }
       -
       -                for _, test := range tests {
       -                        w := s.compareVariants(templateVariants(test.name1), templateVariants(test.name2))
       -                        c.Assert(w, qt.Equals, test.expected)
       -                }
       -        })
       -
       -        t.Run("indexOf", func(t *testing.T) {
       -                c := qt.New(t)
       -
       -                s := &shortcodeTemplates{
       -                        variants: []shortcodeVariant{
       -                                {variants: []string{"a", "b", "c"}},
       -                                {variants: []string{"a", "b", "d"}},
       -                        },
       -                }
       -
       -                c.Assert(s.indexOf([]string{"a", "b", "c"}), qt.Equals, 0)
       -                c.Assert(s.indexOf([]string{"a", "b", "d"}), qt.Equals, 1)
       -                c.Assert(s.indexOf([]string{"a", "b", "x"}), qt.Equals, -1)
       -        })
       -
       -        t.Run("Name", func(t *testing.T) {
       -                c := qt.New(t)
       -
       -                c.Assert(templateBaseName(templateShortcode, "shortcodes/foo.html"), qt.Equals, "foo.html")
       -                c.Assert(templateBaseName(templateShortcode, "_internal/shortcodes/foo.html"), qt.Equals, "foo.html")
       -                c.Assert(templateBaseName(templateShortcode, "shortcodes/test/foo.html"), qt.Equals, "test/foo.html")
       -
       -                c.Assert(true, qt.Equals, true)
       -        })
       -}
 (DIR) diff --git a/tpl/tplimpl/subcategory_string.go b/tpl/tplimpl/subcategory_string.go
       @@ -0,0 +1,25 @@
       +// Code generated by "stringer -type SubCategory"; DO NOT EDIT.
       +
       +package tplimpl
       +
       +import "strconv"
       +
       +func _() {
       +        // An "invalid array index" compiler error signifies that the constant values have changed.
       +        // Re-run the stringer command to generate them again.
       +        var x [1]struct{}
       +        _ = x[SubCategoryMain-0]
       +        _ = x[SubCategoryEmbedded-1]
       +        _ = x[SubCategoryInline-2]
       +}
       +
       +const _SubCategory_name = "SubCategoryMainSubCategoryEmbeddedSubCategoryInline"
       +
       +var _SubCategory_index = [...]uint8{0, 15, 34, 51}
       +
       +func (i SubCategory) String() string {
       +        if i < 0 || i >= SubCategory(len(_SubCategory_index)-1) {
       +                return "SubCategory(" + strconv.FormatInt(int64(i), 10) + ")"
       +        }
       +        return _SubCategory_name[_SubCategory_index[i]:_SubCategory_index[i+1]]
       +}
 (DIR) diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
       @@ -1,1235 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
       -
       -import (
       -        "bytes"
       -        "context"
       -        "embed"
       -        "fmt"
       -        "io"
       -        "io/fs"
       -        "path/filepath"
       -        "reflect"
       -        "regexp"
       -        "sort"
       -        "strings"
       -        "sync"
       -        "time"
       -        "unicode"
       -        "unicode/utf8"
       -
       -        "github.com/gohugoio/hugo/common/maps"
       -        "github.com/gohugoio/hugo/common/types"
       -        "github.com/gohugoio/hugo/output/layouts"
       -
       -        "github.com/gohugoio/hugo/helpers"
       -
       -        "github.com/gohugoio/hugo/output"
       -
       -        "github.com/gohugoio/hugo/deps"
       -        "github.com/spf13/afero"
       -
       -        "github.com/gohugoio/hugo/common/herrors"
       -        "github.com/gohugoio/hugo/hugofs"
       -        "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
       -
       -        htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
       -        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       -
       -        "github.com/gohugoio/hugo/identity"
       -        "github.com/gohugoio/hugo/tpl"
       -)
       -
       -const (
       -        textTmplNamePrefix = "_text/"
       -
       -        shortcodesPathPrefix = "shortcodes/"
       -        internalPathPrefix   = "_internal/"
       -        embeddedPathPrefix   = "_embedded/"
       -        baseFileBase         = "baseof"
       -)
       -
       -// The identifiers may be truncated in the log, e.g.
       -// "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
       -// We need this to identify position in templates with base templates applied.
       -var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
       -
       -// The tweet and twitter shortcodes were deprecated in favor of the x shortcode
       -// in v0.141.0. We can remove these aliases in v0.155.0 or later.
       -var embeddedTemplatesAliases = map[string][]string{
       -        "shortcodes/twitter.html": {"shortcodes/tweet.html"},
       -}
       -
       -var (
       -        _ tpl.TemplateManager         = (*templateExec)(nil)
       -        _ tpl.TemplateHandler         = (*templateExec)(nil)
       -        _ tpl.TemplateFuncGetter      = (*templateExec)(nil)
       -        _ tpl.TemplateFinder          = (*templateExec)(nil)
       -        _ tpl.UnusedTemplatesProvider = (*templateExec)(nil)
       -
       -        _ tpl.Template = (*templateState)(nil)
       -        _ tpl.Info     = (*templateState)(nil)
       -)
       -
       -var baseTemplateDefineRe = regexp.MustCompile(`^{{-?\s*define`)
       -
       -// needsBaseTemplate returns true if the first non-comment template block is a
       -// define block.
       -// If a base template does not exist, we will handle that when it's used.
       -func needsBaseTemplate(templ string) bool {
       -        idx := -1
       -        inComment := false
       -        for i := 0; i < len(templ); {
       -                if !inComment && strings.HasPrefix(templ[i:], "{{/*") {
       -                        inComment = true
       -                        i += 4
       -                } else if !inComment && strings.HasPrefix(templ[i:], "{{- /*") {
       -                        inComment = true
       -                        i += 6
       -                } else if inComment && strings.HasPrefix(templ[i:], "*/}}") {
       -                        inComment = false
       -                        i += 4
       -                } else if inComment && strings.HasPrefix(templ[i:], "*/ -}}") {
       -                        inComment = false
       -                        i += 6
       -                } else {
       -                        r, size := utf8.DecodeRuneInString(templ[i:])
       -                        if !inComment {
       -                                if strings.HasPrefix(templ[i:], "{{") {
       -                                        idx = i
       -                                        break
       -                                } else if !unicode.IsSpace(r) {
       -                                        break
       -                                }
       -                        }
       -                        i += size
       -                }
       -        }
       -
       -        if idx == -1 {
       -                return false
       -        }
       -
       -        return baseTemplateDefineRe.MatchString(templ[idx:])
       -}
       -
       -func newStandaloneTextTemplate(funcs map[string]any) tpl.TemplateParseFinder {
       -        return &textTemplateWrapperWithLock{
       -                RWMutex:  &sync.RWMutex{},
       -                Template: texttemplate.New("").Funcs(funcs),
       -        }
       -}
       -
       -func newTemplateHandlers(d *deps.Deps) (*tpl.TemplateHandlers, error) {
       -        exec, funcs := newTemplateExecuter(d)
       -        funcMap := make(map[string]any)
       -        for k, v := range funcs {
       -                funcMap[k] = v.Interface()
       -        }
       -
       -        var templateUsageTracker map[string]templateInfo
       -        if d.Conf.PrintUnusedTemplates() {
       -                templateUsageTracker = make(map[string]templateInfo)
       -        }
       -
       -        h := &templateHandler{
       -                nameBaseTemplateName: make(map[string]string),
       -                transformNotFound:    make(map[string]*templateState),
       -
       -                shortcodes:   make(map[string]*shortcodeTemplates),
       -                templateInfo: make(map[string]tpl.Info),
       -                baseof:       make(map[string]templateInfo),
       -                needsBaseof:  make(map[string]templateInfo),
       -
       -                main: newTemplateNamespace(funcMap),
       -
       -                Deps:                d,
       -                layoutHandler:       layouts.NewLayoutHandler(),
       -                layoutsFs:           d.BaseFs.Layouts.Fs,
       -                layoutTemplateCache: make(map[layoutCacheKey]layoutCacheEntry),
       -
       -                templateUsageTracker: templateUsageTracker,
       -        }
       -
       -        if err := h.loadEmbedded(); err != nil {
       -                return nil, err
       -        }
       -
       -        if err := h.loadTemplates(); err != nil {
       -                return nil, err
       -        }
       -
       -        if err := h.main.createPrototypes(); err != nil {
       -                return nil, err
       -        }
       -
       -        e := &templateExec{
       -                d:               d,
       -                executor:        exec,
       -                funcs:           funcs,
       -                templateHandler: h,
       -        }
       -
       -        if err := e.postTransform(); err != nil {
       -                return nil, err
       -        }
       -
       -        return &tpl.TemplateHandlers{
       -                Tmpl:    e,
       -                TxtTmpl: newStandaloneTextTemplate(funcMap),
       -        }, nil
       -}
       -
       -func newTemplateNamespace(funcs map[string]any) *templateNamespace {
       -        return &templateNamespace{
       -                prototypeHTML:           htmltemplate.New("").Funcs(funcs),
       -                prototypeText:           texttemplate.New("").Funcs(funcs),
       -                prototypeHTMLCloneCache: maps.NewCache[prototypeCloneID, *htmltemplate.Template](),
       -                prototypeTextCloneCache: maps.NewCache[prototypeCloneID, *texttemplate.Template](),
       -                templateStateMap: &templateStateMap{
       -                        templates: make(map[string]*templateState),
       -                },
       -        }
       -}
       -
       -func newTemplateState(owner *templateState, templ tpl.Template, info templateInfo, id identity.Identity) *templateState {
       -        if id == nil {
       -                id = info
       -        }
       -        return &templateState{
       -                owner:     owner,
       -                info:      info,
       -                typ:       info.resolveType(),
       -                Template:  templ,
       -                parseInfo: tpl.DefaultParseInfo,
       -                id:        id,
       -        }
       -}
       -
       -type layoutCacheKey struct {
       -        d layouts.LayoutDescriptor
       -        f string
       -}
       -
       -type templateExec struct {
       -        d        *deps.Deps
       -        executor texttemplate.Executer
       -        funcs    map[string]reflect.Value
       -
       -        *templateHandler
       -}
       -
       -func (t templateExec) Clone(d *deps.Deps) *templateExec {
       -        exec, funcs := newTemplateExecuter(d)
       -        t.executor = exec
       -        t.funcs = funcs
       -        t.d = d
       -        return &t
       -}
       -
       -func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data any) error {
       -        return t.ExecuteWithContext(context.Background(), templ, wr, data)
       -}
       -
       -func (t *templateExec) ExecuteWithContext(ctx context.Context, templ tpl.Template, wr io.Writer, data any) error {
       -        if rlocker, ok := templ.(types.RLocker); ok {
       -                rlocker.RLock()
       -                defer rlocker.RUnlock()
       -        }
       -        if t.Metrics != nil {
       -                defer t.Metrics.MeasureSince(templ.Name(), time.Now())
       -        }
       -
       -        if t.templateUsageTracker != nil {
       -                if ts, ok := templ.(*templateState); ok {
       -
       -                        t.templateUsageTrackerMu.Lock()
       -                        if _, found := t.templateUsageTracker[ts.Name()]; !found {
       -                                t.templateUsageTracker[ts.Name()] = ts.info
       -                        }
       -
       -                        if !ts.baseInfo.IsZero() {
       -                                if _, found := t.templateUsageTracker[ts.baseInfo.name]; !found {
       -                                        t.templateUsageTracker[ts.baseInfo.name] = ts.baseInfo
       -                                }
       -                        }
       -                        t.templateUsageTrackerMu.Unlock()
       -                }
       -        }
       -
       -        execErr := t.executor.ExecuteWithContext(ctx, templ, wr, data)
       -        if execErr != nil {
       -                owner := templ
       -                if ts, ok := templ.(*templateState); ok && ts.owner != nil {
       -                        owner = ts.owner
       -                }
       -                execErr = t.addFileContext(owner, execErr)
       -        }
       -        return execErr
       -}
       -
       -func (t *templateExec) UnusedTemplates() []tpl.FileInfo {
       -        if t.templateUsageTracker == nil {
       -                return nil
       -        }
       -        var unused []tpl.FileInfo
       -
       -        for _, ti := range t.needsBaseof {
       -                if _, found := t.templateUsageTracker[ti.name]; !found {
       -                        unused = append(unused, ti)
       -                }
       -        }
       -
       -        for _, ti := range t.baseof {
       -                if _, found := t.templateUsageTracker[ti.name]; !found {
       -                        unused = append(unused, ti)
       -                }
       -        }
       -
       -        for _, ts := range t.main.templates {
       -                ti := ts.info
       -                if strings.HasPrefix(ti.name, "_internal/") || ti.meta == nil {
       -                        continue
       -                }
       -
       -                if _, found := t.templateUsageTracker[ti.name]; !found {
       -                        unused = append(unused, ti)
       -                }
       -        }
       -
       -        sort.Slice(unused, func(i, j int) bool {
       -                return unused[i].Name() < unused[j].Name()
       -        })
       -
       -        return unused
       -}
       -
       -func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
       -        v, found := t.funcs[name]
       -        return v, found
       -}
       -
       -type templateHandler struct {
       -        main        *templateNamespace
       -        needsBaseof map[string]templateInfo
       -        baseof      map[string]templateInfo
       -
       -        // This is the filesystem to load the templates from. All the templates are
       -        // stored in the root of this filesystem.
       -        layoutsFs afero.Fs
       -
       -        layoutHandler *layouts.LayoutHandler
       -
       -        layoutTemplateCache   map[layoutCacheKey]layoutCacheEntry
       -        layoutTemplateCacheMu sync.RWMutex
       -
       -        *deps.Deps
       -
       -        // Used to get proper filenames in errors
       -        nameBaseTemplateName map[string]string
       -
       -        // Holds name and source of template definitions not found during the first
       -        // AST transformation pass.
       -        transformNotFound map[string]*templateState
       -
       -        // shortcodes maps shortcode name to template variants
       -        // (language, output format etc.) of that shortcode.
       -        shortcodes map[string]*shortcodeTemplates
       -
       -        // templateInfo maps template name to some additional information about that template.
       -        // Note that for shortcodes that same information is embedded in the
       -        // shortcodeTemplates type.
       -        templateInfo map[string]tpl.Info
       -
       -        // May be nil.
       -        templateUsageTracker   map[string]templateInfo
       -        templateUsageTrackerMu sync.Mutex
       -}
       -
       -type layoutCacheEntry struct {
       -        found bool
       -        templ tpl.Template
       -        err   error
       -}
       -
       -// AddTemplate parses and adds a template to the collection.
       -// Templates with name prefixed with "_text" will be handled as plain
       -// text templates.
       -func (t *templateHandler) AddTemplate(name, tpl string) error {
       -        templ, err := t.addTemplateTo(t.newTemplateInfo(name, tpl), t.main)
       -        if err == nil {
       -                _, err = t.applyTemplateTransformers(t.main, templ)
       -        }
       -        return err
       -}
       -
       -func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
       -        templ, found := t.main.Lookup(name)
       -        if found {
       -                return templ, true
       -        }
       -
       -        return nil, false
       -}
       -
       -func (t *templateHandler) LookupLayout(d layouts.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
       -        key := layoutCacheKey{d, f.Name}
       -        t.layoutTemplateCacheMu.RLock()
       -        if cacheVal, found := t.layoutTemplateCache[key]; found {
       -                t.layoutTemplateCacheMu.RUnlock()
       -                return cacheVal.templ, cacheVal.found, cacheVal.err
       -        }
       -
       -        t.layoutTemplateCacheMu.RUnlock()
       -
       -        t.layoutTemplateCacheMu.Lock()
       -        defer t.layoutTemplateCacheMu.Unlock()
       -
       -        templ, found, err := t.findLayout(d, f)
       -        cacheVal := layoutCacheEntry{found: found, templ: templ, err: err}
       -        t.layoutTemplateCache[key] = cacheVal
       -        return cacheVal.templ, cacheVal.found, cacheVal.err
       -}
       -
       -// This currently only applies to shortcodes and what we get here is the
       -// shortcode name.
       -func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
       -        name = templateBaseName(templateShortcode, name)
       -        s, found := t.shortcodes[name]
       -        if !found {
       -                return nil, false, false
       -        }
       -
       -        sv, found := s.fromVariants(variants)
       -        if !found {
       -                return nil, false, false
       -        }
       -
       -        more := len(s.variants) > 1
       -
       -        return sv.ts, true, more
       -}
       -
       -// LookupVariants returns all variants of name, nil if none found.
       -func (t *templateHandler) LookupVariants(name string) []tpl.Template {
       -        name = templateBaseName(templateShortcode, name)
       -        s, found := t.shortcodes[name]
       -        if !found {
       -                return nil
       -        }
       -
       -        variants := make([]tpl.Template, len(s.variants))
       -        for i := range variants {
       -                variants[i] = s.variants[i].ts
       -        }
       -
       -        return variants
       -}
       -
       -func (t *templateHandler) HasTemplate(name string) bool {
       -        if _, found := t.baseof[name]; found {
       -                return true
       -        }
       -
       -        if _, found := t.needsBaseof[name]; found {
       -                return true
       -        }
       -
       -        _, found := t.Lookup(name)
       -        return found
       -}
       -
       -func (t *templateHandler) GetIdentity(name string) (identity.Identity, bool) {
       -        if _, found := t.needsBaseof[name]; found {
       -                return identity.StringIdentity(name), true
       -        }
       -
       -        if _, found := t.baseof[name]; found {
       -                return identity.StringIdentity(name), true
       -        }
       -
       -        tt, found := t.Lookup(name)
       -        if !found {
       -                return nil, false
       -        }
       -        return tt.(identity.IdentityProvider).GetIdentity(), found
       -}
       -
       -func (t *templateHandler) findLayout(d layouts.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
       -        d.OutputFormatName = f.Name
       -        d.Suffix = f.MediaType.FirstSuffix.Suffix
       -        layouts, _ := t.layoutHandler.For(d)
       -        for _, name := range layouts {
       -                templ, found := t.main.Lookup(name)
       -                if found {
       -                        return templ, true, nil
       -                }
       -
       -                overlay, found := t.needsBaseof[name]
       -
       -                if !found {
       -                        continue
       -                }
       -
       -                d.Baseof = true
       -                baseLayouts, _ := t.layoutHandler.For(d)
       -                var base templateInfo
       -                found = false
       -                for _, l := range baseLayouts {
       -                        base, found = t.baseof[l]
       -                        if found {
       -                                break
       -                        }
       -                }
       -
       -                templ, err := t.applyBaseTemplate(overlay, base)
       -                if err != nil {
       -                        return nil, false, err
       -                }
       -
       -                ts := newTemplateState(nil, templ, overlay, identity.Or(base, overlay))
       -
       -                if found {
       -                        ts.baseInfo = base
       -                }
       -
       -                if _, err := t.applyTemplateTransformers(t.main, ts); err != nil {
       -                        return nil, false, err
       -                }
       -
       -                if err := t.extractPartials(ts.Template); err != nil {
       -                        return nil, false, err
       -                }
       -
       -                return ts, true, nil
       -
       -        }
       -
       -        return nil, false, nil
       -}
       -
       -func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo {
       -        var isText bool
       -        var isEmbedded bool
       -
       -        if strings.HasPrefix(name, embeddedPathPrefix) {
       -                isEmbedded = true
       -                name = strings.TrimPrefix(name, embeddedPathPrefix)
       -        }
       -
       -        name, isText = t.nameIsText(name)
       -        return templateInfo{
       -                name:       name,
       -                isText:     isText,
       -                isEmbedded: isEmbedded,
       -                template:   tpl,
       -        }
       -}
       -
       -func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error {
       -        if strings.HasPrefix(templ.Name(), "_internal") {
       -                return inerr
       -        }
       -
       -        ts, ok := templ.(*templateState)
       -        if !ok {
       -                return inerr
       -        }
       -
       -        identifiers := t.extractIdentifiers(inerr.Error())
       -
       -        checkFilename := func(info templateInfo, inErr error) (error, bool) {
       -                if info.meta == nil {
       -                        return inErr, false
       -                }
       -
       -                lineMatcher := func(m herrors.LineMatcher) int {
       -                        if m.Position.LineNumber != m.LineNumber {
       -                                return -1
       -                        }
       -
       -                        for _, id := range identifiers {
       -                                if strings.Contains(m.Line, id) {
       -                                        // We found the line, but return a 0 to signal to
       -                                        // use the column from the error message.
       -                                        return 0
       -                                }
       -                        }
       -                        return -1
       -                }
       -
       -                f, err := info.meta.Open()
       -                if err != nil {
       -                        return inErr, false
       -                }
       -                defer f.Close()
       -
       -                fe := herrors.NewFileErrorFromName(inErr, info.meta.Filename)
       -                fe.UpdateContent(f, lineMatcher)
       -
       -                if !fe.ErrorContext().Position.IsValid() {
       -                        return inErr, false
       -                }
       -                return fe, true
       -        }
       -
       -        inerr = fmt.Errorf("execute of template failed: %w", inerr)
       -
       -        if err, ok := checkFilename(ts.info, inerr); ok {
       -                return err
       -        }
       -
       -        err, _ := checkFilename(ts.baseInfo, inerr)
       -
       -        return err
       -}
       -
       -func (t *templateHandler) extractIdentifiers(line string) []string {
       -        m := identifiersRe.FindAllStringSubmatch(line, -1)
       -        identifiers := make([]string, len(m))
       -        for i := range m {
       -                identifiers[i] = m[i][1]
       -        }
       -        return identifiers
       -}
       -
       -func (t *templateHandler) addShortcodeVariant(ts *templateState) {
       -        name := ts.Name()
       -        base := templateBaseName(templateShortcode, name)
       -
       -        shortcodename, variants := templateNameAndVariants(base)
       -
       -        templs, found := t.shortcodes[shortcodename]
       -        if !found {
       -                templs = &shortcodeTemplates{}
       -                t.shortcodes[shortcodename] = templs
       -        }
       -
       -        sv := shortcodeVariant{variants: variants, ts: ts}
       -
       -        i := templs.indexOf(variants)
       -
       -        if i != -1 {
       -                // Only replace if it's an override of an internal template.
       -                if !isInternal(name) {
       -                        templs.variants[i] = sv
       -                }
       -        } else {
       -                templs.variants = append(templs.variants, sv)
       -        }
       -}
       -
       -func (t *templateHandler) addTemplateFile(name string, fim hugofs.FileMetaInfo) error {
       -        getTemplate := func(fim hugofs.FileMetaInfo) (templateInfo, error) {
       -                meta := fim.Meta()
       -                f, err := meta.Open()
       -                if err != nil {
       -                        return templateInfo{meta: meta}, err
       -                }
       -                defer f.Close()
       -                b, err := io.ReadAll(f)
       -                if err != nil {
       -                        return templateInfo{meta: meta}, err
       -                }
       -
       -                s := removeLeadingBOM(string(b))
       -
       -                var isText bool
       -                name, isText = t.nameIsText(name)
       -
       -                return templateInfo{
       -                        name:     name,
       -                        isText:   isText,
       -                        template: s,
       -                        meta:     meta,
       -                }, nil
       -        }
       -
       -        tinfo, err := getTemplate(fim)
       -        if err != nil {
       -                return err
       -        }
       -
       -        if isBaseTemplatePath(name) {
       -                // Store it for later.
       -                t.baseof[name] = tinfo
       -                return nil
       -        }
       -
       -        needsBaseof := !t.noBaseNeeded(name) && needsBaseTemplate(tinfo.template)
       -        if needsBaseof {
       -                t.needsBaseof[name] = tinfo
       -                return nil
       -        }
       -
       -        templ, err := t.addTemplateTo(tinfo, t.main)
       -        if err != nil {
       -                return tinfo.errWithFileContext("parse failed", err)
       -        }
       -
       -        if _, err = t.applyTemplateTransformers(t.main, templ); err != nil {
       -                return tinfo.errWithFileContext("transform failed", err)
       -        }
       -
       -        return nil
       -}
       -
       -func (t *templateHandler) addTemplateTo(info templateInfo, to *templateNamespace) (*templateState, error) {
       -        return to.parse(info)
       -}
       -
       -func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
       -        if overlay.isText {
       -                var (
       -                        templ = t.main.getPrototypeText(prototypeCloneIDBaseof).New(overlay.name)
       -                        err   error
       -                )
       -
       -                if !base.IsZero() {
       -                        templ, err = templ.Parse(base.template)
       -                        if err != nil {
       -                                return nil, base.errWithFileContext("text: base: parse failed", err)
       -                        }
       -                }
       -
       -                templ, err = texttemplate.Must(templ.Clone()).Parse(overlay.template)
       -                if err != nil {
       -                        return nil, overlay.errWithFileContext("text: overlay: parse failed", err)
       -                }
       -
       -                // The extra lookup is a workaround, see
       -                // * https://github.com/golang/go/issues/16101
       -                // * https://github.com/gohugoio/hugo/issues/2549
       -                // templ = templ.Lookup(templ.Name())
       -
       -                return templ, nil
       -        }
       -
       -        var (
       -                templ = t.main.getPrototypeHTML(prototypeCloneIDBaseof).New(overlay.name)
       -                err   error
       -        )
       -
       -        if !base.IsZero() {
       -                templ, err = templ.Parse(base.template)
       -                if err != nil {
       -                        return nil, base.errWithFileContext("html: base: parse failed", err)
       -                }
       -        }
       -
       -        templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template)
       -        if err != nil {
       -                return nil, overlay.errWithFileContext("html: overlay: parse failed", err)
       -        }
       -
       -        // The extra lookup is a workaround, see
       -        // * https://github.com/golang/go/issues/16101
       -        // * https://github.com/gohugoio/hugo/issues/2549
       -        templ = templ.Lookup(templ.Name())
       -
       -        return templ, err
       -}
       -
       -func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *templateState) (*templateContext, error) {
       -        c, err := applyTemplateTransformers(ts, ns.newTemplateLookup(ts))
       -        if err != nil {
       -                return nil, err
       -        }
       -
       -        for k := range c.templateNotFound {
       -                t.transformNotFound[k] = ts
       -        }
       -
       -        for k, v := range c.deferNodes {
       -                if err = t.main.addDeferredTemplate(ts, k, v); err != nil {
       -                        return nil, err
       -                }
       -        }
       -
       -        return c, err
       -}
       -
       -//go:embed all:embedded/templates/*
       -//go:embed embedded/templates/_default/*
       -//go:embed embedded/templates/_server/*
       -var embeddedTemplatesFs embed.FS
       -
       -func (t *templateHandler) loadEmbedded() error {
       -        return fs.WalkDir(embeddedTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error {
       -                if err != nil {
       -                        return err
       -                }
       -                if d == nil || d.IsDir() {
       -                        return nil
       -                }
       -
       -                templb, err := embeddedTemplatesFs.ReadFile(path)
       -                if err != nil {
       -                        return err
       -                }
       -
       -                // Get the newlines on Windows in line with how we had it back when we used Go Generate
       -                // to write the templates to Go files.
       -                templ := string(bytes.ReplaceAll(templb, []byte("\r\n"), []byte("\n")))
       -                name := strings.TrimPrefix(filepath.ToSlash(path), "embedded/templates/")
       -                templateName := name
       -
       -                // For the render hooks and the server templates it does not make sense to preserve the
       -                // double _internal double book-keeping,
       -                // just add it if its now provided by the user.
       -                if !strings.Contains(path, "_default/_markup") && !strings.HasPrefix(name, "_server/") && !strings.HasPrefix(name, "partials/_funcs/") {
       -                        templateName = internalPathPrefix + name
       -                }
       -
       -                if _, found := t.Lookup(templateName); !found {
       -                        if err := t.AddTemplate(embeddedPathPrefix+templateName, templ); err != nil {
       -                                return err
       -                        }
       -                }
       -
       -                if aliases, found := embeddedTemplatesAliases[name]; found {
       -                        // TODO(bep) avoid reparsing these aliases
       -                        for _, alias := range aliases {
       -                                alias = internalPathPrefix + alias
       -                                if err := t.AddTemplate(embeddedPathPrefix+alias, templ); err != nil {
       -                                        return err
       -                                }
       -                        }
       -                }
       -
       -                return nil
       -        })
       -}
       -
       -func (t *templateHandler) loadTemplates() error {
       -        walker := func(path string, fi hugofs.FileMetaInfo) error {
       -                if fi.IsDir() {
       -                        return nil
       -                }
       -
       -                if isDotFile(path) || isBackupFile(path) {
       -                        return nil
       -                }
       -
       -                name := strings.TrimPrefix(filepath.ToSlash(path), "/")
       -                filename := filepath.Base(path)
       -                outputFormats := t.Conf.GetConfigSection("outputFormats").(output.Formats)
       -                outputFormat, found := outputFormats.FromFilename(filename)
       -
       -                if found && outputFormat.IsPlainText {
       -                        name = textTmplNamePrefix + name
       -                }
       -
       -                if err := t.addTemplateFile(name, fi); err != nil {
       -                        return err
       -                }
       -
       -                return nil
       -        }
       -
       -        if err := helpers.Walk(t.Layouts.Fs, "", walker); err != nil {
       -                if !herrors.IsNotExist(err) {
       -                        return err
       -                }
       -                return nil
       -        }
       -
       -        return nil
       -}
       -
       -func (t *templateHandler) nameIsText(name string) (string, bool) {
       -        isText := strings.HasPrefix(name, textTmplNamePrefix)
       -        if isText {
       -                name = strings.TrimPrefix(name, textTmplNamePrefix)
       -        }
       -        return name, isText
       -}
       -
       -func (t *templateHandler) noBaseNeeded(name string) bool {
       -        if strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/") {
       -                return true
       -        }
       -        return strings.Contains(name, "_markup/")
       -}
       -
       -func (t *templateHandler) extractPartials(templ tpl.Template) error {
       -        templs := templates(templ)
       -        for _, templ := range templs {
       -                if templ.Name() == "" || !strings.HasPrefix(templ.Name(), "partials/") {
       -                        continue
       -                }
       -
       -                ts := newTemplateState(nil, templ, templateInfo{name: templ.Name()}, nil)
       -                ts.typ = templatePartial
       -
       -                t.main.mu.RLock()
       -                _, found := t.main.templates[templ.Name()]
       -                t.main.mu.RUnlock()
       -
       -                if !found {
       -                        t.main.mu.Lock()
       -                        // This is a template defined inline.
       -                        _, err := applyTemplateTransformers(ts, t.main.newTemplateLookup(ts))
       -                        if err != nil {
       -                                t.main.mu.Unlock()
       -                                return err
       -                        }
       -                        t.main.templates[templ.Name()] = ts
       -                        t.main.mu.Unlock()
       -
       -                }
       -        }
       -
       -        return nil
       -}
       -
       -func (t *templateHandler) postTransform() error {
       -        defineCheckedHTML := false
       -        defineCheckedText := false
       -
       -        for _, v := range t.main.templates {
       -                if v.typ == templateShortcode {
       -                        t.addShortcodeVariant(v)
       -                }
       -
       -                if defineCheckedHTML && defineCheckedText {
       -                        continue
       -                }
       -
       -                isText := isText(v.Template)
       -                if isText {
       -                        if defineCheckedText {
       -                                continue
       -                        }
       -                        defineCheckedText = true
       -                } else {
       -                        if defineCheckedHTML {
       -                                continue
       -                        }
       -                        defineCheckedHTML = true
       -                }
       -
       -                if err := t.extractPartials(v.Template); err != nil {
       -                        return err
       -                }
       -        }
       -
       -        for name, source := range t.transformNotFound {
       -                lookup := t.main.newTemplateLookup(source)
       -                templ := lookup(name)
       -                if templ != nil {
       -                        _, err := applyTemplateTransformers(templ, lookup)
       -                        if err != nil {
       -                                return err
       -                        }
       -                }
       -        }
       -
       -        for _, v := range t.shortcodes {
       -                sort.Slice(v.variants, func(i, j int) bool {
       -                        v1, v2 := v.variants[i], v.variants[j]
       -                        name1, name2 := v1.ts.Name(), v2.ts.Name()
       -                        isHTMl1, isHTML2 := strings.HasSuffix(name1, "html"), strings.HasSuffix(name2, "html")
       -
       -                        // There will be a weighted selection later, but make
       -                        // sure these are sorted to get a stable selection for
       -                        // output formats missing specific templates.
       -                        // Prefer HTML.
       -                        if isHTMl1 || isHTML2 && !(isHTMl1 && isHTML2) {
       -                                return isHTMl1
       -                        }
       -
       -                        return name1 < name2
       -                })
       -        }
       -
       -        return nil
       -}
       -
       -type prototypeCloneID uint16
       -
       -const (
       -        prototypeCloneIDBaseof prototypeCloneID = iota + 1
       -        prototypeCloneIDDefer
       -)
       -
       -type templateNamespace struct {
       -        prototypeText *texttemplate.Template
       -        prototypeHTML *htmltemplate.Template
       -
       -        prototypeHTMLCloneCache *maps.Cache[prototypeCloneID, *htmltemplate.Template]
       -        prototypeTextCloneCache *maps.Cache[prototypeCloneID, *texttemplate.Template]
       -
       -        *templateStateMap
       -}
       -
       -func (t *templateNamespace) getPrototypeText(id prototypeCloneID) *texttemplate.Template {
       -        v, ok := t.prototypeTextCloneCache.Get(id)
       -        if !ok {
       -                return t.prototypeText
       -        }
       -        return v
       -}
       -
       -func (t *templateNamespace) getPrototypeHTML(id prototypeCloneID) *htmltemplate.Template {
       -        v, ok := t.prototypeHTMLCloneCache.Get(id)
       -        if !ok {
       -                return t.prototypeHTML
       -        }
       -        return v
       -}
       -
       -func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
       -        t.mu.RLock()
       -        defer t.mu.RUnlock()
       -
       -        templ, found := t.templates[name]
       -        if !found {
       -                return nil, false
       -        }
       -
       -        return templ, found
       -}
       -
       -func (t *templateNamespace) createPrototypes() error {
       -        for _, id := range []prototypeCloneID{prototypeCloneIDBaseof, prototypeCloneIDDefer} {
       -                t.prototypeHTMLCloneCache.Set(id, htmltemplate.Must(t.prototypeHTML.Clone()))
       -                t.prototypeTextCloneCache.Set(id, texttemplate.Must(t.prototypeText.Clone()))
       -        }
       -        return nil
       -}
       -
       -func (t *templateNamespace) newTemplateLookup(in *templateState) func(name string) *templateState {
       -        return func(name string) *templateState {
       -                if templ, found := t.templates[name]; found {
       -                        if templ.isText() != in.isText() {
       -                                return nil
       -                        }
       -                        return templ
       -                }
       -                if templ, found := findTemplateIn(name, in); found {
       -                        return newTemplateState(nil, templ, templateInfo{name: templ.Name()}, nil)
       -                }
       -                return nil
       -        }
       -}
       -
       -func (t *templateNamespace) addDeferredTemplate(owner *templateState, name string, n *parse.ListNode) error {
       -        t.mu.Lock()
       -        defer t.mu.Unlock()
       -
       -        if _, found := t.templates[name]; found {
       -                return nil
       -        }
       -
       -        var templ tpl.Template
       -
       -        if owner.isText() {
       -                prototype := t.getPrototypeText(prototypeCloneIDDefer)
       -                tt, err := prototype.New(name).Parse("")
       -                if err != nil {
       -                        return fmt.Errorf("failed to parse empty text template %q: %w", name, err)
       -                }
       -                tt.Tree.Root = n
       -                templ = tt
       -        } else {
       -                prototype := t.getPrototypeHTML(prototypeCloneIDDefer)
       -                tt, err := prototype.New(name).Parse("")
       -                if err != nil {
       -                        return fmt.Errorf("failed to parse empty HTML template %q: %w", name, err)
       -                }
       -                tt.Tree.Root = n
       -                templ = tt
       -        }
       -
       -        dts := newTemplateState(owner, templ, templateInfo{name: name}, nil)
       -        t.templates[name] = dts
       -
       -        return nil
       -}
       -
       -func (t *templateNamespace) parse(info templateInfo) (*templateState, error) {
       -        t.mu.Lock()
       -        defer t.mu.Unlock()
       -
       -        if info.isText {
       -                prototype := t.prototypeText
       -
       -                templ, err := prototype.New(info.name).Parse(info.template)
       -                if err != nil {
       -                        return nil, err
       -                }
       -
       -                ts := newTemplateState(nil, templ, info, nil)
       -
       -                t.templates[info.name] = ts
       -
       -                return ts, nil
       -        }
       -
       -        prototype := t.prototypeHTML
       -
       -        templ, err := prototype.New(info.name).Parse(info.template)
       -        if err != nil {
       -                return nil, err
       -        }
       -
       -        ts := newTemplateState(nil, templ, info, nil)
       -
       -        t.templates[info.name] = ts
       -
       -        return ts, nil
       -}
       -
       -var _ tpl.IsInternalTemplateProvider = (*templateState)(nil)
       -
       -type templateState struct {
       -        tpl.Template
       -
       -        // Set for deferred templates.
       -        owner *templateState
       -
       -        typ       templateType
       -        parseInfo tpl.ParseInfo
       -        id        identity.Identity
       -
       -        info     templateInfo
       -        baseInfo templateInfo // Set when a base template is used.
       -}
       -
       -func (t *templateState) IsInternalTemplate() bool {
       -        return t.info.isEmbedded
       -}
       -
       -func (t *templateState) GetIdentity() identity.Identity {
       -        return t.id
       -}
       -
       -func (t *templateState) ParseInfo() tpl.ParseInfo {
       -        return t.parseInfo
       -}
       -
       -func (t *templateState) isText() bool {
       -        return isText(t.Template)
       -}
       -
       -func (t *templateState) String() string {
       -        return t.Name()
       -}
       -
       -func isText(templ tpl.Template) bool {
       -        _, isText := templ.(*texttemplate.Template)
       -        return isText
       -}
       -
       -type templateStateMap struct {
       -        mu        sync.RWMutex
       -        templates map[string]*templateState
       -}
       -
       -type textTemplateWrapperWithLock struct {
       -        *sync.RWMutex
       -        *texttemplate.Template
       -}
       -
       -func (t *textTemplateWrapperWithLock) Lookup(name string) (tpl.Template, bool) {
       -        t.RLock()
       -        templ := t.Template.Lookup(name)
       -        t.RUnlock()
       -        if templ == nil {
       -                return nil, false
       -        }
       -        return &textTemplateWrapperWithLock{
       -                RWMutex:  t.RWMutex,
       -                Template: templ,
       -        }, true
       -}
       -
       -func (t *textTemplateWrapperWithLock) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
       -        panic("not supported")
       -}
       -
       -func (t *textTemplateWrapperWithLock) LookupVariants(name string) []tpl.Template {
       -        panic("not supported")
       -}
       -
       -func (t *textTemplateWrapperWithLock) Parse(name, tpl string) (tpl.Template, error) {
       -        t.Lock()
       -        defer t.Unlock()
       -        return t.Template.New(name).Parse(tpl)
       -}
       -
       -func isBackupFile(path string) bool {
       -        return path[len(path)-1] == '~'
       -}
       -
       -func isBaseTemplatePath(path string) bool {
       -        return strings.Contains(filepath.Base(path), baseFileBase)
       -}
       -
       -func isDotFile(path string) bool {
       -        return filepath.Base(path)[0] == '.'
       -}
       -
       -func removeLeadingBOM(s string) string {
       -        const bom = '\ufeff'
       -
       -        for i, r := range s {
       -                if i == 0 && r != bom {
       -                        return s
       -                }
       -                if i > 0 {
       -                        return s[i:]
       -                }
       -        }
       -
       -        return s
       -}
       -
       -// resolves _internal/shortcodes/param.html => param.html etc.
       -func templateBaseName(typ templateType, name string) string {
       -        name = strings.TrimPrefix(name, internalPathPrefix)
       -        switch typ {
       -        case templateShortcode:
       -                return strings.TrimPrefix(name, shortcodesPathPrefix)
       -        default:
       -                panic("not implemented")
       -        }
       -}
       -
       -func unwrap(templ tpl.Template) tpl.Template {
       -        if ts, ok := templ.(*templateState); ok {
       -                return ts.Template
       -        }
       -        return templ
       -}
       -
       -func templates(in tpl.Template) []tpl.Template {
       -        var templs []tpl.Template
       -        in = unwrap(in)
       -        if textt, ok := in.(*texttemplate.Template); ok {
       -                for _, t := range textt.Templates() {
       -                        templs = append(templs, t)
       -                }
       -        }
       -
       -        if htmlt, ok := in.(*htmltemplate.Template); ok {
       -                for _, t := range htmlt.Templates() {
       -                        templs = append(templs, t)
       -                }
       -        }
       -
       -        return templs
       -}
 (DIR) diff --git a/tpl/tplimpl/templateFuncster.go b/tpl/tplimpl/templateFuncster.go
       @@ -1,14 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
 (DIR) diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go
       @@ -1,51 +0,0 @@
       -// Copyright 2017-present The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
       -
       -import (
       -        "github.com/gohugoio/hugo/deps"
       -        "github.com/gohugoio/hugo/tpl"
       -)
       -
       -// TemplateProvider manages templates.
       -type TemplateProvider struct{}
       -
       -// DefaultTemplateProvider is a globally available TemplateProvider.
       -var DefaultTemplateProvider *TemplateProvider
       -
       -// Update updates the Hugo Template System in the provided Deps
       -// with all the additional features, templates & functions.
       -func (*TemplateProvider) NewResource(dst *deps.Deps) error {
       -        handlers, err := newTemplateHandlers(dst)
       -        if err != nil {
       -                return err
       -        }
       -        dst.SetTempl(handlers)
       -        return nil
       -}
       -
       -// Clone clones.
       -func (*TemplateProvider) CloneResource(dst, src *deps.Deps) error {
       -        t := src.Tmpl().(*templateExec)
       -        c := t.Clone(dst)
       -        funcMap := make(map[string]any)
       -        for k, v := range c.funcs {
       -                funcMap[k] = v.Interface()
       -        }
       -        dst.SetTempl(&tpl.TemplateHandlers{
       -                Tmpl:    c,
       -                TxtTmpl: newStandaloneTextTemplate(funcMap),
       -        })
       -        return nil
       -}
 (DIR) diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
       @@ -1,381 +0,0 @@
       -// Copyright 2016 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
       -
       -import (
       -        "errors"
       -        "fmt"
       -        "strings"
       -
       -        htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
       -        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       -
       -        "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
       -
       -        "github.com/gohugoio/hugo/common/hashing"
       -        "github.com/gohugoio/hugo/common/maps"
       -        "github.com/gohugoio/hugo/tpl"
       -        "github.com/mitchellh/mapstructure"
       -        "slices"
       -)
       -
       -type templateType int
       -
       -const (
       -        templateUndefined templateType = iota
       -        templateShortcode
       -        templatePartial
       -)
       -
       -type templateContext struct {
       -        visited          map[string]bool
       -        templateNotFound map[string]bool
       -        deferNodes       map[string]*parse.ListNode
       -        lookupFn         func(name string) *templateState
       -
       -        // The last error encountered.
       -        err error
       -
       -        // Set when we're done checking for config header.
       -        configChecked bool
       -
       -        t *templateState
       -
       -        // Store away the return node in partials.
       -        returnNode *parse.CommandNode
       -}
       -
       -func (c templateContext) getIfNotVisited(name string) *templateState {
       -        if c.visited[name] {
       -                return nil
       -        }
       -        c.visited[name] = true
       -        templ := c.lookupFn(name)
       -        if templ == nil {
       -                // This may be a inline template defined outside of this file
       -                // and not yet parsed. Unusual, but it happens.
       -                // Store the name to try again later.
       -                c.templateNotFound[name] = true
       -        }
       -
       -        return templ
       -}
       -
       -func newTemplateContext(
       -        t *templateState,
       -        lookupFn func(name string) *templateState,
       -) *templateContext {
       -        return &templateContext{
       -                t:                t,
       -                lookupFn:         lookupFn,
       -                visited:          make(map[string]bool),
       -                templateNotFound: make(map[string]bool),
       -                deferNodes:       make(map[string]*parse.ListNode),
       -        }
       -}
       -
       -func applyTemplateTransformers(
       -        t *templateState,
       -        lookupFn func(name string) *templateState,
       -) (*templateContext, error) {
       -        if t == nil {
       -                return nil, errors.New("expected template, but none provided")
       -        }
       -
       -        c := newTemplateContext(t, lookupFn)
       -        tree := getParseTree(t.Template)
       -
       -        _, err := c.applyTransformations(tree.Root)
       -
       -        if err == nil && c.returnNode != nil {
       -                // This is a partial with a return statement.
       -                c.t.parseInfo.HasReturn = true
       -                tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
       -        }
       -
       -        return c, err
       -}
       -
       -func getParseTree(templ tpl.Template) *parse.Tree {
       -        templ = unwrap(templ)
       -        if text, ok := templ.(*texttemplate.Template); ok {
       -                return text.Tree
       -        }
       -        return templ.(*htmltemplate.Template).Tree
       -}
       -
       -const (
       -        // We parse this template and modify the nodes in order to assign
       -        // the return value of a partial to a contextWrapper via Set. We use
       -        // "range" over a one-element slice so we can shift dot to the
       -        // partial's argument, Arg, while allowing Arg to be falsy.
       -        partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ range (slice .Arg) }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
       -
       -        doDeferTempl = `{{ doDefer ("PLACEHOLDER1") ("PLACEHOLDER2") }}`
       -)
       -
       -var (
       -        partialReturnWrapper *parse.ListNode
       -        doDefer              *parse.ListNode
       -)
       -
       -func init() {
       -        templ, err := texttemplate.New("").Parse(partialReturnWrapperTempl)
       -        if err != nil {
       -                panic(err)
       -        }
       -        partialReturnWrapper = templ.Tree.Root
       -
       -        templ, err = texttemplate.New("").Funcs(texttemplate.FuncMap{"doDefer": func(string, string) string { return "" }}).Parse(doDeferTempl)
       -        if err != nil {
       -                panic(err)
       -        }
       -        doDefer = templ.Tree.Root
       -}
       -
       -// wrapInPartialReturnWrapper copies and modifies the parsed nodes of a
       -// predefined partial return wrapper to insert those of a user-defined partial.
       -func (c *templateContext) wrapInPartialReturnWrapper(n *parse.ListNode) *parse.ListNode {
       -        wrapper := partialReturnWrapper.CopyList()
       -        rangeNode := wrapper.Nodes[2].(*parse.RangeNode)
       -        retn := rangeNode.List.Nodes[0]
       -        setCmd := retn.(*parse.ActionNode).Pipe.Cmds[0]
       -        setPipe := setCmd.Args[1].(*parse.PipeNode)
       -        // Replace PLACEHOLDER with the real return value.
       -        // Note that this is a PipeNode, so it will be wrapped in parens.
       -        setPipe.Cmds = []*parse.CommandNode{c.returnNode}
       -        rangeNode.List.Nodes = append(n.Nodes, retn)
       -
       -        return wrapper
       -}
       -
       -// applyTransformations do 2 things:
       -// 1) Parses partial return statement.
       -// 2) Tracks template (partial) dependencies and some other info.
       -func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
       -        switch x := n.(type) {
       -        case *parse.ListNode:
       -                if x != nil {
       -                        c.applyTransformationsToNodes(x.Nodes...)
       -                }
       -        case *parse.ActionNode:
       -                c.applyTransformationsToNodes(x.Pipe)
       -        case *parse.IfNode:
       -                c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
       -        case *parse.WithNode:
       -                c.handleDefer(x)
       -                c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
       -        case *parse.RangeNode:
       -                c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
       -        case *parse.TemplateNode:
       -                subTempl := c.getIfNotVisited(x.Name)
       -                if subTempl != nil {
       -                        c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
       -                }
       -        case *parse.PipeNode:
       -                c.collectConfig(x)
       -                for i, cmd := range x.Cmds {
       -                        keep, _ := c.applyTransformations(cmd)
       -                        if !keep {
       -                                x.Cmds = slices.Delete(x.Cmds, i, i+1)
       -                        }
       -                }
       -
       -        case *parse.CommandNode:
       -                c.collectInner(x)
       -                keep := c.collectReturnNode(x)
       -
       -                for _, elem := range x.Args {
       -                        switch an := elem.(type) {
       -                        case *parse.PipeNode:
       -                                c.applyTransformations(an)
       -                        }
       -                }
       -                return keep, c.err
       -        }
       -
       -        return true, c.err
       -}
       -
       -func (c *templateContext) handleDefer(withNode *parse.WithNode) {
       -        if len(withNode.Pipe.Cmds) != 1 {
       -                return
       -        }
       -        cmd := withNode.Pipe.Cmds[0]
       -        if len(cmd.Args) != 1 {
       -                return
       -        }
       -        idArg := cmd.Args[0]
       -
       -        p, ok := idArg.(*parse.PipeNode)
       -        if !ok {
       -                return
       -        }
       -
       -        if len(p.Cmds) != 1 {
       -                return
       -        }
       -
       -        cmd = p.Cmds[0]
       -
       -        if len(cmd.Args) != 2 {
       -                return
       -        }
       -
       -        idArg = cmd.Args[0]
       -
       -        id, ok := idArg.(*parse.ChainNode)
       -        if !ok || len(id.Field) != 1 || id.Field[0] != "Defer" {
       -                return
       -        }
       -        if id2, ok := id.Node.(*parse.IdentifierNode); !ok || id2.Ident != "templates" {
       -                return
       -        }
       -
       -        deferArg := cmd.Args[1]
       -        cmd.Args = []parse.Node{idArg}
       -
       -        l := doDefer.CopyList()
       -        n := l.Nodes[0].(*parse.ActionNode)
       -
       -        inner := withNode.List.CopyList()
       -        s := inner.String()
       -        if strings.Contains(s, "resources.PostProcess") {
       -                c.err = errors.New("resources.PostProcess cannot be used in a deferred template")
       -                return
       -        }
       -        innerHash := hashing.XxHashFromStringHexEncoded(s)
       -        deferredID := tpl.HugoDeferredTemplatePrefix + innerHash
       -
       -        c.deferNodes[deferredID] = inner
       -        withNode.List = l
       -
       -        n.Pipe.Cmds[0].Args[1].(*parse.PipeNode).Cmds[0].Args[0].(*parse.StringNode).Text = deferredID
       -        n.Pipe.Cmds[0].Args[2] = deferArg
       -}
       -
       -func (c *templateContext) applyTransformationsToNodes(nodes ...parse.Node) {
       -        for _, node := range nodes {
       -                c.applyTransformations(node)
       -        }
       -}
       -
       -func (c *templateContext) hasIdent(idents []string, ident string) bool {
       -        return slices.Contains(idents, ident)
       -}
       -
       -// collectConfig collects and parses any leading template config variable declaration.
       -// This will be the first PipeNode in the template, and will be a variable declaration
       -// on the form:
       -//
       -//        {{ $_hugo_config:= `{ "version": 1 }` }}
       -func (c *templateContext) collectConfig(n *parse.PipeNode) {
       -        if c.t.typ != templateShortcode {
       -                return
       -        }
       -        if c.configChecked {
       -                return
       -        }
       -        c.configChecked = true
       -
       -        if len(n.Decl) != 1 || len(n.Cmds) != 1 {
       -                // This cannot be a config declaration
       -                return
       -        }
       -
       -        v := n.Decl[0]
       -
       -        if len(v.Ident) == 0 || v.Ident[0] != "$_hugo_config" {
       -                return
       -        }
       -
       -        cmd := n.Cmds[0]
       -
       -        if len(cmd.Args) == 0 {
       -                return
       -        }
       -
       -        if s, ok := cmd.Args[0].(*parse.StringNode); ok {
       -                errMsg := "failed to decode $_hugo_config in template: %w"
       -                m, err := maps.ToStringMapE(s.Text)
       -                if err != nil {
       -                        c.err = fmt.Errorf(errMsg, err)
       -                        return
       -                }
       -                if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil {
       -                        c.err = fmt.Errorf(errMsg, err)
       -                }
       -        }
       -}
       -
       -// collectInner determines if the given CommandNode represents a
       -// shortcode call to its .Inner.
       -func (c *templateContext) collectInner(n *parse.CommandNode) {
       -        if c.t.typ != templateShortcode {
       -                return
       -        }
       -        if c.t.parseInfo.IsInner || len(n.Args) == 0 {
       -                return
       -        }
       -
       -        for _, arg := range n.Args {
       -                var idents []string
       -                switch nt := arg.(type) {
       -                case *parse.FieldNode:
       -                        idents = nt.Ident
       -                case *parse.VariableNode:
       -                        idents = nt.Ident
       -                }
       -
       -                if c.hasIdent(idents, "Inner") || c.hasIdent(idents, "InnerDeindent") {
       -                        c.t.parseInfo.IsInner = true
       -                        break
       -                }
       -        }
       -}
       -
       -func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
       -        if c.t.typ != templatePartial || c.returnNode != nil {
       -                return true
       -        }
       -
       -        if len(n.Args) < 2 {
       -                return true
       -        }
       -
       -        ident, ok := n.Args[0].(*parse.IdentifierNode)
       -        if !ok || ident.Ident != "return" {
       -                return true
       -        }
       -
       -        c.returnNode = n
       -        // Remove the "return" identifiers
       -        c.returnNode.Args = c.returnNode.Args[1:]
       -
       -        return false
       -}
       -
       -func findTemplateIn(name string, in tpl.Template) (tpl.Template, bool) {
       -        in = unwrap(in)
       -        if text, ok := in.(*texttemplate.Template); ok {
       -                if templ := text.Lookup(name); templ != nil {
       -                        return templ, true
       -                }
       -                return nil, false
       -        }
       -        if templ := in.(*htmltemplate.Template).Lookup(name); templ != nil {
       -                return templ, true
       -        }
       -        return nil, false
       -}
 (DIR) diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go
       @@ -1,161 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -package tplimpl
       -
       -import (
       -        "testing"
       -
       -        template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
       -
       -        qt "github.com/frankban/quicktest"
       -        "github.com/gohugoio/hugo/tpl"
       -)
       -
       -// Issue #2927
       -func TestTransformRecursiveTemplate(t *testing.T) {
       -        c := qt.New(t)
       -
       -        recursive := `
       -{{ define "menu-nodes" }}
       -{{ template "menu-node" }}
       -{{ end }}
       -{{ define "menu-node" }}
       -{{ template "menu-node" }}
       -{{ end }}
       -{{ template "menu-nodes" }}
       -`
       -
       -        templ, err := template.New("foo").Parse(recursive)
       -        c.Assert(err, qt.IsNil)
       -        ts := newTestTemplate(templ)
       -
       -        ctx := newTemplateContext(
       -                ts,
       -                newTestTemplateLookup(ts),
       -        )
       -        ctx.applyTransformations(templ.Tree.Root)
       -}
       -
       -func newTestTemplate(templ tpl.Template) *templateState {
       -        return newTemplateState(nil,
       -                templ,
       -                templateInfo{
       -                        name: templ.Name(),
       -                },
       -                nil,
       -        )
       -}
       -
       -func newTestTemplateLookup(in *templateState) func(name string) *templateState {
       -        m := make(map[string]*templateState)
       -        return func(name string) *templateState {
       -                if in.Name() == name {
       -                        return in
       -                }
       -
       -                if ts, found := m[name]; found {
       -                        return ts
       -                }
       -
       -                if templ, found := findTemplateIn(name, in); found {
       -                        ts := newTestTemplate(templ)
       -                        m[name] = ts
       -                        return ts
       -                }
       -
       -                return nil
       -        }
       -}
       -
       -func TestCollectInfo(t *testing.T) {
       -        configStr := `{ "version": 42 }`
       -
       -        tests := []struct {
       -                name      string
       -                tplString string
       -                expected  tpl.ParseInfo
       -        }{
       -                {"Basic Inner", `{{ .Inner }}`, tpl.ParseInfo{IsInner: true, Config: tpl.DefaultParseConfig}},
       -                {"Basic config map", "{{ $_hugo_config := `" + configStr + "`  }}", tpl.ParseInfo{Config: tpl.ParseConfig{Version: 42}}},
       -        }
       -
       -        echo := func(in any) any {
       -                return in
       -        }
       -
       -        funcs := template.FuncMap{
       -                "highlight": echo,
       -        }
       -
       -        for _, test := range tests {
       -                t.Run(test.name, func(t *testing.T) {
       -                        c := qt.New(t)
       -
       -                        templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
       -                        c.Assert(err, qt.IsNil)
       -                        ts := newTestTemplate(templ)
       -                        ts.typ = templateShortcode
       -                        ctx := newTemplateContext(
       -                                ts,
       -                                newTestTemplateLookup(ts),
       -                        )
       -                        ctx.applyTransformations(templ.Tree.Root)
       -                        c.Assert(ctx.t.parseInfo, qt.DeepEquals, test.expected)
       -                })
       -        }
       -}
       -
       -func TestPartialReturn(t *testing.T) {
       -        tests := []struct {
       -                name      string
       -                tplString string
       -                expected  bool
       -        }{
       -                {"Basic", `
       -{{ $a := "Hugo Rocks!" }}
       -{{ return $a }}
       -`, true},
       -                {"Expression", `
       -{{ return add 32 }}
       -`, true},
       -        }
       -
       -        echo := func(in any) any {
       -                return in
       -        }
       -
       -        funcs := template.FuncMap{
       -                "return": echo,
       -                "add":    echo,
       -        }
       -
       -        for _, test := range tests {
       -                t.Run(test.name, func(t *testing.T) {
       -                        c := qt.New(t)
       -
       -                        templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
       -                        c.Assert(err, qt.IsNil)
       -                        ts := newTestTemplate(templ)
       -                        ctx := newTemplateContext(
       -                                ts,
       -                                newTestTemplateLookup(ts),
       -                        )
       -
       -                        _, err = ctx.applyTransformations(templ.Tree.Root)
       -
       -                        // Just check that it doesn't fail in this test. We have functional tests
       -                        // in hugoblib.
       -                        c.Assert(err, qt.IsNil)
       -                })
       -        }
       -}
 (DIR) diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go
       @@ -1,64 +0,0 @@
       -// Copyright 2018 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package tplimpl
       -
       -import (
       -        "fmt"
       -
       -        "github.com/gohugoio/hugo/common/herrors"
       -        "github.com/gohugoio/hugo/hugofs"
       -        "github.com/gohugoio/hugo/identity"
       -)
       -
       -var _ identity.Identity = (*templateInfo)(nil)
       -
       -type templateInfo struct {
       -        name       string
       -        template   string
       -        isText     bool // HTML or plain text template.
       -        isEmbedded bool
       -
       -        meta *hugofs.FileMeta
       -}
       -
       -func (t templateInfo) IdentifierBase() string {
       -        return t.name
       -}
       -
       -func (t templateInfo) Name() string {
       -        return t.name
       -}
       -
       -func (t templateInfo) Filename() string {
       -        return t.meta.Filename
       -}
       -
       -func (t templateInfo) IsZero() bool {
       -        return t.name == ""
       -}
       -
       -func (t templateInfo) resolveType() templateType {
       -        return resolveTemplateType(t.name)
       -}
       -
       -func (info templateInfo) errWithFileContext(what string, err error) error {
       -        err = fmt.Errorf(what+": %w", err)
       -        fe := herrors.NewFileErrorFromName(err, info.meta.Filename)
       -        f, err := info.meta.Open()
       -        if err != nil {
       -                return err
       -        }
       -        defer f.Close()
       -        return fe.UpdateContent(f, nil)
       -}
 (DIR) diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
       @@ -1,4 +1,4 @@
       -// Copyright 2017-present The Hugo Authors. All rights reserved.
       +// Copyright 2025 The Hugo Authors. All rights reserved.
        //
        // Portions Copyright The Go Authors.
        
       @@ -25,46 +25,7 @@ import (
                "github.com/gohugoio/hugo/identity"
                "github.com/gohugoio/hugo/tpl"
        
       -        template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
                texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       -
       -        "github.com/gohugoio/hugo/deps"
       -
       -        "github.com/gohugoio/hugo/tpl/internal"
       -
       -        // Init the namespaces
       -        _ "github.com/gohugoio/hugo/tpl/cast"
       -        _ "github.com/gohugoio/hugo/tpl/collections"
       -        _ "github.com/gohugoio/hugo/tpl/compare"
       -        _ "github.com/gohugoio/hugo/tpl/crypto"
       -        _ "github.com/gohugoio/hugo/tpl/css"
       -        _ "github.com/gohugoio/hugo/tpl/data"
       -        _ "github.com/gohugoio/hugo/tpl/debug"
       -        _ "github.com/gohugoio/hugo/tpl/diagrams"
       -        _ "github.com/gohugoio/hugo/tpl/encoding"
       -        _ "github.com/gohugoio/hugo/tpl/fmt"
       -        _ "github.com/gohugoio/hugo/tpl/hash"
       -        _ "github.com/gohugoio/hugo/tpl/hugo"
       -        _ "github.com/gohugoio/hugo/tpl/images"
       -        _ "github.com/gohugoio/hugo/tpl/inflect"
       -        _ "github.com/gohugoio/hugo/tpl/js"
       -        _ "github.com/gohugoio/hugo/tpl/lang"
       -        _ "github.com/gohugoio/hugo/tpl/math"
       -        _ "github.com/gohugoio/hugo/tpl/openapi/openapi3"
       -        _ "github.com/gohugoio/hugo/tpl/os"
       -        _ "github.com/gohugoio/hugo/tpl/page"
       -        _ "github.com/gohugoio/hugo/tpl/partials"
       -        _ "github.com/gohugoio/hugo/tpl/path"
       -        _ "github.com/gohugoio/hugo/tpl/reflect"
       -        _ "github.com/gohugoio/hugo/tpl/resources"
       -        _ "github.com/gohugoio/hugo/tpl/safe"
       -        _ "github.com/gohugoio/hugo/tpl/site"
       -        _ "github.com/gohugoio/hugo/tpl/strings"
       -        _ "github.com/gohugoio/hugo/tpl/templates"
       -        _ "github.com/gohugoio/hugo/tpl/time"
       -        _ "github.com/gohugoio/hugo/tpl/transform"
       -        _ "github.com/gohugoio/hugo/tpl/urls"
       -        maps0 "maps"
        )
        
        var (
       @@ -212,89 +173,3 @@ func (t *templateExecHelper) trackDependencies(ctx context.Context, tmpl texttem
        
                return ctx
        }
       -
       -func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) {
       -        funcs := createFuncMap(d)
       -        funcsv := make(map[string]reflect.Value)
       -
       -        for k, v := range funcs {
       -                vv := reflect.ValueOf(v)
       -                funcsv[k] = vv
       -        }
       -
       -        // Duplicate Go's internal funcs here for faster lookups.
       -        for k, v := range template.GoFuncs {
       -                if _, exists := funcsv[k]; !exists {
       -                        vv, ok := v.(reflect.Value)
       -                        if !ok {
       -                                vv = reflect.ValueOf(v)
       -                        }
       -                        funcsv[k] = vv
       -                }
       -        }
       -
       -        for k, v := range texttemplate.GoFuncs {
       -                if _, exists := funcsv[k]; !exists {
       -                        funcsv[k] = v
       -                }
       -        }
       -
       -        exeHelper := &templateExecHelper{
       -                watching:   d.Conf.Watching(),
       -                funcs:      funcsv,
       -                site:       reflect.ValueOf(d.Site),
       -                siteParams: reflect.ValueOf(d.Site.Params()),
       -        }
       -
       -        return texttemplate.NewExecuter(
       -                exeHelper,
       -        ), funcsv
       -}
       -
       -func createFuncMap(d *deps.Deps) map[string]any {
       -        if d.TmplFuncMap != nil {
       -                return d.TmplFuncMap
       -        }
       -        funcMap := template.FuncMap{}
       -
       -        nsMap := make(map[string]any)
       -        var onCreated []func(namespaces map[string]any)
       -
       -        // Merge the namespace funcs
       -        for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
       -                ns := nsf(d)
       -                if _, exists := funcMap[ns.Name]; exists {
       -                        panic(ns.Name + " is a duplicate template func")
       -                }
       -                funcMap[ns.Name] = ns.Context
       -                contextV, err := ns.Context(context.Background())
       -                if err != nil {
       -                        panic(err)
       -                }
       -                nsMap[ns.Name] = contextV
       -                for _, mm := range ns.MethodMappings {
       -                        for _, alias := range mm.Aliases {
       -                                if _, exists := funcMap[alias]; exists {
       -                                        panic(alias + " is a duplicate template func")
       -                                }
       -                                funcMap[alias] = mm.Method
       -                        }
       -                }
       -
       -                if ns.OnCreated != nil {
       -                        onCreated = append(onCreated, ns.OnCreated)
       -                }
       -        }
       -
       -        for _, f := range onCreated {
       -                f(nsMap)
       -        }
       -
       -        if d.OverloadedTemplateFuncs != nil {
       -                maps0.Copy(funcMap, d.OverloadedTemplateFuncs)
       -        }
       -
       -        d.TmplFuncMap = funcMap
       -
       -        return d.TmplFuncMap
       -}
 (DIR) diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
       @@ -1,4 +1,4 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       +// Copyright 2025 The Hugo Authors. All rights reserved.
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
 (DIR) diff --git a/tpl/tplimpl/template_info.go b/tpl/tplimpl/template_info.go
       @@ -0,0 +1,46 @@
       +// Copyright 2025 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package tplimpl
       +
       +// Increments on breaking changes.
       +const TemplateVersion = 2
       +
       +// ParseInfo holds information about a parsed ntemplate.
       +type ParseInfo struct {
       +        // Set for shortcode templates with any {{ .Inner }}
       +        IsInner bool
       +
       +        // Set for partials with a return statement.
       +        HasReturn bool
       +
       +        // Config extracted from template.
       +        Config ParseConfig
       +}
       +
       +func (info ParseInfo) IsZero() bool {
       +        return info.Config.Version == 0
       +}
       +
       +// ParseConfig holds configuration extracted from the template.
       +type ParseConfig struct {
       +        Version int
       +}
       +
       +var defaultParseConfig = ParseConfig{
       +        Version: TemplateVersion,
       +}
       +
       +var defaultParseInfo = ParseInfo{
       +        Config: defaultParseConfig,
       +}
 (DIR) diff --git a/tpl/tplimpl/template_test.go b/tpl/tplimpl/template_test.go
       @@ -1,40 +0,0 @@
       -// Copyright 2019 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -package tplimpl
       -
       -import (
       -        "testing"
       -
       -        qt "github.com/frankban/quicktest"
       -)
       -
       -func TestNeedsBaseTemplate(t *testing.T) {
       -        c := qt.New(t)
       -
       -        c.Assert(needsBaseTemplate(`{{ define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`{{define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`{{-  define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`{{-define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`
       -        
       -        {{-define "main" }}
       -        
       -        `), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`    {{ define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`
       -        {{ define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`  A  {{ define "main" }}`), qt.Equals, false)
       -        c.Assert(needsBaseTemplate(`  {{ printf "foo" }}`), qt.Equals, false)
       -        c.Assert(needsBaseTemplate(`{{/* comment */}}    {{ define "main" }}`), qt.Equals, true)
       -        c.Assert(needsBaseTemplate(`     {{/* comment */}}  A  {{ define "main" }}`), qt.Equals, false)
       -}
 (DIR) diff --git a/tpl/tplimpl/templatedescriptor.go b/tpl/tplimpl/templatedescriptor.go
       @@ -0,0 +1,225 @@
       +// Copyright 2025 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package tplimpl
       +
       +import (
       +        "github.com/gohugoio/hugo/resources/kinds"
       +)
       +
       +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.
       +
       +        // Group 2.
       +        OutputFormat string // rss, csv ...
       +        MediaType    string // text/html, text/plain, ...
       +        Lang         string // en, nn, fr, ...
       +
       +        Variant1 string // contextual variant, e.g. "link" in render hooks."
       +        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.
       +}
       +
       +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.Kind == kinds.KindTemporary {
       +                d.Kind = ""
       +        }
       +
       +        if d.Layout == d.Kind {
       +                d.Layout = ""
       +        }
       +}
       +
       +type descriptorHandler struct {
       +        opts StoreOptions
       +}
       +
       +// 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, this, other TemplateDescriptor) weight {
       +        if this.LayoutMustMatch && this.Layout != other.Layout {
       +                return weightNoMatch
       +        }
       +
       +        w := this.doCompare(category, other)
       +        if w.w1 <= 0 {
       +                if category == CategoryMarkup && (this.Variant1 == other.Variant1) && (this.Variant2 == other.Variant2 || this.Variant2 != "" && other.Variant2 == "") {
       +                        // See issue 13242.
       +                        if this.OutputFormat != other.OutputFormat && this.OutputFormat == s.opts.DefaultOutputFormat {
       +                                return w
       +                        }
       +
       +                        w.w1 = 1
       +                        return w
       +                }
       +        }
       +
       +        return w
       +}
       +
       +//lint:ignore ST1006 this vs other makes it easier to reason about.
       +func (this TemplateDescriptor) doCompare(category Category, other TemplateDescriptor) weight {
       +        w := weightNoMatch
       +
       +        // HTML in plain text is OK, but not the other way around.
       +        if other.IsPlainText && !this.IsPlainText {
       +                return w
       +        }
       +        if other.Kind != "" && other.Kind != this.Kind {
       +                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
       +                        }
       +                }
       +
       +                // Test again.
       +                if other.Layout != this.Layout {
       +                        return w
       +                }
       +        }
       +        if other.Lang != "" && other.Lang != this.Lang {
       +                return w
       +        }
       +
       +        if other.OutputFormat != "" && other.OutputFormat != this.OutputFormat {
       +                if this.MediaType != other.MediaType {
       +                        return w
       +                }
       +
       +                // 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)) {
       +                        return w
       +                }
       +
       +                // Continue.
       +        }
       +
       +        // One example of variant1 and 2 is for render codeblocks:
       +        // variant1=codeblock, variant2=go (language).
       +        if other.Variant1 != "" && other.Variant1 != this.Variant1 {
       +                return w
       +        }
       +
       +        // If both are set and different, no match.
       +        if other.Variant2 != "" && this.Variant2 != "" && other.Variant2 != this.Variant2 {
       +                return w
       +        }
       +
       +        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.
       +
       +                // We will use the values for group 2 and 3
       +                // if the distance up to the template is shorter than
       +                // the one we're comparing with.
       +                // E.g for a page in /posts/mypage.md with the
       +                // two templates /layouts/posts/single.html and /layouts/page.html,
       +                // the first one is the best match even if the second one
       +                // has a higher w1 value.
       +                weight2Group1 = 1 // kind, standardl layout (single,list,all)
       +                weight2Group2 = 2 // custom layout (mylayout)
       +
       +                weight3 = 1 // for media type, lang, output format.
       +        )
       +
       +        // Now we now know that the other descriptor is a subset of this.
       +        // Now calculate the weights.
       +        w.w1++
       +
       +        if other.Kind != "" && other.Kind == this.Kind {
       +                w.w1 += weightKind
       +                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 other.Lang != "" && other.Lang == this.Lang {
       +                w.w1 += weightLang
       +                w.w3 += weight3
       +        }
       +
       +        if other.OutputFormat != "" && other.OutputFormat == this.OutputFormat {
       +                w.w1 += weightOutputFormat
       +                w.w3 += weight3
       +        }
       +
       +        if other.MediaType != "" && other.MediaType == this.MediaType {
       +                w.w1 += weightMediaType
       +                w.w3 += weight3
       +        }
       +
       +        if other.Variant1 != "" && other.Variant1 == this.Variant1 {
       +                w.w1 += weightVariant1
       +        }
       +
       +        if other.Variant2 != "" && other.Variant2 == this.Variant2 {
       +                w.w1 += weightVariant2
       +        }
       +        if other.Variant2 != "" && this.Variant2 == "" {
       +                w.w1--
       +        }
       +
       +        return w
       +}
       +
       +func (d TemplateDescriptor) IsZero() bool {
       +        return d == TemplateDescriptor{}
       +}
       +
       +//lint:ignore ST1006 this vs other makes it easier to reason about.
       +func (this TemplateDescriptor) isKindInLayout(layout string) bool {
       +        if this.Kind == "" {
       +                return true
       +        }
       +        if this.Kind != kinds.KindPage {
       +                return layout != layoutSingle
       +        }
       +        return layout != layoutList
       +}
 (DIR) diff --git a/tpl/tplimpl/templatedescriptor_test.go b/tpl/tplimpl/templatedescriptor_test.go
       @@ -0,0 +1,104 @@
       +package tplimpl
       +
       +import (
       +        "testing"
       +
       +        qt "github.com/frankban/quicktest"
       +        "github.com/gohugoio/hugo/output"
       +        "github.com/gohugoio/hugo/resources/kinds"
       +)
       +
       +func TestTemplateDescriptorCompare(t *testing.T) {
       +        c := qt.New(t)
       +
       +        dh := descriptorHandler{
       +                opts: StoreOptions{
       +                        OutputFormats:       output.DefaultFormats,
       +                        DefaultOutputFormat: "html",
       +                },
       +        }
       +
       +        less := func(category Category, this, other1, other2 TemplateDescriptor) {
       +                c.Helper()
       +                result1 := dh.compareDescriptors(category, this, other1)
       +                result2 := dh.compareDescriptors(category, this, other2)
       +                c.Assert(result1.w1 < result2.w1, qt.IsTrue, qt.Commentf("%d < %d", result1, result2))
       +        }
       +
       +        check := func(category Category, this, other TemplateDescriptor, less bool) {
       +                c.Helper()
       +                result := dh.compareDescriptors(category, this, other)
       +                if less {
       +                        c.Assert(result.w1 < 0, qt.IsTrue, qt.Commentf("%d", result))
       +                } else {
       +                        c.Assert(result.w1 >= 0, qt.IsTrue, qt.Commentf("%d", result))
       +                }
       +        }
       +
       +        check(
       +
       +                CategoryBaseof,
       +                TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "404", MediaType: "text/html"},
       +                TemplateDescriptor{Kind: "", Layout: "", 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"},
       +                true,
       +        )
       +
       +        less(
       +                CategoryLayout,
       +                TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "html"},
       +                TemplateDescriptor{Layout: "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"},
       +                false,
       +        )
       +}
       +
       +// INFO  timer:  name resolveTemplate count 779 duration 5.482274ms average 7.037µs median 4µs
       +func BenchmarkCompareDescriptors(b *testing.B) {
       +        dh := descriptorHandler{
       +                opts: StoreOptions{
       +                        OutputFormats:       output.DefaultFormats,
       +                        DefaultOutputFormat: "html",
       +                },
       +        }
       +
       +        pairs := []struct {
       +                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: "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", 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", 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},
       +                },
       +        }
       +
       +        b.ResetTimer()
       +        for i := 0; i < b.N; i++ {
       +                for _, pair := range pairs {
       +                        _ = dh.compareDescriptors(CategoryLayout, pair.d1, pair.d2)
       +                }
       +        }
       +}
 (DIR) diff --git a/tpl/tplimpl/templates.go b/tpl/tplimpl/templates.go
       @@ -0,0 +1,331 @@
       +package tplimpl
       +
       +import (
       +        "io"
       +        "regexp"
       +        "strings"
       +        "unicode"
       +        "unicode/utf8"
       +
       +        "github.com/gohugoio/hugo/common/paths"
       +        "github.com/gohugoio/hugo/tpl"
       +        htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
       +        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       +)
       +
       +func (t *templateNamespace) readTemplateInto(templ *TemplInfo) error {
       +        if err := func() error {
       +                meta := templ.Fi.Meta()
       +                f, err := meta.Open()
       +                if err != nil {
       +                        return err
       +                }
       +                defer f.Close()
       +                b, err := io.ReadAll(f)
       +                if err != nil {
       +                        return err
       +                }
       +                templ.Content = removeLeadingBOM(string(b))
       +                if !templ.NoBaseOf {
       +                        templ.NoBaseOf = !needsBaseTemplate(templ.Content)
       +                }
       +                return nil
       +        }(); err != nil {
       +                return err
       +        }
       +        return nil
       +}
       +
       +// The tweet and twitter shortcodes were deprecated in favor of the x shortcode
       +// in v0.141.0. We can remove these aliases in v0.155.0 or later.
       +var embeddedTemplatesAliases = map[string][]string{
       +        "_shortcodes/twitter.html": {"_shortcodes/tweet.html"},
       +}
       +
       +func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
       +        if !ti.NoBaseOf || ti.Category == CategoryBaseof {
       +                // Delay parsing until we have the base template.
       +                return nil
       +        }
       +        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
       +                err   error
       +        )
       +
       +        if ti.D.IsPlainText {
       +                prototype := t.parseText
       +                templ, err = prototype.New(name).Parse(ti.Content)
       +                if err != nil {
       +                        return err
       +                }
       +        } else {
       +                prototype := t.parseHTML
       +                templ, err = prototype.New(name).Parse(ti.Content)
       +                if err != nil {
       +                        return err
       +                }
       +
       +                if ti.SubCategory == SubCategoryEmbedded {
       +                        // In Hugo 0.146.0 we moved the internal templates around.
       +                        // For the "_internal/twitter_cards.html" style templates, they
       +                        // were moved to the _partials directory.
       +                        // But we need to make them accessible from the old path for a while.
       +                        if pi.Type() == paths.TypePartial {
       +                                aliasName := strings.TrimPrefix(name, "_partials/")
       +                                aliasName = "_internal/" + aliasName
       +                                _, err = prototype.AddParseTree(aliasName, templ.(*htmltemplate.Template).Tree)
       +                                if err != nil {
       +                                        return err
       +                                }
       +                        }
       +
       +                        // This was also possible before Hugo 0.146.0, but this should be deprecated.
       +                        if pi.Type() == paths.TypeShortcode {
       +                                aliasName := strings.TrimPrefix(name, "_shortcodes/")
       +                                aliasName = "_internal/shortcodes/" + aliasName
       +                                _, err = prototype.AddParseTree(aliasName, templ.(*htmltemplate.Template).Tree)
       +                                if err != nil {
       +                                        return err
       +                                }
       +                        }
       +
       +                }
       +        }
       +
       +        ti.Template = templ
       +
       +        return nil
       +}
       +
       +func (t *templateNamespace) applyBaseTemplate(overlay *TemplInfo, base keyTemplateInfo) error {
       +        tb := &TemplWithBaseApplied{
       +                Overlay: overlay,
       +                Base:    base.Info,
       +        }
       +
       +        base.Info.Overlays = append(base.Info.Overlays, overlay)
       +
       +        var templ tpl.Template
       +        if overlay.D.IsPlainText {
       +                tt := texttemplate.Must(t.parseText.Clone()).New(overlay.PathInfo.PathNoLeadingSlash())
       +                var err error
       +                tt, err = tt.Parse(base.Info.Content)
       +                if err != nil {
       +                        return err
       +                }
       +                tt, err = tt.Parse(overlay.Content)
       +                if err != nil {
       +                        return err
       +                }
       +                templ = tt
       +                t.baseofTextClones = append(t.baseofTextClones, tt)
       +        } else {
       +                tt := htmltemplate.Must(t.parseHTML.CloneShallow()).New(overlay.PathInfo.PathNoLeadingSlash())
       +                var err error
       +                tt, err = tt.Parse(base.Info.Content)
       +                if err != nil {
       +                        return err
       +                }
       +                tt, err = tt.Parse(overlay.Content)
       +                if err != nil {
       +                        return err
       +                }
       +                templ = tt
       +
       +                t.baseofHtmlClones = append(t.baseofHtmlClones, tt)
       +
       +        }
       +
       +        tb.Template = &TemplInfo{
       +                Template: templ,
       +                Base:     base.Info,
       +                PathInfo: overlay.PathInfo,
       +                Fi:       overlay.Fi,
       +                D:        overlay.D,
       +                NoBaseOf: true,
       +        }
       +
       +        variants := overlay.BaseVariants.Get(base.Key)
       +        if variants == nil {
       +                variants = make(map[TemplateDescriptor]*TemplWithBaseApplied)
       +                overlay.BaseVariants.Insert(base.Key, variants)
       +        }
       +        variants[base.Info.D] = tb
       +        return nil
       +}
       +
       +func (t *templateNamespace) templatesIn(in tpl.Template) []tpl.Template {
       +        var templs []tpl.Template
       +        if textt, ok := in.(*texttemplate.Template); ok {
       +                for _, t := range textt.Templates() {
       +                        templs = append(templs, t)
       +                }
       +        }
       +        if htmlt, ok := in.(*htmltemplate.Template); ok {
       +                for _, t := range htmlt.Templates() {
       +                        templs = append(templs, t)
       +                }
       +        }
       +        return templs
       +}
       +
       +/*
       +
       +
       +func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
       +        if overlay.isText {
       +                var (
       +                        templ = t.main.getPrototypeText(prototypeCloneIDBaseof).New(overlay.name)
       +                        err   error
       +                )
       +
       +                if !base.IsZero() {
       +                        templ, err = templ.Parse(base.template)
       +                        if err != nil {
       +                                return nil, base.errWithFileContext("text: base: parse failed", err)
       +                        }
       +                }
       +
       +                templ, err = texttemplate.Must(templ.Clone()).Parse(overlay.template)
       +                if err != nil {
       +                        return nil, overlay.errWithFileContext("text: overlay: parse failed", err)
       +                }
       +
       +                // The extra lookup is a workaround, see
       +                // * https://github.com/golang/go/issues/16101
       +                // * https://github.com/gohugoio/hugo/issues/2549
       +                // templ = templ.Lookup(templ.Name())
       +
       +                return templ, nil
       +        }
       +
       +        var (
       +                templ = t.main.getPrototypeHTML(prototypeCloneIDBaseof).New(overlay.name)
       +                err   error
       +        )
       +
       +        if !base.IsZero() {
       +                templ, err = templ.Parse(base.template)
       +                if err != nil {
       +                        return nil, base.errWithFileContext("html: base: parse failed", err)
       +                }
       +        }
       +
       +        templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template)
       +        if err != nil {
       +                return nil, overlay.errWithFileContext("html: overlay: parse failed", err)
       +        }
       +
       +        // The extra lookup is a workaround, see
       +        // * https://github.com/golang/go/issues/16101
       +        // * https://github.com/gohugoio/hugo/issues/2549
       +        templ = templ.Lookup(templ.Name())
       +
       +        return templ, err
       +}
       +
       +*/
       +
       +var baseTemplateDefineRe = regexp.MustCompile(`^{{-?\s*define`)
       +
       +// needsBaseTemplate returns true if the first non-comment template block is a
       +// define block.
       +func needsBaseTemplate(templ string) bool {
       +        idx := -1
       +        inComment := false
       +        for i := 0; i < len(templ); {
       +                if !inComment && strings.HasPrefix(templ[i:], "{{/*") {
       +                        inComment = true
       +                        i += 4
       +                } else if !inComment && strings.HasPrefix(templ[i:], "{{- /*") {
       +                        inComment = true
       +                        i += 6
       +                } else if inComment && strings.HasPrefix(templ[i:], "*/}}") {
       +                        inComment = false
       +                        i += 4
       +                } else if inComment && strings.HasPrefix(templ[i:], "*/ -}}") {
       +                        inComment = false
       +                        i += 6
       +                } else {
       +                        r, size := utf8.DecodeRuneInString(templ[i:])
       +                        if !inComment {
       +                                if strings.HasPrefix(templ[i:], "{{") {
       +                                        idx = i
       +                                        break
       +                                } else if !unicode.IsSpace(r) {
       +                                        break
       +                                }
       +                        }
       +                        i += size
       +                }
       +        }
       +
       +        if idx == -1 {
       +                return false
       +        }
       +
       +        return baseTemplateDefineRe.MatchString(templ[idx:])
       +}
       +
       +func removeLeadingBOM(s string) string {
       +        const bom = '\ufeff'
       +
       +        for i, r := range s {
       +                if i == 0 && r != bom {
       +                        return s
       +                }
       +                if i > 0 {
       +                        return s[i:]
       +                }
       +        }
       +
       +        return s
       +}
       +
       +type templateNamespace struct {
       +        parseText     *texttemplate.Template
       +        parseHTML     *htmltemplate.Template
       +        prototypeText *texttemplate.Template
       +        prototypeHTML *htmltemplate.Template
       +
       +        standaloneText *texttemplate.Template
       +
       +        baseofTextClones []*texttemplate.Template
       +        baseofHtmlClones []*htmltemplate.Template
       +}
       +
       +func (t *templateNamespace) createPrototypesParse() error {
       +        if t.prototypeHTML == nil {
       +                panic("prototypeHTML not set")
       +        }
       +        t.parseHTML = htmltemplate.Must(t.prototypeHTML.Clone())
       +        t.parseText = texttemplate.Must(t.prototypeText.Clone())
       +        return nil
       +}
       +
       +func (t *templateNamespace) createPrototypes(init bool) error {
       +        if init {
       +                t.prototypeHTML = htmltemplate.Must(t.parseHTML.Clone())
       +                t.prototypeText = texttemplate.Must(t.parseText.Clone())
       +        }
       +        // t.execHTML = htmltemplate.Must(t.parseHTML.Clone())
       +        // t.execText = texttemplate.Must(t.parseText.Clone())
       +
       +        return nil
       +}
       +
       +func newTemplateNamespace(funcs map[string]any) *templateNamespace {
       +        return &templateNamespace{
       +                parseHTML:      htmltemplate.New("").Funcs(funcs),
       +                parseText:      texttemplate.New("").Funcs(funcs),
       +                standaloneText: texttemplate.New("").Funcs(funcs),
       +        }
       +}
 (DIR) diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
       @@ -0,0 +1,1854 @@
       +package tplimpl
       +
       +import (
       +        "bytes"
       +        "context"
       +        "embed"
       +        "fmt"
       +        "io"
       +        "io/fs"
       +        "iter"
       +        "os"
       +        "path"
       +        "path/filepath"
       +        "reflect"
       +        "regexp"
       +        "sort"
       +        "strings"
       +        "sync"
       +        "sync/atomic"
       +        "time"
       +
       +        "github.com/gohugoio/hugo/common/herrors"
       +        "github.com/gohugoio/hugo/common/maps"
       +        "github.com/gohugoio/hugo/common/paths"
       +        "github.com/gohugoio/hugo/helpers"
       +        "github.com/gohugoio/hugo/hugofs"
       +        "github.com/gohugoio/hugo/hugofs/files"
       +        "github.com/gohugoio/hugo/hugolib/doctree"
       +        "github.com/gohugoio/hugo/identity"
       +        "github.com/gohugoio/hugo/media"
       +        "github.com/gohugoio/hugo/metrics"
       +        "github.com/gohugoio/hugo/output"
       +        "github.com/gohugoio/hugo/resources/kinds"
       +        "github.com/gohugoio/hugo/resources/page"
       +        "github.com/gohugoio/hugo/tpl"
       +        htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
       +        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       +        "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
       +        "github.com/spf13/afero"
       +)
       +
       +const (
       +        CategoryLayout Category = iota + 1
       +        CategoryBaseof
       +        CategoryMarkup
       +        CategoryShortcode
       +        CategoryPartial
       +        // Internal categories
       +        CategoryServer
       +        CategoryHugo
       +)
       +
       +const (
       +        SubCategoryMain     SubCategory = iota
       +        SubCategoryEmbedded             // Internal Hugo templates
       +        SubCategoryInline               // Inline partials
       +)
       +
       +const (
       +        containerMarkup          = "_markup"
       +        containerShortcodes      = "_shortcodes"
       +        shortcodesPathIdentifier = "/_shortcodes/"
       +        containerPartials        = "_partials"
       +)
       +
       +const (
       +        layoutAll    = "all"
       +        layoutList   = "list"
       +        layoutSingle = "single"
       +)
       +
       +var (
       +        _ identity.IdentityProvider             = (*TemplInfo)(nil)
       +        _ identity.IsProbablyDependentProvider  = (*TemplInfo)(nil)
       +        _ identity.IsProbablyDependencyProvider = (*TemplInfo)(nil)
       +)
       +
       +const (
       +        processingStateInitial processingState = iota
       +        processingStateTransformed
       +)
       +
       +// The identifiers may be truncated in the log, e.g.
       +// "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
       +// We need this to identify position in templates with base templates applied.
       +var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
       +
       +var weightNoMatch = weight{w1: -1}
       +
       +//
       +//go:embed all:embedded/templates/*
       +var embeddedTemplatesFs embed.FS
       +
       +func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) {
       +        html, ok := opts.OutputFormats.GetByName("html")
       +        if !ok {
       +                panic("HTML output format not found")
       +        }
       +        s := &TemplateStore{
       +                opts:            opts,
       +                siteOpts:        siteOpts,
       +                optsOrig:        opts,
       +                siteOptsOrig:    siteOpts,
       +                htmlFormat:      html,
       +                storeSite:       configureSiteStorage(siteOpts, opts.Watching),
       +                treeMain:        doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
       +                treeShortcodes:  doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
       +                templatesByPath: maps.NewCache[string, *TemplInfo](),
       +
       +                // Note that the funcs passed below is just for name validation.
       +                tns: newTemplateNamespace(siteOpts.TemplateFuncs),
       +
       +                dh: descriptorHandler{
       +                        opts: opts,
       +                },
       +        }
       +
       +        if err := s.init(); err != nil {
       +                return nil, err
       +        }
       +        if err := s.insertTemplates(nil, false); err != nil {
       +                return nil, err
       +        }
       +        if err := s.insertEmbedded(); err != nil {
       +                return nil, err
       +        }
       +        if err := s.parseTemplates(); err != nil {
       +                return nil, err
       +        }
       +        if err := s.extractInlinePartials(); err != nil {
       +                return nil, err
       +        }
       +        if err := s.transformTemplates(); err != nil {
       +                return nil, err
       +        }
       +        if err := s.tns.createPrototypes(true); err != nil {
       +                return nil, err
       +        }
       +        if err := s.prepareTemplates(); err != nil {
       +                return nil, err
       +        }
       +        return s, nil
       +}
       +
       +//go:generate stringer -type Category
       +
       +type Category int
       +
       +type SiteOptions struct {
       +        Site          page.Site
       +        TemplateFuncs map[string]any
       +}
       +
       +type StoreOptions struct {
       +        // The filesystem to use.
       +        Fs afero.Fs
       +
       +        // The path parser to use.
       +        PathParser *paths.PathParser
       +
       +        // Set when --enableTemplateMetrics is set.
       +        Metrics metrics.Provider
       +
       +        // All configured output formats.
       +        OutputFormats output.Formats
       +
       +        // All configured media types.
       +        MediaTypes media.Types
       +
       +        // The default content language.
       +        DefaultContentLanguage string
       +
       +        // The default output format.
       +        DefaultOutputFormat string
       +
       +        // Taxonomy config.
       +        TaxonomySingularPlural map[string]string
       +
       +        // Whether we are in watch or server mode.
       +        Watching bool
       +
       +        // compiled.
       +        legacyMappingTaxonomy map[string]legacyOrdinalMapping
       +        legacyMappingTerm     map[string]legacyOrdinalMapping
       +        legacyMappingSection  map[string]legacyOrdinalMapping
       +}
       +
       +//go:generate stringer -type SubCategory
       +
       +type SubCategory int
       +
       +type TemplInfo struct {
       +        // The category of this template.
       +        Category Category
       +
       +        SubCategory SubCategory
       +
       +        // PathInfo info.
       +        PathInfo *paths.Path
       +
       +        // Set when backed by a file.
       +        Fi hugofs.FileMetaInfo
       +
       +        // The template content with any leading BOM removed.
       +        Content string
       +
       +        // The parsed template.
       +        // Note that any baseof template will be applied later.
       +        Template tpl.Template
       +
       +        // If no baseof is needed, this will be set to true.
       +        // E.g. shortcode templates do not need a baseof.
       +        NoBaseOf bool
       +
       +        // If NoBaseOf is false, we will look for the final template in this tree.
       +        BaseVariants *doctree.SimpleTree[map[TemplateDescriptor]*TemplWithBaseApplied]
       +
       +        // The template variants that are based on this template.
       +        Overlays []*TemplInfo
       +
       +        // The base template used, if any.
       +        Base *TemplInfo
       +
       +        // The descriptior that this template represents.
       +        D TemplateDescriptor
       +
       +        // Parser state.
       +        ParseInfo ParseInfo
       +
       +        // The execution counter for this template.
       +        ExecutionCounter atomic.Uint64
       +
       +        // processing state.
       +        state          processingState
       +        isLegacyMapped bool
       +}
       +
       +func (ti *TemplInfo) BaseVariantsSeq() iter.Seq[*TemplWithBaseApplied] {
       +        return func(yield func(*TemplWithBaseApplied) bool) {
       +                ti.BaseVariants.Walk(func(key string, v map[TemplateDescriptor]*TemplWithBaseApplied) (bool, error) {
       +                        for _, vv := range v {
       +                                if !yield(vv) {
       +                                        return true, nil
       +                                }
       +                        }
       +                        return false, nil
       +                })
       +        }
       +}
       +
       +func (t *TemplInfo) IdentifierBase() string {
       +        if t.PathInfo == nil {
       +                return t.Name()
       +        }
       +        return t.PathInfo.IdentifierBase()
       +}
       +
       +func (t *TemplInfo) GetIdentity() identity.Identity {
       +        return t
       +}
       +
       +func (ti *TemplInfo) Name() string {
       +        return ti.Template.Name()
       +}
       +
       +func (ti *TemplInfo) Prepare() (*texttemplate.Template, error) {
       +        return ti.Template.Prepare()
       +}
       +
       +func (t *TemplInfo) IsProbablyDependency(other identity.Identity) bool {
       +        return t.isProbablyTheSameIDAs(other)
       +}
       +
       +func (t *TemplInfo) IsProbablyDependent(other identity.Identity) bool {
       +        for _, overlay := range t.Overlays {
       +                if overlay.isProbablyTheSameIDAs(other) {
       +                        return true
       +                }
       +        }
       +        return t.isProbablyTheSameIDAs(other)
       +}
       +
       +func (ti *TemplInfo) String() string {
       +        if ti == nil {
       +                return "<nil>"
       +        }
       +        return ti.PathInfo.String()
       +}
       +
       +func (ti *TemplInfo) findBestMatchBaseof(s *TemplateStore, k1 string, slashCountK1 int, best *bestMatch) {
       +        if ti.BaseVariants == nil {
       +                return
       +        }
       +
       +        ti.BaseVariants.WalkPath(k1, func(k2 string, v map[TemplateDescriptor]*TemplWithBaseApplied) (bool, error) {
       +                slashCountK2 := strings.Count(k2, "/")
       +                distance := slashCountK1 - slashCountK2
       +
       +                for d, vv := range v {
       +                        weight := s.dh.compareDescriptors(CategoryBaseof, ti.D, d)
       +                        weight.distance = distance
       +                        if best.isBetter(weight, vv.Template) {
       +                                best.updateValues(weight, k2, d, vv.Template)
       +                        }
       +                }
       +                return false, nil
       +        })
       +}
       +
       +func (t *TemplInfo) isProbablyTheSameIDAs(other identity.Identity) bool {
       +        if t.IdentifierBase() == other.IdentifierBase() {
       +                return true
       +        }
       +
       +        if t.Fi != nil && t.Fi.Meta().PathInfo != t.PathInfo {
       +                return other.IdentifierBase() == t.Fi.Meta().PathInfo.IdentifierBase()
       +        }
       +
       +        return false
       +}
       +
       +type TemplWithBaseApplied struct {
       +        // The template that's overlaid on top of the base template.
       +        Overlay *TemplInfo
       +        // The base template.
       +        Base *TemplInfo
       +        // This is the final template that can be used to render a page.
       +        Template *TemplInfo
       +}
       +
       +// TemplateQuery is used in LookupPagesLayout to find the best matching template.
       +type TemplateQuery struct {
       +        // The path to walk down to.
       +        Path string
       +
       +        // The name to look for. Used for shortcode queries.
       +        Name string
       +
       +        // The category to look in.
       +        Category Category
       +
       +        // The template descriptor to match against.
       +        Desc TemplateDescriptor
       +
       +        // Whether to even consider this candidate.
       +        Consider func(candidate *TemplInfo) bool
       +}
       +
       +func (q *TemplateQuery) init() {
       +        if q.Desc.Kind == kinds.KindTemporary {
       +                q.Desc.Kind = ""
       +        } else if kinds.GetKindMain(q.Desc.Kind) == "" {
       +                q.Desc.Kind = ""
       +        }
       +        if q.Desc.Layout == "" && q.Desc.Kind != "" {
       +                if q.Desc.Kind == kinds.KindPage {
       +                        q.Desc.Layout = layoutSingle
       +                } else {
       +                        q.Desc.Layout = layoutList
       +                }
       +        }
       +
       +        if q.Consider == nil {
       +                q.Consider = func(match *TemplInfo) bool {
       +                        return true
       +                }
       +        }
       +
       +        q.Name = strings.ToLower(q.Name)
       +
       +        if q.Category == 0 {
       +                panic("category not set")
       +        }
       +}
       +
       +type TemplateStore struct {
       +        opts       StoreOptions
       +        siteOpts   SiteOptions
       +        htmlFormat output.Format
       +
       +        treeMain        *doctree.SimpleTree[map[nodeKey]*TemplInfo]
       +        treeShortcodes  *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
       +        templatesByPath *maps.Cache[string, *TemplInfo]
       +
       +        dh descriptorHandler
       +
       +        // The template namespace.
       +        tns *templateNamespace
       +
       +        // Site specific state.
       +        // All above this is reused.
       +        storeSite *storeSite
       +
       +        // For testing benchmarking.
       +        optsOrig     StoreOptions
       +        siteOptsOrig SiteOptions
       +}
       +
       +// NewFromOpts creates a new store with the same configuration as the original.
       +// Used for testing/benchmarking.
       +func (s *TemplateStore) NewFromOpts() (*TemplateStore, error) {
       +        return NewStore(s.optsOrig, s.siteOptsOrig)
       +}
       +
       +// In the previous implementation of base templates in Hugo, we parsed and applied these base templates on
       +// request, e.g. in the middle of rendering. The idea was that we coulnd't know upfront which layoyt/base template
       +// combination that would be used.
       +// This, however, added a lot of complexity involving a careful dance of template cloning and parsing
       +// (Go HTML tenplates cannot be parsed after any of the templates in the tree have been executed).
       +// FindAllBaseTemplateCandidates finds all base template candidates for the given descriptor so we can apply them upfront.
       +// In this setup we may end up with unused base templates, but not having to do the cloning should more than make up for that.
       +func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc TemplateDescriptor) []keyTemplateInfo {
       +        var result []keyTemplateInfo
       +        descBaseof := desc
       +        s.treeMain.Walk(func(k string, v map[nodeKey]*TemplInfo) (bool, error) {
       +                for _, vv := range v {
       +                        if vv.Category != CategoryBaseof {
       +                                continue
       +                        }
       +
       +                        if vv.D.isKindInLayout(desc.Layout) && s.dh.compareDescriptors(CategoryBaseof, descBaseof, vv.D).w1 > 0 {
       +                                result = append(result, keyTemplateInfo{Key: k, Info: vv})
       +                        }
       +                }
       +                return false, nil
       +        })
       +
       +        return result
       +}
       +
       +func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, wr io.Writer, data any) error {
       +        defer func() {
       +                ti.ExecutionCounter.Add(1)
       +                if ti.Base != nil {
       +                        ti.Base.ExecutionCounter.Add(1)
       +                }
       +        }()
       +
       +        templ := ti.Template
       +
       +        if t.opts.Metrics != nil {
       +                defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
       +        }
       +
       +        execErr := t.storeSite.executer.ExecuteWithContext(ctx, ti, wr, data)
       +        if execErr != nil {
       +                return t.addFileContext(ti, execErr)
       +        }
       +        return nil
       +}
       +
       +func (t *TemplateStore) GetFunc(name string) (reflect.Value, bool) {
       +        v, found := t.storeSite.execHelper.funcs[name]
       +        return v, found
       +}
       +
       +func (s *TemplateStore) GetIdentity(p string) identity.Identity {
       +        p = paths.AddLeadingSlash(p)
       +        v, found := s.templatesByPath.Get(p)
       +        if !found {
       +                return nil
       +        }
       +        return v.GetIdentity()
       +}
       +
       +func (t *TemplateStore) LookupByPath(templatePath string) *TemplInfo {
       +        v, _ := t.templatesByPath.Get(templatePath)
       +        return v
       +}
       +
       +var bestPool = sync.Pool{
       +        New: func() any {
       +                return &bestMatch{}
       +        },
       +}
       +
       +func (s *TemplateStore) getBest() *bestMatch {
       +        v := bestPool.Get()
       +        b := v.(*bestMatch)
       +        b.defaultOutputformat = s.opts.DefaultOutputFormat
       +        return b
       +}
       +
       +func (s *TemplateStore) putBest(b *bestMatch) {
       +        b.reset()
       +        bestPool.Put(b)
       +}
       +
       +func (s *TemplateStore) LookupPagesLayout(q TemplateQuery) *TemplInfo {
       +        q.init()
       +        key := s.key(q.Path)
       +
       +        slashCountKey := strings.Count(key, "/")
       +        best1 := s.getBest()
       +        defer s.putBest(best1)
       +        s.findBestMatchWalkPath(q, key, slashCountKey, best1)
       +        if best1.w.w1 <= 0 {
       +                return nil
       +        }
       +        m := best1.templ
       +        if m.NoBaseOf {
       +                return m
       +        }
       +        best1.reset()
       +        m.findBestMatchBaseof(s, key, slashCountKey, best1)
       +        if best1.w.w1 <= 0 {
       +                return nil
       +        }
       +        return best1.templ
       +}
       +
       +func (s *TemplateStore) LookupPartial(pth string, desc TemplateDescriptor) *TemplInfo {
       +        if desc.Layout != "" {
       +                panic("shortcode template descriptor must not have a layout")
       +        }
       +        best := s.getBest()
       +        defer s.putBest(best)
       +        s.findBestMatchGet(s.key(path.Join(containerPartials, pth)), CategoryPartial, nil, desc, best)
       +        return best.templ
       +}
       +
       +func (s *TemplateStore) LookupShortcode(q TemplateQuery) *TemplInfo {
       +        q.init()
       +        k1 := s.key(q.Path)
       +
       +        slashCountK1 := strings.Count(k1, "/")
       +
       +        best := s.getBest()
       +        defer s.putBest(best)
       +
       +        s.treeShortcodes.WalkPath(k1, func(k2 string, m map[string]map[TemplateDescriptor]*TemplInfo) (bool, error) {
       +                slashCountK2 := strings.Count(k2, "/")
       +                distance := slashCountK1 - slashCountK2
       +
       +                v, found := m[q.Name]
       +                if !found {
       +                        return false, nil
       +                }
       +
       +                for k, vv := range v {
       +                        if !q.Consider(vv) {
       +                                continue
       +                        }
       +
       +                        weight := s.dh.compareDescriptors(q.Category, q.Desc, k)
       +                        weight.distance = distance
       +                        if best.isBetter(weight, vv) {
       +                                best.updateValues(weight, k2, k, vv)
       +                        }
       +                }
       +
       +                return false, nil
       +        })
       +
       +        // Any match will do.
       +        return best.templ
       +}
       +
       +// PrintDebug is for testing/debugging only.
       +func (s *TemplateStore) PrintDebug(prefix string, category Category, w io.Writer) {
       +        if w == nil {
       +                w = os.Stdout
       +        }
       +
       +        printOne := func(key string, vv *TemplInfo) {
       +                level := strings.Count(key, "/")
       +                if category != vv.Category {
       +                        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)
       +                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) {
       +                for _, vv := range v {
       +                        printOne(key, vv)
       +                }
       +                return false, nil
       +        })
       +        s.treeShortcodes.WalkPrefix(prefix, func(key string, v map[string]map[TemplateDescriptor]*TemplInfo) (bool, error) {
       +                for _, vv := range v {
       +                        for _, vv2 := range vv {
       +                                printOne(key, vv2)
       +                        }
       +                }
       +                return false, nil
       +        })
       +}
       +
       +// RefreshFiles refreshes this store for the files matching the given predicate.
       +func (s *TemplateStore) RefreshFiles(include func(fi hugofs.FileMetaInfo) bool) error {
       +        if err := s.tns.createPrototypesParse(); err != nil {
       +                return err
       +        }
       +        if err := s.insertTemplates(include, true); err != nil {
       +                return err
       +        }
       +        if err := s.parseTemplates(); err != nil {
       +                return err
       +        }
       +        if err := s.extractInlinePartials(); err != nil {
       +                return err
       +        }
       +        if err := s.transformTemplates(); err != nil {
       +                return err
       +        }
       +        if err := s.tns.createPrototypes(false); err != nil {
       +                return err
       +        }
       +        if err := s.prepareTemplates(); err != nil {
       +                return err
       +        }
       +        return nil
       +}
       +
       +func (s *TemplateStore) HasTemplate(templatePath string) bool {
       +        templatePath = paths.AddLeadingSlash(templatePath)
       +        return s.templatesByPath.Contains(templatePath)
       +}
       +
       +func (t *TemplateStore) TextLookup(name string) *TemplInfo {
       +        templ := t.tns.standaloneText.Lookup(name)
       +        if templ == nil {
       +                return nil
       +        }
       +        return &TemplInfo{
       +                Template: templ,
       +        }
       +}
       +
       +func (t *TemplateStore) TextParse(name, tpl string) (*TemplInfo, error) {
       +        templ, err := t.tns.standaloneText.New(name).Parse(tpl)
       +        if err != nil {
       +                return nil, err
       +        }
       +        return &TemplInfo{
       +                Template: templ,
       +        }, nil
       +}
       +
       +func (t *TemplateStore) UnusedTemplates() []*TemplInfo {
       +        var unused []*TemplInfo
       +
       +        for vv := range t.templates() {
       +                if vv.SubCategory != SubCategoryMain {
       +                        // Skip inline partials and internal templates.
       +                        continue
       +                }
       +                if vv.NoBaseOf {
       +                        if vv.ExecutionCounter.Load() == 0 {
       +                                unused = append(unused, vv)
       +                        }
       +                } else {
       +                        for vvv := range vv.BaseVariantsSeq() {
       +                                if vvv.Template.ExecutionCounter.Load() == 0 {
       +                                        unused = append(unused, vvv.Template)
       +                                }
       +                        }
       +                }
       +        }
       +
       +        sort.Sort(byPath(unused))
       +        return unused
       +}
       +
       +// WithSiteOpts creates a new store with the given site options.
       +// This is used to create per site template store, all sharing the same templates,
       +// but with a different template function execution context.
       +func (s TemplateStore) WithSiteOpts(opts SiteOptions) *TemplateStore {
       +        s.siteOpts = opts
       +        s.storeSite = configureSiteStorage(opts, s.opts.Watching)
       +        return &s
       +}
       +
       +func (s *TemplateStore) findBestMatchGet(key string, category Category, consider func(candidate *TemplInfo) bool, desc TemplateDescriptor, best *bestMatch) {
       +        key = strings.ToLower(key)
       +
       +        v := s.treeMain.Get(key)
       +        if v == nil {
       +                return
       +        }
       +
       +        for k, vv := range v {
       +                if vv.Category != category {
       +                        continue
       +                }
       +
       +                if consider != nil && !consider(vv) {
       +                        continue
       +                }
       +
       +                weight := s.dh.compareDescriptors(category, desc, k.d)
       +                if best.isBetter(weight, vv) {
       +                        best.updateValues(weight, key, k.d, vv)
       +                }
       +        }
       +}
       +
       +func (s *TemplateStore) findBestMatchWalkPath(q TemplateQuery, k1 string, slashCountK1 int, best *bestMatch) {
       +        s.treeMain.WalkPath(k1, func(k2 string, v map[nodeKey]*TemplInfo) (bool, error) {
       +                slashCountK2 := strings.Count(k2, "/")
       +                distance := slashCountK1 - slashCountK2
       +
       +                for k, vv := range v {
       +                        if vv.Category != q.Category {
       +                                continue
       +                        }
       +
       +                        if !q.Consider(vv) {
       +                                continue
       +                        }
       +
       +                        weight := s.dh.compareDescriptors(q.Category, q.Desc, k.d)
       +
       +                        weight.distance = distance
       +                        isBetter := best.isBetter(weight, vv)
       +
       +                        if isBetter {
       +                                best.updateValues(weight, k2, k.d, vv)
       +                        }
       +                }
       +
       +                return false, nil
       +        })
       +}
       +
       +func (t *TemplateStore) addDeferredTemplate(owner *TemplInfo, name string, n *parse.ListNode) error {
       +        if _, found := t.templatesByPath.Get(name); found {
       +                return nil
       +        }
       +
       +        var templ tpl.Template
       +
       +        if owner.D.IsPlainText {
       +                prototype := t.tns.parseText
       +                tt, err := prototype.New(name).Parse("")
       +                if err != nil {
       +                        return fmt.Errorf("failed to parse empty text template %q: %w", name, err)
       +                }
       +                tt.Tree.Root = n
       +                templ = tt
       +        } else {
       +                prototype := t.tns.parseHTML
       +                tt, err := prototype.New(name).Parse("")
       +                if err != nil {
       +                        return fmt.Errorf("failed to parse empty HTML template %q: %w", name, err)
       +                }
       +                tt.Tree.Root = n
       +                templ = tt
       +        }
       +
       +        t.templatesByPath.Set(name, &TemplInfo{
       +                Fi:       owner.Fi,
       +                PathInfo: owner.PathInfo,
       +                D:        owner.D,
       +                Template: templ,
       +        })
       +
       +        return nil
       +}
       +
       +func (s *TemplateStore) addFileContext(ti *TemplInfo, inerr error) error {
       +        if ti.Fi == nil {
       +                return inerr
       +        }
       +
       +        identifiers := s.extractIdentifiers(inerr.Error())
       +
       +        checkFilename := func(fi hugofs.FileMetaInfo, inErr error) (error, bool) {
       +                lineMatcher := func(m herrors.LineMatcher) int {
       +                        if m.Position.LineNumber != m.LineNumber {
       +                                return -1
       +                        }
       +
       +                        for _, id := range identifiers {
       +                                if strings.Contains(m.Line, id) {
       +                                        // We found the line, but return a 0 to signal to
       +                                        // use the column from the error message.
       +                                        return 0
       +                                }
       +                        }
       +                        return -1
       +                }
       +
       +                f, err := fi.Meta().Open()
       +                if err != nil {
       +                        return inErr, false
       +                }
       +                defer f.Close()
       +
       +                fe := herrors.NewFileErrorFromName(inErr, fi.Meta().Filename)
       +                fe.UpdateContent(f, lineMatcher)
       +
       +                if !fe.ErrorContext().Position.IsValid() {
       +                        return inErr, false
       +                }
       +                return fe, true
       +        }
       +
       +        inerr = fmt.Errorf("execute of template failed: %w", inerr)
       +
       +        if err, ok := checkFilename(ti.Fi, inerr); ok {
       +                return err
       +        }
       +
       +        if ti.Base != nil {
       +                if err, ok := checkFilename(ti.Base.Fi, inerr); ok {
       +                        return err
       +                }
       +        }
       +
       +        return inerr
       +}
       +
       +func (s *TemplateStore) extractIdentifiers(line string) []string {
       +        m := identifiersRe.FindAllStringSubmatch(line, -1)
       +        identifiers := make([]string, len(m))
       +        for i := range m {
       +                identifiers[i] = m[i][1]
       +        }
       +        return identifiers
       +}
       +
       +func (s *TemplateStore) extractInlinePartials() error {
       +        isPartialName := func(s string) bool {
       +                return strings.HasPrefix(s, "partials/") || strings.HasPrefix(s, "_partials/")
       +        }
       +
       +        p := s.tns
       +        // We may find both inline and external partials in the current template namespaces,
       +        // so only add the ones we have not seen before.
       +        addIfNotSeen := func(isText bool, templs ...tpl.Template) error {
       +                for _, templ := range templs {
       +                        if templ.Name() == "" || !isPartialName(templ.Name()) {
       +                                continue
       +                        }
       +                        name := templ.Name()
       +                        if !paths.HasExt(name) {
       +                                // Assume HTML. This in line with how the lookup works.
       +                                name = name + ".html"
       +                        }
       +                        if !strings.HasPrefix(name, "_") {
       +                                name = "_" + name
       +                        }
       +                        pi := s.opts.PathParser.Parse(files.ComponentFolderLayouts, name)
       +                        ti, err := s.insertTemplate(pi, nil, false, s.treeMain)
       +                        if err != nil {
       +                                return err
       +                        }
       +
       +                        if ti != nil {
       +                                ti.Template = templ
       +                                ti.NoBaseOf = true
       +                                ti.SubCategory = SubCategoryInline
       +                                ti.D.IsPlainText = isText
       +                        }
       +
       +                }
       +                return nil
       +        }
       +        addIfNotSeen(false, p.templatesIn(p.parseHTML)...)
       +        addIfNotSeen(true, p.templatesIn(p.parseText)...)
       +
       +        for _, t := range p.baseofHtmlClones {
       +                if err := addIfNotSeen(false, p.templatesIn(t)...); err != nil {
       +                        return err
       +                }
       +        }
       +        for _, t := range p.baseofTextClones {
       +                if err := addIfNotSeen(true, p.templatesIn(t)...); err != nil {
       +                        return err
       +                }
       +        }
       +        return nil
       +}
       +
       +func (s *TemplateStore) insertEmbedded() error {
       +        return fs.WalkDir(embeddedTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error {
       +                if err != nil {
       +                        return err
       +                }
       +                if d == nil || d.IsDir() || strings.HasPrefix(d.Name(), ".") {
       +                        return nil
       +                }
       +
       +                templb, err := embeddedTemplatesFs.ReadFile(path)
       +                if err != nil {
       +                        return err
       +                }
       +
       +                // Get the newlines on Windows in line with how we had it back when we used Go Generate
       +                // to write the templates to Go files.
       +                templ := string(bytes.ReplaceAll(templb, []byte("\r\n"), []byte("\n")))
       +                name := strings.TrimPrefix(filepath.ToSlash(path), "embedded/templates/")
       +
       +                insertOne := func(name, content string) error {
       +                        pi := s.opts.PathParser.Parse(files.ComponentFolderLayouts, name)
       +                        var (
       +                                ti  *TemplInfo
       +                                err error
       +                        )
       +                        if pi.Section() == containerShortcodes {
       +                                ti, err = s.insertShortcode(pi, nil, false, s.treeShortcodes)
       +                                if err != nil {
       +                                        return err
       +                                }
       +                        } else {
       +                                ti, err = s.insertTemplate(pi, nil, false, s.treeMain)
       +                                if err != nil {
       +                                        return err
       +                                }
       +                        }
       +
       +                        if ti != nil {
       +                                // Currently none of the embedded templates need a baseof template.
       +                                ti.NoBaseOf = true
       +                                ti.Content = content
       +                                ti.SubCategory = SubCategoryEmbedded
       +                        }
       +
       +                        return nil
       +                }
       +
       +                if err := insertOne(name, templ); err != nil {
       +                        return err
       +                }
       +
       +                if aliases, found := embeddedTemplatesAliases[name]; found {
       +                        for _, alias := range aliases {
       +                                if err := insertOne(alias, templ); err != nil {
       +                                        return err
       +                                }
       +                        }
       +                }
       +
       +                return nil
       +        })
       +}
       +
       +func (s *TemplateStore) setTemplateByPath(p string, ti *TemplInfo) {
       +        s.templatesByPath.Set(p, ti)
       +}
       +
       +func (s *TemplateStore) insertShortcode(pi *paths.Path, fi hugofs.FileMetaInfo, replace bool, tree doctree.Tree[map[string]map[TemplateDescriptor]*TemplInfo]) (*TemplInfo, error) {
       +        k1, k2, _, d := s.toKeyCategoryAndDescriptor(pi)
       +        m := tree.Get(k1)
       +        if m == nil {
       +                m = make(map[string]map[TemplateDescriptor]*TemplInfo)
       +                tree.Insert(k1, m)
       +        }
       +
       +        m1, found := m[k2]
       +        if found {
       +                if _, found := m1[d]; found {
       +                        if !replace {
       +                                return nil, nil
       +                        }
       +                }
       +        } else {
       +                m1 = make(map[TemplateDescriptor]*TemplInfo)
       +                m[k2] = m1
       +        }
       +
       +        ti := &TemplInfo{
       +                PathInfo: pi,
       +                Fi:       fi,
       +                D:        d,
       +                Category: CategoryShortcode,
       +                NoBaseOf: true,
       +        }
       +
       +        m1[d] = ti
       +
       +        s.setTemplateByPath(pi.Path(), ti)
       +
       +        if fi != nil {
       +                if pi2 := fi.Meta().PathInfo; pi2 != pi {
       +                        s.setTemplateByPath(pi2.Path(), ti)
       +                }
       +        }
       +
       +        return ti, nil
       +}
       +
       +func (s *TemplateStore) insertTemplate(pi *paths.Path, fi hugofs.FileMetaInfo, replace bool, tree doctree.Tree[map[nodeKey]*TemplInfo]) (*TemplInfo, error) {
       +        key, _, category, d := s.toKeyCategoryAndDescriptor(pi)
       +
       +        return s.insertTemplate2(pi, fi, key, category, d, replace, false, tree)
       +}
       +
       +func (s *TemplateStore) insertTemplate2(
       +        pi *paths.Path,
       +        fi hugofs.FileMetaInfo,
       +        key string,
       +        category Category,
       +        d TemplateDescriptor,
       +        replace, isLegacyMapped bool,
       +        tree doctree.Tree[map[nodeKey]*TemplInfo],
       +) (*TemplInfo, error) {
       +        if category == 0 {
       +                panic("category not set")
       +        }
       +
       +        m := tree.Get(key)
       +        nk := nodeKey{c: category, d: d}
       +
       +        if m == nil {
       +                m = make(map[nodeKey]*TemplInfo)
       +                tree.Insert(key, m)
       +        }
       +
       +        if !replace {
       +                if v, found := m[nk]; found {
       +                        if len(pi.IdentifiersUnknown()) >= len(v.PathInfo.IdentifiersUnknown()) {
       +                                // e.g. /pages/home.foo.html and  /pages/home.html where foo may be a valid language name in another site.
       +                                return nil, nil
       +                        }
       +                }
       +        }
       +
       +        ti := &TemplInfo{
       +                PathInfo:       pi,
       +                Fi:             fi,
       +                D:              d,
       +                Category:       category,
       +                NoBaseOf:       category > CategoryLayout,
       +                isLegacyMapped: isLegacyMapped,
       +        }
       +
       +        m[nk] = ti
       +
       +        if !isLegacyMapped {
       +                s.setTemplateByPath(pi.Path(), ti)
       +                if fi != nil {
       +                        if pi2 := fi.Meta().PathInfo; pi2 != pi {
       +                                s.setTemplateByPath(pi2.Path(), ti)
       +                        }
       +                }
       +        }
       +
       +        return ti, nil
       +}
       +
       +func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) bool, replace bool) error {
       +        if include == nil {
       +                include = func(fi hugofs.FileMetaInfo) bool {
       +                        return true
       +                }
       +        }
       +
       +        // Set if we need to reset the base variants.
       +        var (
       +                resetBaseVariants bool
       +        )
       +
       +        legacyOrdinalMappings := map[legacyTargetPathIdentifiers]legacyOrdinalMappingFi{}
       +
       +        walker := func(pth string, fi hugofs.FileMetaInfo) error {
       +                piOrig := fi.Meta().PathInfo
       +                if fi.IsDir() {
       +                        return nil
       +                }
       +
       +                if !include(fi) {
       +                        return nil
       +                }
       +
       +                // Convert any legacy value to new format.
       +                fromLegacyPath := func(pi *paths.Path) *paths.Path {
       +                        p := pi.Path()
       +                        p = strings.TrimPrefix(p, "/_default")
       +                        if strings.HasPrefix(p, "/shortcodes") || strings.HasPrefix(p, "/partials") {
       +                                // Insert an underscore so it becomes /_shortcodes or /_partials.
       +                                p = "/_" + p[1:]
       +                        }
       +
       +                        if strings.Contains(p, "-"+baseNameBaseof) {
       +                                // Before Hugo 0.146.0 we prepended one identifier (layout, type or kind) in front of the baseof keyword,
       +                                // and then separated with a hyphen before the baseof keyword.
       +                                // This identifier needs to be moved right after the baseof keyword and the hyphen removed, e.g.
       +                                // /docs/list-baseof.html => /docs/baseof.list.html.
       +                                dir, name := path.Split(p)
       +                                hyphenIdx := strings.Index(name, "-")
       +                                if hyphenIdx > 0 {
       +                                        id := name[:hyphenIdx]
       +                                        name = name[hyphenIdx+1+len(baseNameBaseof):]
       +                                        if !strings.HasPrefix(name, ".") {
       +                                                name = "." + name
       +                                        }
       +                                        p = path.Join(dir, baseNameBaseof+"."+id+name)
       +                                }
       +                        }
       +                        if p == pi.Path() {
       +                                return pi
       +                        }
       +                        return s.opts.PathParser.Parse(files.ComponentFolderLayouts, p)
       +                }
       +
       +                pi := piOrig
       +                var applyLegacyMapping bool
       +                switch pi.Section() {
       +                case containerPartials, containerShortcodes, containerMarkup:
       +                        // OK.
       +                default:
       +                        applyLegacyMapping = true
       +                        pi = fromLegacyPath(pi)
       +                }
       +
       +                if applyLegacyMapping {
       +                        handleMapping := func(m1 legacyOrdinalMapping) {
       +                                key := legacyTargetPathIdentifiers{
       +                                        targetPath:     m1.mapping.targetPath,
       +                                        targetCategory: m1.mapping.targetCategory,
       +                                        kind:           m1.mapping.targetDesc.Kind,
       +                                        lang:           pi.Lang(),
       +                                        ext:            pi.Ext(),
       +                                        outputFormat:   pi.OutputFormat(),
       +                                }
       +                                if m2, ok := legacyOrdinalMappings[key]; ok {
       +                                        if m1.ordinal < m2.m.ordinal {
       +                                                // Higher up == better match.
       +                                                legacyOrdinalMappings[key] = legacyOrdinalMappingFi{m1, fi}
       +                                        }
       +                                } else {
       +                                        legacyOrdinalMappings[key] = legacyOrdinalMappingFi{m1, fi}
       +                                }
       +                        }
       +
       +                        if m1, ok := s.opts.legacyMappingTaxonomy[piOrig.PathBeforeLangAndOutputFormatAndExt()]; ok {
       +                                handleMapping(m1)
       +                        }
       +
       +                        if m1, ok := s.opts.legacyMappingTerm[piOrig.PathBeforeLangAndOutputFormatAndExt()]; ok {
       +                                handleMapping(m1)
       +                        }
       +
       +                        const (
       +                                sectionKindToken = "SECTIONKIND"
       +                                sectionToken     = "THESECTION"
       +                        )
       +
       +                        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
       +                        }
       +
       +                        for _, section := range identifiers {
       +                                if section == baseNameBaseof {
       +                                        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)
       +                                }
       +                        }
       +
       +                }
       +
       +                if replace && pi.NameNoIdentifier() == baseNameBaseof {
       +                        // A baseof file has changed.
       +                        resetBaseVariants = true
       +                }
       +
       +                var ti *TemplInfo
       +                var err error
       +                if pi.Type() == paths.TypeShortcode {
       +                        ti, err = s.insertShortcode(pi, fi, replace, s.treeShortcodes)
       +                        if err != nil || ti == nil {
       +                                return err
       +                        }
       +                } else {
       +                        ti, err = s.insertTemplate(pi, fi, replace, s.treeMain)
       +                        if err != nil || ti == nil {
       +                                return err
       +                        }
       +                }
       +
       +                if err := s.tns.readTemplateInto(ti); err != nil {
       +                        return err
       +                }
       +
       +                return nil
       +        }
       +
       +        if err := helpers.Walk(s.opts.Fs, "", walker); err != nil {
       +                if !herrors.IsNotExist(err) {
       +                        return err
       +                }
       +                return nil
       +        }
       +
       +        for k, v := range legacyOrdinalMappings {
       +                targetPath := k.targetPath
       +                m := v.m.mapping
       +                fi := v.fi
       +                pi := fi.Meta().PathInfo
       +                outputFormat, mediaType := s.resolveOutputFormatAndOrMediaType(k.outputFormat, k.ext)
       +                category := m.targetCategory
       +                desc := m.targetDesc
       +                desc.Kind = k.kind
       +                desc.Lang = k.lang
       +                desc.OutputFormat = outputFormat.Name
       +                desc.IsPlainText = outputFormat.IsPlainText
       +                desc.MediaType = mediaType.Type
       +
       +                ti, err := s.insertTemplate2(pi, fi, targetPath, category, desc, true, true, s.treeMain)
       +                if err != nil {
       +                        return err
       +                }
       +                if ti == nil {
       +                        continue
       +                }
       +                ti.isLegacyMapped = true
       +                if err := s.tns.readTemplateInto(ti); err != nil {
       +                        return err
       +                }
       +        }
       +
       +        if resetBaseVariants {
       +                s.tns.baseofHtmlClones = nil
       +                s.tns.baseofTextClones = nil
       +                s.treeMain.Walk(func(key string, v map[nodeKey]*TemplInfo) (bool, error) {
       +                        for _, vv := range v {
       +                                if !vv.NoBaseOf {
       +                                        vv.state = processingStateInitial
       +                                }
       +                        }
       +                        return false, nil
       +                })
       +        }
       +
       +        return nil
       +}
       +
       +func (s *TemplateStore) key(dir string) string {
       +        dir = paths.AddLeadingSlash(dir)
       +        if dir == "/" {
       +                return ""
       +        }
       +        return paths.TrimTrailing(dir)
       +}
       +
       +func (s *TemplateStore) parseTemplates() error {
       +        if err := func() error {
       +                // Read and parse all templates.
       +                for _, v := range s.treeMain.All() {
       +                        for _, vv := range v {
       +                                if vv.state == processingStateTransformed {
       +                                        continue
       +                                }
       +                                if err := s.tns.parseTemplate(vv); err != nil {
       +                                        return err
       +                                }
       +                        }
       +                }
       +
       +                // Lookup and apply base templates where needed.
       +                for key, v := range s.treeMain.All() {
       +                        for _, vv := range v {
       +                                if vv.state == processingStateTransformed {
       +                                        continue
       +                                }
       +                                if !vv.NoBaseOf {
       +                                        d := vv.D
       +                                        // Find all compatible base templates.
       +                                        baseTemplates := s.FindAllBaseTemplateCandidates(key, d)
       +                                        if len(baseTemplates) == 0 {
       +                                                // The regular expression used to detect if a template needs a base template has some
       +                                                // rare false positives. Assume we don't need one.
       +                                                vv.NoBaseOf = true
       +                                                if err := s.tns.parseTemplate(vv); err != nil {
       +                                                        return err
       +                                                }
       +                                                continue
       +                                        }
       +                                        vv.BaseVariants = doctree.NewSimpleTree[map[TemplateDescriptor]*TemplWithBaseApplied]()
       +
       +                                        for _, base := range baseTemplates {
       +                                                if err := s.tns.applyBaseTemplate(vv, base); err != nil {
       +                                                        return err
       +                                                }
       +                                        }
       +
       +                                }
       +                        }
       +                }
       +
       +                return nil
       +        }(); err != nil {
       +                return err
       +        }
       +
       +        // Prese shortcodes.
       +        for _, v := range s.treeShortcodes.All() {
       +                for _, vv := range v {
       +                        for _, vvv := range vv {
       +                                if vvv.state == processingStateTransformed {
       +                                        continue
       +                                }
       +                                if err := s.tns.parseTemplate(vvv); err != nil {
       +                                        return err
       +                                }
       +                        }
       +                }
       +        }
       +
       +        return nil
       +}
       +
       +// prepareTemplates prepares all templates for execution.
       +func (s *TemplateStore) prepareTemplates() error {
       +        for t := range s.templates() {
       +                if t.Category == CategoryBaseof {
       +                        continue
       +                }
       +                if _, err := t.Prepare(); err != nil {
       +                        return err
       +                }
       +        }
       +        return nil
       +}
       +
       +// TemplateDescriptorFromPath returns a template descriptor from the given path.
       +// This is currently used in partial lookups only.
       +func (s *TemplateStore) TemplateDescriptorFromPath(pth string) (string, TemplateDescriptor) {
       +        var (
       +                mt media.Type
       +                of output.Format
       +        )
       +
       +        // Common cases.
       +        dotCount := strings.Count(pth, ".")
       +        if dotCount <= 1 {
       +                if dotCount == 0 {
       +                        // Asume HTML.
       +                        of, mt = s.resolveOutputFormatAndOrMediaType("html", "")
       +                } else {
       +                        pth = strings.TrimPrefix(pth, "/")
       +                        ext := path.Ext(pth)
       +                        pth = strings.TrimSuffix(pth, ext)
       +                        ext = ext[1:]
       +                        of, mt = s.resolveOutputFormatAndOrMediaType("", ext)
       +                }
       +        } else {
       +                path := s.opts.PathParser.Parse(files.ComponentFolderLayouts, pth)
       +                pth = path.PathNoIdentifier()
       +                of, mt = s.resolveOutputFormatAndOrMediaType(path.OutputFormat(), path.Ext())
       +        }
       +
       +        return pth, TemplateDescriptor{
       +                OutputFormat: of.Name,
       +                MediaType:    mt.Type,
       +                IsPlainText:  of.IsPlainText,
       +        }
       +}
       +
       +// resolveOutputFormatAndOrMediaType resolves the output format and/or media type
       +// based on the given output format suffix and media type suffix.
       +// Either of the suffixes can be empty, and the function will try to find a match
       +// based on the other suffix. If both are empty, the function will return zero values.
       +func (s *TemplateStore) resolveOutputFormatAndOrMediaType(ofs, mns string) (output.Format, media.Type) {
       +        var outputFormat output.Format
       +        var mediaType media.Type
       +
       +        if ofs != "" {
       +                if of, found := s.opts.OutputFormats.GetByName(ofs); found {
       +                        outputFormat = of
       +                        mediaType = of.MediaType
       +                }
       +        }
       +
       +        if mns != "" && mediaType.IsZero() {
       +                if of, found := s.opts.OutputFormats.GetBySuffix(mns); found {
       +                        outputFormat = of
       +                        mediaType = of.MediaType
       +                } else {
       +                        if mt, _, found := s.opts.MediaTypes.GetFirstBySuffix(mns); found {
       +                                mediaType = mt
       +                                if outputFormat.IsZero() {
       +                                        // For e.g. index.xml we will in the default confg now have the application/rss+xml  media type.
       +                                        // Try a last time to find the output format using the SubType as the name.
       +                                        // As to template resolution, this value is currently only used to
       +                                        // decide if this is a text or HTML template.
       +                                        outputFormat, _ = s.opts.OutputFormats.GetByName(mt.SubType)
       +                                }
       +                        }
       +                }
       +        }
       +
       +        return outputFormat, mediaType
       +}
       +
       +func (s *TemplateStore) templates() iter.Seq[*TemplInfo] {
       +        return func(yield func(*TemplInfo) bool) {
       +                for _, v := range s.treeMain.All() {
       +                        for _, vv := range v {
       +                                if !vv.NoBaseOf {
       +                                        for vvv := range vv.BaseVariantsSeq() {
       +                                                if !yield(vvv.Template) {
       +                                                        return
       +                                                }
       +                                        }
       +                                } else {
       +                                        if !yield(vv) {
       +                                                return
       +                                        }
       +                                }
       +                        }
       +                }
       +                for _, v := range s.treeShortcodes.All() {
       +                        for _, vv := range v {
       +                                for _, vvv := range vv {
       +                                        if !yield(vvv) {
       +                                                return
       +                                        }
       +                                }
       +                        }
       +                }
       +        }
       +}
       +
       +func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, string, Category, TemplateDescriptor) {
       +        k1 := p.Dir()
       +        k2 := ""
       +
       +        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(),
       +                Layout:       layout,
       +                IsPlainText:  outputFormat.IsPlainText,
       +        }
       +
       +        d.normalizeFromFile()
       +
       +        section := p.Section()
       +
       +        var category Category
       +        switch p.Type() {
       +        case paths.TypeShortcode:
       +                category = CategoryShortcode
       +        case paths.TypePartial:
       +                category = CategoryPartial
       +        case paths.TypeMarkup:
       +                category = CategoryMarkup
       +        }
       +
       +        if category == 0 {
       +                if nameNoIdentifier == baseNameBaseof {
       +                        category = CategoryBaseof
       +                } else {
       +                        switch section {
       +                        case "_hugo":
       +                                category = CategoryHugo
       +                        case "_server":
       +                                category = CategoryServer
       +                        default:
       +                                category = CategoryLayout
       +                        }
       +                }
       +        }
       +
       +        if category == CategoryPartial {
       +                d.Layout = ""
       +                k1 = p.PathNoIdentifier()
       +        }
       +
       +        if category == CategoryShortcode {
       +                k1 = p.PathNoIdentifier()
       +                parts := strings.Split(k1, "/"+containerShortcodes+"/")
       +                k1 = parts[0]
       +                if len(parts) > 1 {
       +                        k2 = parts[1]
       +                }
       +                k1 = s.key(k1)
       +        }
       +
       +        // Legacy layout for home page.
       +        if d.Layout == "index" {
       +                if d.Kind == "" {
       +                        d.Kind = kinds.KindHome
       +                }
       +                d.Layout = ""
       +        }
       +
       +        if d.Layout == d.Kind {
       +                d.Layout = ""
       +        }
       +
       +        k1 = strings.TrimPrefix(k1, "/_default")
       +        if k1 == "/" {
       +                k1 = ""
       +        }
       +
       +        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, "-")
       +                if len(parts) < 2 {
       +                        panic("markup template must have at least 2 parts")
       +                }
       +                // Either 2 or 3 parts, e.g. render-codeblock-go.
       +                d.Variant1 = parts[1]
       +                if len(parts) > 2 {
       +                        d.Variant2 = parts[2]
       +                }
       +                d.Layout = "" // This allows using page layout as part of the key for lookups.
       +        }
       +
       +        return k1, k2, category, d
       +}
       +
       +func (s *TemplateStore) transformTemplates() error {
       +        lookup := func(name string, in *TemplInfo) *TemplInfo {
       +                if in.D.IsPlainText {
       +                        templ := in.Template.(*texttemplate.Template).Lookup(name)
       +                        if templ != nil {
       +                                return &TemplInfo{
       +                                        Template: templ,
       +                                }
       +                        }
       +                } else {
       +                        templ := in.Template.(*htmltemplate.Template).Lookup(name)
       +                        if templ != nil {
       +                                return &TemplInfo{
       +                                        Template: templ,
       +                                }
       +                        }
       +                }
       +
       +                return nil
       +        }
       +
       +        for vv := range s.templates() {
       +                if vv.state == processingStateTransformed {
       +                        continue
       +                }
       +                vv.state = processingStateTransformed
       +                if vv.Category == CategoryBaseof {
       +                        continue
       +                }
       +                if !vv.NoBaseOf {
       +                        for vvv := range vv.BaseVariantsSeq() {
       +                                tctx, err := applyTemplateTransformers(vvv.Template, lookup)
       +                                if err != nil {
       +                                        return err
       +                                }
       +
       +                                for name, node := range tctx.deferNodes {
       +                                        if err := s.addDeferredTemplate(vvv.Overlay, name, node); err != nil {
       +                                                return err
       +                                        }
       +                                }
       +                        }
       +                } else {
       +                        tctx, err := applyTemplateTransformers(vv, lookup)
       +                        if err != nil {
       +                                return err
       +                        }
       +
       +                        for name, node := range tctx.deferNodes {
       +                                if err := s.addDeferredTemplate(vv, name, node); err != nil {
       +                                        return err
       +                                }
       +                        }
       +                }
       +        }
       +
       +        return nil
       +}
       +
       +func (s *TemplateStore) init() error {
       +        // Before Hugo 0.146 we had a very elaborate template lookup system, especially for
       +        // terms and taxonomies. This is a way of preserving backwards compatibility
       +        // by mapping old paths into the new tree.
       +        s.opts.legacyMappingTaxonomy = make(map[string]legacyOrdinalMapping)
       +        s.opts.legacyMappingTerm = make(map[string]legacyOrdinalMapping)
       +        s.opts.legacyMappingSection = make(map[string]legacyOrdinalMapping)
       +
       +        // Placeholders.
       +        const singular = "SINGULAR"
       +        const plural = "PLURAL"
       +
       +        replaceTokens := func(s, singularv, pluralv string) string {
       +                s = strings.Replace(s, singular, singularv, -1)
       +                s = strings.Replace(s, plural, pluralv, -1)
       +                return s
       +        }
       +
       +        hasSingularOrPlural := func(s string) bool {
       +                return strings.Contains(s, singular) || strings.Contains(s, plural)
       +        }
       +
       +        expand := func(v layoutLegacyMapping) []layoutLegacyMapping {
       +                var result []layoutLegacyMapping
       +
       +                if hasSingularOrPlural(v.sourcePath) || hasSingularOrPlural(v.target.targetPath) {
       +                        for s, p := range s.opts.TaxonomySingularPlural {
       +                                target := v.target
       +                                target.targetPath = replaceTokens(target.targetPath, s, p)
       +                                vv := replaceTokens(v.sourcePath, s, p)
       +                                result = append(result, layoutLegacyMapping{sourcePath: vv, target: target})
       +                        }
       +                } else {
       +                        result = append(result, v)
       +                }
       +                return result
       +        }
       +
       +        expandSections := func(v layoutLegacyMapping) []layoutLegacyMapping {
       +                var result []layoutLegacyMapping
       +                result = append(result, v)
       +                baseofVariant := v
       +                baseofVariant.sourcePath += "-" + baseNameBaseof
       +                baseofVariant.target.targetCategory = CategoryBaseof
       +                result = append(result, baseofVariant)
       +                return result
       +        }
       +
       +        var terms []layoutLegacyMapping
       +        for _, v := range legacyTermMappings {
       +                terms = append(terms, expand(v)...)
       +        }
       +        var taxonomies []layoutLegacyMapping
       +        for _, v := range legacyTaxonomyMappings {
       +                taxonomies = append(taxonomies, expand(v)...)
       +        }
       +        var sections []layoutLegacyMapping
       +        for _, v := range legacySectionMappings {
       +                sections = append(sections, expandSections(v)...)
       +        }
       +
       +        for i, m := range terms {
       +                s.opts.legacyMappingTerm[m.sourcePath] = legacyOrdinalMapping{ordinal: i, mapping: m.target}
       +        }
       +        for i, m := range taxonomies {
       +                s.opts.legacyMappingTaxonomy[m.sourcePath] = legacyOrdinalMapping{ordinal: i, mapping: m.target}
       +        }
       +        for i, m := range sections {
       +                s.opts.legacyMappingSection[m.sourcePath] = legacyOrdinalMapping{ordinal: i, mapping: m.target}
       +        }
       +
       +        return nil
       +}
       +
       +type TemplateStoreProvider interface {
       +        GetTemplateStore() *TemplateStore
       +}
       +
       +type TextTemplatHandler interface {
       +        ExecuteWithContext(ctx context.Context, ti *TemplInfo, wr io.Writer, data any) error
       +        TextLookup(name string) *TemplInfo
       +        TextParse(name, tpl string) (*TemplInfo, error)
       +}
       +
       +type bestMatch struct {
       +        templ *TemplInfo
       +        desc  TemplateDescriptor
       +        w     weight
       +        key   string
       +
       +        // settings.
       +        defaultOutputformat string
       +}
       +
       +func (best *bestMatch) reset() {
       +        best.templ = nil
       +        best.w = weight{}
       +        best.desc = TemplateDescriptor{}
       +        best.key = ""
       +}
       +
       +func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
       +        if best.templ == nil {
       +                // Anything is better than nothing.
       +                return true
       +        }
       +        if w.w1 <= 0 {
       +                if best.w.w1 <= 0 {
       +                        return ti.PathInfo.Path() < best.templ.PathInfo.Path()
       +                }
       +                return false
       +        }
       +
       +        if best.w.w1 > 0 {
       +                currentBestIsEmbedded := best.templ.SubCategory == SubCategoryEmbedded
       +                if currentBestIsEmbedded {
       +                        if ti.SubCategory != SubCategoryEmbedded {
       +                                return true
       +                        }
       +                } else {
       +                        if ti.SubCategory == SubCategoryEmbedded {
       +                                // Prefer user provided template.
       +                                return false
       +                        }
       +                }
       +        }
       +
       +        if w.distance < best.w.distance {
       +                if w.w2 < best.w.w2 {
       +                        return false
       +                }
       +                if w.w3 < best.w.w3 {
       +                        return false
       +                }
       +        } else {
       +                if w.w1 < best.w.w1 {
       +                        return false
       +                }
       +        }
       +
       +        if w.isEqualWeights(best.w) {
       +                // Tie breakers.
       +                if w.distance < best.w.distance {
       +                        return true
       +                }
       +
       +                if ti.D.Layout != "" && best.desc.Layout != "" {
       +                        return ti.D.Layout != layoutAll
       +                }
       +
       +                return w.distance < best.w.distance || ti.PathInfo.Path() < best.templ.PathInfo.Path()
       +        }
       +
       +        return true
       +}
       +
       +func (best *bestMatch) updateValues(w weight, key string, k TemplateDescriptor, vv *TemplInfo) {
       +        best.w = w
       +        best.templ = vv
       +        best.desc = k
       +        best.key = key
       +}
       +
       +type byPath []*TemplInfo
       +
       +func (a byPath) Len() int { return len(a) }
       +func (a byPath) Less(i, j int) bool {
       +        return a[i].PathInfo.Path() < a[j].PathInfo.Path()
       +}
       +
       +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
       +
       +type keyTemplateInfo struct {
       +        Key  string
       +        Info *TemplInfo
       +}
       +
       +type nodeKey struct {
       +        c Category
       +        d TemplateDescriptor
       +}
       +
       +type processingState int
       +
       +// the parts of a template store that's set per site.
       +type storeSite struct {
       +        opts       SiteOptions
       +        execHelper *templateExecHelper
       +        executer   texttemplate.Executer
       +}
       +
       +type weight struct {
       +        w1       int
       +        w2       int
       +        w3       int
       +        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:
       +                return true
       +        default:
       +                return false
       +        }
       +}
       +
       +func configureSiteStorage(opts SiteOptions, watching bool) *storeSite {
       +        funcsv := make(map[string]reflect.Value)
       +
       +        for k, v := range opts.TemplateFuncs {
       +                vv := reflect.ValueOf(v)
       +                funcsv[k] = vv
       +        }
       +
       +        // Duplicate Go's internal funcs here for faster lookups.
       +        for k, v := range htmltemplate.GoFuncs {
       +                if _, exists := funcsv[k]; !exists {
       +                        vv, ok := v.(reflect.Value)
       +                        if !ok {
       +                                vv = reflect.ValueOf(v)
       +                        }
       +                        funcsv[k] = vv
       +                }
       +        }
       +
       +        for k, v := range texttemplate.GoFuncs {
       +                if _, exists := funcsv[k]; !exists {
       +                        funcsv[k] = v
       +                }
       +        }
       +
       +        s := &storeSite{
       +                opts: opts,
       +                execHelper: &templateExecHelper{
       +                        watching:   watching,
       +                        funcs:      funcsv,
       +                        site:       reflect.ValueOf(opts.Site),
       +                        siteParams: reflect.ValueOf(opts.Site.Params()),
       +                },
       +        }
       +
       +        s.executer = texttemplate.NewExecuter(s.execHelper)
       +
       +        return s
       +}
 (DIR) diff --git a/tpl/tplimpl/templatestore_integration_test.go b/tpl/tplimpl/templatestore_integration_test.go
       @@ -0,0 +1,842 @@
       +package tplimpl_test
       +
       +import (
       +        "testing"
       +
       +        qt "github.com/frankban/quicktest"
       +        "github.com/gohugoio/hugo/hugolib"
       +        "github.com/gohugoio/hugo/resources/kinds"
       +        "github.com/gohugoio/hugo/tpl/tplimpl"
       +)
       +
       +// Old as in before Hugo v0.146.0.
       +func TestLayoutsOldSetup(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +defaultContentLanguage = "en"
       +defaultContentLanguageInSubdir = true
       +[languages]
       +[languages.en]
       +title = "Title in English"
       +weight = 1
       +[languages.nn]
       +title = "Tittel på nynorsk"
       +weight = 2
       +-- layouts/index.html --
       +Home.
       +{{ template "_internal/twitter_cards.html" . }}
       +-- layouts/_default/single.html --
       +Single.
       +-- layouts/_default/single.nn.html --
       +Single NN.
       +-- layouts/_default/list.html --
       +List HTML.
       +-- layouts/docs/list-baseof.html --
       +Docs Baseof List HTML.
       +{{ block "main" . }}Docs Baseof List HTML main block.{{ end }}
       +-- layouts/docs/list.section.html --
       +{{ define "main" }}
       +Docs List HTML.
       +{{ end }}
       +-- layouts/_default/list.json --
       +List JSON.
       +-- layouts/_default/list.rss.xml --
       +List RSS.
       +-- layouts/_default/list.nn.rss.xml --
       +List NN RSS.
       +-- layouts/_default/baseof.html --
       +Base.
       +-- layouts/partials/mypartial.html --
       +Partial.
       +-- layouts/shortcodes/myshortcode.html --
       +Shortcode.
       +-- content/docs/p1.md --
       +---
       +title: "P1"
       +---
       +
       +        `
       +
       +        b := hugolib.Test(t, files)
       +
       +        //        b.DebugPrint("", tplimpl.CategoryBaseof)
       +
       +        b.AssertFileContent("public/en/docs/index.html", "Docs Baseof List HTML.\n\nDocs List HTML.")
       +}
       +
       +func TestLayoutsOldSetupBaseofPrefix(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/_default/layout1-baseof.html --
       +Baseof layout1. {{ block "main" . }}{{ end }}
       +-- layouts/_default/layout2-baseof.html --
       +Baseof layout2. {{ block "main" . }}{{ end }}
       +-- layouts/_default/layout1.html --
       +{{ define "main" }}Layout1. {{ .Title }}{{ end }}
       +-- layouts/_default/layout2.html --
       +{{ define "main" }}Layout2. {{ .Title }}{{ end }}
       +-- content/p1.md --
       +---
       +title: "P1"
       +layout: "layout1"
       +---
       +-- content/p2.md --
       +---
       +title: "P2"
       +layout: "layout2"
       +---
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p1/index.html", "Baseof layout1. Layout1. P1")
       +        b.AssertFileContent("public/p2/index.html", "Baseof layout2. Layout2. P2")
       +}
       +
       +func TestLayoutsOldSetupTaxonomyAndTerm(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +[taxonomies]
       +cat = 'cats'
       +dog = 'dogs'
       +# Templates for term taxonomy, old setup.
       +-- layouts/dogs/terms.html --
       +Dogs Terms. Most specific taxonomy template.
       +-- layouts/taxonomy/terms.html --
       +Taxonomy Terms. Down the list.
       +# Templates for term term, old setup.
       +-- layouts/dogs/term.html --
       +Dogs Term. Most specific term template.
       +-- layouts/term/term.html --
       +Term Term. Down the list.
       +-- layouts/dogs/max/list.html --
       +max: {{ .Title }}
       +-- layouts/_default/list.html --
       +Default list.
       +-- layouts/_default/single.html --
       +Default single.
       +-- content/p1.md --
       +---
       +title: "P1"
       +dogs: ["luna", "daisy", "max"]
       +---
       +
       +`
       +        b := hugolib.Test(t, files, hugolib.TestOptWarn())
       +
       +        b.AssertLogContains("! WARN")
       +
       +        b.AssertFileContent("public/dogs/index.html", "Dogs Terms. Most specific taxonomy template.")
       +        b.AssertFileContent("public/dogs/luna/index.html", "Dogs Term. Most specific term template.")
       +        b.AssertFileContent("public/dogs/max/index.html", "max: Max") // layouts/dogs/max/list.html wins over layouts/term/term.html because of distance.
       +}
       +
       +func TestLayoutsOldSetupCustomRSS(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableKinds = ["taxonomy", "term", "page"]
       +[outputs]
       +home = ["rss"]
       +-- layouts/_default/list.rss.xml --
       +List RSS.
       +`
       +        b := hugolib.Test(t, files)
       +        b.AssertFileContent("public/index.xml", "List RSS.")
       +}
       +
       +var newSetupTestSites = `
       +-- hugo.toml --
       +defaultContentLanguage = "en"
       +defaultContentLanguageInSubdir = true
       +[languages]
       +[languages.en]
       +title = "Title in English"
       +weight = 1
       +[languages.nn]
       +title = "Tittel på nynorsk"
       +weight = 2
       +[languages.fr]
       +title = "Titre en français"
       +weight = 3
       +
       +[outputs]
       +home     = ["html", "rss", "redir"]
       +
       +[outputFormats]
       +[outputFormats.redir]
       +mediatype   = "text/plain"
       +baseName    = "_redirects"
       +isPlainText = true
       +-- layouts/404.html --
       +{{ define "main" }}
       +404.
       +{{ end }}
       +-- layouts/home.html --
       +{{ define "main" }}
       +Home: {{ .Title }}|{{ .Content }}|
       +Inline Partial: {{ partial "my-inline-partial.html" . }}
       +{{ end }}
       +{{ define "hero" }}
       +Home hero.
       +{{ end }}
       +{{ define "partials/my-inline-partial.html" }}
       +{{ $value := 32 }}
       +{{ return $value }}
       +{{ end }}
       +-- layouts/index.redir --
       +Redir.
       +-- layouts/single.html --
       +{{ define "main" }}
       +Single needs base.
       +{{ end }}
       +-- layouts/foo/bar/single.html --
       +{{ define "main" }}
       +Single sub path.
       +{{ end }}
       +-- layouts/_markup/render-codeblock.html --
       +Render codeblock.
       +-- layouts/_markup/render-blockquote.html --
       +Render blockquote.
       +-- layouts/_markup/render-codeblock-go.html --
       + Render codeblock go.
       +-- layouts/_markup/render-link.html --
       +Link: {{ .Destination | safeURL }}
       +-- layouts/foo/baseof.html --
       +Base sub path.{{ block "main" . }}{{ end }}
       +-- layouts/foo/bar/baseof.page.html --
       +Base sub path.{{ block "main" . }}{{ end }}
       +-- layouts/list.html --
       +{{ define "main" }}
       +List needs base.
       +{{ end }}
       +-- layouts/section.html --
       +Section.
       +-- layouts/mysectionlayout.section.fr.amp.html --
       +Section with layout.
       +-- layouts/baseof.html --
       +Base.{{ block "main" . }}{{ end }}
       +Hero:{{ block "hero" . }}{{ end }}:
       +{{ with (templates.Defer (dict "key" "global")) }}
       +Defer Block.
       +{{ end }}
       +-- layouts/baseof.fr.html --
       +Base fr.{{ block "main" . }}{{ end }}
       +-- layouts/baseof.term.html --
       +Base term.
       +-- layouts/baseof.section.fr.amp.html --
       +Base with identifiers.{{ block "main" . }}{{ end }}
       +-- layouts/partials/mypartial.html --
       +Partial. {{ partial "_inline/my-inline-partial-in-partial-with-no-ext" . }}
       +{{ define "partials/_inline/my-inline-partial-in-partial-with-no-ext" }}
       +Partial in partial.
       +{{ end }}
       +-- layouts/partials/returnfoo.html --
       +{{ $v := "foo" }}
       +{{ return $v }}
       +-- layouts/shortcodes/myshortcode.html --
       +Shortcode. {{ partial "mypartial.html" . }}|return:{{ partial "returnfoo.html" . }}|
       +-- content/_index.md --
       +---
       +title: Home sweet home!
       +---
       +
       +{{< myshortcode >}}
       +
       +> My blockquote.
       +
       +
       +Markdown link: [Foo](/foo)
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +-- content/foo/bar/index.md --
       +---
       +title: "Foo Bar"
       +---
       +
       +{{< myshortcode >}}
       +
       +-- content/single-list.md --
       +---
       +title: "Single List"
       +layout: "list"
       +---
       +
       +`
       +
       +func TestLayoutsType(t *testing.T) {
       +        files := `
       +-- hugo.toml --
       +disableKinds = ["taxonomy", "term"]
       +-- layouts/list.html --
       +List.
       +-- layouts/mysection/single.html --
       +mysection/single|{{ .Title }}
       +-- layouts/mytype/single.html --
       +mytype/single|{{ .Title }}
       +-- content/mysection/_index.md --
       +-- content/mysection/mysubsection/_index.md --
       +-- content/mysection/mysubsection/p1.md --
       +---
       +title: "P1"
       +---
       +-- content/mysection/mysubsection/p2.md --
       +---
       +title: "P2"
       +type: "mytype"
       +---
       +
       +`
       +
       +        b := hugolib.Test(t, files, hugolib.TestOptWarn())
       +
       +        b.AssertLogContains("! WARN")
       +
       +        b.AssertFileContent("public/mysection/mysubsection/p1/index.html", "mysection/single|P1")
       +        b.AssertFileContent("public/mysection/mysubsection/p2/index.html", "mytype/single|P2")
       +}
       +
       +// New, as in from Hugo v0.146.0.
       +func TestLayoutsNewSetup(t *testing.T) {
       +        const numIterations = 1
       +        for range numIterations {
       +
       +                b := hugolib.Test(t, newSetupTestSites, hugolib.TestOptWarn())
       +
       +                b.AssertLogContains("! WARN")
       +
       +                b.AssertFileContent("public/en/index.html",
       +                        "Base.\nHome: Home sweet home!|",
       +                        "|Shortcode.\n|",
       +                        "<p>Markdown link: Link: /foo</p>",
       +                        "|return:foo|",
       +                        "Defer Block.",
       +                        "Home hero.",
       +                        "Render blockquote.",
       +                )
       +
       +                b.AssertFileContent("public/en/p1/index.html", "Base.\nSingle needs base.\n\nHero::\n\nDefer Block.")
       +                b.AssertFileContent("public/en/404.html", "404.")
       +                b.AssertFileContent("public/nn/404.html", "404.")
       +                b.AssertFileContent("public/fr/404.html", "404.")
       +
       +        }
       +}
       +
       +func TestHomeRSSAndHTMLWithHTMLOnlyShortcode(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableKinds = ["taxonomy", "term"]
       +[outputs]
       +home = ["html", "rss"]
       +-- layouts/home.html --
       +Home: {{ .Title }}|{{ .Content }}|
       +-- layouts/single.html --
       +Single: {{ .Title }}|{{ .Content }}|
       +-- layouts/shortcodes/myshortcode.html --
       +Myshortcode: Count: {{ math.Counter }}|
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +
       +{{< myshortcode >}}
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p1/index.html", "Single: P1|Myshortcode: Count: 1|")
       +        b.AssertFileContent("public/index.xml", "Myshortcode: Count: 1")
       +}
       +
       +func TestHomeRSSAndHTMLWithHTMLOnlyRenderHook(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableKinds = ["taxonomy", "term"]
       +[outputs]
       +home = ["html", "rss"]
       +-- layouts/home.html --
       +Home: {{ .Title }}|{{ .Content }}|
       +-- layouts/single.html --
       +Single: {{ .Title }}|{{ .Content }}|
       +-- layouts/_markup/render-link.html --
       +Render Link: {{ math.Counter }}|
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +
       +Link: [Foo](/foo)
       +`
       +
       +        for range 2 {
       +                b := hugolib.Test(t, files)
       +                b.AssertFileContent("public/index.xml", "Link: Render Link: 1|")
       +                b.AssertFileContent("public/p1/index.html", "Single: P1|<p>Link: Render Link: 1|<")
       +        }
       +}
       +
       +func TestRenderCodeblockSpecificity(t *testing.T) {
       +        files := `
       +-- hugo.toml --
       +-- layouts/_markup/render-codeblock.html --
       +Render codeblock.|{{ .Inner }}|
       +-- layouts/_markup/render-codeblock-go.html --
       +Render codeblock go.|{{ .Inner }}|
       +-- layouts/single.html --
       +{{ .Title }}|{{ .Content }}|
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +
       +§§§
       +Basic
       +§§§
       +
       +§§§ go
       +Go
       +§§§
       +
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p1/index.html", "P1|Render codeblock.|Basic|Render codeblock go.|Go|")
       +}
       +
       +func TestPrintUnusedTemplates(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- config.toml --
       +baseURL = 'http://example.com/'
       +printUnusedTemplates=true
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +{{< usedshortcode >}}
       +-- layouts/baseof.html --
       +{{ block "main" . }}{{ end }}
       +-- layouts/baseof.json --
       +{{ block "main" . }}{{ end }}
       +-- layouts/index.html --
       +{{ define "main" }}FOO{{ end }}
       +-- layouts/_default/single.json --
       +-- layouts/_default/single.html --
       +{{ define "main" }}MAIN{{ end }}
       +-- layouts/post/single.html --
       +{{ define "main" }}MAIN{{ end }}
       +-- layouts/_partials/usedpartial.html --
       +-- layouts/_partials/unusedpartial.html --
       +-- layouts/_shortcodes/usedshortcode.html --
       +{{ partial "usedpartial.html" }}
       +-- layouts/shortcodes/unusedshortcode.html --
       +
       +        `
       +
       +        b := hugolib.NewIntegrationTestBuilder(
       +                hugolib.IntegrationTestConfig{
       +                        T:           t,
       +                        TxtarString: files,
       +                        NeedsOsFS:   true,
       +                },
       +        )
       +        b.Build()
       +
       +        unused := b.H.GetTemplateStore().UnusedTemplates()
       +        var names []string
       +        for _, tmpl := range unused {
       +                if fi := tmpl.Fi; fi != nil {
       +                        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"})
       +}
       +
       +func TestCreateManyTemplateStores(t *testing.T) {
       +        t.Parallel()
       +        b := hugolib.Test(t, newSetupTestSites)
       +        store := b.H.TemplateStore
       +
       +        for range 70 {
       +                newStore, err := store.NewFromOpts()
       +                b.Assert(err, qt.IsNil)
       +                b.Assert(newStore, qt.Not(qt.IsNil))
       +        }
       +}
       +
       +func BenchmarkLookupPagesLayout(b *testing.B) {
       +        files := `
       +-- hugo.toml --
       +-- layouts/single.html --
       +{{ define "main" }}
       + Main.
       +{{ end }}
       +-- layouts/baseof.html --
       +baseof: {{ block "main" . }}{{ end }}
       +-- layouts/foo/bar/single.html --
       +{{ define "main" }}
       + Main.
       +{{ end }}
       +
       +`
       +        bb := hugolib.Test(b, files)
       +        store := bb.H.TemplateStore
       +
       +        b.ResetTimer()
       +        b.Run("Single root", func(b *testing.B) {
       +                q := tplimpl.TemplateQuery{
       +                        Path:     "/baz",
       +                        Category: tplimpl.CategoryLayout,
       +                        Desc:     tplimpl.TemplateDescriptor{Kind: kinds.KindPage, Layout: "single", OutputFormat: "html"},
       +                }
       +                for i := 0; i < b.N; i++ {
       +                        store.LookupPagesLayout(q)
       +                }
       +        })
       +
       +        b.Run("Single sub folder", func(b *testing.B) {
       +                q := tplimpl.TemplateQuery{
       +                        Path:     "/foo/bar",
       +                        Category: tplimpl.CategoryLayout,
       +                        Desc:     tplimpl.TemplateDescriptor{Kind: kinds.KindPage, Layout: "single", OutputFormat: "html"},
       +                }
       +                for i := 0; i < b.N; i++ {
       +                        store.LookupPagesLayout(q)
       +                }
       +        })
       +}
       +
       +func BenchmarkNewTemplateStore(b *testing.B) {
       +        bb := hugolib.Test(b, newSetupTestSites)
       +        store := bb.H.TemplateStore
       +
       +        b.ResetTimer()
       +        for i := 0; i < b.N; i++ {
       +                newStore, err := store.NewFromOpts()
       +                if err != nil {
       +                        b.Fatal(err)
       +                }
       +                if newStore == nil {
       +                        b.Fatal("newStore is nil")
       +                }
       +        }
       +}
       +
       +func TestLayoutsLookupVariants(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +defaultContentLanguage = "en"
       +defaultContentLanguageInSubdir = true
       +[outputs]
       +home = ["html", "rss"]
       +page = ["html", "rss",  "amp"]
       +section = ["html", "rss"]
       +
       +[languages]
       +[languages.en]
       +title = "Title in English"
       +weight = 1
       +[languages.nn]
       +title = "Tittel på nynorsk"
       +weight = 2
       +-- layouts/list.xml --
       +layouts/list.xml
       +-- layouts/_shortcodes/myshortcode.html --
       +layouts/shortcodes/myshortcode.html
       +-- layouts/foo/bar/_shortcodes/myshortcode.html --
       +layouts/foo/bar/_shortcodes/myshortcode.html
       +-- layouts/_markup/render-codeblock.html --
       +layouts/_markup/render-codeblock.html|{{ .Type }}|
       +-- layouts/_markup/render-codeblock-go.html --
       +layouts/_markup/render-codeblock-go.html|{{ .Type }}|
       +-- layouts/single.xml --
       +layouts/single.xml
       +-- layouts/single.rss.xml --
       +layouts/single.rss.xml
       +-- layouts/single.nn.rss.xml --
       +layouts/single.nn.rss.xml
       +-- layouts/list.html --
       +layouts/list.html
       +-- layouts/single.html --
       +layouts/single.html
       +{{ .Content }}
       +-- layouts/mylayout.html --
       +layouts/mylayout.html
       +-- layouts/mylayout.nn.html --
       +layouts/mylayout.nn.html
       +-- layouts/foo/single.rss.xml --
       +layouts/foo/single.rss.xml
       +-- layouts/foo/single.amp.html --
       +layouts/foo/single.amp.html
       +-- layouts/foo/bar/page.html --
       +layouts/foo/bar/page.html
       +-- layouts/foo/bar/baz/single.html --
       +layouts/foo/bar/baz/single.html
       +{{ .Content }}
       +-- layouts/qux/mylayout.html --
       +layouts/qux/mylayout.html
       +-- layouts/qux/single.xml --
       +layouts/qux/single.xml
       +-- layouts/qux/mylayout.section.html --
       +layouts/qux/mylayout.section.html
       +-- content/p.md --
       +---
       +---
       +§§§
       +code
       +§§§
       +
       +§§§ go
       +code
       +§§§
       +
       +{{< myshortcode >}}
       +-- content/foo/p.md --
       +-- content/foo/p.nn.md --
       +-- content/foo/bar/p.md --
       +-- content/foo/bar/withmylayout.md --
       +---
       +layout: mylayout
       +---
       +-- content/foo/bar/_index.md --
       +-- content/foo/bar/baz/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +-- content/qux/p.md --
       +-- content/qux/_index.md --
       +---
       +layout: mylayout
       +---
       +-- content/qux/quux/p.md --
       +-- content/qux/quux/withmylayout.md --
       +---
       +layout: mylayout
       +---
       +-- content/qux/quux/withmylayout.nn.md --
       +---
       +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.
       +        // output format: html.
       +        b.AssertFileContent("public/en/p/index.html", "layouts/single.html",
       +                "layouts/_markup/render-codeblock.html|",
       +                "layouts/_markup/render-codeblock-go.html|go|",
       +                "layouts/shortcodes/myshortcode.html",
       +        )
       +        b.AssertFileContent("public/en/foo/p/index.html", "layouts/single.html")
       +        b.AssertFileContent("public/en/foo/bar/p/index.html", "layouts/foo/bar/page.html")
       +        b.AssertFileContent("public/en/foo/bar/withmylayout/index.html", "layouts/mylayout.html")
       +        b.AssertFileContent("public/en/foo/bar/baz/p/index.html", "layouts/foo/bar/baz/single.html", "layouts/foo/bar/_shortcodes/myshortcode.html")
       +        b.AssertFileContent("public/en/qux/quux/withmylayout/index.html", "layouts/qux/mylayout.html")
       +        // output format: amp.
       +        b.AssertFileContent("public/en/amp/p/index.html", "layouts/single.html")
       +        b.AssertFileContent("public/en/amp/foo/p/index.html", "layouts/foo/single.amp.html")
       +        // output format: rss.
       +        b.AssertFileContent("public/en/p/index.xml", "layouts/single.rss.xml")
       +        b.AssertFileContent("public/en/foo/p/index.xml", "layouts/foo/single.rss.xml")
       +        b.AssertFileContent("public/nn/foo/p/index.xml", "layouts/single.nn.rss.xml")
       +
       +        // Note: There is qux/single.xml that's closer, but the one in the root is used becaulse of the output format match.
       +        b.AssertFileContent("public/en/qux/p/index.xml", "layouts/single.rss.xml")
       +
       +        // Note.
       +        b.AssertFileContent("public/nn/qux/quux/withmylayout/index.html", "layouts/mylayout.nn.html")
       +
       +        // Section pages.
       +        // output format: html.
       +        b.AssertFileContent("public/en/foo/index.html", "layouts/list.html")
       +        b.AssertFileContent("public/en/qux/index.html", "layouts/qux/mylayout.section.html")
       +        // output format: rss.
       +        b.AssertFileContent("public/en/foo/index.xml", "layouts/list.xml")
       +}
       +
       +func TestLookupShortcodeDepth(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/_shortcodes/myshortcode.html --
       +layouts/_shortcodes/myshortcode.html
       +-- layouts/foo/_shortcodes/myshortcode.html --
       +layouts/foo/_shortcodes/myshortcode.html
       +-- layouts/single.html --
       +{{ .Content }}|
       +-- content/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +-- content/foo/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +-- content/foo/bar/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p/index.html", "layouts/_shortcodes/myshortcode.html")
       +        b.AssertFileContent("public/foo/p/index.html", "layouts/foo/_shortcodes/myshortcode.html")
       +        b.AssertFileContent("public/foo/bar/p/index.html", "layouts/foo/_shortcodes/myshortcode.html")
       +}
       +
       +func TestLookupShortcodeLayout(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/_shortcodes/myshortcode.single.html --
       +layouts/_shortcodes/myshortcode.single.html
       +-- layouts/_shortcodes/myshortcode.list.html --
       +layouts/_shortcodes/myshortcode.list.html
       +-- layouts/single.html --
       +{{ .Content }}|
       +-- layouts/list.html --
       +{{ .Content }}|
       +-- content/_index.md --
       +---
       +---
       +{{< myshortcode >}}
       +-- content/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +-- content/foo/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +-- content/foo/bar/p.md --
       +---
       +---
       +{{< myshortcode >}}
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p/index.html", "layouts/_shortcodes/myshortcode.single.html")
       +        b.AssertFileContent("public/index.html", "layouts/_shortcodes/myshortcode.list.html")
       +}
       +
       +func TestLayoutAll(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/single.html --
       +Single.
       +-- layouts/all.html --
       +All.
       +-- content/p1.md --
       +
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/p1/index.html", "Single.")
       +        b.AssertFileContent("public/index.html", "All.")
       +}
       +
       +func TestLayoutAllNested(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableKinds = ['rss','sitemap','taxonomy','term']
       +-- content/s1/p1.md --
       +---
       +title: p1
       +---
       +-- content/s2/p2.md --
       +---
       +title: p2
       +---
       +-- layouts/single.html --
       +layouts/single.html
       +-- layouts/list.html --
       +layouts/list.html
       +-- layouts/s1/all.html --
       +layouts/s1/all.html
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/index.html", "layouts/list.html")
       +        b.AssertFileContent("public/s1/index.html", "layouts/s1/all.html")
       +        b.AssertFileContent("public/s1/p1/index.html", "layouts/s1/all.html")
       +        b.AssertFileContent("public/s2/index.html", "layouts/list.html")
       +        b.AssertFileContent("public/s2/p2/index.html", "layouts/single.html")
       +}
       +
       +func TestPartialHTML(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +-- layouts/all.html --
       +<html>
       +<head>
       +{{ partial "css.html" .}}
       +</head>
       +</html>
       +-- layouts/partials/css.html --
       +<link rel="stylesheet" href="/css/style.css">
       +`
       +
       +        b := hugolib.Test(t, files)
       +
       +        b.AssertFileContent("public/index.html", "<link rel=\"stylesheet\" href=\"/css/style.css\">")
       +}
       +
       +// Issue #13515
       +func TestPrintPathWarningOnDotRemoval(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +baseURL = "https://example.com"
       +printPathWarnings = true
       +-- content/v0.124.0.md --
       +-- content/v0.123.0.md --
       +-- layouts/all.html --
       +All.
       +-- layouts/_default/single.html --
       +{{ .Title }}|
       +`
       +
       +        b := hugolib.Test(t, files, hugolib.TestOptWarn())
       +
       +        b.AssertLogContains("Duplicate content path")
       +}
 (DIR) diff --git a/tpl/tplimpl/templatetransform.go b/tpl/tplimpl/templatetransform.go
       @@ -0,0 +1,349 @@
       +package tplimpl
       +
       +import (
       +        "errors"
       +        "fmt"
       +        "slices"
       +        "strings"
       +
       +        "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
       +
       +        htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
       +        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
       +
       +        "github.com/gohugoio/hugo/common/hashing"
       +        "github.com/gohugoio/hugo/common/maps"
       +        "github.com/gohugoio/hugo/tpl"
       +        "github.com/mitchellh/mapstructure"
       +)
       +
       +type templateTransformContext struct {
       +        visited          map[string]bool
       +        templateNotFound map[string]bool
       +        deferNodes       map[string]*parse.ListNode
       +        lookupFn         func(name string, in *TemplInfo) *TemplInfo
       +
       +        // The last error encountered.
       +        err error
       +
       +        // Set when we're done checking for config header.
       +        configChecked bool
       +
       +        t *TemplInfo
       +
       +        // Store away the return node in partials.
       +        returnNode *parse.CommandNode
       +}
       +
       +func (c templateTransformContext) getIfNotVisited(name string) *TemplInfo {
       +        if c.visited[name] {
       +                return nil
       +        }
       +        c.visited[name] = true
       +        templ := c.lookupFn(name, c.t)
       +        if templ == nil {
       +                // This may be a inline template defined outside of this file
       +                // and not yet parsed. Unusual, but it happens.
       +                // Store the name to try again later.
       +                c.templateNotFound[name] = true
       +        }
       +
       +        return templ
       +}
       +
       +func newTemplateTransformContext(
       +        t *TemplInfo,
       +        lookupFn func(name string, in *TemplInfo) *TemplInfo,
       +) *templateTransformContext {
       +        return &templateTransformContext{
       +                t:                t,
       +                lookupFn:         lookupFn,
       +                visited:          make(map[string]bool),
       +                templateNotFound: make(map[string]bool),
       +                deferNodes:       make(map[string]*parse.ListNode),
       +        }
       +}
       +
       +func applyTemplateTransformers(
       +        t *TemplInfo,
       +        lookupFn func(name string, in *TemplInfo) *TemplInfo,
       +) (*templateTransformContext, error) {
       +        if t == nil {
       +                return nil, errors.New("expected template, but none provided")
       +        }
       +
       +        c := newTemplateTransformContext(t, lookupFn)
       +        c.t.ParseInfo = defaultParseInfo
       +        tree := getParseTree(t.Template)
       +        if tree == nil {
       +                panic(fmt.Errorf("template %s not parsed", t))
       +        }
       +
       +        _, err := c.applyTransformations(tree.Root)
       +
       +        if err == nil && c.returnNode != nil {
       +                // This is a partial with a return statement.
       +                c.t.ParseInfo.HasReturn = true
       +                tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
       +        }
       +
       +        return c, err
       +}
       +
       +func getParseTree(templ tpl.Template) *parse.Tree {
       +        if text, ok := templ.(*texttemplate.Template); ok {
       +                return text.Tree
       +        }
       +        return templ.(*htmltemplate.Template).Tree
       +}
       +
       +const (
       +        // We parse this template and modify the nodes in order to assign
       +        // the return value of a partial to a contextWrapper via Set. We use
       +        // "range" over a one-element slice so we can shift dot to the
       +        // partial's argument, Arg, while allowing Arg to be falsy.
       +        partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ range (slice .Arg) }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
       +
       +        doDeferTempl = `{{ doDefer ("PLACEHOLDER1") ("PLACEHOLDER2") }}`
       +)
       +
       +var (
       +        partialReturnWrapper *parse.ListNode
       +        doDefer              *parse.ListNode
       +)
       +
       +func init() {
       +        templ, err := texttemplate.New("").Parse(partialReturnWrapperTempl)
       +        if err != nil {
       +                panic(err)
       +        }
       +        partialReturnWrapper = templ.Tree.Root
       +
       +        templ, err = texttemplate.New("").Funcs(texttemplate.FuncMap{"doDefer": func(string, string) string { return "" }}).Parse(doDeferTempl)
       +        if err != nil {
       +                panic(err)
       +        }
       +        doDefer = templ.Tree.Root
       +}
       +
       +// wrapInPartialReturnWrapper copies and modifies the parsed nodes of a
       +// predefined partial return wrapper to insert those of a user-defined partial.
       +func (c *templateTransformContext) wrapInPartialReturnWrapper(n *parse.ListNode) *parse.ListNode {
       +        wrapper := partialReturnWrapper.CopyList()
       +        rangeNode := wrapper.Nodes[2].(*parse.RangeNode)
       +        retn := rangeNode.List.Nodes[0]
       +        setCmd := retn.(*parse.ActionNode).Pipe.Cmds[0]
       +        setPipe := setCmd.Args[1].(*parse.PipeNode)
       +        // Replace PLACEHOLDER with the real return value.
       +        // Note that this is a PipeNode, so it will be wrapped in parens.
       +        setPipe.Cmds = []*parse.CommandNode{c.returnNode}
       +        rangeNode.List.Nodes = append(n.Nodes, retn)
       +
       +        return wrapper
       +}
       +
       +// applyTransformations do 2 things:
       +// 1) Parses partial return statement.
       +// 2) Tracks template (partial) dependencies and some other info.
       +func (c *templateTransformContext) applyTransformations(n parse.Node) (bool, error) {
       +        switch x := n.(type) {
       +        case *parse.ListNode:
       +                if x != nil {
       +                        c.applyTransformationsToNodes(x.Nodes...)
       +                }
       +        case *parse.ActionNode:
       +                c.applyTransformationsToNodes(x.Pipe)
       +        case *parse.IfNode:
       +                c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
       +        case *parse.WithNode:
       +                c.handleDefer(x)
       +                c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
       +        case *parse.RangeNode:
       +                c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
       +        case *parse.TemplateNode:
       +                subTempl := c.getIfNotVisited(x.Name)
       +                if subTempl != nil {
       +                        c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
       +                }
       +        case *parse.PipeNode:
       +                c.collectConfig(x)
       +                for i, cmd := range x.Cmds {
       +                        keep, _ := c.applyTransformations(cmd)
       +                        if !keep {
       +                                x.Cmds = slices.Delete(x.Cmds, i, i+1)
       +                        }
       +                }
       +
       +        case *parse.CommandNode:
       +                c.collectInner(x)
       +                keep := c.collectReturnNode(x)
       +
       +                for _, elem := range x.Args {
       +                        switch an := elem.(type) {
       +                        case *parse.PipeNode:
       +                                c.applyTransformations(an)
       +                        }
       +                }
       +                return keep, c.err
       +        }
       +
       +        return true, c.err
       +}
       +
       +func (c *templateTransformContext) handleDefer(withNode *parse.WithNode) {
       +        if len(withNode.Pipe.Cmds) != 1 {
       +                return
       +        }
       +        cmd := withNode.Pipe.Cmds[0]
       +        if len(cmd.Args) != 1 {
       +                return
       +        }
       +        idArg := cmd.Args[0]
       +
       +        p, ok := idArg.(*parse.PipeNode)
       +        if !ok {
       +                return
       +        }
       +
       +        if len(p.Cmds) != 1 {
       +                return
       +        }
       +
       +        cmd = p.Cmds[0]
       +
       +        if len(cmd.Args) != 2 {
       +                return
       +        }
       +
       +        idArg = cmd.Args[0]
       +
       +        id, ok := idArg.(*parse.ChainNode)
       +        if !ok || len(id.Field) != 1 || id.Field[0] != "Defer" {
       +                return
       +        }
       +        if id2, ok := id.Node.(*parse.IdentifierNode); !ok || id2.Ident != "templates" {
       +                return
       +        }
       +
       +        deferArg := cmd.Args[1]
       +        cmd.Args = []parse.Node{idArg}
       +
       +        l := doDefer.CopyList()
       +        n := l.Nodes[0].(*parse.ActionNode)
       +
       +        inner := withNode.List.CopyList()
       +        s := inner.String()
       +        if strings.Contains(s, "resources.PostProcess") {
       +                c.err = errors.New("resources.PostProcess cannot be used in a deferred template")
       +                return
       +        }
       +        innerHash := hashing.XxHashFromStringHexEncoded(s)
       +        deferredID := tpl.HugoDeferredTemplatePrefix + innerHash
       +
       +        c.deferNodes[deferredID] = inner
       +        withNode.List = l
       +
       +        n.Pipe.Cmds[0].Args[1].(*parse.PipeNode).Cmds[0].Args[0].(*parse.StringNode).Text = deferredID
       +        n.Pipe.Cmds[0].Args[2] = deferArg
       +}
       +
       +func (c *templateTransformContext) applyTransformationsToNodes(nodes ...parse.Node) {
       +        for _, node := range nodes {
       +                c.applyTransformations(node)
       +        }
       +}
       +
       +func (c *templateTransformContext) hasIdent(idents []string, ident string) bool {
       +        return slices.Contains(idents, ident)
       +}
       +
       +// collectConfig collects and parses any leading template config variable declaration.
       +// This will be the first PipeNode in the template, and will be a variable declaration
       +// on the form:
       +//
       +//        {{ $_hugo_config:= `{ "version": 1 }` }}
       +func (c *templateTransformContext) collectConfig(n *parse.PipeNode) {
       +        if c.t.Category != CategoryShortcode {
       +                return
       +        }
       +        if c.configChecked {
       +                return
       +        }
       +        c.configChecked = true
       +
       +        if len(n.Decl) != 1 || len(n.Cmds) != 1 {
       +                // This cannot be a config declaration
       +                return
       +        }
       +
       +        v := n.Decl[0]
       +
       +        if len(v.Ident) == 0 || v.Ident[0] != "$_hugo_config" {
       +                return
       +        }
       +
       +        cmd := n.Cmds[0]
       +
       +        if len(cmd.Args) == 0 {
       +                return
       +        }
       +
       +        if s, ok := cmd.Args[0].(*parse.StringNode); ok {
       +                errMsg := "failed to decode $_hugo_config in template: %w"
       +                m, err := maps.ToStringMapE(s.Text)
       +                if err != nil {
       +                        c.err = fmt.Errorf(errMsg, err)
       +                        return
       +                }
       +                if err := mapstructure.WeakDecode(m, &c.t.ParseInfo.Config); err != nil {
       +                        c.err = fmt.Errorf(errMsg, err)
       +                }
       +        }
       +}
       +
       +// collectInner determines if the given CommandNode represents a
       +// shortcode call to its .Inner.
       +func (c *templateTransformContext) collectInner(n *parse.CommandNode) {
       +        if c.t.Category != CategoryShortcode {
       +                return
       +        }
       +        if c.t.ParseInfo.IsInner || len(n.Args) == 0 {
       +                return
       +        }
       +
       +        for _, arg := range n.Args {
       +                var idents []string
       +                switch nt := arg.(type) {
       +                case *parse.FieldNode:
       +                        idents = nt.Ident
       +                case *parse.VariableNode:
       +                        idents = nt.Ident
       +                }
       +
       +                if c.hasIdent(idents, "Inner") || c.hasIdent(idents, "InnerDeindent") {
       +                        c.t.ParseInfo.IsInner = true
       +                        break
       +                }
       +        }
       +}
       +
       +func (c *templateTransformContext) collectReturnNode(n *parse.CommandNode) bool {
       +        if c.t.Category != CategoryPartial || c.returnNode != nil {
       +                return true
       +        }
       +
       +        if len(n.Args) < 2 {
       +                return true
       +        }
       +
       +        ident, ok := n.Args[0].(*parse.IdentifierNode)
       +        if !ok || ident.Ident != "return" {
       +                return true
       +        }
       +
       +        c.returnNode = n
       +        // Remove the "return" identifiers
       +        c.returnNode.Args = c.returnNode.Args[1:]
       +
       +        return false
       +}
 (DIR) diff --git a/tpl/tplimpl/tplimpl_integration_test.go b/tpl/tplimpl/tplimpl_integration_test.go
       @@ -1,66 +1,13 @@
        package tplimpl_test
        
        import (
       -        "path/filepath"
                "strings"
                "testing"
        
                qt "github.com/frankban/quicktest"
                "github.com/gohugoio/hugo/hugolib"
       -        "github.com/gohugoio/hugo/tpl"
        )
        
       -func TestPrintUnusedTemplates(t *testing.T) {
       -        t.Parallel()
       -
       -        files := `
       --- config.toml --
       -baseURL = 'http://example.com/'
       -printUnusedTemplates=true
       --- content/p1.md --
       ----
       -title: "P1"
       ----
       -{{< usedshortcode >}}
       --- layouts/baseof.html --
       -{{ block "main" . }}{{ end }}
       --- layouts/baseof.json --
       -{{ block "main" . }}{{ end }}
       --- layouts/index.html --
       -{{ define "main" }}FOO{{ end }}
       --- layouts/_default/single.json --
       --- layouts/_default/single.html --
       -{{ define "main" }}MAIN{{ end }}
       --- layouts/post/single.html --
       -{{ define "main" }}MAIN{{ end }}
       --- layouts/partials/usedpartial.html --
       --- layouts/partials/unusedpartial.html --
       --- layouts/shortcodes/usedshortcode.html --
       -{{ partial "usedpartial.html" }}
       --- layouts/shortcodes/unusedshortcode.html --
       -
       -        `
       -
       -        b := hugolib.NewIntegrationTestBuilder(
       -                hugolib.IntegrationTestConfig{
       -                        T:           t,
       -                        TxtarString: files,
       -                        NeedsOsFS:   true,
       -                },
       -        )
       -        b.Build()
       -
       -        unused := b.H.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
       -
       -        var names []string
       -        for _, tmpl := range unused {
       -                names = append(names, tmpl.Name())
       -        }
       -
       -        b.Assert(names, qt.DeepEquals, []string{"_default/single.json", "baseof.json", "partials/unusedpartial.html", "post/single.html", "shortcodes/unusedshortcode.html"})
       -        b.Assert(unused[0].Filename(), qt.Equals, filepath.Join(b.Cfg.WorkingDir, "layouts/_default/single.json"))
       -}
       -
        // Verify that the new keywords in Go 1.18 is available.
        func TestGo18Constructs(t *testing.T) {
                t.Parallel()
       @@ -627,9 +574,9 @@ Home!
        
                b := hugolib.TestRunning(t, files)
                b.AssertFileContent("public/index.html", "Home!")
       -        b.EditFileReplaceAll("layouts/_default/baseof.html", "Baseof", "Baseof!").Build()
       +        b.EditFileReplaceAll("layouts/_default/baseof.html", "baseof", "Baseof!").Build()
                b.BuildPartial("/")
       -        b.AssertFileContent("public/index.html", "Baseof!!")
       +        b.AssertFileContent("public/index.html", "Baseof!")
                b.BuildPartial("/mybundle1/")
       -        b.AssertFileContent("public/mybundle1/index.html", "Baseof!!")
       +        b.AssertFileContent("public/mybundle1/index.html", "Baseof!")
        }
 (DIR) diff --git a/tpl/tplimplinit/tplimplinit.go b/tpl/tplimplinit/tplimplinit.go
       @@ -0,0 +1,96 @@
       +// Copyright 2025 The Hugo Authors. All rights reserved.
       +//
       +// Portions Copyright The Go Authors.
       +
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package tplimplinit
       +
       +import (
       +        // Init the template funcs namespaces
       +        "context"
       +        "html/template"
       +
       +        "github.com/gohugoio/hugo/deps"
       +        _ "github.com/gohugoio/hugo/tpl/cast"
       +        _ "github.com/gohugoio/hugo/tpl/collections"
       +        _ "github.com/gohugoio/hugo/tpl/compare"
       +        _ "github.com/gohugoio/hugo/tpl/crypto"
       +        _ "github.com/gohugoio/hugo/tpl/css"
       +        _ "github.com/gohugoio/hugo/tpl/data"
       +        _ "github.com/gohugoio/hugo/tpl/debug"
       +        _ "github.com/gohugoio/hugo/tpl/diagrams"
       +        _ "github.com/gohugoio/hugo/tpl/encoding"
       +        _ "github.com/gohugoio/hugo/tpl/fmt"
       +        _ "github.com/gohugoio/hugo/tpl/hash"
       +        _ "github.com/gohugoio/hugo/tpl/hugo"
       +        _ "github.com/gohugoio/hugo/tpl/images"
       +        _ "github.com/gohugoio/hugo/tpl/inflect"
       +        "github.com/gohugoio/hugo/tpl/internal"
       +        _ "github.com/gohugoio/hugo/tpl/js"
       +        _ "github.com/gohugoio/hugo/tpl/lang"
       +        _ "github.com/gohugoio/hugo/tpl/math"
       +        _ "github.com/gohugoio/hugo/tpl/openapi/openapi3"
       +        _ "github.com/gohugoio/hugo/tpl/os"
       +        _ "github.com/gohugoio/hugo/tpl/page"
       +        _ "github.com/gohugoio/hugo/tpl/partials"
       +        _ "github.com/gohugoio/hugo/tpl/path"
       +        _ "github.com/gohugoio/hugo/tpl/reflect"
       +        _ "github.com/gohugoio/hugo/tpl/resources"
       +        _ "github.com/gohugoio/hugo/tpl/safe"
       +        _ "github.com/gohugoio/hugo/tpl/site"
       +        _ "github.com/gohugoio/hugo/tpl/strings"
       +        _ "github.com/gohugoio/hugo/tpl/templates"
       +        _ "github.com/gohugoio/hugo/tpl/time"
       +        _ "github.com/gohugoio/hugo/tpl/transform"
       +        _ "github.com/gohugoio/hugo/tpl/urls"
       +)
       +
       +// CreateFuncMap creates a template.FuncMap with all of Hugo's template funcs,
       +// excluding the Go built-ins.
       +func CreateFuncMap(d *deps.Deps) map[string]any {
       +        funcMap := template.FuncMap{}
       +        nsMap := make(map[string]any)
       +        var onCreated []func(namespaces map[string]any)
       +
       +        // Merge the namespace funcs
       +        for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
       +                ns := nsf(d)
       +                if _, exists := funcMap[ns.Name]; exists {
       +                        panic(ns.Name + " is a duplicate template func")
       +                }
       +                funcMap[ns.Name] = ns.Context
       +                contextV, err := ns.Context(context.Background())
       +                if err != nil {
       +                        panic(err)
       +                }
       +                nsMap[ns.Name] = contextV
       +                for _, mm := range ns.MethodMappings {
       +                        for _, alias := range mm.Aliases {
       +                                if _, exists := funcMap[alias]; exists {
       +                                        panic(alias + " is a duplicate template func")
       +                                }
       +                                funcMap[alias] = mm.Method
       +                        }
       +                }
       +
       +                if ns.OnCreated != nil {
       +                        onCreated = append(onCreated, ns.OnCreated)
       +                }
       +        }
       +
       +        for _, f := range onCreated {
       +                f(nsMap)
       +        }
       +
       +        return funcMap
       +}