mirror of
https://github.com/golang/go
synced 2024-11-25 03:07:56 -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
|
Data items may be values or pointers; the interface hides the
|
||||||
indirection.
|
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):
|
Major constructs ({} are metacharacters; [] marks optional elements):
|
||||||
|
|
||||||
{# comment }
|
{# comment }
|
||||||
@ -604,8 +610,8 @@ func (st *state) findVar(s string) reflect.Value {
|
|||||||
return st.data
|
return st.data
|
||||||
}
|
}
|
||||||
data := st.data
|
data := st.data
|
||||||
elems := strings.Split(s, ".", 0)
|
for _, elem := range strings.Split(s, ".", 0) {
|
||||||
for i := 0; i < len(elems); i++ {
|
origData := data // for method lookup need value before indirection.
|
||||||
// Look up field; data must be a struct or map.
|
// Look up field; data must be a struct or map.
|
||||||
data = reflect.Indirect(data)
|
data = reflect.Indirect(data)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
@ -614,20 +620,73 @@ func (st *state) findVar(s string) reflect.Value {
|
|||||||
|
|
||||||
switch typ := data.Type().(type) {
|
switch typ := data.Type().(type) {
|
||||||
case *reflect.StructType:
|
case *reflect.StructType:
|
||||||
field, ok := typ.FieldByName(elems[i])
|
if field, ok := typ.FieldByName(elem); ok {
|
||||||
if !ok {
|
data = data.(*reflect.StructValue).FieldByIndex(field.Index)
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
data = data.(*reflect.StructValue).FieldByIndex(field.Index)
|
|
||||||
case *reflect.MapType:
|
case *reflect.MapType:
|
||||||
data = data.(*reflect.MapValue).Elem(reflect.NewValue(elems[i]))
|
data = data.(*reflect.MapValue).Elem(reflect.NewValue(elem))
|
||||||
default:
|
continue
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No luck with that name; is it a method?
|
||||||
|
if result, found := callMethod(origData, elem); found {
|
||||||
|
data = result
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return data
|
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?
|
// Is there no data to look at?
|
||||||
func empty(v reflect.Value) bool {
|
func empty(v reflect.Value) bool {
|
||||||
v = reflect.Indirect(v)
|
v = reflect.Indirect(v)
|
||||||
@ -649,7 +708,7 @@ func empty(v reflect.Value) bool {
|
|||||||
return true
|
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 {
|
func (t *Template) varValue(name string, st *state) reflect.Value {
|
||||||
field := st.findVar(name)
|
field := st.findVar(name)
|
||||||
if field == nil {
|
if field == nil {
|
||||||
|
@ -45,6 +45,10 @@ type S struct {
|
|||||||
bytes []byte
|
bytes []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *S) pointerMethod() string { return "ptrmethod!" }
|
||||||
|
|
||||||
|
func (s S) valueMethod() string { return "valmethod!" }
|
||||||
|
|
||||||
var t1 = T{"ItemNumber1", "ValueNumber1"}
|
var t1 = T{"ItemNumber1", "ValueNumber1"}
|
||||||
var t2 = T{"ItemNumber2", "ValueNumber2"}
|
var t2 = T{"ItemNumber2", "ValueNumber2"}
|
||||||
|
|
||||||
@ -95,6 +99,19 @@ var tests = []*Test{
|
|||||||
out: "Header=77\n",
|
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
|
// Section
|
||||||
&Test{
|
&Test{
|
||||||
in: "{.section data }\n" +
|
in: "{.section data }\n" +
|
||||||
|
Loading…
Reference in New Issue
Block a user