Skip to content

Commit

Permalink
tpl/collections: Allow dict to create nested structures
Browse files Browse the repository at this point in the history
Fixes #6497
  • Loading branch information
bep committed Nov 11, 2019
1 parent 1a36ce9 commit a2670bf
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 17 deletions.
6 changes: 6 additions & 0 deletions docs/content/en/functions/dict.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
29 changes: 24 additions & 5 deletions tpl/collections/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 19 additions & 12 deletions tpl/collections/collections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ func TestDelimit(t *testing.T) {
}

func TestDictionary(t *testing.T) {
t.Parallel()
c := qt.New(t)

ns := New(&deps.Deps{})
Expand All @@ -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)

result, err := ns.Dictionary(test.values...)

if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil), errMsg)
continue
}

c.Assert(err, qt.IsNil, errMsg)
c.Assert(result, qt.DeepEquals, test.expect, errMsg)
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...)

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, qt.Commentf(fmt.Sprint(result)))
})
}
}

Expand Down

0 comments on commit a2670bf

Please sign in to comment.