| package stdlib |
| |
| import ( |
| "strings" |
| |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/function" |
| "github.com/zclconf/go-cty/cty/gocty" |
| "github.com/apparentlymart/go-textseg/textseg" |
| ) |
| |
| var UpperFunc = function.New(&function.Spec{ |
| Params: []function.Parameter{ |
| { |
| Name: "str", |
| Type: cty.String, |
| AllowDynamicType: true, |
| }, |
| }, |
| Type: function.StaticReturnType(cty.String), |
| Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { |
| in := args[0].AsString() |
| out := strings.ToUpper(in) |
| return cty.StringVal(out), nil |
| }, |
| }) |
| |
| var LowerFunc = function.New(&function.Spec{ |
| Params: []function.Parameter{ |
| { |
| Name: "str", |
| Type: cty.String, |
| AllowDynamicType: true, |
| }, |
| }, |
| Type: function.StaticReturnType(cty.String), |
| Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { |
| in := args[0].AsString() |
| out := strings.ToLower(in) |
| return cty.StringVal(out), nil |
| }, |
| }) |
| |
| var ReverseFunc = function.New(&function.Spec{ |
| Params: []function.Parameter{ |
| { |
| Name: "str", |
| Type: cty.String, |
| AllowDynamicType: true, |
| }, |
| }, |
| Type: function.StaticReturnType(cty.String), |
| Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { |
| in := []byte(args[0].AsString()) |
| out := make([]byte, len(in)) |
| pos := len(out) |
| |
| inB := []byte(in) |
| for i := 0; i < len(in); { |
| d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true) |
| cluster := in[i : i+d] |
| pos -= len(cluster) |
| copy(out[pos:], cluster) |
| i += d |
| } |
| |
| return cty.StringVal(string(out)), nil |
| }, |
| }) |
| |
| var StrlenFunc = function.New(&function.Spec{ |
| Params: []function.Parameter{ |
| { |
| Name: "str", |
| Type: cty.String, |
| AllowDynamicType: true, |
| }, |
| }, |
| Type: function.StaticReturnType(cty.Number), |
| Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { |
| in := args[0].AsString() |
| l := 0 |
| |
| inB := []byte(in) |
| for i := 0; i < len(in); { |
| d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true) |
| l++ |
| i += d |
| } |
| |
| return cty.NumberIntVal(int64(l)), nil |
| }, |
| }) |
| |
| var SubstrFunc = function.New(&function.Spec{ |
| Params: []function.Parameter{ |
| { |
| Name: "str", |
| Type: cty.String, |
| AllowDynamicType: true, |
| }, |
| { |
| Name: "offset", |
| Type: cty.Number, |
| AllowDynamicType: true, |
| }, |
| { |
| Name: "length", |
| Type: cty.Number, |
| AllowDynamicType: true, |
| }, |
| }, |
| Type: function.StaticReturnType(cty.String), |
| Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { |
| in := []byte(args[0].AsString()) |
| var offset, length int |
| |
| var err error |
| err = gocty.FromCtyValue(args[1], &offset) |
| if err != nil { |
| return cty.NilVal, err |
| } |
| err = gocty.FromCtyValue(args[2], &length) |
| if err != nil { |
| return cty.NilVal, err |
| } |
| |
| if offset < 0 { |
| totalLenNum, err := Strlen(args[0]) |
| if err != nil { |
| // should never happen |
| panic("Stdlen returned an error") |
| } |
| |
| var totalLen int |
| err = gocty.FromCtyValue(totalLenNum, &totalLen) |
| if err != nil { |
| // should never happen |
| panic("Stdlen returned a non-int number") |
| } |
| |
| offset += totalLen |
| } |
| |
| sub := in |
| pos := 0 |
| var i int |
| |
| // First we'll seek forward to our offset |
| if offset > 0 { |
| for i = 0; i < len(sub); { |
| d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true) |
| i += d |
| pos++ |
| if pos == offset { |
| break |
| } |
| if i >= len(in) { |
| return cty.StringVal(""), nil |
| } |
| } |
| |
| sub = sub[i:] |
| } |
| |
| if length < 0 { |
| // Taking the remainder of the string is a fast path since |
| // we can just return the rest of the buffer verbatim. |
| return cty.StringVal(string(sub)), nil |
| } |
| |
| // Otherwise we need to start seeking forward again until we |
| // reach the length we want. |
| pos = 0 |
| for i = 0; i < len(sub); { |
| d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true) |
| i += d |
| pos++ |
| if pos == length { |
| break |
| } |
| } |
| |
| sub = sub[:i] |
| |
| return cty.StringVal(string(sub)), nil |
| }, |
| }) |
| |
| // Upper is a Function that converts a given string to uppercase. |
| func Upper(str cty.Value) (cty.Value, error) { |
| return UpperFunc.Call([]cty.Value{str}) |
| } |
| |
| // Lower is a Function that converts a given string to lowercase. |
| func Lower(str cty.Value) (cty.Value, error) { |
| return LowerFunc.Call([]cty.Value{str}) |
| } |
| |
| // Reverse is a Function that reverses the order of the characters in the |
| // given string. |
| // |
| // As usual, "character" for the sake of this function is a grapheme cluster, |
| // so combining diacritics (for example) will be considered together as a |
| // single character. |
| func Reverse(str cty.Value) (cty.Value, error) { |
| return ReverseFunc.Call([]cty.Value{str}) |
| } |
| |
| // Strlen is a Function that returns the length of the given string in |
| // characters. |
| // |
| // As usual, "character" for the sake of this function is a grapheme cluster, |
| // so combining diacritics (for example) will be considered together as a |
| // single character. |
| func Strlen(str cty.Value) (cty.Value, error) { |
| return StrlenFunc.Call([]cty.Value{str}) |
| } |
| |
| // Substr is a Function that extracts a sequence of characters from another |
| // string and creates a new string. |
| // |
| // As usual, "character" for the sake of this function is a grapheme cluster, |
| // so combining diacritics (for example) will be considered together as a |
| // single character. |
| // |
| // The "offset" index may be negative, in which case it is relative to the |
| // end of the given string. |
| // |
| // The "length" may be -1, in which case the remainder of the string after |
| // the given offset will be returned. |
| func Substr(str cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) { |
| return SubstrFunc.Call([]cty.Value{str, offset, length}) |
| } |