Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

two new functions splitKeyValuePairs and groupByMultiKeyValuePairs #319

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
* *`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.
* *`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.
* *`groupByMultiKeyValuePairs $containers $fieldPath $listSep $kvpSep [$defaultKey]`*: Like `groupByMulti`, but the string value specified by `$fieldPath` is split by `splitKeyValuePairs` into a list of key value pairs. The container grouping is done based on the keys.A container will show up in the map output under each of the keys.
* *`groupByLabel $containers $label`*: Returns the same as `groupBy` but grouping by the given label's value.
* *`hasPrefix $prefix $string`*: Returns whether `$prefix` is a prefix of `$string`.
* *`hasSuffix $suffix $string`*: Returns whether `$suffix` is a suffix of `$string`.
Expand All @@ -368,6 +369,8 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
* *`sha1 $string`*: Returns the hexadecimal representation of the SHA1 hash of `$string`.
* *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split)
* *`splitN $string $sep $count`*: Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`. Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN)
* *`splitKeyValuePairs $string $listSep $kvpSep [$defaultKey]`*: Splits `$string` into a slice of substrings delimited by `$listSep`, each substring is then splitted by `$kvpSep`, the result is a map of key value pairs. `$defaultKey` is used for substrings which do not contain `$kvpSep` and therfore the substring cannot be splitted into a key value pair.
E.g `$string` = `key1=value1,value2`, first the string is splitted by e.g `$listSep` = `,`, which results in two strings `key1=value1` and `value2`. In a next step each string is splitted by e.g. `$kvpSep`= `=`: first string is splitted into `key1` and `value1`. The second string does not contain the `$kvpSep` = `=`: If `$defaultKey` is omitted or empty the string is splitted into `value1` as key and `value1` as value. If `$defaultKey` is set the string is splitted into value of`$defaultKey` as key and `value1`.
* *`trimPrefix $prefix $string`*: If `$prefix` is a prefix of `$string`, return `$string` with `$prefix` trimmed from the beginning. Otherwise, return `$string` unchanged.
* *`trimSuffix $suffix $string`*: If `$suffix` is a suffix of `$string`, return `$string` with `$suffix` trimmed from the end. Otherwise, return `$string` unchanged.
* *`trim $string`*: Removes whitespace from both sides of `$string`.
Expand Down
119 changes: 82 additions & 37 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,49 @@ func generalizedGroupByKey(funcName string, entries interface{}, key string, add
return generalizedGroupBy(funcName, entries, getKey, addEntry)
}

// splitKeyValuePairs splits a input string into a map of key value pairs, first string is split by listSep into list items, then each list item is split by kvpSep into key value pair
// if a list item does not contai the kvpSep a defaultKey can be provided, where these values are grouped, or if omitted these values are used as key and value
func splitKeyValuePairs(input string, listSep string, kvpSep string, defaultKey ...string) map[string]string {
keyValuePairs := strings.Split(input, listSep)

output := map[string]string{}
for _, kvp := range keyValuePairs {
var key string
var value string
if strings.Contains(kvp, kvpSep) {
splitted := strings.Split(kvp, kvpSep)
key = splitted[0]
value = splitted[1]
} else if len(defaultKey) == 0 || defaultKey[0] == "" {
// no key found, no default key specified
key = kvp
value = kvp
} else {
// no key found, use default key specified instead
key = defaultKey[0]
value = kvp
}

output[key] = value
}

return output
}

// groupByMultiKeyValuePairs similar to groupByMulti, but the key value ist split into a list (delimited by listSep) of key value pairs (seperated by kvpSep: <key>kvpSep<value, e.g key1=value1>)
// An array or slice entry will show up in the output map under all of the list key value pair keys
func groupByMultiKeyValuePairs(entries interface{}, key, listSep string, kvpSep string, defaultKey string) (map[string][]interface{}, error) {
return generalizedGroupByKey("groupByMultiKeyValuePairs", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {

keyValuePairs := splitKeyValuePairs(value.(string), listSep, kvpSep, defaultKey)
for key := range keyValuePairs {
groups[key] = append(groups[key], v)
}
})
}

// groupByMulti groups a generic array or slice by the path property keys value, where the path value is first split by sep into a list of key strings.
// An array or slice entry will show up in the output map under all of the list keys
func groupByMulti(entries interface{}, key, sep string) (map[string][]interface{}, error) {
return generalizedGroupByKey("groupByMulti", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) {
items := strings.Split(value.(string), sep)
Expand Down Expand Up @@ -420,43 +463,45 @@ func when(condition bool, trueValue, falseValue interface{}) interface{} {

func newTemplate(name string) *template.Template {
tmpl := template.New(name).Funcs(template.FuncMap{
"closest": arrayClosest,
"coalesce": coalesce,
"contains": contains,
"dict": dict,
"dir": dirList,
"exists": exists,
"first": arrayFirst,
"groupBy": groupBy,
"groupByKeys": groupByKeys,
"groupByMulti": groupByMulti,
"groupByLabel": groupByLabel,
"hasPrefix": hasPrefix,
"hasSuffix": hasSuffix,
"json": marshalJson,
"intersect": intersect,
"keys": keys,
"last": arrayLast,
"replace": strings.Replace,
"parseBool": strconv.ParseBool,
"parseJson": unmarshalJson,
"queryEscape": url.QueryEscape,
"sha1": hashSha1,
"split": strings.Split,
"splitN": strings.SplitN,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"trim": trim,
"when": when,
"where": where,
"whereNot": whereNot,
"whereExist": whereExist,
"whereNotExist": whereNotExist,
"whereAny": whereAny,
"whereAll": whereAll,
"whereLabelExists": whereLabelExists,
"whereLabelDoesNotExist": whereLabelDoesNotExist,
"whereLabelValueMatches": whereLabelValueMatches,
"closest": arrayClosest,
"coalesce": coalesce,
"contains": contains,
"dict": dict,
"dir": dirList,
"exists": exists,
"first": arrayFirst,
"groupBy": groupBy,
"groupByKeys": groupByKeys,
"groupByMulti": groupByMulti,
"groupByMultiKeyValuePairs": groupByMultiKeyValuePairs,
"groupByLabel": groupByLabel,
"hasPrefix": hasPrefix,
"hasSuffix": hasSuffix,
"json": marshalJson,
"intersect": intersect,
"keys": keys,
"last": arrayLast,
"replace": strings.Replace,
"parseBool": strconv.ParseBool,
"parseJson": unmarshalJson,
"queryEscape": url.QueryEscape,
"sha1": hashSha1,
"split": strings.Split,
"splitN": strings.SplitN,
"splitKeyValuePairs": splitKeyValuePairs,
"trimPrefix": trimPrefix,
"trimSuffix": trimSuffix,
"trim": trim,
"when": when,
"where": where,
"whereNot": whereNot,
"whereExist": whereExist,
"whereNotExist": whereNotExist,
"whereAny": whereAny,
"whereAll": whereAll,
"whereLabelExists": whereLabelExists,
"whereLabelDoesNotExist": whereLabelDoesNotExist,
"whereLabelValueMatches": whereLabelValueMatches,
})
return tmpl
}
Expand Down
67 changes: 67 additions & 0 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,73 @@ func TestGroupByMulti(t *testing.T) {
}
}

func TestGroupByMultiKeyValuePairs(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_PORT": "443:3000,3000:3000",
},
ID: "1",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_PORT": "1111,250:360",
},
ID: "2",
},
&RuntimeContainer{
Env: map[string]string{
"VIRTUAL_PORT": "123",
},
ID: "3",
},
}

groups, _ := groupByMultiKeyValuePairs(containers, "Env.VIRTUAL_PORT", ",", ":", "445")
if len(groups) != 4 {
t.Fatalf("expected 4 got %d", len(groups))
}

if len(groups["445"]) != 2 {
t.Fatalf("expected 2 got %d", len(groups["445"]))
}

if len(groups["443"]) != 1 {
t.Fatalf("expected 1 got %d", len(groups["443"]))
}
if groups["445"][1].(RuntimeContainer).ID != "3" {
t.Fatalf("expected 1 got %s", groups["445"][0].(RuntimeContainer).ID)
}
if len(groups["3000"]) != 1 {
t.Fatalf("expect 1 got %d", len(groups["3000"]))
}
if groups["250"][0].(RuntimeContainer).ID != "2" {
t.Fatalf("expected 1 got %s", groups["250"][0].(RuntimeContainer).ID)
}
}

func TestSplitKeyValuePairs1(t *testing.T) {
list := splitKeyValuePairs("key=value,1=2,test", ",", "=")

if len(list) != 3 {
t.Fatalf("expected 3 got %d", len(list))
}
if list["test"] != "test" {
t.Fatalf("expected value 'test' got '%s'", list["test"])
}
}

func TestSplitKeyValuePairs2(t *testing.T) {
list := splitKeyValuePairs("key:value/1:2/test", "/", ":")

if len(list) != 3 {
t.Fatalf("expected 3 got %d", len(list))
}
if list["test"] != "test" {
t.Fatalf("expected value 'test' got '%s'", list["test"])
}
}

func TestWhere(t *testing.T) {
containers := []*RuntimeContainer{
&RuntimeContainer{
Expand Down