]> Cypherpunks repositories - gostls13.git/commitdiff
Map support for template.Execute().
authorJames Meneghello <rawrz0r@gmail.com>
Fri, 20 Nov 2009 05:08:05 +0000 (21:08 -0800)
committerRob Pike <r@golang.org>
Fri, 20 Nov 2009 05:08:05 +0000 (21:08 -0800)
Allows the developer to pass a map either by itself for
evaluation, or inside a struct. Access to data inside
maps is identical to the current system for structs, ie.

-Psuedocode-

mp map[string]string = {
"header" : "A fantastic header!",
"footer" : "A not-so-fantastic footer!",
}
template.Execute(mp)

...can be accessed using {header} and {footer} in
the template. Similarly, for maps inside structs:

type s struct {
mp map[string]string,
}
s1 = new s
s1.mp["header"] = "A fantastic header!";
template.Execute(s1)

...is accessed using {mp.header}. Multi-maps, ie.
map[string](map[string]string) and maps of structs
containing more maps are unsupported, but then, I'm
not even sure if that's supported by the language.

Map elements can be of any type that can be written
by the formatters. Keys should really only be strings.

Fixes #259.

R=r, rsc
https://golang.org/cl/157088

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

index 9a819db61e91c41fd9aaf8ebb038a326a0ddb08c..6964d67f43fbc0d2c303ad6242b5fb6c7521b7b6 100644 (file)
 
        Templates are executed by applying them to a data structure.
        Annotations in the template refer to elements of the data
-       structure (typically a field of a struct) to control execution
-       and derive values to be displayed.  The template walks the
-       structure as it executes and the "cursor" @ represents the
-       value at the current location in the structure.
+       structure (typically a field of a struct or a key in a map)
+       to control execution and derive values to be displayed.
+       The template walks the structure as it executes and the
+       "cursor" @ represents the value at the current location
+       in the structure.
 
        Data items may be values or pointers; the interface hides the
        indirection.
@@ -605,20 +606,24 @@ func (st *state) findVar(s string) reflect.Value {
        data := st.data;
        elems := strings.Split(s, ".", 0);
        for i := 0; i < len(elems); i++ {
-               // Look up field; data must be a struct.
+               // Look up field; data must be a struct or map.
                data = reflect.Indirect(data);
                if data == nil {
                        return nil
                }
-               typ, ok := data.Type().(*reflect.StructType);
-               if !ok {
-                       return nil
-               }
-               field, ok := typ.FieldByName(elems[i]);
-               if !ok {
+
+               switch typ := data.Type().(type) {
+               case *reflect.StructType:
+                       field, ok := typ.FieldByName(elems[i]);
+                       if !ok {
+                               return nil
+                       }
+                       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.StructValue).FieldByIndex(field.Index);
        }
        return data;
 }
index 0b95fcff41caacbc425dcce062064ae9b41d3007..8dadd27f7de3f03efa5cc3a132c966fe2ca5a4a6 100644 (file)
@@ -21,6 +21,10 @@ type T struct {
        value   string;
 }
 
+type U struct {
+       mp map[string]int;
+}
+
 type S struct {
        header          string;
        integer         int;
@@ -35,6 +39,8 @@ type S struct {
        vec             *vector.Vector;
        true            bool;
        false           bool;
+       mp              map[string]string;
+       innermap        U;
 }
 
 var t1 = T{"ItemNumber1", "ValueNumber1"}
@@ -275,6 +281,20 @@ var tests = []*Test{
 
                out: "1\n4\n",
        },
+
+       // Maps
+
+       &Test{
+               in: "{mp.mapkey}\n",
+
+               out: "Ahoy!\n",
+       },
+
+       &Test{
+               in: "{innermap.mp.innerkey}\n",
+
+               out: "55\n",
+       },
 }
 
 func TestAll(t *testing.T) {
@@ -293,6 +313,10 @@ func TestAll(t *testing.T) {
        s.vec.Push("elt2");
        s.true = true;
        s.false = false;
+       s.mp = make(map[string]string);
+       s.mp["mapkey"] = "Ahoy!";
+       s.innermap.mp = make(map[string]int);
+       s.innermap.mp["innerkey"] = 55;
 
        var buf bytes.Buffer;
        for _, test := range tests {
@@ -318,6 +342,24 @@ func TestAll(t *testing.T) {
        }
 }
 
+func TestMapDriverType(t *testing.T) {
+       mp := map[string]string{"footer": "Ahoy!"};
+       tmpl, err := Parse("template: {footer}", nil);
+       if err != nil {
+               t.Error("unexpected parse error:", err)
+       }
+       var b bytes.Buffer;
+       err = tmpl.Execute(mp, &b);
+       if err != nil {
+               t.Error("unexpected execute error:", err)
+       }
+       s := b.String();
+       expected := "template: Ahoy!";
+       if s != expected {
+               t.Errorf("failed passing string as data: expected %q got %q", "template: Ahoy!", s)
+       }
+}
+
 func TestStringDriverType(t *testing.T) {
        tmpl, err := Parse("template: {@}", nil);
        if err != nil {