diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 2ab3c4dbd5..89c83ca61d 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -112,6 +112,7 @@ func Funcs() map[string]ast.Function { "substr": interpolationFuncSubstr(), "timestamp": interpolationFuncTimestamp(), "title": interpolationFuncTitle(), + "transpose": interpolationFuncTranspose(), "trimspace": interpolationFuncTrimSpace(), "upper": interpolationFuncUpper(), "urlencode": interpolationFuncURLEncode(), @@ -1548,6 +1549,53 @@ func interpolationFuncURLEncode() ast.Function { } } +// interpolationFuncTranspose implements the "transpose" function +// that converts a map (string,list) to a map (string,list) where +// the unique values of the original lists become the keys of the +// new map and the keys of the original map become values for the +// corresponding new keys. +func interpolationFuncTranspose() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeMap}, + ReturnType: ast.TypeMap, + Callback: func(args []interface{}) (interface{}, error) { + + inputMap := args[0].(map[string]ast.Variable) + outputMap := make(map[string]ast.Variable) + tmpMap := make(map[string][]string) + + for inKey, inVal := range inputMap { + if inVal.Type != ast.TypeList { + return nil, fmt.Errorf("transpose requires a map of lists of strings") + } + values := inVal.Value.([]ast.Variable) + for _, listVal := range values { + if listVal.Type != ast.TypeString { + return nil, fmt.Errorf("transpose requires the given map values to be lists of strings") + } + outKey := listVal.Value.(string) + if _, ok := tmpMap[outKey]; !ok { + tmpMap[outKey] = make([]string, 0) + } + outVal := tmpMap[outKey] + outVal = append(outVal, inKey) + sort.Strings(outVal) + tmpMap[outKey] = outVal + } + } + + for outKey, outVal := range tmpMap { + values := make([]ast.Variable, 0) + for _, v := range outVal { + values = append(values, ast.Variable{Type: ast.TypeString, Value: v}) + } + outputMap[outKey] = ast.Variable{Type: ast.TypeList, Value: values} + } + return outputMap, nil + }, + } +} + // interpolationFuncAbs returns the absolute value of a given float. func interpolationFuncAbs() ast.Function { return ast.Function{ diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 4522fd3483..04fcf1dec9 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -2615,6 +2615,82 @@ func TestInterpolateFuncURLEncode(t *testing.T) { }) } +func TestInterpolateFuncTranspose(t *testing.T) { + testFunction(t, testFunctionConfig{ + Vars: map[string]ast.Variable{ + "var.map": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key1": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + {Type: ast.TypeString, Value: "a"}, + {Type: ast.TypeString, Value: "b"}, + }, + }, + "key2": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + {Type: ast.TypeString, Value: "a"}, + {Type: ast.TypeString, Value: "b"}, + {Type: ast.TypeString, Value: "c"}, + }, + }, + "key3": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + {Type: ast.TypeString, Value: "c"}, + }, + }, + "key4": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{}, + }, + }}, + "var.badmap": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key1": ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + {Type: ast.TypeList, Value: []ast.Variable{}}, + {Type: ast.TypeList, Value: []ast.Variable{}}, + }, + }, + }}, + "var.worsemap": ast.Variable{ + Type: ast.TypeMap, + Value: map[string]ast.Variable{ + "key1": ast.Variable{ + Type: ast.TypeString, + Value: "not-a-list", + }, + }}, + }, + Cases: []testFunctionCase{ + { + `${transpose(var.map)}`, + map[string]interface{}{ + "a": []interface{}{"key1", "key2"}, + "b": []interface{}{"key1", "key2"}, + "c": []interface{}{"key2", "key3"}, + }, + false, + }, + { + `${transpose(var.badmap)}`, + nil, + true, + }, + { + `${transpose(var.worsemap)}`, + nil, + true, + }, + }, + }) +} + func TestInterpolateFuncAbs(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/website/docs/configuration/interpolation.html.md b/website/docs/configuration/interpolation.html.md index db93681be1..4afdf1fa5e 100644 --- a/website/docs/configuration/interpolation.html.md +++ b/website/docs/configuration/interpolation.html.md @@ -392,6 +392,8 @@ The supported built-in functions are: * `title(string)` - Returns a copy of the string with the first characters of all the words capitalized. + * `transpose(map)` - Swaps the keys and list values in a map of lists of strings. For example, transpose(map("a", list("1", "2"), "b", list("2", "3")) produces a value equivalent to map("1", list("a"), "2", list("a", "b"), "3", list("b")). + * `trimspace(string)` - Returns a copy of the string with all leading and trailing white spaces removed. * `upper(string)` - Returns a copy of the string with all Unicode letters mapped to their upper case.