diff --git a/src/pkg/template/template.go b/src/pkg/template/template.go index 078463aafd..1874851668 100644 --- a/src/pkg/template/template.go +++ b/src/pkg/template/template.go @@ -53,6 +53,13 @@ If it is not found, the search continues in outer sections until the top level is reached. + If the field value is a pointer, leading asterisks indicate + that the value to be inserted should be evaluated through the + pointer. For example, if x.p is of type *int, {x.p} will + insert the value of the pointer but {*x.p} will insert the + value of the underlying integer. If the value is nil or not a + pointer, asterisks have no effect. + If a formatter is specified, it must be named in the formatter map passed to the template set up routines or in the default set ("html","str","") and is used to process the data for @@ -633,6 +640,23 @@ func (t *Template) lookup(st *state, v reflect.Value, name string) reflect.Value return v } +// indirectPtr returns the item numLevels levels of indirection below the value. +// It is forgiving: if the value is not a pointer, it returns it rather than giving +// an error. If the pointer is nil, it is returned as is. +func indirectPtr(v reflect.Value, numLevels int) reflect.Value { + for i := numLevels; v != nil && i > 0; i++ { + if p, ok := v.(*reflect.PtrValue); ok { + if p.IsNil() { + return v + } + v = p.Elem() + } else { + break + } + } + return v +} + // Walk v through pointers and interfaces, extracting the elements within. func indirect(v reflect.Value) reflect.Value { loop: @@ -654,12 +678,16 @@ loop: // The special name "@" (the "cursor") denotes the current data. // The value coming in (st.data) might need indirecting to reach // a struct while the return value is not indirected - that is, -// it represents the actual named field. +// it represents the actual named field. Leading stars indicate +// levels of indirection to be applied to the value. func (t *Template) findVar(st *state, s string) reflect.Value { - if s == "@" { - return st.data - } data := st.data + flattenedName := strings.TrimLeft(s, "*") + numStars := len(s) - len(flattenedName) + s = flattenedName + if s == "@" { + return indirectPtr(data, numStars) + } for _, elem := range strings.Split(s, ".", -1) { // Look up field; data must be a struct or map. data = t.lookup(st, data, elem) @@ -667,7 +695,7 @@ func (t *Template) findVar(st *state, s string) reflect.Value { return nil } } - return data + return indirectPtr(data, numStars) } // Is there no data to look at? diff --git a/src/pkg/template/template_test.go b/src/pkg/template/template_test.go index 3842b6d6b5..c8707e6617 100644 --- a/src/pkg/template/template_test.go +++ b/src/pkg/template/template_test.go @@ -31,7 +31,10 @@ type U struct { type S struct { Header string + HeaderPtr *string Integer int + IntegerPtr *int + NilPtr *int Raw string InnerT T InnerPointerT *T @@ -47,6 +50,7 @@ type S struct { JSON interface{} Innermap U Stringmap map[string]string + Ptrmap map[string]*string Bytes []byte Iface interface{} Ifaceptr interface{} @@ -118,6 +122,24 @@ var tests = []*Test{ out: "Header=77\n", }, + &Test{ + in: "Pointers: {*HeaderPtr}={*IntegerPtr}\n", + + out: "Pointers: Header=77\n", + }, + + &Test{ + in: "Stars but not pointers: {*Header}={*Integer}\n", + + out: "Stars but not pointers: Header=77\n", + }, + + &Test{ + in: "nil pointer: {*NilPtr}={*Integer}\n", + + out: "nil pointer: =77\n", + }, + // Method at top level &Test{ in: "ptrmethod={PointerMethod}\n", @@ -407,6 +429,20 @@ var tests = []*Test{ out: "\tstringresult\n" + "\tstringresult\n", }, + &Test{ + in: "{*Ptrmap.stringkey1}\n", + + out: "pointedToString\n", + }, + &Test{ + in: "{.repeated section Ptrmap}\n" + + "{*@}\n" + + "{.end}", + + out: "pointedToString\n" + + "pointedToString\n", + }, + // Interface values @@ -460,7 +496,9 @@ func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) { s := new(S) // initialized by hand for clarity. s.Header = "Header" + s.HeaderPtr = &s.Header s.Integer = 77 + s.IntegerPtr = &s.Integer s.Raw = "&<>!@ #$%^" s.InnerT = t1 s.Data = []T{t1, t2} @@ -480,6 +518,10 @@ func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) { s.Stringmap = make(map[string]string) s.Stringmap["stringkey1"] = "stringresult" // the same value so repeated section is order-independent s.Stringmap["stringkey2"] = "stringresult" + s.Ptrmap = make(map[string]*string) + x := "pointedToString" + s.Ptrmap["stringkey1"] = &x // the same value so repeated section is order-independent + s.Ptrmap["stringkey2"] = &x s.Bytes = []byte("hello") s.Iface = []int{1, 2, 3} s.Ifaceptr = &T{"Item", "Value"}