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  }