mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-25 22:00:58 +02:00
@@ -42,6 +42,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -194,11 +195,12 @@ func newTemplateNamespace(funcs map[string]any) *templateNamespace {
|
||||
}
|
||||
}
|
||||
|
||||
func newTemplateState(templ tpl.Template, info templateInfo, id identity.Identity) *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,
|
||||
@@ -260,7 +262,11 @@ func (t *templateExec) ExecuteWithContext(ctx context.Context, templ tpl.Templat
|
||||
|
||||
execErr := t.executor.ExecuteWithContext(ctx, templ, wr, data)
|
||||
if execErr != nil {
|
||||
execErr = t.addFileContext(templ, execErr)
|
||||
owner := templ
|
||||
if ts, ok := templ.(*templateState); ok && ts.owner != nil {
|
||||
owner = ts.owner
|
||||
}
|
||||
execErr = t.addFileContext(owner, execErr)
|
||||
}
|
||||
return execErr
|
||||
}
|
||||
@@ -312,6 +318,9 @@ func (t *templateExec) MarkReady() error {
|
||||
// We only need the clones if base templates are in use.
|
||||
if len(t.needsBaseof) > 0 {
|
||||
err = t.main.createPrototypes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -369,7 +378,7 @@ type layoutCacheEntry struct {
|
||||
func (t *templateHandler) AddTemplate(name, tpl string) error {
|
||||
templ, err := t.addTemplateTo(t.newTemplateInfo(name, tpl), t.main)
|
||||
if err == nil {
|
||||
t.applyTemplateTransformers(t.main, templ)
|
||||
_, err = t.applyTemplateTransformers(t.main, templ)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -390,6 +399,7 @@ func (t *templateHandler) LookupLayout(d layouts.LayoutDescriptor, f output.Form
|
||||
t.layoutTemplateCacheMu.RUnlock()
|
||||
return cacheVal.templ, cacheVal.found, cacheVal.err
|
||||
}
|
||||
|
||||
t.layoutTemplateCacheMu.RUnlock()
|
||||
|
||||
t.layoutTemplateCacheMu.Lock()
|
||||
@@ -497,13 +507,15 @@ func (t *templateHandler) findLayout(d layouts.LayoutDescriptor, f output.Format
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
ts := newTemplateState(templ, overlay, identity.Or(base, overlay))
|
||||
ts := newTemplateState(nil, templ, overlay, identity.Or(base, overlay))
|
||||
|
||||
if found {
|
||||
ts.baseInfo = base
|
||||
}
|
||||
|
||||
t.applyTemplateTransformers(t.main, ts)
|
||||
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
|
||||
@@ -674,7 +686,10 @@ func (t *templateHandler) addTemplateFile(name string, fim hugofs.FileMetaInfo)
|
||||
if err != nil {
|
||||
return tinfo.errWithFileContext("parse failed", err)
|
||||
}
|
||||
t.applyTemplateTransformers(t.main, templ)
|
||||
|
||||
if _, err = t.applyTemplateTransformers(t.main, templ); err != nil {
|
||||
return tinfo.errWithFileContext("transform failed", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -745,6 +760,12 @@ func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *t
|
||||
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
|
||||
}
|
||||
|
||||
@@ -858,7 +879,7 @@ func (t *templateHandler) extractPartials(templ tpl.Template) error {
|
||||
continue
|
||||
}
|
||||
|
||||
ts := newTemplateState(templ, templateInfo{name: templ.Name()}, nil)
|
||||
ts := newTemplateState(nil, templ, templateInfo{name: templ.Name()}, nil)
|
||||
ts.typ = templatePartial
|
||||
|
||||
t.main.mu.RLock()
|
||||
@@ -954,18 +975,18 @@ type templateNamespace struct {
|
||||
*templateStateMap
|
||||
}
|
||||
|
||||
func (t templateNamespace) Clone() *templateNamespace {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.templateStateMap = &templateStateMap{
|
||||
templates: make(map[string]*templateState),
|
||||
func (t *templateNamespace) getPrototypeText() *texttemplate.Template {
|
||||
if t.prototypeTextClone != nil {
|
||||
return t.prototypeTextClone
|
||||
}
|
||||
return t.prototypeText
|
||||
}
|
||||
|
||||
t.prototypeText = texttemplate.Must(t.prototypeText.Clone())
|
||||
t.prototypeHTML = htmltemplate.Must(t.prototypeHTML.Clone())
|
||||
|
||||
return &t
|
||||
func (t *templateNamespace) getPrototypeHTML() *htmltemplate.Template {
|
||||
if t.prototypeHTMLClone != nil {
|
||||
return t.prototypeHTMLClone
|
||||
}
|
||||
return t.prototypeHTML
|
||||
}
|
||||
|
||||
func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
|
||||
@@ -996,12 +1017,46 @@ func (t *templateNamespace) newTemplateLookup(in *templateState) func(name strin
|
||||
return templ
|
||||
}
|
||||
if templ, found := findTemplateIn(name, in); found {
|
||||
return newTemplateState(templ, templateInfo{name: templ.Name()}, nil)
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
@@ -1014,7 +1069,7 @@ func (t *templateNamespace) parse(info templateInfo) (*templateState, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts := newTemplateState(templ, info, nil)
|
||||
ts := newTemplateState(nil, templ, info, nil)
|
||||
|
||||
t.templates[info.name] = ts
|
||||
|
||||
@@ -1028,7 +1083,7 @@ func (t *templateNamespace) parse(info templateInfo) (*templateState, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts := newTemplateState(templ, info, nil)
|
||||
ts := newTemplateState(nil, templ, info, nil)
|
||||
|
||||
t.templates[info.name] = ts
|
||||
|
||||
@@ -1040,6 +1095,9 @@ var _ tpl.IsInternalTemplateProvider = (*templateState)(nil)
|
||||
type templateState struct {
|
||||
tpl.Template
|
||||
|
||||
// Set for deferred templates.
|
||||
owner *templateState
|
||||
|
||||
typ templateType
|
||||
parseInfo tpl.ParseInfo
|
||||
id identity.Identity
|
||||
|
@@ -17,6 +17,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
||||
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
||||
|
||||
@@ -38,6 +39,7 @@ const (
|
||||
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.
|
||||
@@ -77,6 +79,7 @@ func newTemplateContext(
|
||||
lookupFn: lookupFn,
|
||||
visited: make(map[string]bool),
|
||||
templateNotFound: make(map[string]bool),
|
||||
deferNodes: make(map[string]*parse.ListNode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +119,14 @@ const (
|
||||
// "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
|
||||
var (
|
||||
partialReturnWrapper *parse.ListNode
|
||||
doDefer *parse.ListNode
|
||||
)
|
||||
|
||||
func init() {
|
||||
templ, err := texttemplate.New("").Parse(partialReturnWrapperTempl)
|
||||
@@ -126,6 +134,12 @@ func init() {
|
||||
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
|
||||
@@ -158,6 +172,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
|
||||
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)
|
||||
@@ -191,6 +206,58 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
|
||||
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()
|
||||
innerHash := helpers.MD5String(inner.String())
|
||||
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)
|
||||
|
@@ -47,7 +47,7 @@ func TestTransformRecursiveTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
func newTestTemplate(templ tpl.Template) *templateState {
|
||||
return newTemplateState(
|
||||
return newTemplateState(nil,
|
||||
templ,
|
||||
templateInfo{
|
||||
name: templ.Name(),
|
||||
|
Reference in New Issue
Block a user