Skip to content

Commit

Permalink
feat: Add pruneEmptyDicts template function
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Feb 8, 2023
1 parent 0bebd8d commit 7ff0aca
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `pruneEmptyDicts` *dict*

`pruneEmptyDicts` modifies *dict* to remove nested empty dicts. Properties are
pruned from the bottom up, so any nested dicts that themselves only contain
empty dicts are pruned.

!!! example

```
{{ dict "key" "value" inner (dict) | pruneEmptyDicts | toJson }}
```
1 change: 1 addition & 0 deletions assets/chezmoi.io/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ nav:
- lstat: reference/templates/functions/lstat.md
- mozillaInstallHash: reference/templates/functions/mozillaInstallHash.md
- output: reference/templates/functions/output.md
- pruneEmptyDicts: reference/templates/functions/pruneEmptyDicts.md
- quoteList: reference/templates/functions/quoteList.md
- replaceAllRegex: reference/templates/functions/replaceAllRegex.md
- setValueAtPath: reference/templates/functions/setValueAtPath.md
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ func newConfig(options ...configOption) (*Config, error) {
"passFields": c.passFieldsTemplateFunc,
"passRaw": c.passRawTemplateFunc,
"passhole": c.passholeTemplateFunc,
"pruneEmptyDicts": c.pruneEmptyDictsTemplateFunc,
"quoteList": c.quoteListTemplateFunc,
"replaceAllRegex": c.replaceAllRegexTemplateFunc,
"secret": c.secretTemplateFunc,
Expand Down
20 changes: 20 additions & 0 deletions pkg/cmd/templatefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ func (c *Config) outputTemplateFunc(name string, args ...string) string {
return string(output)
}

func (c *Config) pruneEmptyDictsTemplateFunc(dict map[string]any) map[string]any {
pruneEmptyMaps(dict)
return dict
}

func (c *Config) quoteListTemplateFunc(list []any) []string {
result := make([]string, 0, len(list))
for _, elem := range list {
Expand Down Expand Up @@ -587,6 +592,21 @@ func needsQuote(s string) bool {
return false
}

// pruneEmptyMaps prunes all empty maps from m and returns if m is now empty
// itself.
func pruneEmptyMaps(m map[string]any) bool {
for key, value := range m {
nestedMap, ok := value.(map[string]any)
if !ok {
continue
}
if pruneEmptyMaps(nestedMap) {
delete(m, key)
}
}
return len(m) == 0
}

func sortedKeys[K constraints.Ordered, V any](m map[K]V) []K {
keys := maps.Keys(m)
slices.Sort(keys)
Expand Down
33 changes: 33 additions & 0 deletions pkg/cmd/templatefuncs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,39 @@ func TestDeleteValueAtPathTemplateFunc(t *testing.T) {
}
}

func TestPruneEmptyDicts(t *testing.T) {
for _, tc := range []struct {
name string
dict map[string]any
expected map[string]any
}{
{
name: "nil",
dict: nil,
expected: nil,
},
{
name: "empty",
dict: map[string]any{},
expected: map[string]any{},
},
{
name: "nested_empty",
dict: map[string]any{
"key1": map[string]any{},
"key2": 0,
},
expected: map[string]any{
"key2": 0,
},
},
} {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, (&Config{}).pruneEmptyDictsTemplateFunc(tc.dict))
})
}
}

func TestSetValueAtPathTemplateFunc(t *testing.T) {
for _, tc := range []struct {
name string
Expand Down
7 changes: 7 additions & 0 deletions pkg/cmd/testdata/scripts/templatefuncs.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ stdout 2656FF1E876E9973
[!(windows||illumos)] stderr 'error calling output: .*/false: exit status 1'
[illumos] stderr 'error calling output: .*/false: exit status 255'

# test pruneEmptyDicts template function
exec chezmoi execute-template '{{ dict "key1" "value1" "key2" (dict) | pruneEmptyDicts | toJson }}'
rmfinalnewline golden/pruneEmptyDicts
cmp stdout golden/pruneEmptyDicts

# test replaceAllRegex template function
exec chezmoi execute-template '{{ "foo bar baz" | replaceAllRegex "ba" "BA" }}'
stdout 'foo BAr BAz'
Expand Down Expand Up @@ -191,6 +196,8 @@ file2.txt
# contents of .include
-- golden/include-relpath --
# contents of .local/share/chezmoi/.include
-- golden/pruneEmptyDicts --
{"key1":"value1"}
-- golden/setValueAtPath --
{"key1":{"key2":"value2"}}
-- golden/toIni --
Expand Down

0 comments on commit 7ff0aca

Please sign in to comment.