mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +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")
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user