Add merge interpolation function
Add a `merge` interpolation function, which merges any number of maps. Duplicate keys are OK, with the last write winning.
This commit is contained in:
parent
8c4b4edd2f
commit
39bbbb8da6
|
@ -73,6 +73,7 @@ func Funcs() map[string]ast.Function {
|
||||||
"lower": interpolationFuncLower(),
|
"lower": interpolationFuncLower(),
|
||||||
"map": interpolationFuncMap(),
|
"map": interpolationFuncMap(),
|
||||||
"md5": interpolationFuncMd5(),
|
"md5": interpolationFuncMd5(),
|
||||||
|
"merge": interpolationFuncMerge(),
|
||||||
"uuid": interpolationFuncUUID(),
|
"uuid": interpolationFuncUUID(),
|
||||||
"replace": interpolationFuncReplace(),
|
"replace": interpolationFuncReplace(),
|
||||||
"sha1": interpolationFuncSha1(),
|
"sha1": interpolationFuncSha1(),
|
||||||
|
@ -898,6 +899,26 @@ func interpolationFuncMd5() ast.Function {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func interpolationFuncMerge() ast.Function {
|
||||||
|
return ast.Function{
|
||||||
|
ArgTypes: []ast.Type{ast.TypeMap},
|
||||||
|
ReturnType: ast.TypeMap,
|
||||||
|
Variadic: true,
|
||||||
|
VariadicType: ast.TypeMap,
|
||||||
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
|
outputMap := make(map[string]ast.Variable)
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
for k, v := range arg.(map[string]ast.Variable) {
|
||||||
|
outputMap[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputMap, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// interpolationFuncUpper implements the "upper" function that does
|
// interpolationFuncUpper implements the "upper" function that does
|
||||||
// string upper casing.
|
// string upper casing.
|
||||||
func interpolationFuncUpper() ast.Function {
|
func interpolationFuncUpper() ast.Function {
|
||||||
|
|
|
@ -498,6 +498,103 @@ func TestInterpolateFuncConcat(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterpolateFuncMerge(t *testing.T) {
|
||||||
|
testFunction(t, testFunctionConfig{
|
||||||
|
Cases: []testFunctionCase{
|
||||||
|
// basic merge
|
||||||
|
{
|
||||||
|
`${merge(map("a", "b"), map("c", "d"))}`,
|
||||||
|
map[string]interface{}{"a": "b", "c": "d"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// merge with conflicts is ok, last in wins.
|
||||||
|
{
|
||||||
|
`${merge(map("a", "b", "c", "X"), map("c", "d"))}`,
|
||||||
|
map[string]interface{}{"a": "b", "c": "d"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// merge variadic
|
||||||
|
{
|
||||||
|
`${merge(map("a", "b"), map("c", "d"), map("e", "f"))}`,
|
||||||
|
map[string]interface{}{"a": "b", "c": "d", "e": "f"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// merge with variables
|
||||||
|
{
|
||||||
|
`${merge(var.maps[0], map("c", "d"))}`,
|
||||||
|
map[string]interface{}{"key1": "a", "key2": "b", "c": "d"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// only accept maps
|
||||||
|
{
|
||||||
|
`${merge(map("a", "b"), list("c", "d"))}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// merge maps of maps
|
||||||
|
{
|
||||||
|
`${merge(map("a", var.maps[0]), map("b", var.maps[1]))}`,
|
||||||
|
map[string]interface{}{
|
||||||
|
"b": map[string]interface{}{"key3": "d", "key4": "c"},
|
||||||
|
"a": map[string]interface{}{"key1": "a", "key2": "b"},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// merge maps of lists
|
||||||
|
{
|
||||||
|
`${merge(map("a", list("b")), map("c", list("d", "e")))}`,
|
||||||
|
map[string]interface{}{"a": []interface{}{"b"}, "c": []interface{}{"d", "e"}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// merge map of various kinds
|
||||||
|
{
|
||||||
|
`${merge(map("a", var.maps[0]), map("b", list("c", "d")))}`,
|
||||||
|
map[string]interface{}{"a": map[string]interface{}{"key1": "a", "key2": "b"}, "b": []interface{}{"c", "d"}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Vars: map[string]ast.Variable{
|
||||||
|
"var.maps": {
|
||||||
|
Type: ast.TypeList,
|
||||||
|
Value: []ast.Variable{
|
||||||
|
{
|
||||||
|
Type: ast.TypeMap,
|
||||||
|
Value: map[string]ast.Variable{
|
||||||
|
"key1": {
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
"key2": {
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: ast.TypeMap,
|
||||||
|
Value: map[string]ast.Variable{
|
||||||
|
"key3": {
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: "d",
|
||||||
|
},
|
||||||
|
"key4": {
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestInterpolateFuncDistinct(t *testing.T) {
|
func TestInterpolateFuncDistinct(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
|
@ -1542,7 +1639,6 @@ type testFunctionCase struct {
|
||||||
|
|
||||||
func testFunction(t *testing.T, config testFunctionConfig) {
|
func testFunction(t *testing.T, config testFunctionConfig) {
|
||||||
for i, tc := range config.Cases {
|
for i, tc := range config.Cases {
|
||||||
fmt.Println("running", i)
|
|
||||||
ast, err := hil.Parse(tc.Input)
|
ast, err := hil.Parse(tc.Input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Case #%d: input: %#v\nerr: %v", i, tc.Input, err)
|
t.Fatalf("Case #%d: input: %#v\nerr: %v", i, tc.Input, err)
|
||||||
|
|
Loading…
Reference in New Issue