mirror of
https://github.com/golang/go
synced 2024-11-21 17:54:39 -07:00
add the ability to invoke niladic single-valued methods in templates.
Fixes #389. R=rsc CC=golang-dev https://golang.org/cl/180061
This commit is contained in:
parent
a8fbf5dc2c
commit
42a2e95989
@ -19,6 +19,12 @@
|
||||
Data items may be values or pointers; the interface hides the
|
||||
indirection.
|
||||
|
||||
In the following, 'field' is one of several things, according to the data.
|
||||
- the name of a field of a struct (result = data.field)
|
||||
- the value stored in a map under that key (result = data[field])
|
||||
- the result of invoking a niladic single-valued method with that name
|
||||
(result = data.field())
|
||||
|
||||
Major constructs ({} are metacharacters; [] marks optional elements):
|
||||
|
||||
{# comment }
|
||||
@ -604,8 +610,8 @@ func (st *state) findVar(s string) reflect.Value {
|
||||
return st.data
|
||||
}
|
||||
data := st.data
|
||||
elems := strings.Split(s, ".", 0)
|
||||
for i := 0; i < len(elems); i++ {
|
||||
for _, elem := range strings.Split(s, ".", 0) {
|
||||
origData := data // for method lookup need value before indirection.
|
||||
// Look up field; data must be a struct or map.
|
||||
data = reflect.Indirect(data)
|
||||
if data == nil {
|
||||
@ -614,20 +620,73 @@ func (st *state) findVar(s string) reflect.Value {
|
||||
|
||||
switch typ := data.Type().(type) {
|
||||
case *reflect.StructType:
|
||||
field, ok := typ.FieldByName(elems[i])
|
||||
if !ok {
|
||||
return nil
|
||||
if field, ok := typ.FieldByName(elem); ok {
|
||||
data = data.(*reflect.StructValue).FieldByIndex(field.Index)
|
||||
continue
|
||||
}
|
||||
data = data.(*reflect.StructValue).FieldByIndex(field.Index)
|
||||
case *reflect.MapType:
|
||||
data = data.(*reflect.MapValue).Elem(reflect.NewValue(elems[i]))
|
||||
default:
|
||||
return nil
|
||||
data = data.(*reflect.MapValue).Elem(reflect.NewValue(elem))
|
||||
continue
|
||||
}
|
||||
|
||||
// No luck with that name; is it a method?
|
||||
if result, found := callMethod(origData, elem); found {
|
||||
data = result
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// See if name is a method of the value at some level of indirection.
|
||||
// The return values are the result of the call (which may be nil if
|
||||
// there's trouble) and whether a method of the right name exists with
|
||||
// any signature.
|
||||
func callMethod(data reflect.Value, name string) (result reflect.Value, found bool) {
|
||||
found = false
|
||||
// Method set depends on pointerness, and the value may be arbitrarily
|
||||
// indirect. Simplest approach is to walk down the pointer chain and
|
||||
// see if we can find the method at each step.
|
||||
// Most steps will see NumMethod() == 0.
|
||||
for {
|
||||
typ := data.Type()
|
||||
if nMethod := data.Type().NumMethod(); nMethod > 0 {
|
||||
for i := 0; i < nMethod; i++ {
|
||||
method := typ.Method(i)
|
||||
if method.Name == name {
|
||||
found = true // we found the name regardless
|
||||
// does receiver type match? (pointerness might be off)
|
||||
if typ == method.Type.In(0) {
|
||||
return call(data, method), found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if nd, ok := data.(*reflect.PtrValue); ok {
|
||||
data = nd.Elem()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Invoke the method. If its signature is wrong, return nil.
|
||||
func call(v reflect.Value, method reflect.Method) reflect.Value {
|
||||
funcType := method.Type
|
||||
// Method must take no arguments, meaning as a func it has one argument (the receiver)
|
||||
if funcType.NumIn() != 1 {
|
||||
return nil
|
||||
}
|
||||
// Method must return a single value.
|
||||
if funcType.NumOut() != 1 {
|
||||
return nil
|
||||
}
|
||||
// Result will be the zeroth element of the returned slice.
|
||||
return method.Func.Call([]reflect.Value{v})[0]
|
||||
}
|
||||
|
||||
// Is there no data to look at?
|
||||
func empty(v reflect.Value) bool {
|
||||
v = reflect.Indirect(v)
|
||||
@ -649,7 +708,7 @@ func empty(v reflect.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Look up a variable, up through the parent if necessary.
|
||||
// Look up a variable or method, up through the parent if necessary.
|
||||
func (t *Template) varValue(name string, st *state) reflect.Value {
|
||||
field := st.findVar(name)
|
||||
if field == nil {
|
||||
|
@ -45,6 +45,10 @@ type S struct {
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
func (s *S) pointerMethod() string { return "ptrmethod!" }
|
||||
|
||||
func (s S) valueMethod() string { return "valmethod!" }
|
||||
|
||||
var t1 = T{"ItemNumber1", "ValueNumber1"}
|
||||
var t2 = T{"ItemNumber2", "ValueNumber2"}
|
||||
|
||||
@ -95,6 +99,19 @@ var tests = []*Test{
|
||||
out: "Header=77\n",
|
||||
},
|
||||
|
||||
// Method at top level
|
||||
&Test{
|
||||
in: "ptrmethod={pointerMethod}\n",
|
||||
|
||||
out: "ptrmethod=ptrmethod!\n",
|
||||
},
|
||||
|
||||
&Test{
|
||||
in: "valmethod={valueMethod}\n",
|
||||
|
||||
out: "valmethod=valmethod!\n",
|
||||
},
|
||||
|
||||
// Section
|
||||
&Test{
|
||||
in: "{.section data }\n" +
|
||||
|
Loading…
Reference in New Issue
Block a user