Merge pull request #1096 from hashicorp/f-sprintf

config: add "format' function [GH-758]
This commit is contained in:
Mitchell Hashimoto 2015-03-04 16:05:25 -08:00
commit 23609a7af5
6 changed files with 75 additions and 8 deletions

View File

@ -17,6 +17,7 @@ var Funcs map[string]ast.Function
func init() { func init() {
Funcs = map[string]ast.Function{ Funcs = map[string]ast.Function{
"file": interpolationFuncFile(), "file": interpolationFuncFile(),
"format": interpolationFuncFormat(),
"join": interpolationFuncJoin(), "join": interpolationFuncJoin(),
"element": interpolationFuncElement(), "element": interpolationFuncElement(),
"replace": interpolationFuncReplace(), "replace": interpolationFuncReplace(),
@ -66,6 +67,21 @@ func interpolationFuncFile() ast.Function {
} }
} }
// interpolationFuncFormat implements the "replace" function that does
// string replacement.
func interpolationFuncFormat() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
Variadic: true,
VariadicType: ast.TypeAny,
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
format := args[0].(string)
return fmt.Sprintf(format, args[1:]...), nil
},
}
}
// interpolationFuncJoin implements the "join" function that allows // interpolationFuncJoin implements the "join" function that allows
// multi-variable values to be joined by some character. // multi-variable values to be joined by some character.
func interpolationFuncJoin() ast.Function { func interpolationFuncJoin() ast.Function {

View File

@ -70,6 +70,42 @@ func TestInterpolateFuncFile(t *testing.T) {
}) })
} }
func TestInterpolateFuncFormat(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${format("hello")}`,
"hello",
false,
},
{
`${format("hello %s", "world")}`,
"hello world",
false,
},
{
`${format("hello %d", 42)}`,
"hello 42",
false,
},
{
`${format("hello %05d", 42)}`,
"hello 00042",
false,
},
{
`${format("hello %05d", 12345)}`,
"hello 12345",
false,
},
},
})
}
func TestInterpolateFuncJoin(t *testing.T) { func TestInterpolateFuncJoin(t *testing.T) {
testFunction(t, testFunctionConfig{ testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{ Cases: []testFunctionCase{

View File

@ -48,7 +48,8 @@ type Type uint32
const ( const (
TypeInvalid Type = 0 TypeInvalid Type = 0
TypeString Type = 1 << iota TypeAny Type = 1 << iota
TypeString
TypeInt TypeInt
TypeFloat TypeFloat
) )

View File

@ -6,16 +6,18 @@ import "fmt"
const ( const (
_Type_name_0 = "TypeInvalid" _Type_name_0 = "TypeInvalid"
_Type_name_1 = "TypeString" _Type_name_1 = "TypeAny"
_Type_name_2 = "TypeInt" _Type_name_2 = "TypeString"
_Type_name_3 = "TypeFloat" _Type_name_3 = "TypeInt"
_Type_name_4 = "TypeFloat"
) )
var ( var (
_Type_index_0 = [...]uint8{0, 11} _Type_index_0 = [...]uint8{0, 11}
_Type_index_1 = [...]uint8{0, 10} _Type_index_1 = [...]uint8{0, 7}
_Type_index_2 = [...]uint8{0, 7} _Type_index_2 = [...]uint8{0, 10}
_Type_index_3 = [...]uint8{0, 9} _Type_index_3 = [...]uint8{0, 7}
_Type_index_4 = [...]uint8{0, 9}
) )
func (i Type) String() string { func (i Type) String() string {
@ -28,6 +30,8 @@ func (i Type) String() string {
return _Type_name_2 return _Type_name_2
case i == 8: case i == 8:
return _Type_name_3 return _Type_name_3
case i == 16:
return _Type_name_4
default: default:
return fmt.Sprintf("Type(%d)", i) return fmt.Sprintf("Type(%d)", i)
} }

View File

@ -174,6 +174,10 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
// Verify the args // Verify the args
for i, expected := range function.ArgTypes { for i, expected := range function.ArgTypes {
if expected == ast.TypeAny {
continue
}
if args[i] != expected { if args[i] != expected {
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
if cn != nil { if cn != nil {
@ -188,7 +192,7 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
} }
// If we're variadic, then verify the types there // If we're variadic, then verify the types there
if function.Variadic { if function.Variadic && function.VariadicType != ast.TypeAny {
args = args[len(function.ArgTypes):] args = args[len(function.ArgTypes):]
for i, t := range args { for i, t := range args {
if t != function.VariadicType { if t != function.VariadicType {

View File

@ -80,6 +80,12 @@ The supported built-in functions are:
in this file are _not_ interpolated. The contents of the file are in this file are _not_ interpolated. The contents of the file are
read as-is. read as-is.
* `format(format, args...)` - Formats a string according to the given
format. The syntax for the format is standard `sprintf` syntax.
Good documentation for the syntax can be [found here](http://golang.org/pkg/fmt/).
Example to zero-prefix a count, used commonly for naming servers:
`format("web-%03d", count.index+1)`.
* `join(delim, list)` - Joins the list with the delimiter. A list is * `join(delim, list)` - Joins the list with the delimiter. A list is
only possible with splat variables from resources with a count only possible with splat variables from resources with a count
greater than one. Example: `join(",", aws_instance.foo.*.id)` greater than one. Example: `join(",", aws_instance.foo.*.id)`