mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
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:
@@ -658,7 +658,7 @@ var (
|
||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||
// as the function itself.
|
||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
func (s *state) evalCallOld(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
if args != nil {
|
||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||
}
|
||||
|
@@ -34,8 +34,9 @@ type Preparer interface {
|
||||
|
||||
// ExecHelper allows some custom eval hooks.
|
||||
type ExecHelper interface {
|
||||
GetFunc(name string) (reflect.Value, bool)
|
||||
GetMapValue(receiver, key reflect.Value) (reflect.Value, bool)
|
||||
GetFunc(tmpl Preparer, name string) (reflect.Value, bool)
|
||||
GetMethod(tmpl Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value)
|
||||
GetMapValue(tmpl Preparer, receiver, key reflect.Value) (reflect.Value, bool)
|
||||
}
|
||||
|
||||
// Executer executes a given template.
|
||||
@@ -64,6 +65,7 @@ func (t *executer) Execute(p Preparer, wr io.Writer, data interface{}) error {
|
||||
|
||||
state := &state{
|
||||
helper: t.helper,
|
||||
prep: p,
|
||||
tmpl: tmpl,
|
||||
wr: wr,
|
||||
vars: []variable{{"$", value}},
|
||||
@@ -75,7 +77,6 @@ func (t *executer) Execute(p Preparer, wr io.Writer, data interface{}) error {
|
||||
|
||||
// Prepare returns a template ready for execution.
|
||||
func (t *Template) Prepare() (*Template, error) {
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@ func (t *Template) executeWithState(state *state, value reflect.Value) (err erro
|
||||
// can execute in parallel.
|
||||
type state struct {
|
||||
tmpl *Template
|
||||
prep Preparer // Added for Hugo.
|
||||
helper ExecHelper // Added for Hugo.
|
||||
wr io.Writer
|
||||
node parse.Node // current node, for errors
|
||||
@@ -110,7 +112,7 @@ func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd
|
||||
var ok bool
|
||||
if s.helper != nil {
|
||||
// Added for Hugo.
|
||||
function, ok = s.helper.GetFunc(name)
|
||||
function, ok = s.helper.GetFunc(s.prep, name)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
@@ -148,9 +150,23 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
|
||||
if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||
// Added for Hugo.
|
||||
var first reflect.Value
|
||||
var method reflect.Value
|
||||
if s.helper != nil {
|
||||
method, first = s.helper.GetMethod(s.prep, ptr, fieldName)
|
||||
} else {
|
||||
method = ptr.MethodByName(fieldName)
|
||||
}
|
||||
|
||||
if method.IsValid() {
|
||||
if first != zero {
|
||||
return s.evalCall(dot, method, node, fieldName, args, final, first)
|
||||
}
|
||||
|
||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||
}
|
||||
|
||||
hasArgs := len(args) > 1 || final != missingVal
|
||||
// It's not a method; must be a field of a struct or an element of a map.
|
||||
switch receiver.Kind() {
|
||||
@@ -177,7 +193,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
|
||||
var result reflect.Value
|
||||
if s.helper != nil {
|
||||
// Added for Hugo.
|
||||
result, _ = s.helper.GetMapValue(receiver, nameVal)
|
||||
result, _ = s.helper.GetMapValue(s.prep, receiver, nameVal)
|
||||
} else {
|
||||
result = receiver.MapIndex(nameVal)
|
||||
}
|
||||
@@ -209,3 +225,79 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
|
||||
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||
// as the function itself.
|
||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value, first ...reflect.Value) reflect.Value {
|
||||
if args != nil {
|
||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||
}
|
||||
typ := fun.Type()
|
||||
numFirst := len(first)
|
||||
numIn := len(args) + numFirst // // Added for Hugo
|
||||
if final != missingVal {
|
||||
numIn++
|
||||
}
|
||||
numFixed := len(args) + len(first)
|
||||
if typ.IsVariadic() {
|
||||
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||
if numIn < numFixed {
|
||||
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||
}
|
||||
} else if numIn != typ.NumIn() {
|
||||
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), numIn)
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||
}
|
||||
// Build the arg list.
|
||||
argv := make([]reflect.Value, numIn)
|
||||
// Args must be evaluated. Fixed args first.
|
||||
i := len(first)
|
||||
for ; i < numFixed && i < len(args)+numFirst; i++ {
|
||||
argv[i] = s.evalArg(dot, typ.In(i), args[i-numFirst])
|
||||
}
|
||||
// Now the ... args.
|
||||
if typ.IsVariadic() {
|
||||
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||
for ; i < len(args)+numFirst; i++ {
|
||||
argv[i] = s.evalArg(dot, argType, args[i-numFirst])
|
||||
}
|
||||
|
||||
}
|
||||
// Add final value if necessary.
|
||||
if final != missingVal {
|
||||
t := typ.In(typ.NumIn() - 1)
|
||||
if typ.IsVariadic() {
|
||||
if numIn-1 < numFixed {
|
||||
// The added final argument corresponds to a fixed parameter of the function.
|
||||
// Validate against the type of the actual parameter.
|
||||
t = typ.In(numIn - 1)
|
||||
} else {
|
||||
// The added final argument corresponds to the variadic part.
|
||||
// Validate against the type of the elements of the variadic slice.
|
||||
t = t.Elem()
|
||||
}
|
||||
}
|
||||
argv[i] = s.validateType(final, t)
|
||||
}
|
||||
|
||||
// Added for Hugo
|
||||
for i := 0; i < len(first); i++ {
|
||||
argv[i] = s.validateType(first[i], typ.In(i))
|
||||
}
|
||||
|
||||
v, err := safeCall(fun, argv)
|
||||
// If we have an error that is not nil, stop execution and return that
|
||||
// error to the caller.
|
||||
if err != nil {
|
||||
s.at(node)
|
||||
s.errorf("error calling %s: %v", name, err)
|
||||
}
|
||||
if v.Type() == reflectValueType {
|
||||
v = v.Interface().(reflect.Value)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
@@ -27,10 +27,18 @@ type TestStruct struct {
|
||||
M map[string]string
|
||||
}
|
||||
|
||||
func (t TestStruct) Hello1(arg string) string {
|
||||
return arg
|
||||
}
|
||||
|
||||
func (t TestStruct) Hello2(arg1, arg2 string) string {
|
||||
return arg1 + " " + arg2
|
||||
}
|
||||
|
||||
type execHelper struct {
|
||||
}
|
||||
|
||||
func (e *execHelper) GetFunc(name string) (reflect.Value, bool) {
|
||||
func (e *execHelper) GetFunc(tmpl Preparer, name string) (reflect.Value, bool) {
|
||||
if name == "print" {
|
||||
return zero, false
|
||||
}
|
||||
@@ -39,11 +47,19 @@ func (e *execHelper) GetFunc(name string) (reflect.Value, bool) {
|
||||
}), true
|
||||
}
|
||||
|
||||
func (e *execHelper) GetMapValue(m, key reflect.Value) (reflect.Value, bool) {
|
||||
func (e *execHelper) GetMapValue(tmpl Preparer, m, key reflect.Value) (reflect.Value, bool) {
|
||||
key = reflect.ValueOf(strings.ToLower(key.String()))
|
||||
return m.MapIndex(key), true
|
||||
}
|
||||
|
||||
func (e *execHelper) GetMethod(tmpl Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) {
|
||||
if name != "Hello1" {
|
||||
return zero, zero
|
||||
}
|
||||
m := receiver.MethodByName("Hello2")
|
||||
return m, reflect.ValueOf("v2")
|
||||
}
|
||||
|
||||
func TestTemplateExecutor(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
@@ -51,6 +67,7 @@ func TestTemplateExecutor(t *testing.T) {
|
||||
{{ print "foo" }}
|
||||
{{ printf "hugo" }}
|
||||
Map: {{ .M.A }}
|
||||
Method: {{ .Hello1 "v1" }}
|
||||
|
||||
`)
|
||||
|
||||
@@ -67,5 +84,6 @@ Map: {{ .M.A }}
|
||||
c.Assert(got, qt.Contains, "foo")
|
||||
c.Assert(got, qt.Contains, "hello hugo")
|
||||
c.Assert(got, qt.Contains, "Map: av")
|
||||
c.Assert(got, qt.Contains, "Method: v2 v1")
|
||||
|
||||
}
|
||||
|
@@ -116,9 +116,9 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
|
||||
return "", fmt.Errorf("partial %q not found", name)
|
||||
}
|
||||
|
||||
var info tpl.Info
|
||||
if ip, ok := templ.(tpl.TemplateInfoProvider); ok {
|
||||
info = ip.TemplateInfo()
|
||||
var info tpl.ParseInfo
|
||||
if ip, ok := templ.(tpl.Info); ok {
|
||||
info = ip.ParseInfo()
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
|
@@ -24,8 +24,6 @@ import (
|
||||
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
||||
)
|
||||
|
||||
var _ TemplateInfoProvider = (*TemplateInfo)(nil)
|
||||
|
||||
// TemplateManager manages the collection of templates.
|
||||
type TemplateManager interface {
|
||||
TemplateHandler
|
||||
@@ -34,7 +32,6 @@ type TemplateManager interface {
|
||||
AddLateTemplate(name, tpl string) error
|
||||
LoadTemplates(prefix string) error
|
||||
|
||||
MarkReady() error
|
||||
RebuildClone()
|
||||
}
|
||||
|
||||
@@ -80,11 +77,6 @@ type Template interface {
|
||||
Prepare() (*texttemplate.Template, error)
|
||||
}
|
||||
|
||||
// TemplateInfoProvider provides some contextual information about a template.
|
||||
type TemplateInfoProvider interface {
|
||||
TemplateInfo() Info
|
||||
}
|
||||
|
||||
// TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain.
|
||||
type TemplateParser interface {
|
||||
Parse(name, tpl string) (Template, error)
|
||||
@@ -101,10 +93,31 @@ type TemplateDebugger interface {
|
||||
Debug()
|
||||
}
|
||||
|
||||
// TemplateInfo wraps a Template with some additional information.
|
||||
type TemplateInfo struct {
|
||||
// templateInfo wraps a Template with some additional information.
|
||||
type templateInfo struct {
|
||||
Template
|
||||
Info Info
|
||||
Info
|
||||
}
|
||||
|
||||
// templateInfo wraps a Template with some additional information.
|
||||
type templateInfoManager struct {
|
||||
Template
|
||||
InfoManager
|
||||
}
|
||||
|
||||
// WithInfo wraps the info in a template.
|
||||
func WithInfo(templ Template, info Info) Template {
|
||||
if manager, ok := info.(InfoManager); ok {
|
||||
return &templateInfoManager{
|
||||
Template: templ,
|
||||
InfoManager: manager,
|
||||
}
|
||||
}
|
||||
|
||||
return &templateInfo{
|
||||
Template: templ,
|
||||
Info: info,
|
||||
}
|
||||
}
|
||||
|
||||
var baseOfRe = regexp.MustCompile("template: (.*?):")
|
||||
@@ -117,10 +130,6 @@ func extractBaseOf(err string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *TemplateInfo) TemplateInfo() Info {
|
||||
return t.Info
|
||||
}
|
||||
|
||||
// TemplateFuncGetter allows to find a template func by name.
|
||||
type TemplateFuncGetter interface {
|
||||
GetFunc(name string) (reflect.Value, bool)
|
||||
|
@@ -13,12 +13,44 @@
|
||||
|
||||
package tpl
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
// Increments on breaking changes.
|
||||
const TemplateVersion = 2
|
||||
|
||||
// Info holds some info extracted from a parsed template.
|
||||
type Info struct {
|
||||
type Info interface {
|
||||
ParseInfo() ParseInfo
|
||||
|
||||
// Identifies this template and its dependencies.
|
||||
identity.Provider
|
||||
}
|
||||
|
||||
type InfoManager interface {
|
||||
ParseInfo() ParseInfo
|
||||
|
||||
// Identifies and manages this template and its dependencies.
|
||||
identity.Manager
|
||||
}
|
||||
|
||||
type defaultInfo struct {
|
||||
identity.Manager
|
||||
parseInfo ParseInfo
|
||||
}
|
||||
|
||||
func NewInfo(id identity.Manager, parseInfo ParseInfo) Info {
|
||||
return &defaultInfo{
|
||||
Manager: id,
|
||||
parseInfo: parseInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (info *defaultInfo) ParseInfo() ParseInfo {
|
||||
return info.parseInfo
|
||||
}
|
||||
|
||||
type ParseInfo struct {
|
||||
// Set for shortcode templates with any {{ .Inner }}
|
||||
IsInner bool
|
||||
|
||||
@@ -26,17 +58,25 @@ type Info struct {
|
||||
HasReturn bool
|
||||
|
||||
// Config extracted from template.
|
||||
Config Config
|
||||
Config ParseConfig
|
||||
}
|
||||
|
||||
func (info Info) IsZero() bool {
|
||||
func (info ParseInfo) IsZero() bool {
|
||||
return info.Config.Version == 0
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// Info holds some info extracted from a parsed template.
|
||||
type Info1 struct {
|
||||
}
|
||||
|
||||
type ParseConfig struct {
|
||||
Version int
|
||||
}
|
||||
|
||||
var DefaultConfig = Config{
|
||||
var DefaultParseConfig = ParseConfig{
|
||||
Version: TemplateVersion,
|
||||
}
|
||||
|
||||
var DefaultParseInfo = ParseInfo{
|
||||
Config: DefaultParseConfig,
|
||||
}
|
||||
|
@@ -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--
|
||||
}
|
||||
|
@@ -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},
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
)
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user