ref: a2670bf460e10ed5de69f90abbe7c4e2b32068cf
parent: 1a36ce9b0903e02a5068aed5f807ed9d21f48ece
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Mon Nov 11 09:37:37 EST 2019
tpl/collections: Allow dict to create nested structures Fixes #6497
--- a/docs/content/en/functions/dict.md
+++ b/docs/content/en/functions/dict.md
@@ -21,6 +21,12 @@
`dict` is especially useful for passing more than one value to a partial template.
+Note that the `key` can be either a `string` or a `string slice`. The latter is useful to create a deply nested structure, e.g.:
+
+```go-text-template
+{{ $m := dict (slice "a" "b" "c") "value" }}
+```
+
## Example: Using `dict` to pass multiple values to a `partial`
--- a/tpl/collections/collections.go
+++ b/tpl/collections/collections.go
@@ -145,22 +145,41 @@
// Dictionary creates a map[string]interface{} from the given parameters by
// walking the parameters and treating them as key-value pairs. The number
// of parameters must be even.
+// The keys can be string slices, which will create the needed nested structure.
func (ns *Namespace) Dictionary(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dictionary call")
}
- dict := make(map[string]interface{}, len(values)/2)
+ root := make(map[string]interface{})
for i := 0; i < len(values); i += 2 {
- key, ok := values[i].(string)
- if !ok {
- return nil, errors.New("dictionary keys must be strings")
+ dict := root
+ var key string
+ switch v := values[i].(type) {
+ case string:
+ key = v
+ case []string:
+ for i := 0; i < len(v)-1; i++ {
+ key = v[i]
+ var m map[string]interface{}
+ v, found := dict[key]
+ if found {
+ m = v.(map[string]interface{})
+ } else {
+ m = make(map[string]interface{})
+ dict[key] = m
+ }
+ dict = m
+ }
+ key = v[len(v)-1]
+ default:
+ return nil, errors.New("invalid dictionary key")
}
dict[key] = values[i+1]
}
- return dict, nil
+ return root, nil
}
// EchoParam returns a given value if it is set; otherwise, it returns an
--- a/tpl/collections/collections_test.go
+++ b/tpl/collections/collections_test.go
@@ -182,7 +182,6 @@
}
func TestDictionary(t *testing.T) {
- t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
@@ -192,22 +191,30 @@
expect interface{}
}{
{[]interface{}{"a", "b"}, map[string]interface{}{"a": "b"}},
+ {[]interface{}{[]string{"a", "b"}, "c"}, map[string]interface{}{"a": map[string]interface{}{"b": "c"}}},
+ {[]interface{}{[]string{"a", "b"}, "c", []string{"a", "b2"}, "c2", "b", "c"},
+ map[string]interface{}{"a": map[string]interface{}{"b": "c", "b2": "c2"}, "b": "c"}},
{[]interface{}{"a", 12, "b", []int{4}}, map[string]interface{}{"a": 12, "b": []int{4}}},
// errors
{[]interface{}{5, "b"}, false},
{[]interface{}{"a", "b", "c"}, false},
} {
- errMsg := qt.Commentf("[%d] %v", i, test.values)
+ i := i
+ test := test
+ c.Run(fmt.Sprint(i), func(c *qt.C) {
+ c.Parallel()
+ errMsg := qt.Commentf("[%d] %v", i, test.values)
- result, err := ns.Dictionary(test.values...)
+ result, err := ns.Dictionary(test.values...)
- if b, ok := test.expect.(bool); ok && !b {
- c.Assert(err, qt.Not(qt.IsNil), errMsg)
- continue
- }
+ if b, ok := test.expect.(bool); ok && !b {
+ c.Assert(err, qt.Not(qt.IsNil), errMsg)
+ return
+ }
- c.Assert(err, qt.IsNil, errMsg)
- c.Assert(result, qt.DeepEquals, test.expect, errMsg)
+ c.Assert(err, qt.IsNil, errMsg)
+ c.Assert(result, qt.DeepEquals, test.expect, qt.Commentf(fmt.Sprint(result)))
+ })
}
}