config: add formatlist
formatlist distributes formatting over lists. See the docs for details. As a colleague commented: "It happens all the time that we want a set of outputs, but in a slightly different way than just simple joining or concatting." formatlist (combined with join) makes it easy to satisfy those needs.
This commit is contained in:
parent
a3f79cd790
commit
02e751e356
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
|
@ -17,13 +18,14 @@ var Funcs map[string]ast.Function
|
|||
|
||||
func init() {
|
||||
Funcs = map[string]ast.Function{
|
||||
"file": interpolationFuncFile(),
|
||||
"format": interpolationFuncFormat(),
|
||||
"join": interpolationFuncJoin(),
|
||||
"element": interpolationFuncElement(),
|
||||
"replace": interpolationFuncReplace(),
|
||||
"split": interpolationFuncSplit(),
|
||||
"length": interpolationFuncLength(),
|
||||
"file": interpolationFuncFile(),
|
||||
"format": interpolationFuncFormat(),
|
||||
"formatlist": interpolationFuncFormatList(),
|
||||
"join": interpolationFuncJoin(),
|
||||
"element": interpolationFuncElement(),
|
||||
"replace": interpolationFuncReplace(),
|
||||
"split": interpolationFuncSplit(),
|
||||
"length": interpolationFuncLength(),
|
||||
|
||||
// Concat is a little useless now since we supported embeddded
|
||||
// interpolations but we keep it around for backwards compat reasons.
|
||||
|
@ -88,6 +90,69 @@ func interpolationFuncFormat() ast.Function {
|
|||
}
|
||||
}
|
||||
|
||||
// interpolationFuncFormatList implements the "formatlist" function that does
|
||||
// string formatting on lists.
|
||||
func interpolationFuncFormatList() ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
Variadic: true,
|
||||
VariadicType: ast.TypeAny,
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
// Make a copy of the variadic part of args
|
||||
// to avoid modifying the original.
|
||||
varargs := make([]interface{}, len(args)-1)
|
||||
copy(varargs, args[1:])
|
||||
|
||||
// Convert arguments that are lists into slices.
|
||||
// Confirm along the way that all lists have the same length (n).
|
||||
var n int
|
||||
for i := 1; i < len(args); i++ {
|
||||
s, ok := args[i].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(s, InterpSplitDelim)
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
varargs[i-1] = parts
|
||||
if n == 0 {
|
||||
// first list we've seen
|
||||
n = len(parts)
|
||||
continue
|
||||
}
|
||||
if n != len(parts) {
|
||||
return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts))
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil, errors.New("no lists in arguments to formatlist")
|
||||
}
|
||||
|
||||
// Do the formatting.
|
||||
format := args[0].(string)
|
||||
|
||||
// Generate a list of formatted strings.
|
||||
list := make([]string, n)
|
||||
fmtargs := make([]interface{}, len(varargs))
|
||||
for i := 0; i < n; i++ {
|
||||
for j, arg := range varargs {
|
||||
switch arg := arg.(type) {
|
||||
default:
|
||||
fmtargs[j] = arg
|
||||
case []string:
|
||||
fmtargs[j] = arg[i]
|
||||
}
|
||||
}
|
||||
list[i] = fmt.Sprintf(format, fmtargs...)
|
||||
}
|
||||
return strings.Join(list, InterpSplitDelim), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// interpolationFuncJoin implements the "join" function that allows
|
||||
// multi-variable values to be joined by some character.
|
||||
func interpolationFuncJoin() ast.Function {
|
||||
|
|
|
@ -106,6 +106,59 @@ func TestInterpolateFuncFormat(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncFormatList(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
// formatlist requires at least one list
|
||||
{
|
||||
`${formatlist("hello")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`${formatlist("hello %s", "world")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
// formatlist applies to each list element in turn
|
||||
{
|
||||
`${formatlist("<%s>", split(",", "A,B"))}`,
|
||||
"<A>" + InterpSplitDelim + "<B>",
|
||||
false,
|
||||
},
|
||||
// formatlist repeats scalar elements
|
||||
{
|
||||
`${join(", ", formatlist("%s=%s", "x", split(",", "A,B,C")))}`,
|
||||
"x=A, x=B, x=C",
|
||||
false,
|
||||
},
|
||||
// Multiple lists are walked in parallel
|
||||
{
|
||||
`${join(", ", formatlist("%s=%s", split(",", "A,B,C"), split(",", "1,2,3")))}`,
|
||||
"A=1, B=2, C=3",
|
||||
false,
|
||||
},
|
||||
// formatlist of lists of length zero/one are repeated, just as scalars are
|
||||
{
|
||||
`${join(", ", formatlist("%s=%s", split(",", ""), split(",", "1,2,3")))}`,
|
||||
"=1, =2, =3",
|
||||
false,
|
||||
},
|
||||
{
|
||||
`${join(", ", formatlist("%s=%s", split(",", "A"), split(",", "1,2,3")))}`,
|
||||
"A=1, A=2, A=3",
|
||||
false,
|
||||
},
|
||||
// Mismatched list lengths generate an error
|
||||
{
|
||||
`${formatlist("%s=%2s", split(",", "A,B,C,D"), split(",", "1,2,3"))}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncJoin(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
|
|
|
@ -89,6 +89,16 @@ The supported built-in functions are:
|
|||
Example to zero-prefix a count, used commonly for naming servers:
|
||||
`format("web-%03d", count.index+1)`.
|
||||
|
||||
* `formatlist(format, args...)` - Formats each element of a list
|
||||
according to the given format, similarly to `format`, and returns a list.
|
||||
Non-list arguments are repeated for each list element.
|
||||
For example, to convert a list of DNS addresses to a list of URLs, you might use:
|
||||
`formatlist("https://%s:%s/", aws_instance.foo.*.public_dns, var.port)`.
|
||||
If multiple args are lists, and they have the same number of elements, then the formatting is applied to the elements of the lists in parallel.
|
||||
Example:
|
||||
`formatlist("instance %v has private ip %v", aws_instance.foo.*.id, aws_instance.foo.*.private_ip)`.
|
||||
Passing lists with different lengths to formatlist results in an error.
|
||||
|
||||
* `join(delim, list)` - Joins the list with the delimiter. A list is
|
||||
only possible with splat variables from resources with a count
|
||||
greater than one. Example: `join(",", aws_instance.foo.*.id)`
|
||||
|
|
Loading…
Reference in New Issue