diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 69e327e37..1b5d33ed6 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "regexp" "strconv" "strings" @@ -15,11 +16,15 @@ var Funcs map[string]ast.Function func init() { Funcs = map[string]ast.Function{ - "concat": interpolationFuncConcat(), "file": interpolationFuncFile(), "join": interpolationFuncJoin(), "element": interpolationFuncElement(), + "replace": interpolationFuncReplace(), "split": interpolationFuncSplit(), + + // Concat is a little useless now since we supported embeddded + // interpolations but we keep it around for backwards compat reasons. + "concat": interpolationFuncConcat(), } } @@ -79,6 +84,33 @@ func interpolationFuncJoin() ast.Function { } } +// interpolationFuncReplace implements the "replace" function that does +// string replacement. +func interpolationFuncReplace() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + s := args[0].(string) + search := args[1].(string) + replace := args[2].(string) + + // We search/replace using a regexp if the string is surrounded + // in forward slashes. + if len(search) > 0 && search[0] == '/' && search[len(search)-1] == '/' { + re, err := regexp.Compile(search[1 : len(search)-1]) + if err != nil { + return nil, err + } + + return re.ReplaceAllString(s, replace), nil + } + + return strings.Replace(s, search, replace, -1), nil + }, + } +} + // interpolationFuncSplit implements the "split" function that allows // strings to split into multi-variable values func interpolationFuncSplit() ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 9710852db..978c96285 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -107,6 +107,39 @@ func TestInterpolateFuncJoin(t *testing.T) { }) } +func TestInterpolateFuncReplace(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + // Regular search and replace + { + `${replace("hello", "hel", "bel")}`, + "bello", + false, + }, + + // Search string doesn't match + { + `${replace("hello", "nope", "bel")}`, + "hello", + false, + }, + + // Regular expression + { + `${replace("hello", "/l/", "L")}`, + "heLLo", + false, + }, + + { + `${replace("helo", "/(l)/", "$1$1")}`, + "hello", + false, + }, + }, + }) +} + func TestInterpolateFuncSplit(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ @@ -225,7 +258,9 @@ func testFunction(t *testing.T, config testFunctionConfig) { } if !reflect.DeepEqual(out, tc.Result) { - t.Fatalf("%d: bad: %#v", i, out) + t.Fatalf( + "%d: bad output for input: %s\n\nOutput: %#v", + i, tc.Input, out) } } }