mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-26 13:40:38 +03:00
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:
parent
af0602c343
commit
d4c6dd16b1
13 changed files with 322 additions and 123 deletions
|
@ -139,9 +139,13 @@ func (i HugoInfo) IsMultilingual() bool {
|
|||
return i.conf.IsMultilingual()
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
type contextKey uint8
|
||||
|
||||
var markupScope = hcontext.NewContextDispatcher[string](contextKey("markupScope"))
|
||||
const (
|
||||
contextKeyMarkupScope contextKey = iota
|
||||
)
|
||||
|
||||
var markupScope = hcontext.NewContextDispatcher[string](contextKeyMarkupScope)
|
||||
|
||||
type Context struct{}
|
||||
|
||||
|
|
|
@ -667,7 +667,13 @@ func (c *cachedContentScope) mustContentToC(ctx context.Context) contentTableOfC
|
|||
return ct
|
||||
}
|
||||
|
||||
var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageContentOutput, contentTableOfContents)]("contentCallback")
|
||||
type contextKey uint8
|
||||
|
||||
const (
|
||||
contextKeyContentCallback contextKey = iota
|
||||
)
|
||||
|
||||
var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageContentOutput, contentTableOfContents)](contextKeyContentCallback)
|
||||
|
||||
func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfContents, error) {
|
||||
cp := c.pco
|
||||
|
|
|
@ -120,7 +120,7 @@ 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.GetTemplateStore(), templ, pco.po.p)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Template.Name(), err))
|
||||
return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Name(), err))
|
||||
}
|
||||
return template.HTML(res), nil
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ func (pco *pageContentOutput) initRenderHooks() error {
|
|||
return false
|
||||
}
|
||||
|
||||
if ignoreInternal && candidate.SubCategory == tplimpl.SubCategoryEmbedded {
|
||||
if ignoreInternal && candidate.SubCategory() == tplimpl.SubCategoryEmbedded {
|
||||
// Don't consider the internal hook templates.
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -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.Template.Name(), targetPath)
|
||||
return fmt.Sprintf("rendering outputFormat %q kind %q using layout %q to %q", p.pageOutput.f.Name, p.Kind(), templ.Name(), targetPath)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -55,12 +55,16 @@ type Client struct {
|
|||
remoteResourceLogger logg.LevelLogger
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
type contextKey uint8
|
||||
|
||||
const (
|
||||
contextKeyResourceID contextKey = iota
|
||||
)
|
||||
|
||||
// New creates a new Client with the given specification.
|
||||
func New(rs *resources.Spec) *Client {
|
||||
fileCache := rs.FileCaches.GetResourceCache()
|
||||
resourceIDDispatcher := hcontext.NewContextDispatcher[string](contextKey("resourceID"))
|
||||
resourceIDDispatcher := hcontext.NewContextDispatcher[string](contextKeyResourceID)
|
||||
httpCacheConfig := rs.Cfg.GetConfigSection("httpCacheCompiled").(hhttpcache.ConfigCompiled)
|
||||
var remoteResourceChecker *tasks.RunEvery
|
||||
if rs.Cfg.Watching() && !httpCacheConfig.IsPollingDisabled() {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue