From 42a2e9598979f998ccdd91f63dc149795b6f6d22 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Wed, 16 Dec 2009 03:10:50 -0800 Subject: [PATCH] add the ability to invoke niladic single-valued methods in templates. Fixes #389. R=rsc CC=golang-dev https://golang.org/cl/180061 --- src/pkg/template/template.go | 79 +++++++++++++++++++++++++++---- src/pkg/template/template_test.go | 17 +++++++ 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" +