Add render template hooks for links and images

This commit also

* revises the change detection for templates used by content files in server mode.
* Adds a Page.RenderString method

Fixes #6545
Fixes #4663
Closes #6043
This commit is contained in:
Bjørn Erik Pedersen
2019-11-27 13:42:36 +01:00
parent 67f3aa72cf
commit e625088ef5
59 changed files with 2234 additions and 542 deletions

View File

@@ -83,10 +83,12 @@ func (s *shortcodeTemplates) fromVariantsSlice(variants []string) (shortcodeVari
func (s *shortcodeTemplates) compareVariants(a, b []string) int {
weight := 0
k := len(a)
for i, av := range a {
bv := b[i]
if av == bv {
weight++
// Add more weight to the left side (language...).
weight = weight + k - i
} else {
weight--
}

View File

@@ -53,10 +53,10 @@ func TestShortcodesTemplate(t *testing.T) {
name2 string
expected int
}{
{"Same suffix", "figure.html", "figure.html", 3},
{"Same suffix and output format", "figure.html.html", "figure.html.html", 3},
{"Same suffix, output format and language", "figure.no.html.html", "figure.no.html.html", 3},
{"No suffix", "figure", "figure", 3},
{"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},
}

View File

@@ -20,6 +20,10 @@ import (
"regexp"
"time"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/common/herrors"
"strings"
@@ -27,7 +31,6 @@ import (
template "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/hugofs"
"github.com/gohugoio/hugo/tpl/tplimpl/embedded"
@@ -81,6 +84,7 @@ func newTemplateAdapter(deps *deps.Deps) *templateHandler {
common := &templatesCommon{
nameBaseTemplateName: make(map[string]string),
transformNotFound: make(map[string]bool),
identityNotFound: make(map[string][]identity.Manager),
}
htmlT := &htmlTemplates{
@@ -100,13 +104,16 @@ func newTemplateAdapter(deps *deps.Deps) *templateHandler {
Deps: deps,
layoutsFs: deps.BaseFs.Layouts.Fs,
templateHandlerCommon: &templateHandlerCommon{
shortcodes: make(map[string]*shortcodeTemplates),
templateInfo: make(map[string]tpl.Info),
html: htmlT,
text: textT,
shortcodes: make(map[string]*shortcodeTemplates),
templateInfo: make(map[string]tpl.Info),
templateInfoTree: make(map[string]*templateInfoTree),
html: htmlT,
text: textT,
},
}
textT.textTemplate.templates = textT
textT.standalone.templates = textT
common.handler = h
return h
@@ -152,27 +159,26 @@ func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error)
return t.addTemplateIn(t.t, name, tpl)
}
func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) (*templateContext, error) {
templ, err := tt.New(name).Parse(tpl)
func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, templstr string) (*templateContext, error) {
templ, err := tt.New(name).Parse(templstr)
if err != nil {
return nil, err
}
typ := resolveTemplateType(name)
c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
c, err := t.handler.applyTemplateTransformersToHMLTTemplate(typ, templ)
if err != nil {
return nil, err
}
for k := range c.notFound {
for k := range c.templateNotFound {
t.transformNotFound[k] = true
t.identityNotFound[k] = append(t.identityNotFound[k], c.id)
}
if typ == templateShortcode {
t.handler.addShortcodeVariant(name, c.Info, templ)
} else {
t.handler.templateInfo[name] = c.Info
for k := range c.identityNotFound {
t.identityNotFound[k] = append(t.identityNotFound[k], c.id)
}
return c, nil
@@ -208,7 +214,7 @@ func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename strin
// * https://github.com/golang/go/issues/16101
// * https://github.com/gohugoio/hugo/issues/2549
overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
if _, err := applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
if _, err := t.handler.applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
return err
}
@@ -253,6 +259,8 @@ func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVarian
// It implements the templateLoader and tpl.TemplateHandler interfaces.
// There is one templateHandler created per Site.
type templateHandler struct {
ready bool
executor texttemplate.Executer
funcs map[string]reflect.Value
@@ -324,6 +332,7 @@ func (t *templateHandler) LoadTemplates(prefix string) error {
// Lookup tries to find a template with the given name in both template
// collections: First HTML, then the plain text template collection.
func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
if strings.HasPrefix(name, textTmplNamePrefix) {
// The caller has explicitly asked for a text template, so only look
// in the text template collection.
@@ -345,6 +354,9 @@ func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
// 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) {
if !t.ready {
panic("handler not ready")
}
name = templateBaseName(templateShortcode, name)
s, found := t.shortcodes[name]
if !found {
@@ -358,18 +370,17 @@ func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVarian
more := len(s.variants) > 1
return &tpl.TemplateInfo{
Template: sv.templ,
Info: sv.info,
}, true, more
return tpl.WithInfo(sv.templ, sv.info), true, more
}
// MarkReady marks the templates as "ready for execution". No changes allowed
// markReady marks the templates as "ready for execution". No changes allowed
// after this is set.
// TODO(bep) if this proves to be resource heavy, we could detect
// earlier if we really need this, or make it lazy.
func (t *templateHandler) MarkReady() error {
func (t *templateHandler) markReady() error {
defer func() {
t.ready = true
}()
if err := t.postTransform(); err != nil {
return err
}
@@ -483,6 +494,7 @@ func (t *templateHandler) addInternalTemplate(name, tpl string) error {
}
func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
base := templateBaseName(templateShortcode, name)
shortcodename, variants := templateNameAndVariants(base)
@@ -561,18 +573,9 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e
}
func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
if adapter, ok := templ.(*tpl.TemplateInfo); ok {
if adapter.Info.IsZero() {
if info, found := t.templateInfo[templ.Name()]; found {
adapter.Info = info
}
}
} else if templ != nil {
if templ != nil {
if info, found := t.templateInfo[templ.Name()]; found {
return &tpl.TemplateInfo{
Template: templ,
Info: info,
}, true
return tpl.WithInfo(templ, info), true
}
}
@@ -586,7 +589,11 @@ func (t *templateHandler) checkState() {
}
func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
if !t.ready {
panic("invalid state")
}
c := &templateHandler{
ready: true,
Deps: d,
layoutsFs: d.BaseFs.Layouts.Fs,
}
@@ -703,36 +710,69 @@ func (t *templateHandler) loadTemplates(prefix string) error {
}
func (t *templateHandler) postTransform() error {
if len(t.html.transformNotFound) == 0 && len(t.text.transformNotFound) == 0 {
return nil
func (t *templateHandler) getOrCreateTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) {
info, found := t.templateInfo[name]
if found {
return info.(identity.Manager), info.ParseInfo()
}
return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo
}
func (t *templateHandler) createTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) {
_, found := t.templateInfo[name]
if found {
panic("already created: " + name)
}
defer func() {
t.text.transformNotFound = make(map[string]bool)
t.html.transformNotFound = make(map[string]bool)
}()
return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo
}
func (t *templateHandler) postTransform() error {
for k, v := range t.templateInfoTree {
if v.id != nil {
info := tpl.NewInfo(
v.id,
v.info,
)
t.templateInfo[k] = info
if v.typ == templateShortcode {
t.addShortcodeVariant(k, info, v.templ)
}
}
}
for _, s := range []struct {
lookup func(name string) *parse.Tree
lookup func(name string) *templateInfoTree
transformNotFound map[string]bool
identityNotFound map[string][]identity.Manager
}{
// html templates
{func(name string) *parse.Tree {
{func(name string) *templateInfoTree {
templ := t.html.lookup(name)
if templ == nil {
return nil
}
return templ.Tree
}, t.html.transformNotFound},
id, info := t.getOrCreateTemplateInfo(name)
return &templateInfoTree{
id: id,
info: info,
tree: templ.Tree,
}
}, t.html.transformNotFound, t.html.identityNotFound},
// text templates
{func(name string) *parse.Tree {
{func(name string) *templateInfoTree {
templT := t.text.lookup(name)
if templT == nil {
return nil
}
return templT.Tree
}, t.text.transformNotFound},
id, info := t.getOrCreateTemplateInfo(name)
return &templateInfoTree{
id: id,
info: info,
tree: templT.Tree,
}
}, t.text.transformNotFound, t.text.identityNotFound},
} {
for name := range s.transformNotFound {
templ := s.lookup(name)
@@ -743,6 +783,15 @@ func (t *templateHandler) postTransform() error {
}
}
}
for k, v := range s.identityNotFound {
tmpl := s.lookup(k)
if tmpl != nil {
for _, im := range v {
im.Add(tmpl.id)
}
}
}
}
return nil
@@ -758,7 +807,6 @@ func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFi
tt,
new(nopLookupVariant),
}
}
type templateHandlerCommon struct {
@@ -771,6 +819,9 @@ type templateHandlerCommon struct {
// shortcodeTemplates type.
templateInfo map[string]tpl.Info
// Used to track templates during the AST transformations.
templateInfoTree map[string]*templateInfoTree
// text holds all the pure text templates.
text *textTemplates
html *htmlTemplates
@@ -795,9 +846,12 @@ type templatesCommon struct {
// Used to get proper filenames in errors
nameBaseTemplateName map[string]string
// Holds names of the templates not found during the first AST transformation
// Holds names of the template definitions not found during the first AST transformation
// pass.
transformNotFound map[string]bool
// Holds identities of templates not found during first pass.
identityNotFound map[string][]identity.Manager
}
func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon {
@@ -806,8 +860,9 @@ func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon {
}
type textTemplate struct {
mu sync.RWMutex
t *texttemplate.Template
mu sync.RWMutex
t *texttemplate.Template
templates *textTemplates
}
func (t *textTemplate) Lookup(name string) (tpl.Template, bool) {
@@ -831,7 +886,7 @@ func (t *textTemplate) parseIn(tt *texttemplate.Template, name, tpl string) (*te
return nil, err
}
if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
if _, err := t.templates.handler.applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
return nil, err
}
return templ, nil
@@ -868,30 +923,24 @@ func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error)
return t.addTemplateIn(t.t, name, tpl)
}
func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) (*templateContext, error) {
func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tplstr string) (*templateContext, error) {
name = strings.TrimPrefix(name, textTmplNamePrefix)
templ, err := t.parseIn(tt, name, tpl)
templ, err := t.parseIn(tt, name, tplstr)
if err != nil {
return nil, err
}
typ := resolveTemplateType(name)
c, err := applyTemplateTransformersToTextTemplate(typ, templ)
c, err := t.handler.applyTemplateTransformersToTextTemplate(typ, templ)
if err != nil {
return nil, err
}
for k := range c.notFound {
for k := range c.templateNotFound {
t.transformNotFound[k] = true
}
if typ == templateShortcode {
t.handler.addShortcodeVariant(name, c.Info, templ)
} else {
t.handler.templateInfo[name] = c.Info
}
return c, nil
}
@@ -924,7 +973,7 @@ func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename strin
}
overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, overlayTpl); err != nil {
if _, err := t.handler.applyTemplateTransformersToTextTemplate(templateUndefined, overlayTpl); err != nil {
return err
}
t.overlays[name] = overlayTpl

View File

@@ -44,16 +44,13 @@ func (*TemplateProvider) Update(deps *deps.Deps) error {
}
return newTmpl.MarkReady()
return newTmpl.markReady()
}
// Clone clones.
func (*TemplateProvider) Clone(d *deps.Deps) error {
t := d.Tmpl.(*templateHandler)
clone := t.clone(d)
return clone.MarkReady()
t.clone(d)
return nil
}

View File

@@ -14,8 +14,12 @@
package tplimpl
import (
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
"regexp"
"strings"
"github.com/gohugoio/hugo/identity"
template "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"
@@ -34,9 +38,10 @@ const (
)
type templateContext struct {
visited map[string]bool
notFound map[string]bool
lookupFn func(name string) *parse.Tree
visited map[string]bool
templateNotFound map[string]bool
identityNotFound map[string]bool
lookupFn func(name string) *templateInfoTree
// The last error encountered.
err error
@@ -47,13 +52,14 @@ type templateContext struct {
configChecked bool
// Contains some info about the template
tpl.Info
parseInfo *tpl.ParseInfo
id identity.Manager
// Store away the return node in partials.
returnNode *parse.CommandNode
}
func (c templateContext) getIfNotVisited(name string) *parse.Tree {
func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
if c.visited[name] {
return nil
}
@@ -63,59 +69,95 @@ func (c templateContext) getIfNotVisited(name string) *parse.Tree {
// 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.notFound[name] = true
c.templateNotFound[name] = true
}
return templ
}
func newTemplateContext(lookupFn func(name string) *parse.Tree) *templateContext {
return &templateContext{
Info: tpl.Info{Config: tpl.DefaultConfig},
lookupFn: lookupFn,
visited: make(map[string]bool),
notFound: make(map[string]bool)}
}
func newTemplateContext(
id identity.Manager,
info *tpl.ParseInfo,
lookupFn func(name string) *templateInfoTree) *templateContext {
func createParseTreeLookup(templ *template.Template) func(nn string) *parse.Tree {
return func(nn string) *parse.Tree {
tt := templ.Lookup(nn)
if tt != nil {
return tt.Tree
}
return nil
return &templateContext{
id: id,
parseInfo: info,
lookupFn: lookupFn,
visited: make(map[string]bool),
templateNotFound: make(map[string]bool),
identityNotFound: make(map[string]bool),
}
}
func applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
return applyTemplateTransformers(typ, templ.Tree, createParseTreeLookup(templ))
func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) func(nn string) *templateInfoTree {
return func(nn string) *templateInfoTree {
return getID(nn)
}
}
func applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
return applyTemplateTransformers(typ, templ.Tree,
func(nn string) *parse.Tree {
tt := templ.Lookup(nn)
if tt != nil {
return tt.Tree
}
return nil
})
func (t *templateHandler) applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
id, info := t.createTemplateInfo(templ.Name())
ti := &templateInfoTree{
tree: templ.Tree,
templ: templ,
typ: typ,
id: id,
info: info,
}
t.templateInfoTree[templ.Name()] = ti
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
return t.templateInfoTree[name]
})
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
}
func applyTemplateTransformers(typ templateType, templ *parse.Tree, lookupFn func(name string) *parse.Tree) (*templateContext, error) {
func (t *templateHandler) applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
id, info := t.createTemplateInfo(templ.Name())
ti := &templateInfoTree{
tree: templ.Tree,
templ: templ,
typ: typ,
id: id,
info: info,
}
t.templateInfoTree[templ.Name()] = ti
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
return t.templateInfoTree[name]
})
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
}
type templateInfoTree struct {
info tpl.ParseInfo
typ templateType
id identity.Manager
templ tpl.Template
tree *parse.Tree
}
func applyTemplateTransformers(
typ templateType,
templ *templateInfoTree,
lookupFn func(name string) *templateInfoTree) (*templateContext, error) {
if templ == nil {
return nil, errors.New("expected template, but none provided")
}
c := newTemplateContext(lookupFn)
c := newTemplateContext(templ.id, &templ.info, lookupFn)
c.typ = typ
_, err := c.applyTransformations(templ.Root)
_, err := c.applyTransformations(templ.tree.Root)
if err == nil && c.returnNode != nil {
// This is a partial with a return statement.
c.Info.HasReturn = true
templ.Root = c.wrapInPartialReturnWrapper(templ.Root)
c.parseInfo.HasReturn = true
templ.tree.Root = c.wrapInPartialReturnWrapper(templ.tree.Root)
}
return c, err
@@ -125,7 +167,9 @@ const (
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
)
var partialReturnWrapper *parse.ListNode
var (
partialReturnWrapper *parse.ListNode
)
func init() {
templ, err := texttemplate.New("").Parse(partialReturnWrapperTempl)
@@ -133,6 +177,7 @@ func init() {
panic(err)
}
partialReturnWrapper = templ.Tree.Root
}
func (c *templateContext) wrapInPartialReturnWrapper(n *parse.ListNode) *parse.ListNode {
@@ -156,6 +201,7 @@ func (c *templateContext) wrapInPartialReturnWrapper(n *parse.ListNode) *parse.L
// getif works slightly different than the Go built-in in that it also
// considers any IsZero methods on the values (as in time.Time).
// See https://github.com/gohugoio/hugo/issues/5738
// TODO(bep) get rid of this.
func (c *templateContext) wrapWithGetIf(p *parse.PipeNode) {
if len(p.Cmds) == 0 {
return
@@ -176,9 +222,9 @@ func (c *templateContext) wrapWithGetIf(p *parse.PipeNode) {
}
// applyTransformations do 3 things:
// 1) Make all .Params.CamelCase and similar into lowercase.
// 2) Wraps every with and if pipe in getif
// 3) Collects some information about the template content.
// 1) Wraps every with and if pipe in getif
// 2) Parses partial return statement.
// 3) Tracks template (partial) dependencies and some other info.
func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
switch x := n.(type) {
case *parse.ListNode:
@@ -198,7 +244,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
case *parse.TemplateNode:
subTempl := c.getIfNotVisited(x.Name)
if subTempl != nil {
c.applyTransformationsToNodes(subTempl.Root)
c.applyTransformationsToNodes(subTempl.tree.Root)
}
case *parse.PipeNode:
c.collectConfig(x)
@@ -210,6 +256,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
}
case *parse.CommandNode:
c.collectPartialInfo(x)
c.collectInner(x)
keep := c.collectReturnNode(x)
@@ -277,11 +324,10 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
c.err = errors.Wrap(err, errMsg)
return
}
if err := mapstructure.WeakDecode(m, &c.Info.Config); err != nil {
if err := mapstructure.WeakDecode(m, &c.parseInfo.Config); err != nil {
c.err = errors.Wrap(err, errMsg)
}
}
}
// collectInner determines if the given CommandNode represents a
@@ -290,7 +336,7 @@ func (c *templateContext) collectInner(n *parse.CommandNode) {
if c.typ != templateShortcode {
return
}
if c.Info.IsInner || len(n.Args) == 0 {
if c.parseInfo.IsInner || len(n.Args) == 0 {
return
}
@@ -304,13 +350,45 @@ func (c *templateContext) collectInner(n *parse.CommandNode) {
}
if c.hasIdent(idents, "Inner") {
c.Info.IsInner = true
c.parseInfo.IsInner = true
break
}
}
}
var partialRe = regexp.MustCompile(`^partial(Cached)?$|^partials\.Include(Cached)?$`)
func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
if len(x.Args) < 2 {
return
}
first := x.Args[0]
var id string
switch v := first.(type) {
case *parse.IdentifierNode:
id = v.Ident
case *parse.ChainNode:
id = v.String()
}
if partialRe.MatchString(id) {
partialName := strings.Trim(x.Args[1].String(), "\"")
if !strings.Contains(partialName, ".") {
partialName += ".html"
}
partialName = "partials/" + partialName
info := c.lookupFn(partialName)
if info != nil {
c.id.Add(info.id)
} else {
// Delay for later
c.identityNotFound[partialName] = true
}
}
}
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
if c.typ != templatePartial || c.returnNode != nil {
return true

View File

@@ -15,14 +15,17 @@ package tplimpl
import (
"strings"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
"github.com/gohugoio/hugo/hugofs/files"
"testing"
"time"
"github.com/gohugoio/hugo/tpl"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
)
// Issue #2927
@@ -33,7 +36,7 @@ func TestTransformRecursiveTemplate(t *testing.T) {
{{ define "menu-nodes" }}
{{ template "menu-node" }}
{{ end }}
{{ define "menu-node" }}
{{ define "menu-nßode" }}
{{ template "menu-node" }}
{{ end }}
{{ template "menu-nodes" }}
@@ -41,12 +44,25 @@ func TestTransformRecursiveTemplate(t *testing.T) {
templ, err := template.New("foo").Parse(recursive)
c.Assert(err, qt.IsNil)
parseInfo := tpl.DefaultParseInfo
ctx := newTemplateContext(createParseTreeLookup(templ))
ctx := newTemplateContext(
newTemplateInfo("test").(identity.Manager),
&parseInfo,
createGetTemplateInfoTree(templ.Tree),
)
ctx.applyTransformations(templ.Tree.Root)
}
func createGetTemplateInfoTree(tree *parse.Tree) func(name string) *templateInfoTree {
return func(name string) *templateInfoTree {
return &templateInfoTree{
tree: tree,
}
}
}
type I interface {
Method0()
}
@@ -80,13 +96,10 @@ func TestInsertIsZeroFunc(t *testing.T) {
{{ with .TimeZero }}.TimeZero1 with: {{ . }}{{ else }}.TimeZero1 with: FALSE{{ end }}
{{ template "mytemplate" . }}
{{ if .T.NonEmptyInterfaceTypedNil }}.NonEmptyInterfaceTypedNil: TRUE{{ else }}.NonEmptyInterfaceTypedNil: FALSE{{ end }}
{{ template "other-file-template" . }}
{{ define "mytemplate" }}
{{ if .TimeZero }}.TimeZero1: mytemplate: TRUE{{ else }}.TimeZero1: mytemplate: FALSE{{ end }}
{{ end }}
`
// https://github.com/gohugoio/hugo/issues/5865
@@ -97,7 +110,7 @@ func TestInsertIsZeroFunc(t *testing.T) {
)
d := newD(c)
h := d.Tmpl.(tpl.TemplateManager)
h := d.Tmpl.(*templateHandler)
// HTML templates
c.Assert(h.AddTemplate("mytemplate.html", templ1), qt.IsNil)
@@ -107,15 +120,13 @@ func TestInsertIsZeroFunc(t *testing.T) {
c.Assert(h.AddTemplate("_text/mytexttemplate.txt", templ1), qt.IsNil)
c.Assert(h.AddTemplate("_text/myothertexttemplate.txt", templ2), qt.IsNil)
c.Assert(h.MarkReady(), qt.IsNil)
c.Assert(h.markReady(), qt.IsNil)
for _, name := range []string{"mytemplate.html", "mytexttemplate.txt"} {
var sb strings.Builder
tt, _ := d.Tmpl.Lookup(name)
sb := &strings.Builder{}
err := d.Tmpl.Execute(tt, sb, ctx)
err := h.Execute(tt, &sb, ctx)
c.Assert(err, qt.IsNil)
result := sb.String()
c.Assert(result, qt.Contains, ".True: TRUE")
@@ -138,14 +149,10 @@ func TestCollectInfo(t *testing.T) {
tests := []struct {
name string
tplString string
expected tpl.Info
expected tpl.ParseInfo
}{
{"Basic Inner", `{{ .Inner }}`, tpl.Info{IsInner: true, Config: tpl.DefaultConfig}},
{"Basic config map", "{{ $_hugo_config := `" + configStr + "` }}", tpl.Info{
Config: tpl.Config{
Version: 42,
},
}},
{"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 interface{}) interface{} {
@@ -162,12 +169,13 @@ func TestCollectInfo(t *testing.T) {
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
c.Assert(err, qt.IsNil)
parseInfo := tpl.DefaultParseInfo
ctx := newTemplateContext(createParseTreeLookup(templ))
ctx := newTemplateContext(
newTemplateInfo("test").(identity.Manager), &parseInfo, createGetTemplateInfoTree(templ.Tree))
ctx.typ = templateShortcode
ctx.applyTransformations(templ.Tree.Root)
c.Assert(ctx.Info, qt.Equals, test.expected)
c.Assert(ctx.parseInfo, qt.DeepEquals, &test.expected)
})
}
@@ -205,7 +213,10 @@ func TestPartialReturn(t *testing.T) {
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
c.Assert(err, qt.IsNil)
_, err = applyTemplateTransformers(templatePartial, templ.Tree, createParseTreeLookup(templ))
_, err = applyTemplateTransformers(
templatePartial,
&templateInfoTree{tree: templ.Tree, info: tpl.DefaultParseInfo},
createGetTemplateInfoTree(templ.Tree))
// Just check that it doesn't fail in this test. We have functional tests
// in hugoblib.
@@ -215,3 +226,10 @@ func TestPartialReturn(t *testing.T) {
}
}
func newTemplateInfo(name string) tpl.Info {
return tpl.NewInfo(
identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)),
tpl.DefaultParseInfo,
)
}

View File

@@ -19,6 +19,8 @@ import (
"reflect"
"strings"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/common/maps"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
@@ -62,14 +64,14 @@ type templateExecHelper struct {
funcs map[string]reflect.Value
}
func (t *templateExecHelper) GetFunc(name string) (reflect.Value, bool) {
func (t *templateExecHelper) GetFunc(tmpl texttemplate.Preparer, name string) (reflect.Value, bool) {
if fn, found := t.funcs[name]; found {
return fn, true
}
return zero, false
}
func (t *templateExecHelper) GetMapValue(receiver, key reflect.Value) (reflect.Value, bool) {
func (t *templateExecHelper) GetMapValue(tmpl texttemplate.Preparer, receiver, key reflect.Value) (reflect.Value, bool) {
if params, ok := receiver.Interface().(maps.Params); ok {
// Case insensitive.
keystr := strings.ToLower(key.String())
@@ -85,6 +87,22 @@ func (t *templateExecHelper) GetMapValue(receiver, key reflect.Value) (reflect.V
return v, v.IsValid()
}
func (t *templateExecHelper) GetMethod(tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) {
// This is a hot path and receiver.MethodByName really shows up in the benchmarks.
// Page.Render is the only method with a WithTemplateInfo as of now, so let's just
// check that for now.
// TODO(bep) find a more flexible, but still fast, way.
if name == "Render" {
if info, ok := tmpl.(tpl.Info); ok {
if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() {
return m, reflect.ValueOf(info)
}
}
}
return receiver.MethodByName(name), zero
}
func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) {
funcs := createFuncMap(d)
funcsv := make(map[string]reflect.Value)
@@ -120,9 +138,7 @@ func createFuncMap(d *deps.Deps) map[string]interface{} {
}
funcMap[alias] = mm.Method
}
}
}
if d.OverloadedTemplateFuncs != nil {

View File

@@ -24,18 +24,19 @@ import (
func TestTemplateInfoShortcode(t *testing.T) {
c := qt.New(t)
d := newD(c)
h := d.Tmpl.(tpl.TemplateManager)
h := d.Tmpl.(*templateHandler)
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
{{ .Inner }}
`), qt.IsNil)
c.Assert(h.markReady(), qt.IsNil)
tt, found, _ := d.Tmpl.LookupVariant("mytemplate", tpl.TemplateVariants{})
c.Assert(found, qt.Equals, true)
tti, ok := tt.(tpl.TemplateInfoProvider)
tti, ok := tt.(tpl.Info)
c.Assert(ok, qt.Equals, true)
c.Assert(tti.TemplateInfo().IsInner, qt.Equals, true)
c.Assert(tti.ParseInfo().IsInner, qt.Equals, true)
}