tpl/collections: Allow dict to create nested structures

Fixes #6497
This commit is contained in:
Bjørn Erik Pedersen 2019-11-11 14:37:37 +01:00
parent 1a36ce9b09
commit a2670bf460
3 changed files with 46 additions and 14 deletions

View file

@ -21,6 +21,12 @@ aliases: []
`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`

View file

@ -145,22 +145,41 @@ func (ns *Namespace) Delimit(seq, delimiter interface{}, last ...interface{}) (t
// 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

View file

@ -182,7 +182,6 @@ func TestDelimit(t *testing.T) {
}
func TestDictionary(t *testing.T) {
t.Parallel()
c := qt.New(t)
ns := New(&deps.Deps{})
@ -192,22 +191,30 @@ func TestDictionary(t *testing.T) {
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)))
})
}
}