diff options
author | Bjørn Erik Pedersen <[email protected]> | 2019-11-27 13:42:36 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2019-12-18 11:44:40 +0100 |
commit | e625088ef5a970388ad50e464e87db56b358dac4 (patch) | |
tree | f7b26dec1f3695411558d05ca7d0995817a42250 /tpl/internal | |
parent | 67f3aa72cf9aaf3d6e447fa6bc12de704d46adf7 (diff) | |
download | hugo-e625088ef5a970388ad50e464e87db56b358dac4.tar.gz hugo-e625088ef5a970388ad50e464e87db56b358dac4.zip |
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
Diffstat (limited to 'tpl/internal')
-rw-r--r-- | tpl/internal/go_templates/texttemplate/exec.go | 2 | ||||
-rw-r--r-- | tpl/internal/go_templates/texttemplate/hugo_template.go | 104 | ||||
-rw-r--r-- | tpl/internal/go_templates/texttemplate/hugo_template_test.go | 22 |
3 files changed, 119 insertions, 9 deletions
diff --git a/tpl/internal/go_templates/texttemplate/exec.go b/tpl/internal/go_templates/texttemplate/exec.go index db64edcb2..078bcf643 100644 --- a/tpl/internal/go_templates/texttemplate/exec.go +++ b/tpl/internal/go_templates/texttemplate/exec.go @@ -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. } diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go index be8a5558f..a39f027fb 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template.go @@ -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 +} diff --git a/tpl/internal/go_templates/texttemplate/hugo_template_test.go b/tpl/internal/go_templates/texttemplate/hugo_template_test.go index 2424a0a48..98a2575eb 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template_test.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template_test.go @@ -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") } |