From ec3fa07a160e69e47b0d544bdbe5a6cd6933925b Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 5 May 2024 12:59:37 +0200 Subject: [PATCH] feat: groupByWithDefault template function --- README.md | 1 + internal/template/groupby.go | 14 ++++ internal/template/groupby_test.go | 112 +++++++++++------------------- internal/template/template.go | 1 + 4 files changed, 58 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 12f1d8c0..80dfa588 100644 --- a/README.md +++ b/README.md @@ -382,6 +382,7 @@ For example, this is a JSON version of an emitted RuntimeContainer struct: - _`exists $path`_: Returns `true` if `$path` refers to an existing file or directory. Takes a string. - _`eval $templateName [$data]`_: Evaluates the named template like Go's built-in `template` action, but instead of writing out the result it returns the result as a string so that it can be post-processed. The `$data` argument may be omitted, which is equivalent to passing `nil`. - _`groupBy $containers $fieldPath`_: Groups an array of `RuntimeContainer` instances based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value, which must be a string. Returns a map from the value of the field path expression to an array of containers having that value. Containers that do not have a value for the field path in question are omitted. +- _`groupByWithDefault $containers $fieldPath $defaultValue`_: Returns the same as `groupBy`, but containers that do not have a value for the field path are instead included in the map under the `$defaultValue` key. - _`groupByKeys $containers $fieldPath`_: Returns the same as `groupBy` but only returns the keys of the map. - _`groupByMulti $containers $fieldPath $sep`_: Like `groupBy`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. A container whose `$fieldPath` value contains a list of strings will show up in the map output under each of those strings. - _`groupByLabel $containers $label`_: Returns the same as `groupBy` but grouping by the given label's value. diff --git a/internal/template/groupby.go b/internal/template/groupby.go index b7063b93..c40a0fd2 100644 --- a/internal/template/groupby.go +++ b/internal/template/groupby.go @@ -52,6 +52,20 @@ func groupBy(entries interface{}, key string) (map[string][]interface{}, error) }) } +// groupByWithDefault is the same as groupBy but allows a default value to be set +func groupByWithDefault(entries interface{}, key string, defaultValue string) (map[string][]interface{}, error) { + getValueWithDefault := func(v interface{}) (interface{}, error) { + value := deepGet(v, key) + if value == nil { + return defaultValue, nil + } + return value, nil + } + return generalizedGroupBy("groupByWithDefault", entries, getValueWithDefault, func(groups map[string][]interface{}, value interface{}, v interface{}) { + groups[value.(string)] = append(groups[value.(string)], v) + }) +} + // groupByKeys is the same as groupBy but only returns a list of keys func groupByKeys(entries interface{}, key string) ([]string, error) { keys, err := generalizedGroupByKey("groupByKeys", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) { diff --git a/internal/template/groupby_test.go b/internal/template/groupby_test.go index 628bb7cd..29126ba7 100644 --- a/internal/template/groupby_test.go +++ b/internal/template/groupby_test.go @@ -7,29 +7,34 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGroupByExistingKey(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - }, - ID: "3", - }, - } +var groupByContainers = []*context.RuntimeContainer{ + { + Env: map[string]string{ + "VIRTUAL_HOST": "demo1.localhost", + "EXTERNAL": "true", + }, + ID: "1", + }, + { + Env: map[string]string{ + "VIRTUAL_HOST": "demo1.localhost", + }, + ID: "2", + }, + { + Env: map[string]string{ + "VIRTUAL_HOST": "demo2.localhost", + "EXTERNAL": "true", + }, + ID: "3", + }, + { + ID: "4", + }, +} - groups, err := groupBy(containers, "Env.VIRTUAL_HOST") +func TestGroupByExistingKey(t *testing.T) { + groups, err := groupBy(groupByContainers, "Env.VIRTUAL_HOST") assert.NoError(t, err) assert.Len(t, groups, 2) @@ -39,30 +44,7 @@ func TestGroupByExistingKey(t *testing.T) { } func TestGroupByAfterWhere(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - "EXTERNAL": "true", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - "EXTERNAL": "true", - }, - ID: "3", - }, - } - - filtered, _ := where(containers, "Env.EXTERNAL", "true") + filtered, _ := where(groupByContainers, "Env.EXTERNAL", "true") groups, err := groupBy(filtered, "Env.VIRTUAL_HOST") assert.NoError(t, err) @@ -72,35 +54,25 @@ func TestGroupByAfterWhere(t *testing.T) { assert.Equal(t, "3", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID) } -func TestGroupByKeys(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - }, - ID: "3", - }, - } +func TestGroupByWithDefault(t *testing.T) { + groups, err := groupByWithDefault(groupByContainers, "Env.VIRTUAL_HOST", "default.localhost") + assert.NoError(t, err) + assert.Len(t, groups, 3) + assert.Len(t, groups["demo1.localhost"], 2) + assert.Len(t, groups["demo2.localhost"], 1) + assert.Len(t, groups["default.localhost"], 1) + assert.Equal(t, "4", groups["default.localhost"][0].(*context.RuntimeContainer).ID) +} + +func TestGroupByKeys(t *testing.T) { expected := []string{"demo1.localhost", "demo2.localhost"} - groups, err := groupByKeys(containers, "Env.VIRTUAL_HOST") + groups, err := groupByKeys(groupByContainers, "Env.VIRTUAL_HOST") assert.NoError(t, err) assert.ElementsMatch(t, expected, groups) - expected = []string{"1", "2", "3"} - groups, err = groupByKeys(containers, "ID") + expected = []string{"1", "2", "3", "4"} + groups, err = groupByKeys(groupByContainers, "ID") assert.NoError(t, err) assert.ElementsMatch(t, expected, groups) } diff --git a/internal/template/template.go b/internal/template/template.go index def540d3..9f31c1f6 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -65,6 +65,7 @@ func newTemplate(name string) *template.Template { "eval": eval, "exists": utils.PathExists, "groupBy": groupBy, + "groupByWithDefault": groupByWithDefault, "groupByKeys": groupByKeys, "groupByMulti": groupByMulti, "groupByLabel": groupByLabel,