aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2009-12-16 03:10:50 -0800
committerRob Pike <r@golang.org>2009-12-16 03:10:50 -0800
commit42a2e9598979f998ccdd91f63dc149795b6f6d22 (patch)
tree5e5cadf91c7b1a0ae3dd4b365fe0f04237edf0e7
parenta8fbf5dc2cd5b58167402df47bb06217c5e8fd22 (diff)
downloadgo-42a2e9598979f998ccdd91f63dc149795b6f6d22.tar.gz
go-42a2e9598979f998ccdd91f63dc149795b6f6d22.zip
add the ability to invoke niladic single-valued methods in templates.
Fixes #389. R=rsc CC=golang-dev https://golang.org/cl/180061
-rw-r--r--src/pkg/template/template.go79
-rw-r--r--src/pkg/template/template_test.go17
2 files changed, 86 insertions, 10 deletions
diff --git a/src/pkg/template/template.go b/src/pkg/template/template.go
index 8e39d802ee..b46d28613c 100644
--- a/src/pkg/template/template.go
+++ b/src/pkg/template/template.go
@@ -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 {
diff --git a/src/pkg/template/template_test.go b/src/pkg/template/template_test.go
index aa156d2f8f..c2bc5125fa 100644
--- a/src/pkg/template/template_test.go
+++ b/src/pkg/template/template_test.go
@@ -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" +