tpl: Add templates.Current

This commit also

* Unexport all internal state in TemplateInfo.
* Make the dispatcher keys used for passing context.Context into uint8 from string to save memory allocations.

Co-authored-by: Joe Mooring <joe@mooring.com>

Updates #13571
This commit is contained in:
Bjørn Erik Pedersen 2025-04-08 18:41:06 +02:00
parent af0602c343
commit d4c6dd16b1
13 changed files with 322 additions and 123 deletions

View file

@ -157,8 +157,7 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any)
if len(dataList) > 0 {
data = dataList[0]
}
name, desc := ns.deps.TemplateStore.TemplateDescriptorFromPath(name)
v := ns.deps.TemplateStore.LookupPartial(name, desc)
v := ns.deps.TemplateStore.LookupPartial(name)
if v == nil {
return includeResult{err: fmt.Errorf("partial %q not found", name)}
}
@ -199,7 +198,7 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any)
}
return includeResult{
name: templ.Template.Name(),
name: templ.Name(),
result: result,
}
}

View file

@ -16,6 +16,7 @@ package tpl
import (
"context"
"slices"
"strings"
"sync"
"unicode"
@ -41,7 +42,17 @@ type RenderingContext struct {
SiteOutIdx int
}
type contextKey string
type (
contextKey uint8
)
const (
contextKeyDependencyManagerScopedProvider contextKey = iota
contextKeyDependencyScope
contextKeyPage
contextKeyIsInGoldmark
cntextKeyCurrentTemplateInfo
)
// Context manages values passed in the context to templates.
var Context = struct {
@ -50,11 +61,13 @@ var Context = struct {
DependencyScope hcontext.ContextDispatcher[int]
Page hcontext.ContextDispatcher[page]
IsInGoldmark hcontext.ContextDispatcher[bool]
CurrentTemplate hcontext.ContextDispatcher[*CurrentTemplateInfo]
}{
DependencyManagerScopedProvider: hcontext.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKey("DependencyManagerScopedProvider")),
DependencyScope: hcontext.NewContextDispatcher[int](contextKey("DependencyScope")),
Page: hcontext.NewContextDispatcher[page](contextKey("Page")),
IsInGoldmark: hcontext.NewContextDispatcher[bool](contextKey("IsInGoldmark")),
DependencyManagerScopedProvider: hcontext.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKeyDependencyManagerScopedProvider),
DependencyScope: hcontext.NewContextDispatcher[int](contextKeyDependencyScope),
Page: hcontext.NewContextDispatcher[page](contextKeyPage),
IsInGoldmark: hcontext.NewContextDispatcher[bool](contextKeyIsInGoldmark),
CurrentTemplate: hcontext.NewContextDispatcher[*CurrentTemplateInfo](cntextKeyCurrentTemplateInfo),
}
func init() {
@ -130,3 +143,46 @@ type DeferredExecution struct {
Executed bool
Result string
}
type CurrentTemplateInfoOps interface {
CurrentTemplateInfoCommonOps
Base() CurrentTemplateInfoCommonOps
}
type CurrentTemplateInfoCommonOps interface {
// Template name.
Name() string
// Template source filename.
// Will be empty for internal templates.
Filename() string
}
// CurrentTemplateInfo as returned in templates.Current.
type CurrentTemplateInfo struct {
Parent *CurrentTemplateInfo
CurrentTemplateInfoOps
}
// CurrentTemplateInfos is a slice of CurrentTemplateInfo.
type CurrentTemplateInfos []*CurrentTemplateInfo
// Reverse creates a copy of the slice and reverses it.
func (c CurrentTemplateInfos) Reverse() CurrentTemplateInfos {
if len(c) == 0 {
return c
}
r := make(CurrentTemplateInfos, len(c))
copy(r, c)
slices.Reverse(r)
return r
}
// Ancestors returns the ancestors of the current template.
func (ti *CurrentTemplateInfo) Ancestors() CurrentTemplateInfos {
var ancestors []*CurrentTemplateInfo
for ti.Parent != nil {
ti = ti.Parent
ancestors = append(ancestors, ti)
}
return ancestors
}

View file

@ -102,3 +102,8 @@ func (ns *Namespace) DoDefer(ctx context.Context, id string, optsv any) string {
return id
}
// Get information about the currently executing template.
func (ns *Namespace) Current(ctx context.Context) *tpl.CurrentTemplateInfo {
return tpl.Context.CurrentTemplate.Get(ctx)
}

View file

@ -14,6 +14,7 @@
package templates_test
import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/hugolib"
@ -127,3 +128,41 @@ Try printf: {{ (try (printf "hello %s" "world")).Value }}
"Try printf: hello world",
)
}
func TestTemplatesCurrent(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- layouts/baseof.html --
baseof: {{ block "main" . }}{{ end }}
-- layouts/all.html --
{{ define "main" }}
all.current: {{ templates.Current.Name }}
all.current.filename: {{ templates.Current.Filename }}
all.base: {{ with templates.Current.Base }}{{ .Name }}{{ end }}|
all.parent: {{ with .Parent }}Name: {{ .Name }}{{ end }}|
{{ partial "p1.html" . }}
{{ end }}
-- layouts/_partials/p1.html --
p1.current: {{ with templates.Current }}Name: {{ .Name }}|{{ with .Parent }}Parent.Name: {{ .Name }}{{ end }}{{ end }}|
p1.current.Ancestors: {{ with templates.Current }}{{ range .Ancestors }}{{ .Name }}|{{ end }}{{ end }}
{{ partial "p2.html" . }}
-- layouts/_partials/p2.html --
p2.current: {{ with templates.Current }}Name: {{ .Name }}|{{ with .Parent }}Parent.Name: {{ .Name }}{{ end }}{{ end }}|
p2.current.Ancestors: {{ with templates.Current }}{{ range .Ancestors }}{{ .Name }}|{{ end }}{{ end }}
p3.current.Ancestors.Reverse: {{ with templates.Current }}{{ range .Ancestors.Reverse }}{{ .Name }}|{{ end }}{{ end }}
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html",
"all.current: all.html",
filepath.FromSlash("all.current.filename: /layouts/all.html"),
"all.base: baseof.html",
"all.parent: |",
"p1.current: Name: _partials/p1.html|Parent.Name: all.html|",
"p1.current.Ancestors: all.html|",
"p2.current.Ancestors: _partials/p1.html|all.html",
)
}

