Commit 5008c63e authored by Rob Pike's avatar Rob Pike

template: allow a leading '*' to indicate that evaulation should

indirect through a pointer.

Fixes #1478.

R=rsc, r2
CC=golang-dev
https://golang.org/cl/4131045
parent 6e615a57
......@@ -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 {
data := st.data
flattenedName := strings.TrimLeft(s, "*")
numStars := len(s) - len(flattenedName)
s = flattenedName
if s == "@" {
return st.data
return indirectPtr(data, numStars)
}
data := st.data
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?
......
......@@ -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: <nil>=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"}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment