Skip to content

Commit

Permalink
Merge pull request #254 from nyarly/dig-into-dict
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Farina <matt@mattfarina.com>
  • Loading branch information
mattfarina committed Dec 2, 2020
2 parents 0111002 + eaeb5ac commit ef25c39
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 2 deletions.
26 changes: 26 additions & 0 deletions dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,29 @@ func deepCopy(i interface{}) interface{} {
func mustDeepCopy(i interface{}) (interface{}, error) {
return copystructure.Copy(i)
}

func dig(ps ...interface{}) (interface{}, error) {
if len(ps) < 3 {
panic("dig needs at least three arguments")
}
dict := ps[len(ps)-1].(map[string]interface{})
def := ps[len(ps)-2]
ks := make([]string, len(ps)-2)
for i := 0; i < len(ks); i++ {
ks[i] = ps[i].(string)
}

return digFromDict(dict, def, ks)
}

func digFromDict(dict map[string]interface{}, d interface{}, ks []string) (interface{}, error) {
k, ns := ks[0], ks[1:len(ks)]
step, has := dict[k]
if !has {
return d, nil
}
if len(ns) == 0 {
return step, nil
}
return digFromDict(step.(map[string]interface{}), d, ns)
}
15 changes: 15 additions & 0 deletions dict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,18 @@ func TestMustDeepCopy(t *testing.T) {
}
}
}

func TestDig(t *testing.T) {
tests := map[string]string{
`{{- $d := dict "a" (dict "b" (dict "c" 1)) }}{{ dig "a" "b" "c" "" $d }}`: "1",
`{{- $d := dict "a" (dict "b" (dict "c" 1)) }}{{ dig "a" "b" "z" "2" $d }}`: "2",
`{{ dict "a" 1 | dig "a" "" }}`: "1",
`{{ dict "a" 1 | dig "z" "2" }}`: "2",
}

for tpl, expect := range tests {
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
}
34 changes: 34 additions & 0 deletions docs/dicts.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,40 @@ inserted.
A common idiom in Sprig templates is to uses `pluck... | first` to get the first
matching key out of a collection of dictionaries.

## dig

The `dig` function traverses a nested set of dicts, selecting keys from a list
of values. It returns a default value if any of the keys are not found at the
associated dict.

```
dig "user" "role" "humanName" "guest" $dict
```

Given a dict structured like
```
{
user: {
role: {
humanName: "curator"
}
}
}
```

the above would return `"curator"`. If the dict lacked even a `user` field,
the result would be `"guest"`.

Dig can be very useful in cases where you'd like to avoid guard clauses,
especially since Go's template package's `and` doesn't shortcut. For instance
`and a.maybeNil a.maybeNil.iNeedThis` will always evaluate
`a.maybeNil.iNeedThis`, and panic if `a` lacks a `maybeNil` field.)

`dig` accepts its dict argument last in order to support pipelining. For instance:
```
merge a b c | dig "one" "two" "three" "<missing>"
```

## merge, mustMerge

Merge two or more dictionaries into one, giving precedence to the dest dictionary:
Expand Down
3 changes: 1 addition & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The Sprig library provides over 70 template functions for Go's template language
- [Defaults Functions](defaults.md): `default`, `empty`, `coalesce`, `fromJson`, `toJson`, `toPrettyJson`, `toRawJson`, `ternary`
- [Encoding Functions](encoding.md): `b64enc`, `b64dec`, etc.
- [Lists and List Functions](lists.md): `list`, `first`, `uniq`, etc.
- [Dictionaries and Dict Functions](dicts.md): `get`, `set`, `dict`, `hasKey`, `pluck`, `deepCopy`, etc.
- [Dictionaries and Dict Functions](dicts.md): `get`, `set`, `dict`, `hasKey`, `pluck`, `dig`, `deepCopy`, etc.
- [Type Conversion Functions](conversion.md): `atoi`, `int64`, `toString`, etc.
- [File Path Functions](paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`
- [Flow Control Functions](flow_control.md): `fail`
Expand All @@ -21,4 +21,3 @@ The Sprig library provides over 70 template functions for Go's template language
- [Reflection](reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
- [Cryptographic and Security Functions](crypto.md): `derivePassword`, `sha256sum`, `genPrivateKey`, etc.
- [Network](network.md): `getHostByName`

1 change: 1 addition & 0 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ var genericMap = map[string]interface{}{
"slice": slice,
"mustSlice": mustSlice,
"concat": concat,
"dig": dig,

// Crypto:
"bcrypt": bcrypt,
Expand Down

0 comments on commit ef25c39

Please sign in to comment.