]> Cypherpunks repositories - gostls13.git/commitdiff
template: allow a leading '*' to indicate that evaulation should
authorRob Pike <r@golang.org>
Fri, 4 Feb 2011 23:21:08 +0000 (15:21 -0800)
committerRob Pike <r@golang.org>
Fri, 4 Feb 2011 23:21:08 +0000 (15:21 -0800)
indirect through a pointer.

Fixes #1478.

R=rsc, r2
CC=golang-dev
https://golang.org/cl/4131045

src/pkg/template/template.go
src/pkg/template/template_test.go

index 078463aafdaaef354b4f16f51121a1638797b590..1874851668ae1f06142e25b61c364e3b78d46f78 100644 (file)
        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?
index 3842b6d6b5b047bf1dfbb843559e25c2f346dc20..c8707e6617ad542fbcda42db88b5c5f1c4d018a4 100644 (file)
@@ -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"}