View file

@ -25,9 +25,9 @@ func (t *templateNamespace) readTemplateInto(templ *TemplInfo) error {
if err != nil {
return err
}
templ.Content = removeLeadingBOM(string(b))
if !templ.NoBaseOf {
templ.NoBaseOf = !needsBaseTemplate(templ.Content)
templ.content = removeLeadingBOM(string(b))
if !templ.noBaseOf {
templ.noBaseOf = !needsBaseTemplate(templ.content)
}
return nil
}(); err != nil {
@ -43,7 +43,7 @@ var embeddedTemplatesAliases = map[string][]string{
}
func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
if !ti.NoBaseOf || ti.Category == CategoryBaseof {
if !ti.noBaseOf || ti.category == CategoryBaseof {
// Delay parsing until we have the base template.
return nil
}
@ -62,18 +62,18 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
if ti.D.IsPlainText {
prototype := t.parseText
templ, err = prototype.New(name).Parse(ti.Content)
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)
templ, err = prototype.New(name).Parse(ti.content)
if err != nil {
return err
}
if ti.SubCategory == SubCategoryEmbedded {
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.
@ -111,17 +111,17 @@ func (t *templateNamespace) applyBaseTemplate(overlay *TemplInfo, base keyTempla
Base: base.Info,
}
base.Info.Overlays = append(base.Info.Overlays, overlay)
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)
tt, err = tt.Parse(base.Info.content)
if err != nil {
return err
}
tt, err = tt.Parse(overlay.Content)
tt, err = tt.Parse(overlay.content)
if err != nil {
return err
}
@ -130,11 +130,11 @@ func (t *templateNamespace) applyBaseTemplate(overlay *TemplInfo, base keyTempla
} else {
tt := htmltemplate.Must(t.parseHTML.CloneShallow()).New(overlay.PathInfo.PathNoLeadingSlash())
var err error
tt, err = tt.Parse(base.Info.Content)
tt, err = tt.Parse(base.Info.content)
if err != nil {
return err
}
tt, err = tt.Parse(overlay.Content)
tt, err = tt.Parse(overlay.content)
if err != nil {
return err
}
@ -146,17 +146,17 @@ func (t *templateNamespace) applyBaseTemplate(overlay *TemplInfo, base keyTempla
tb.Template = &TemplInfo{
Template: templ,
Base: base.Info,
base: base.Info,
PathInfo: overlay.PathInfo,
Fi: overlay.Fi,
D: overlay.D,
NoBaseOf: true,
noBaseOf: true,
}
variants := overlay.BaseVariants.Get(base.Key)
variants := overlay.baseVariants.Get(base.Key)
if variants == nil {
variants = make(map[TemplateDescriptor]*TemplWithBaseApplied)
overlay.BaseVariants.Insert(base.Key, variants)
overlay.baseVariants.Insert(base.Key, variants)
}
variants[base.Info.D] = tb
return nil

View file

@ -97,15 +97,16 @@ func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) {
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](),
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](),
templateDescriptorByPath: maps.NewCache[string, PathTemplateDescriptor](),
// Note that the funcs passed below is just for name validation.
tns: newTemplateNamespace(siteOpts.TemplateFuncs),
@ -191,9 +192,9 @@ type SubCategory int
type TemplInfo struct {
// The category of this template.
Category Category
category Category
SubCategory SubCategory
subCategory SubCategory
// PathInfo info.
PathInfo *paths.Path
@ -202,7 +203,7 @@ type TemplInfo struct {
Fi hugofs.FileMetaInfo
// The template content with any leading BOM removed.
Content string
content string
// The parsed template.
// Note that any baseof template will be applied later.
@ -210,16 +211,16 @@ type TemplInfo struct {
// If no baseof is needed, this will be set to true.
// E.g. shortcode templates do not need a baseof.
NoBaseOf bool
noBaseOf bool
// If NoBaseOf is false, we will look for the final template in this tree.
BaseVariants *doctree.SimpleTree[map[TemplateDescriptor]*TemplWithBaseApplied]
baseVariants *doctree.SimpleTree[map[TemplateDescriptor]*TemplWithBaseApplied]
// The template variants that are based on this template.
Overlays []*TemplInfo
overlays []*TemplInfo
// The base template used, if any.
Base *TemplInfo
base *TemplInfo
// The descriptior that this template represents.
D TemplateDescriptor
@ -228,16 +229,20 @@ type TemplInfo struct {
ParseInfo ParseInfo
// The execution counter for this template.
ExecutionCounter atomic.Uint64
executionCounter atomic.Uint64
// processing state.
state processingState
isLegacyMapped bool
}
func (ti *TemplInfo) SubCategory() SubCategory {
return ti.subCategory
}
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) {
ti.baseVariants.Walk(func(key string, v map[TemplateDescriptor]*TemplWithBaseApplied) (bool, error) {
for _, vv := range v {
if !yield(vv) {
return true, nil
@ -260,6 +265,11 @@ func (t *TemplInfo) GetIdentity() identity.Identity {
}
func (ti *TemplInfo) Name() string {
if ti.Template == nil {
if ti.PathInfo != nil {
return ti.PathInfo.PathNoLeadingSlash()
}
}
return ti.Template.Name()
}
@ -272,7 +282,7 @@ func (t *TemplInfo) IsProbablyDependency(other identity.Identity) bool {
}
func (t *TemplInfo) IsProbablyDependent(other identity.Identity) bool {
for _, overlay := range t.Overlays {
for _, overlay := range t.overlays {
if overlay.isProbablyTheSameIDAs(other) {
return true
}
@ -288,11 +298,11 @@ func (ti *TemplInfo) String() string {
}
func (ti *TemplInfo) findBestMatchBaseof(s *TemplateStore, k1 string, slashCountK1 int, best *bestMatch) {
if ti.BaseVariants == nil {
if ti.baseVariants == nil {
return
}
ti.BaseVariants.WalkPath(k1, func(k2 string, v map[TemplateDescriptor]*TemplWithBaseApplied) (bool, error) {
ti.baseVariants.WalkPath(k1, func(k2 string, v map[TemplateDescriptor]*TemplWithBaseApplied) (bool, error) {
slashCountK2 := strings.Count(k2, "/")
distance := slashCountK1 - slashCountK2
@ -319,6 +329,18 @@ func (t *TemplInfo) isProbablyTheSameIDAs(other identity.Identity) bool {
return false
}
// Implements the additional methods in tpl.CurrentTemplateInfoOps.
func (ti *TemplInfo) Base() tpl.CurrentTemplateInfoCommonOps {
return ti.base
}
func (ti *TemplInfo) Filename() string {
if ti.Fi == nil {
return ""
}
return ti.Fi.Meta().Filename
}
type TemplWithBaseApplied struct {
// The template that's overlaid on top of the base template.
Overlay *TemplInfo
@ -378,9 +400,10 @@ type TemplateStore struct {
siteOpts SiteOptions
htmlFormat output.Format
treeMain *doctree.SimpleTree[map[nodeKey]*TemplInfo]
treeShortcodes *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
templatesByPath *maps.Cache[string, *TemplInfo]
treeMain *doctree.SimpleTree[map[nodeKey]*TemplInfo]
treeShortcodes *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
templatesByPath *maps.Cache[string, *TemplInfo]
templateDescriptorByPath *maps.Cache[string, PathTemplateDescriptor]
dh descriptorHandler
@ -414,7 +437,7 @@ func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc Te
descBaseof := desc
s.treeMain.Walk(func(k string, v map[nodeKey]*TemplInfo) (bool, error) {
for _, vv := range v {
if vv.Category != CategoryBaseof {
if vv.category != CategoryBaseof {
continue
}
@ -430,14 +453,21 @@ func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc Te
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)
ti.executionCounter.Add(1)
if ti.base != nil {
ti.base.executionCounter.Add(1)
}
}()
templ := ti.Template
currentTi := &tpl.CurrentTemplateInfo{
Parent: tpl.Context.CurrentTemplate.Get(ctx),
CurrentTemplateInfoOps: ti,
}
ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)
if t.opts.Metrics != nil {
defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
}
@ -498,7 +528,7 @@ func (s *TemplateStore) LookupPagesLayout(q TemplateQuery) *TemplInfo {
return nil
}
m := best1.templ
if m.NoBaseOf {
if m.noBaseOf {
return m
}
best1.reset()
@ -509,13 +539,15 @@ func (s *TemplateStore) LookupPagesLayout(q TemplateQuery) *TemplInfo {
return best1.templ
}
func (s *TemplateStore) LookupPartial(pth string, desc TemplateDescriptor) *TemplInfo {
func (s *TemplateStore) LookupPartial(pth string) *TemplInfo {
d := s.templateDescriptorFromPath(pth)
desc := d.Desc
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)
s.findBestMatchGet(s.key(path.Join(containerPartials, d.Path)), CategoryPartial, nil, desc, best)
return best.templ
}
@ -564,10 +596,10 @@ func (s *TemplateStore) PrintDebug(prefix string, category Category, w io.Writer
printOne := func(key string, vv *TemplInfo) {
level := strings.Count(key, "/")
if category != vv.Category {
if category != vv.category {
return
}
s := strings.ReplaceAll(strings.TrimSpace(vv.Content), "\n", " ")
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)
}
@ -642,17 +674,17 @@ func (t *TemplateStore) UnusedTemplates() []*TemplInfo {
var unused []*TemplInfo
for vv := range t.templates() {
if vv.SubCategory != SubCategoryMain {
if vv.subCategory != SubCategoryMain {
// Skip inline partials and internal templates.
continue
}
if vv.NoBaseOf {
if vv.ExecutionCounter.Load() == 0 {
if vv.noBaseOf {
if vv.executionCounter.Load() == 0 {
unused = append(unused, vv)
}
} else {
for vvv := range vv.BaseVariantsSeq() {
if vvv.Template.ExecutionCounter.Load() == 0 {
if vvv.Template.executionCounter.Load() == 0 {
unused = append(unused, vvv.Template)
}
}
@ -681,7 +713,7 @@ func (s *TemplateStore) findBestMatchGet(key string, category Category, consider
}
for k, vv := range v {
if vv.Category != category {
if vv.category != category {
continue
}
@ -702,7 +734,7 @@ func (s *TemplateStore) findBestMatchWalkPath(q TemplateQuery, k1 string, slashC
distance := slashCountK1 - slashCountK2
for k, vv := range v {
if vv.Category != q.Category {
if vv.category != q.Category {
continue
}
@ -803,8 +835,8 @@ func (s *TemplateStore) addFileContext(ti *TemplInfo, inerr error) error {
return err
}
if ti.Base != nil {
if err, ok := checkFilename(ti.Base.Fi, inerr); ok {
if ti.base != nil {
if err, ok := checkFilename(ti.base.Fi, inerr); ok {
return err
}
}
@ -850,8 +882,8 @@ func (s *TemplateStore) extractInlinePartials() error {
if ti != nil {
ti.Template = templ
ti.NoBaseOf = true
ti.SubCategory = SubCategoryInline
ti.noBaseOf = true
ti.subCategory = SubCategoryInline
ti.D.IsPlainText = isText
}
@ -913,9 +945,9 @@ func (s *TemplateStore) insertEmbedded() error {
if ti != nil {
// Currently none of the embedded templates need a baseof template.
ti.NoBaseOf = true
ti.Content = content
ti.SubCategory = SubCategoryEmbedded
ti.noBaseOf = true
ti.content = content
ti.subCategory = SubCategoryEmbedded
}
return nil
@ -965,8 +997,8 @@ func (s *TemplateStore) insertShortcode(pi *paths.Path, fi hugofs.FileMetaInfo,
PathInfo: pi,
Fi: fi,
D: d,
Category: CategoryShortcode,
NoBaseOf: true,
category: CategoryShortcode,
noBaseOf: true,
}
m1[d] = ti
@ -1022,8 +1054,8 @@ func (s *TemplateStore) insertTemplate2(
PathInfo: pi,
Fi: fi,
D: d,
Category: category,
NoBaseOf: category > CategoryLayout,
category: category,
noBaseOf: category > CategoryLayout,
isLegacyMapped: isLegacyMapped,
}
@ -1231,7 +1263,7 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
s.tns.baseofTextClones = nil
s.treeMain.Walk(func(key string, v map[nodeKey]*TemplInfo) (bool, error) {
for _, vv := range v {
if !vv.NoBaseOf {
if !vv.noBaseOf {
vv.state = processingStateInitial
}
}
@ -1270,20 +1302,20 @@ func (s *TemplateStore) parseTemplates() error {
if vv.state == processingStateTransformed {
continue
}
if !vv.NoBaseOf {
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
vv.noBaseOf = true
if err := s.tns.parseTemplate(vv); err != nil {
return err
}
continue
}
vv.BaseVariants = doctree.NewSimpleTree[map[TemplateDescriptor]*TemplWithBaseApplied]()
vv.baseVariants = doctree.NewSimpleTree[map[TemplateDescriptor]*TemplWithBaseApplied]()
for _, base := range baseTemplates {
if err := s.tns.applyBaseTemplate(vv, base); err != nil {
@ -1320,7 +1352,7 @@ func (s *TemplateStore) parseTemplates() error {
// prepareTemplates prepares all templates for execution.
func (s *TemplateStore) prepareTemplates() error {
for t := range s.templates() {
if t.Category == CategoryBaseof {
if t.category == CategoryBaseof {
continue
}
if _, err := t.Prepare(); err != nil {
@ -1330,38 +1362,51 @@ func (s *TemplateStore) prepareTemplates() error {
return nil
}
// TemplateDescriptorFromPath returns a template descriptor from the given path.
type PathTemplateDescriptor struct {
Path string
Desc TemplateDescriptor
}
// 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
)
func (s *TemplateStore) templateDescriptorFromPath(pth string) PathTemplateDescriptor {
// Check cache first.
d, _ := s.templateDescriptorByPath.GetOrCreate(pth, func() (PathTemplateDescriptor, error) {
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", "")
// 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 {
pth = strings.TrimPrefix(pth, "/")
ext := path.Ext(pth)
pth = strings.TrimSuffix(pth, ext)
ext = ext[1:]
of, mt = s.resolveOutputFormatAndOrMediaType("", ext)
path := s.opts.PathParser.Parse(files.ComponentFolderLayouts, pth)
pth = path.PathNoIdentifier()
of, mt = s.resolveOutputFormatAndOrMediaType(path.OutputFormat(), path.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,
}
return PathTemplateDescriptor{
Path: pth,
Desc: TemplateDescriptor{
OutputFormat: of.Name,
MediaType: mt.Type,
IsPlainText: of.IsPlainText,
},
}, nil
})
return d
}
// resolveOutputFormatAndOrMediaType resolves the output format and/or media type
@ -1404,7 +1449,7 @@ 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 {
if !vv.noBaseOf {
for vvv := range vv.BaseVariantsSeq() {
if !yield(vvv.Template) {
return
@ -1562,10 +1607,10 @@ func (s *TemplateStore) transformTemplates() error {
continue
}
vv.state = processingStateTransformed
if vv.Category == CategoryBaseof {
if vv.category == CategoryBaseof {
continue
}
if !vv.NoBaseOf {
if !vv.noBaseOf {
for vvv := range vv.BaseVariantsSeq() {
tctx, err := applyTemplateTransformers(vvv.Template, lookup)
if err != nil {
@ -1709,13 +1754,13 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
}
if best.w.w1 > 0 {
currentBestIsEmbedded := best.templ.SubCategory == SubCategoryEmbedded
currentBestIsEmbedded := best.templ.subCategory == SubCategoryEmbedded
if currentBestIsEmbedded {
if ti.SubCategory != SubCategoryEmbedded {
if ti.subCategory != SubCategoryEmbedded {
return true
}
} else {
if ti.SubCategory == SubCategoryEmbedded {
if ti.subCategory == SubCategoryEmbedded {
// Prefer user provided template.
return false
}

View file

@ -1,6 +1,8 @@
package tplimpl_test
import (
"context"
"io"
"testing"
qt "github.com/frankban/quicktest"
@ -840,3 +842,42 @@ All.
b.AssertLogContains("Duplicate content path")
}
func BenchmarkExecuteWithContext(b *testing.B) {
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "home"]
-- layouts/all.html --
{{ .Title }}|
{{ partial "p1.html" . }}
-- layouts/_partials/p1.html --
p1.
{{ partial "p2.html" . }}
{{ partial "p2.html" . }}
{{ partial "p3.html" . }}
{{ partial "p2.html" . }}
{{ partial "p2.html" . }}
{{ partial "p2.html" . }}
{{ partial "p3.html" . }}
-- layouts/_partials/p2.html --
{{ partial "p3.html" . }}
-- layouts/_partials/p3.html --
p3
-- content/p1.md --
`
bb := hugolib.Test(b, files)
store := bb.H.TemplateStore
ti := store.LookupByPath("/all.html")
bb.Assert(ti, qt.Not(qt.IsNil))
p := bb.H.Sites[0].RegularPages()[0]
bb.Assert(p, qt.Not(qt.IsNil))
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := store.ExecuteWithContext(context.Background(), ti, io.Discard, p)
bb.Assert(err, qt.IsNil)
}
}

View file

@ -263,7 +263,7 @@ func (c *templateTransformContext) hasIdent(idents []string, ident string) bool
//
// {{ $_hugo_config:= `{ "version": 1 }` }}
func (c *templateTransformContext) collectConfig(n *parse.PipeNode) {
if c.t.Category != CategoryShortcode {
if c.t.category != CategoryShortcode {
return
}
if c.configChecked {
@ -304,7 +304,7 @@ func (c *templateTransformContext) collectConfig(n *parse.PipeNode) {
// 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 {
if c.t.category != CategoryShortcode {
return
}
if c.t.ParseInfo.IsInner || len(n.Args) == 0 {
@ -328,7 +328,7 @@ func (c *templateTransformContext) collectInner(n *parse.CommandNode) {
}
func (c *templateTransformContext) collectReturnNode(n *parse.CommandNode) bool {
if c.t.Category != CategoryPartial || c.returnNode != nil {
if c.t.category != CategoryPartial || c.returnNode != nil {
return true
}