From b39ddc7d477e98dcbe14c85288d0b32a208902ec Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 2 Mar 2015 10:26:06 -0800 Subject: [PATCH 1/2] config: add format function --- config/interpolate_funcs.go | 16 ++++++++++++++ config/interpolate_funcs_test.go | 36 ++++++++++++++++++++++++++++++++ config/lang/ast/ast.go | 3 ++- config/lang/ast/type_string.go | 16 ++++++++------ config/lang/check_types.go | 6 +++++- 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index d6d5e3779..8bb76c532 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -17,6 +17,7 @@ var Funcs map[string]ast.Function func init() { Funcs = map[string]ast.Function{ "file": interpolationFuncFile(), + "format": interpolationFuncFormat(), "join": interpolationFuncJoin(), "element": interpolationFuncElement(), "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 // multi-variable values to be joined by some character. func interpolationFuncJoin() ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 73a793c0f..2061e6ad8 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -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) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/config/lang/ast/ast.go b/config/lang/ast/ast.go index fc6c966b0..1d784c78a 100644 --- a/config/lang/ast/ast.go +++ b/config/lang/ast/ast.go @@ -48,7 +48,8 @@ type Type uint32 const ( TypeInvalid Type = 0 - TypeString Type = 1 << iota + TypeAny Type = 1 << iota + TypeString TypeInt TypeFloat ) diff --git a/config/lang/ast/type_string.go b/config/lang/ast/type_string.go index fd0e9e355..d9b5a2df4 100644 --- a/config/lang/ast/type_string.go +++ b/config/lang/ast/type_string.go @@ -6,16 +6,18 @@ import "fmt" const ( _Type_name_0 = "TypeInvalid" - _Type_name_1 = "TypeString" - _Type_name_2 = "TypeInt" - _Type_name_3 = "TypeFloat" + _Type_name_1 = "TypeAny" + _Type_name_2 = "TypeString" + _Type_name_3 = "TypeInt" + _Type_name_4 = "TypeFloat" ) var ( _Type_index_0 = [...]uint8{0, 11} - _Type_index_1 = [...]uint8{0, 10} - _Type_index_2 = [...]uint8{0, 7} - _Type_index_3 = [...]uint8{0, 9} + _Type_index_1 = [...]uint8{0, 7} + _Type_index_2 = [...]uint8{0, 10} + _Type_index_3 = [...]uint8{0, 7} + _Type_index_4 = [...]uint8{0, 9} ) func (i Type) String() string { @@ -28,6 +30,8 @@ func (i Type) String() string { return _Type_name_2 case i == 8: return _Type_name_3 + case i == 16: + return _Type_name_4 default: return fmt.Sprintf("Type(%d)", i) } diff --git a/config/lang/check_types.go b/config/lang/check_types.go index f5cf16680..0396eb1f3 100644 --- a/config/lang/check_types.go +++ b/config/lang/check_types.go @@ -174,6 +174,10 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { // Verify the args for i, expected := range function.ArgTypes { + if expected == ast.TypeAny { + continue + } + if args[i] != expected { cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) 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 function.Variadic { + if function.Variadic && function.VariadicType != ast.TypeAny { args = args[len(function.ArgTypes):] for i, t := range args { if t != function.VariadicType { From bf43cabcc2806916353e3f77ba30b475189f084c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 2 Mar 2015 10:27:58 -0800 Subject: [PATCH 2/2] website: document format --- website/source/docs/configuration/interpolation.html.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index 70a73f826..15f39ddf1 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -80,6 +80,12 @@ The supported built-in functions are: in this file are _not_ interpolated. The contents of the file are 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 only possible with splat variables from resources with a count greater than one. Example: `join(",", aws_instance.foo.*.id)`