github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/lang/funcs/string.go (about) 1 package funcs 2 3 import ( 4 "fmt" 5 "regexp" 6 "sort" 7 "strings" 8 9 "github.com/zclconf/go-cty/cty" 10 "github.com/zclconf/go-cty/cty/function" 11 "github.com/zclconf/go-cty/cty/gocty" 12 ) 13 14 var JoinFunc = function.New(&function.Spec{ 15 Params: []function.Parameter{ 16 { 17 Name: "separator", 18 Type: cty.String, 19 }, 20 }, 21 VarParam: &function.Parameter{ 22 Name: "lists", 23 Type: cty.List(cty.String), 24 }, 25 Type: function.StaticReturnType(cty.String), 26 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 27 sep := args[0].AsString() 28 listVals := args[1:] 29 if len(listVals) < 1 { 30 return cty.UnknownVal(cty.String), fmt.Errorf("at least one list is required") 31 } 32 33 l := 0 34 for _, list := range listVals { 35 if !list.IsWhollyKnown() { 36 return cty.UnknownVal(cty.String), nil 37 } 38 l += list.LengthInt() 39 } 40 41 items := make([]string, 0, l) 42 for ai, list := range listVals { 43 ei := 0 44 for it := list.ElementIterator(); it.Next(); { 45 _, val := it.Element() 46 if val.IsNull() { 47 if len(listVals) > 1 { 48 return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d of list %d is null; cannot concatenate null values", ei, ai+1) 49 } 50 return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d is null; cannot concatenate null values", ei) 51 } 52 items = append(items, val.AsString()) 53 ei++ 54 } 55 } 56 57 return cty.StringVal(strings.Join(items, sep)), nil 58 }, 59 }) 60 61 var SortFunc = function.New(&function.Spec{ 62 Params: []function.Parameter{ 63 { 64 Name: "list", 65 Type: cty.List(cty.String), 66 }, 67 }, 68 Type: function.StaticReturnType(cty.List(cty.String)), 69 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 70 listVal := args[0] 71 72 if !listVal.IsWhollyKnown() { 73 // If some of the element values aren't known yet then we 74 // can't yet predict the order of the result. 75 return cty.UnknownVal(retType), nil 76 } 77 if listVal.LengthInt() == 0 { // Easy path 78 return listVal, nil 79 } 80 81 list := make([]string, 0, listVal.LengthInt()) 82 for it := listVal.ElementIterator(); it.Next(); { 83 iv, v := it.Element() 84 if v.IsNull() { 85 return cty.UnknownVal(retType), fmt.Errorf("given list element %s is null; a null string cannot be sorted", iv.AsBigFloat().String()) 86 } 87 list = append(list, v.AsString()) 88 } 89 90 sort.Strings(list) 91 retVals := make([]cty.Value, len(list)) 92 for i, s := range list { 93 retVals[i] = cty.StringVal(s) 94 } 95 return cty.ListVal(retVals), nil 96 }, 97 }) 98 99 var SplitFunc = function.New(&function.Spec{ 100 Params: []function.Parameter{ 101 { 102 Name: "separator", 103 Type: cty.String, 104 }, 105 { 106 Name: "str", 107 Type: cty.String, 108 }, 109 }, 110 Type: function.StaticReturnType(cty.List(cty.String)), 111 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 112 sep := args[0].AsString() 113 str := args[1].AsString() 114 elems := strings.Split(str, sep) 115 elemVals := make([]cty.Value, len(elems)) 116 for i, s := range elems { 117 elemVals[i] = cty.StringVal(s) 118 } 119 if len(elemVals) == 0 { 120 return cty.ListValEmpty(cty.String), nil 121 } 122 return cty.ListVal(elemVals), nil 123 }, 124 }) 125 126 // ChompFunc constructs a function that removes newline characters at the end of a string. 127 var ChompFunc = function.New(&function.Spec{ 128 Params: []function.Parameter{ 129 { 130 Name: "str", 131 Type: cty.String, 132 }, 133 }, 134 Type: function.StaticReturnType(cty.String), 135 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 136 newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) 137 return cty.StringVal(newlines.ReplaceAllString(args[0].AsString(), "")), nil 138 }, 139 }) 140 141 // IndentFunc constructs a function that adds a given number of spaces to the 142 // beginnings of all but the first line in a given multi-line string. 143 var IndentFunc = function.New(&function.Spec{ 144 Params: []function.Parameter{ 145 { 146 Name: "spaces", 147 Type: cty.Number, 148 }, 149 { 150 Name: "str", 151 Type: cty.String, 152 }, 153 }, 154 Type: function.StaticReturnType(cty.String), 155 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 156 var spaces int 157 if err := gocty.FromCtyValue(args[0], &spaces); err != nil { 158 return cty.UnknownVal(cty.String), err 159 } 160 data := args[1].AsString() 161 pad := strings.Repeat(" ", spaces) 162 return cty.StringVal(strings.Replace(data, "\n", "\n"+pad, -1)), nil 163 }, 164 }) 165 166 // ReplaceFunc constructs a function that searches a given string for another 167 // given substring, and replaces each occurence with a given replacement string. 168 var ReplaceFunc = function.New(&function.Spec{ 169 Params: []function.Parameter{ 170 { 171 Name: "str", 172 Type: cty.String, 173 }, 174 { 175 Name: "substr", 176 Type: cty.String, 177 }, 178 { 179 Name: "replace", 180 Type: cty.String, 181 }, 182 }, 183 Type: function.StaticReturnType(cty.String), 184 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 185 str := args[0].AsString() 186 substr := args[1].AsString() 187 replace := args[2].AsString() 188 189 // We search/replace using a regexp if the string is surrounded 190 // in forward slashes. 191 if len(substr) > 1 && substr[0] == '/' && substr[len(substr)-1] == '/' { 192 re, err := regexp.Compile(substr[1 : len(substr)-1]) 193 if err != nil { 194 return cty.UnknownVal(cty.String), err 195 } 196 197 return cty.StringVal(re.ReplaceAllString(str, replace)), nil 198 } 199 200 return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil 201 }, 202 }) 203 204 // TitleFunc constructs a function that converts the first letter of each word 205 // in the given string to uppercase. 206 var TitleFunc = function.New(&function.Spec{ 207 Params: []function.Parameter{ 208 { 209 Name: "str", 210 Type: cty.String, 211 }, 212 }, 213 Type: function.StaticReturnType(cty.String), 214 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 215 return cty.StringVal(strings.Title(args[0].AsString())), nil 216 }, 217 }) 218 219 // TrimSpaceFunc constructs a function that removes any space characters from 220 // the start and end of the given string. 221 var TrimSpaceFunc = function.New(&function.Spec{ 222 Params: []function.Parameter{ 223 { 224 Name: "str", 225 Type: cty.String, 226 }, 227 }, 228 Type: function.StaticReturnType(cty.String), 229 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 230 return cty.StringVal(strings.TrimSpace(args[0].AsString())), nil 231 }, 232 }) 233 234 // TrimFunc constructs a function that removes the specified characters from 235 // the start and end of the given string. 236 var TrimFunc = function.New(&function.Spec{ 237 Params: []function.Parameter{ 238 { 239 Name: "str", 240 Type: cty.String, 241 }, 242 { 243 Name: "cutset", 244 Type: cty.String, 245 }, 246 }, 247 Type: function.StaticReturnType(cty.String), 248 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 249 str := args[0].AsString() 250 cutset := args[1].AsString() 251 return cty.StringVal(strings.Trim(str, cutset)), nil 252 }, 253 }) 254 255 // TrimPrefixFunc constructs a function that removes the specified characters from 256 // the start the given string. 257 var TrimPrefixFunc = function.New(&function.Spec{ 258 Params: []function.Parameter{ 259 { 260 Name: "str", 261 Type: cty.String, 262 }, 263 { 264 Name: "prefix", 265 Type: cty.String, 266 }, 267 }, 268 Type: function.StaticReturnType(cty.String), 269 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 270 str := args[0].AsString() 271 prefix := args[1].AsString() 272 return cty.StringVal(strings.TrimPrefix(str, prefix)), nil 273 }, 274 }) 275 276 // TrimSuffixFunc constructs a function that removes the specified characters from 277 // the end of the given string. 278 var TrimSuffixFunc = function.New(&function.Spec{ 279 Params: []function.Parameter{ 280 { 281 Name: "str", 282 Type: cty.String, 283 }, 284 { 285 Name: "suffix", 286 Type: cty.String, 287 }, 288 }, 289 Type: function.StaticReturnType(cty.String), 290 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 291 str := args[0].AsString() 292 cutset := args[1].AsString() 293 return cty.StringVal(strings.TrimSuffix(str, cutset)), nil 294 }, 295 }) 296 297 // Join concatenates together the string elements of one or more lists with a 298 // given separator. 299 func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) { 300 args := make([]cty.Value, len(lists)+1) 301 args[0] = sep 302 copy(args[1:], lists) 303 return JoinFunc.Call(args) 304 } 305 306 // Sort re-orders the elements of a given list of strings so that they are 307 // in ascending lexicographical order. 308 func Sort(list cty.Value) (cty.Value, error) { 309 return SortFunc.Call([]cty.Value{list}) 310 } 311 312 // Split divides a given string by a given separator, returning a list of 313 // strings containing the characters between the separator sequences. 314 func Split(sep, str cty.Value) (cty.Value, error) { 315 return SplitFunc.Call([]cty.Value{sep, str}) 316 } 317 318 // Chomp removes newline characters at the end of a string. 319 func Chomp(str cty.Value) (cty.Value, error) { 320 return ChompFunc.Call([]cty.Value{str}) 321 } 322 323 // Indent adds a given number of spaces to the beginnings of all but the first 324 // line in a given multi-line string. 325 func Indent(spaces, str cty.Value) (cty.Value, error) { 326 return IndentFunc.Call([]cty.Value{spaces, str}) 327 } 328 329 // Replace searches a given string for another given substring, 330 // and replaces all occurences with a given replacement string. 331 func Replace(str, substr, replace cty.Value) (cty.Value, error) { 332 return ReplaceFunc.Call([]cty.Value{str, substr, replace}) 333 } 334 335 // Title converts the first letter of each word in the given string to uppercase. 336 func Title(str cty.Value) (cty.Value, error) { 337 return TitleFunc.Call([]cty.Value{str}) 338 } 339 340 // TrimSpace removes any space characters from the start and end of the given string. 341 func TrimSpace(str cty.Value) (cty.Value, error) { 342 return TrimSpaceFunc.Call([]cty.Value{str}) 343 } 344 345 // Trim removes the specified characters from the start and end of the given string. 346 func Trim(str, cutset cty.Value) (cty.Value, error) { 347 return TrimFunc.Call([]cty.Value{str, cutset}) 348 } 349 350 // TrimPrefix removes the specified prefix from the start of the given string. 351 func TrimPrefix(str, prefix cty.Value) (cty.Value, error) { 352 return TrimPrefixFunc.Call([]cty.Value{str, prefix}) 353 } 354 355 // TrimSuffix removes the specified suffix from the end of the given string. 356 func TrimSuffix(str, suffix cty.Value) (cty.Value, error) { 357 return TrimSuffixFunc.Call([]cty.Value{str, suffix}) 358 }