Expand list interpolation to lists and maps

Allow lists and maps within the list interpolation function via variable
interpolation. Since this requires setting the variadic type to TypeAny,
we check for non-heterogeneous lists in the callback.
This commit is contained in:
James Bardin 2016-07-19 13:39:40 -04:00
parent 58dd41f3b1
commit 2bd7cfd5fe
3 changed files with 79 additions and 7 deletions

View File

@ -91,15 +91,34 @@ func interpolationFuncList() ast.Function {
ArgTypes: []ast.Type{}, ArgTypes: []ast.Type{},
ReturnType: ast.TypeList, ReturnType: ast.TypeList,
Variadic: true, Variadic: true,
VariadicType: ast.TypeString, VariadicType: ast.TypeAny,
Callback: func(args []interface{}) (interface{}, error) { Callback: func(args []interface{}) (interface{}, error) {
var outputList []string var outputList []ast.Variable
for _, val := range args { for i, val := range args {
outputList = append(outputList, val.(string)) switch v := val.(type) {
case string:
outputList = append(outputList, ast.Variable{Type: ast.TypeString, Value: v})
case []ast.Variable:
outputList = append(outputList, ast.Variable{Type: ast.TypeList, Value: v})
case map[string]ast.Variable:
outputList = append(outputList, ast.Variable{Type: ast.TypeMap, Value: v})
default:
return nil, fmt.Errorf("unexpected type %T for argument %d in list", v, i)
}
} }
return stringSliceToVariableValue(outputList), nil // we don't support heterogeneous types, so make sure all types match the first
if len(outputList) > 0 {
firstType := outputList[0].Type
for i, v := range outputList[1:] {
if v.Type != firstType {
return nil, fmt.Errorf("unexpected type %s for argument %d in list", v.Type, i+1)
}
}
}
return outputList, nil
}, },
} }
} }

View File

@ -38,7 +38,28 @@ func TestInterpolateFuncList(t *testing.T) {
// not a string input gives error // not a string input gives error
{ {
`${list("hello", "${var.list}")}`, `${list("hello", 42)}`,
nil,
true,
},
// list of lists
{
`${list("${var.list}", "${var.list2}")}`,
[]interface{}{[]interface{}{"Hello", "World"}, []interface{}{"bar", "baz"}},
false,
},
// list of maps
{
`${list("${var.map}", "${var.map2}")}`,
[]interface{}{map[string]interface{}{"key": "bar"}, map[string]interface{}{"key2": "baz"}},
false,
},
// error on a heterogeneous list
{
`${list("first", "${var.list}")}`,
nil, nil,
true, true,
}, },
@ -57,6 +78,38 @@ func TestInterpolateFuncList(t *testing.T) {
}, },
}, },
}, },
"var.list2": {
Type: ast.TypeList,
Value: []ast.Variable{
{
Type: ast.TypeString,
Value: "bar",
},
{
Type: ast.TypeString,
Value: "baz",
},
},
},
"var.map": {
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"key": {
Type: ast.TypeString,
Value: "bar",
},
},
},
"var.map2": {
Type: ast.TypeMap,
Value: map[string]ast.Variable{
"key2": {
Type: ast.TypeString,
Value: "baz",
},
},
},
}, },
}) })
} }

View File

@ -172,7 +172,7 @@ The supported built-in functions are:
This function provides a way of representing list literals in interpolation. This function provides a way of representing list literals in interpolation.
* `${list("a", "b", "c")}` returns a list of `"a", "b", "c"`. * `${list("a", "b", "c")}` returns a list of `"a", "b", "c"`.
* `${list()}` returns an empty list. * `${list()}` returns an empty list.
* `lookup(map, key [, default])` - Performs a dynamic lookup into a mapping * `lookup(map, key [, default])` - Performs a dynamic lookup into a mapping
variable. The `map` parameter should be another variable, such variable. The `map` parameter should be another variable, such
as `var.amis`. If `key` does not exist in `map`, the interpolation will as `var.amis`. If `key` does not exist in `map`, the interpolation will