diff --git a/.travis.yml b/.travis.yml index 4b488582..5fc0b584 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.11.x - - 1.12.x + - oldstable + - stable - tip before_install: diff --git a/cty/function/stdlib/string.go b/cty/function/stdlib/string.go index 01ebc47f..0b8eee23 100644 --- a/cty/function/stdlib/string.go +++ b/cty/function/stdlib/string.go @@ -96,6 +96,54 @@ var StrlenFunc = function.New(&function.Spec{ }, }) +var StartsWithFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + AllowDynamicType: true, + }, + { + Name: "prefix", + Type: cty.String, + AllowDynamicType: true, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + str := []byte(args[0].AsString()) + prefix := []byte(args[1].AsString()) + + // Empty prefix always matches + prefixLenNum, err := Strlen(args[1]) + if err != nil { + // should never happen + panic("Stdlen returned an error") + } + var prefixLen int + err = gocty.FromCtyValue(prefixLenNum, &prefixLen) + if err != nil { + // should never happen + panic("Stdlen returned a non-int number") + } + if prefixLen == 0 { + return cty.BoolVal(true), nil + } + + // For each character of prefix, check the matching character of str. + // If they don't match, fail + var i int + for i = 0; i < prefixLen; { + if str[i] != prefix[i] { + return cty.BoolVal(false), nil + } + } + + // We do match + return cty.BoolVal(true), nil + }, +}) + var SubstrFunc = function.New(&function.Spec{ Params: []function.Parameter{ { @@ -151,7 +199,6 @@ var SubstrFunc = function.New(&function.Spec{ return cty.StringVal(""), nil } - sub := in pos := 0 var i int @@ -473,6 +520,10 @@ func Strlen(str cty.Value) (cty.Value, error) { return StrlenFunc.Call([]cty.Value{str}) } +func StartsWith(str cty.Value, prefix cty.Value) (cty.Value, error) { + return StartsWithFunc.Call([]cty.Value{str, prefix}) +} + // Substr is a Function that extracts a sequence of characters from another // string and creates a new string. // diff --git a/cty/function/stdlib/string_test.go b/cty/function/stdlib/string_test.go index 234386cf..cf7bfa21 100644 --- a/cty/function/stdlib/string_test.go +++ b/cty/function/stdlib/string_test.go @@ -268,6 +268,64 @@ func TestStrlen(t *testing.T) { } } +func TestStartsWith(t *testing.T) { + tests := []struct { + String cty.Value + Prefix cty.Value + Want cty.Value + }{ + { + cty.StringVal("hello"), + cty.StringVal("h"), + cty.BoolVal(true), + }, + { + cty.StringVal("HELLO"), + cty.StringVal("h"), + cty.BoolVal(false), + }, + { + cty.StringVal(""), + cty.StringVal("foo"), + cty.BoolVal(true), + } + { + cty.StringVal("foo"), + cty.StringVal(""), + cty.BoolVal(true), + }, + { + cty.StringVal(""), + cty.StringVal(""), + cty.BoolVal(true), + }, + { + cty.StringVal("short1"), + cty.StringVal("short1extra"), + cty.BoolVal(false), + }, + { + cty.StringVal("short2"), + cty.StringVal("longerprefix"), + cty.BoolVal(false), + }, + } + for _, test := range tests { + t.Run(test.String.GoString(), func(t *testing.T) { + got, err := StartsWith(test.String, test.Prefix) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !got.RawEquals(test.Want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) + } + }) + } + +} + func TestSubstr(t *testing.T) { tests := []struct { Input cty.Value