mirror of
https://github.com/golang/go.git
synced 2024-09-30 14:57:10 +00:00
Map support for template.Execute().
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
This commit is contained in:
parent
c16f5cd9a6
commit
bfbb31595b
@ -10,10 +10,11 @@
|
|||||||
|
|
||||||
Templates are executed by applying them to a data structure.
|
Templates are executed by applying them to a data structure.
|
||||||
Annotations in the template refer to elements of the data
|
Annotations in the template refer to elements of the data
|
||||||
structure (typically a field of a struct) to control execution
|
structure (typically a field of a struct or a key in a map)
|
||||||
and derive values to be displayed. The template walks the
|
to control execution and derive values to be displayed.
|
||||||
structure as it executes and the "cursor" @ represents the
|
The template walks the structure as it executes and the
|
||||||
value at the current location in the structure.
|
"cursor" @ represents the value at the current location
|
||||||
|
in the structure.
|
||||||
|
|
||||||
Data items may be values or pointers; the interface hides the
|
Data items may be values or pointers; the interface hides the
|
||||||
indirection.
|
indirection.
|
||||||
@ -605,20 +606,24 @@ func (st *state) findVar(s string) reflect.Value {
|
|||||||
data := st.data;
|
data := st.data;
|
||||||
elems := strings.Split(s, ".", 0);
|
elems := strings.Split(s, ".", 0);
|
||||||
for i := 0; i < len(elems); i++ {
|
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);
|
data = reflect.Indirect(data);
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
typ, ok := data.Type().(*reflect.StructType);
|
|
||||||
if !ok {
|
switch typ := data.Type().(type) {
|
||||||
return nil
|
case *reflect.StructType:
|
||||||
}
|
|
||||||
field, ok := typ.FieldByName(elems[i]);
|
field, ok := typ.FieldByName(elems[i]);
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data = data.(*reflect.StructValue).FieldByIndex(field.Index);
|
data = data.(*reflect.StructValue).FieldByIndex(field.Index);
|
||||||
|
case *reflect.MapType:
|
||||||
|
data = data.(*reflect.MapValue).Elem(reflect.NewValue(elems[i]))
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ type T struct {
|
|||||||
value string;
|
value string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type U struct {
|
||||||
|
mp map[string]int;
|
||||||
|
}
|
||||||
|
|
||||||
type S struct {
|
type S struct {
|
||||||
header string;
|
header string;
|
||||||
integer int;
|
integer int;
|
||||||
@ -35,6 +39,8 @@ type S struct {
|
|||||||
vec *vector.Vector;
|
vec *vector.Vector;
|
||||||
true bool;
|
true bool;
|
||||||
false bool;
|
false bool;
|
||||||
|
mp map[string]string;
|
||||||
|
innermap U;
|
||||||
}
|
}
|
||||||
|
|
||||||
var t1 = T{"ItemNumber1", "ValueNumber1"}
|
var t1 = T{"ItemNumber1", "ValueNumber1"}
|
||||||
@ -275,6 +281,20 @@ var tests = []*Test{
|
|||||||
|
|
||||||
out: "1\n4\n",
|
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) {
|
func TestAll(t *testing.T) {
|
||||||
@ -293,6 +313,10 @@ func TestAll(t *testing.T) {
|
|||||||
s.vec.Push("elt2");
|
s.vec.Push("elt2");
|
||||||
s.true = true;
|
s.true = true;
|
||||||
s.false = false;
|
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;
|
var buf bytes.Buffer;
|
||||||
for _, test := range tests {
|
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) {
|
func TestStringDriverType(t *testing.T) {
|
||||||
tmpl, err := Parse("template: {@}", nil);
|
tmpl, err := Parse("template: {@}", nil);
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user