github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/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 preduct 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 constructions 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 constructions 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 constructions 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 constructions 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 constructions 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  // Join concatenates together the string elements of one or more lists with a
   235  // given separator.
   236  func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) {
   237  	args := make([]cty.Value, len(lists)+1)
   238  	args[0] = sep
   239  	copy(args[1:], lists)
   240  	return JoinFunc.Call(args)
   241  }
   242  
   243  // Sort re-orders the elements of a given list of strings so that they are
   244  // in ascending lexicographical order.
   245  func Sort(list cty.Value) (cty.Value, error) {
   246  	return SortFunc.Call([]cty.Value{list})
   247  }
   248  
   249  // Split divides a given string by a given separator, returning a list of
   250  // strings containing the characters between the separator sequences.
   251  func Split(sep, str cty.Value) (cty.Value, error) {
   252  	return SplitFunc.Call([]cty.Value{sep, str})
   253  }
   254  
   255  // Chomp removes newline characters at the end of a string.
   256  func Chomp(str cty.Value) (cty.Value, error) {
   257  	return ChompFunc.Call([]cty.Value{str})
   258  }
   259  
   260  // Indent adds a given number of spaces to the beginnings of all but the first
   261  // line in a given multi-line string.
   262  func Indent(spaces, str cty.Value) (cty.Value, error) {
   263  	return IndentFunc.Call([]cty.Value{spaces, str})
   264  }
   265  
   266  // Replace searches a given string for another given substring,
   267  // and replaces all occurences with a given replacement string.
   268  func Replace(str, substr, replace cty.Value) (cty.Value, error) {
   269  	return ReplaceFunc.Call([]cty.Value{str, substr, replace})
   270  }
   271  
   272  // Title converts the first letter of each word in the given string to uppercase.
   273  func Title(str cty.Value) (cty.Value, error) {
   274  	return TitleFunc.Call([]cty.Value{str})
   275  }
   276  
   277  // TrimSpace removes any space characters from the start and end of the given string.
   278  func TrimSpace(str cty.Value) (cty.Value, error) {
   279  	return TrimSpaceFunc.Call([]cty.Value{str})
   280  }