diff --git a/internal/lang/funcs/string.go b/internal/lang/funcs/string.go index ab6da72778..9ef709c7fb 100644 --- a/internal/lang/funcs/string.go +++ b/internal/lang/funcs/string.go @@ -8,6 +8,58 @@ import ( "github.com/zclconf/go-cty/cty/function" ) +// StartsWithFunc constructs a function that checks if a string starts with +// a specific prefix using strings.HasPrefix +var StartsWithFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + { + Name: "prefix", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + str := args[0].AsString() + prefix := args[1].AsString() + + if strings.HasPrefix(str, prefix) { + return cty.True, nil + } + + return cty.False, nil + }, +}) + +// EndsWithFunc constructs a function that checks if a string ends with +// a specific suffix using strings.HasSuffix +var EndsWithFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + { + Name: "suffix", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + str := args[0].AsString() + suffix := args[1].AsString() + + if strings.HasSuffix(str, suffix) { + return cty.True, nil + } + + return cty.False, nil + }, +}) + // ReplaceFunc constructs a function that searches a given string for another // given substring, and replaces each occurence with a given replacement string. var ReplaceFunc = function.New(&function.Spec{ diff --git a/internal/lang/functions.go b/internal/lang/functions.go index 1b3f88ce04..ee520965ca 100644 --- a/internal/lang/functions.go +++ b/internal/lang/functions.go @@ -59,6 +59,7 @@ func (s *Scope) Functions() map[string]function.Function { "dirname": funcs.DirnameFunc, "distinct": stdlib.DistinctFunc, "element": stdlib.ElementFunc, + "endswith": funcs.EndsWithFunc, "chunklist": stdlib.ChunklistFunc, "file": funcs.MakeFileFunc(s.BaseDir, false), "fileexists": funcs.MakeFileExistsFunc(s.BaseDir), @@ -115,6 +116,7 @@ func (s *Scope) Functions() map[string]function.Function { "slice": stdlib.SliceFunc, "sort": stdlib.SortFunc, "split": stdlib.SplitFunc, + "startswith": funcs.StartsWithFunc, "strrev": stdlib.ReverseFunc, "substr": stdlib.SubstrFunc, "sum": funcs.SumFunc, diff --git a/internal/lang/functions_test.go b/internal/lang/functions_test.go index ea2091eb97..f2a6f738c4 100644 --- a/internal/lang/functions_test.go +++ b/internal/lang/functions_test.go @@ -314,6 +314,47 @@ func TestFunctions(t *testing.T) { }, }, + "endswith": { + { + `endswith("hello world", "world")`, + cty.True, + }, + { + `endswith("hello world", "hello")`, + cty.False, + }, + { + `endswith("hello world", "")`, + cty.True, + // Completely empty suffix value ( "" ) + // will always evaluate to true for all strings. + }, + { + `endswith("hello world", " ")`, + cty.False, + }, + { + `endswith("", "")`, + cty.True, + }, + { + `endswith("", " ")`, + cty.False, + }, + { + `endswith(" ", "")`, + cty.True, + }, + { + `endswith("", "hello")`, + cty.False, + }, + { + `endswith(" ", "hello")`, + cty.False, + }, + }, + "file": { { `file("hello.txt")`, @@ -816,6 +857,47 @@ func TestFunctions(t *testing.T) { }, }, + "startswith": { + { + `startswith("hello world", "hello")`, + cty.True, + }, + { + `startswith("hello world", "world")`, + cty.False, + }, + { + `startswith("hello world", "")`, + cty.True, + // Completely empty prefix value ( "" ) + // will always evaluate to true for all strings. + }, + { + `startswith("hello world", " ")`, + cty.False, + }, + { + `startswith("", "")`, + cty.True, + }, + { + `startswith("", " ")`, + cty.False, + }, + { + `startswith(" ", "")`, + cty.True, + }, + { + `startswith("", "hello")`, + cty.False, + }, + { + `startswith(" ", "hello")`, + cty.False, + }, + }, + "strrev": { { `strrev("hello world")`, diff --git a/website/data/language-nav-data.json b/website/data/language-nav-data.json index a0ec64131e..60a649ada4 100644 --- a/website/data/language-nav-data.json +++ b/website/data/language-nav-data.json @@ -319,6 +319,10 @@ "title": "chomp", "href": "/language/functions/chomp" }, + { + "title": "endswith", + "href": "/language/functions/endswith" + }, { "title": "format", "href": "/language/functions/format" @@ -352,6 +356,10 @@ "title": "split", "href": "/language/functions/split" }, + { + "title": "startswith", + "href": "/language/functions/startswith" + }, { "title": "strrev", "href": "/language/functions/strrev" @@ -776,6 +784,7 @@ { "title": "dirname", "path": "functions/dirname", "hidden": true }, { "title": "distinct", "path": "functions/distinct", "hidden": true }, { "title": "element", "path": "functions/element", "hidden": true }, + { "title": "endswith", "path": "functions/endswith", "hidden": true }, { "title": "file", "path": "functions/file", "hidden": true }, { "title": "filebase64", "path": "functions/filebase64", "hidden": true }, { @@ -851,6 +860,7 @@ { "title": "slice", "path": "functions/slice", "hidden": true }, { "title": "sort", "path": "functions/sort", "hidden": true }, { "title": "split", "path": "functions/split", "hidden": true }, + { "title": "startswith", "path": "functions/startswith", "hidden": true }, { "title": "strrev", "path": "functions/strrev", "hidden": true }, { "title": "substr", "path": "functions/substr", "hidden": true }, { "title": "sum", "path": "functions/sum", "hidden": true }, diff --git a/website/docs/language/functions/endswith.mdx b/website/docs/language/functions/endswith.mdx new file mode 100644 index 0000000000..d96cc88aad --- /dev/null +++ b/website/docs/language/functions/endswith.mdx @@ -0,0 +1,27 @@ +--- +page_title: endswith - Functions - Configuration Language +description: |- + The endswith function takes two values: a string to check and a suffix string. It returns true if the first string ends with that exact suffix. +--- + +# `endswith` Function + +`endswith` takes two values: a string to check and a suffix string. The function returns true if the first string ends with that exact suffix. + +```hcl +endswith(string, suffix) +``` + +## Examples + +``` +> endswith("hello world", "world") +true + +> endswith("hello world", "hello") +false +``` + +## Related Functions + +- [`startswith`](/language/functions/startswith) takes two values: a string to check and a prefix string. The function returns true if the string begins with that exact prefix. diff --git a/website/docs/language/functions/startswith.mdx b/website/docs/language/functions/startswith.mdx new file mode 100644 index 0000000000..d49f7aa0ba --- /dev/null +++ b/website/docs/language/functions/startswith.mdx @@ -0,0 +1,27 @@ +--- +page_title: startsswith - Functions - Configuration Language +description: |- + The startswith function takes two values: a string to check and a prefix string. It returns true if the string begins with that exact prefix. +--- + +# `startswith` Function + +`startswith` takes two values: a string to check and a prefix string. The function returns true if the string begins with that exact prefix. + +```hcl +startswith(string, prefix) +``` + +## Examples + +``` +> startswith("hello world", "hello") +true + +> startswith("hello world", "world") +false +``` + +## Related Functions + +- [`endswith`](/language/functions/endswith) takes two values: a string to check and a suffix string. The function returns true if the first string ends with that exact suffix. \ No newline at end of file diff --git a/website/layouts/language.erb b/website/layouts/language.erb index 7f7e55e7f3..f2bf83dede 100644 --- a/website/layouts/language.erb +++ b/website/layouts/language.erb @@ -370,6 +370,10 @@ chomp +
  • + endswith +
  • +
  • format
  • @@ -406,6 +410,10 @@ split +
  • + startswith +
  • +
  • strrev