]>
Commit | Line | Data |
---|---|---|
1 | package stdlib | |
2 | ||
3 | import ( | |
4 | "strings" | |
5 | ||
6 | "github.com/zclconf/go-cty/cty" | |
7 | "github.com/zclconf/go-cty/cty/function" | |
8 | "github.com/zclconf/go-cty/cty/gocty" | |
9 | "github.com/apparentlymart/go-textseg/textseg" | |
10 | ) | |
11 | ||
12 | var UpperFunc = function.New(&function.Spec{ | |
13 | Params: []function.Parameter{ | |
14 | { | |
15 | Name: "str", | |
16 | Type: cty.String, | |
17 | AllowDynamicType: true, | |
18 | }, | |
19 | }, | |
20 | Type: function.StaticReturnType(cty.String), | |
21 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | |
22 | in := args[0].AsString() | |
23 | out := strings.ToUpper(in) | |
24 | return cty.StringVal(out), nil | |
25 | }, | |
26 | }) | |
27 | ||
28 | var LowerFunc = function.New(&function.Spec{ | |
29 | Params: []function.Parameter{ | |
30 | { | |
31 | Name: "str", | |
32 | Type: cty.String, | |
33 | AllowDynamicType: true, | |
34 | }, | |
35 | }, | |
36 | Type: function.StaticReturnType(cty.String), | |
37 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | |
38 | in := args[0].AsString() | |
39 | out := strings.ToLower(in) | |
40 | return cty.StringVal(out), nil | |
41 | }, | |
42 | }) | |
43 | ||
44 | var ReverseFunc = function.New(&function.Spec{ | |
45 | Params: []function.Parameter{ | |
46 | { | |
47 | Name: "str", | |
48 | Type: cty.String, | |
49 | AllowDynamicType: true, | |
50 | }, | |
51 | }, | |
52 | Type: function.StaticReturnType(cty.String), | |
53 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | |
54 | in := []byte(args[0].AsString()) | |
55 | out := make([]byte, len(in)) | |
56 | pos := len(out) | |
57 | ||
58 | inB := []byte(in) | |
59 | for i := 0; i < len(in); { | |
60 | d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true) | |
61 | cluster := in[i : i+d] | |
62 | pos -= len(cluster) | |
63 | copy(out[pos:], cluster) | |
64 | i += d | |
65 | } | |
66 | ||
67 | return cty.StringVal(string(out)), nil | |
68 | }, | |
69 | }) | |
70 | ||
71 | var StrlenFunc = function.New(&function.Spec{ | |
72 | Params: []function.Parameter{ | |
73 | { | |
74 | Name: "str", | |
75 | Type: cty.String, | |
76 | AllowDynamicType: true, | |
77 | }, | |
78 | }, | |
79 | Type: function.StaticReturnType(cty.Number), | |
80 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | |
81 | in := args[0].AsString() | |
82 | l := 0 | |
83 | ||
84 | inB := []byte(in) | |
85 | for i := 0; i < len(in); { | |
86 | d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true) | |
87 | l++ | |
88 | i += d | |
89 | } | |
90 | ||
91 | return cty.NumberIntVal(int64(l)), nil | |
92 | }, | |
93 | }) | |
94 | ||
95 | var SubstrFunc = function.New(&function.Spec{ | |
96 | Params: []function.Parameter{ | |
97 | { | |
98 | Name: "str", | |
99 | Type: cty.String, | |
100 | AllowDynamicType: true, | |
101 | }, | |
102 | { | |
103 | Name: "offset", | |
104 | Type: cty.Number, | |
105 | AllowDynamicType: true, | |
106 | }, | |
107 | { | |
108 | Name: "length", | |
109 | Type: cty.Number, | |
110 | AllowDynamicType: true, | |
111 | }, | |
112 | }, | |
113 | Type: function.StaticReturnType(cty.String), | |
114 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | |
115 | in := []byte(args[0].AsString()) | |
116 | var offset, length int | |
117 | ||
118 | var err error | |
119 | err = gocty.FromCtyValue(args[1], &offset) | |
120 | if err != nil { | |
121 | return cty.NilVal, err | |
122 | } | |
123 | err = gocty.FromCtyValue(args[2], &length) | |
124 | if err != nil { | |
125 | return cty.NilVal, err | |
126 | } | |
127 | ||
128 | if offset < 0 { | |
129 | totalLenNum, err := Strlen(args[0]) | |
130 | if err != nil { | |
131 | // should never happen | |
132 | panic("Stdlen returned an error") | |
133 | } | |
134 | ||
135 | var totalLen int | |
136 | err = gocty.FromCtyValue(totalLenNum, &totalLen) | |
137 | if err != nil { | |
138 | // should never happen | |
139 | panic("Stdlen returned a non-int number") | |
140 | } | |
141 | ||
142 | offset += totalLen | |
143 | } | |
144 | ||
145 | sub := in | |
146 | pos := 0 | |
147 | var i int | |
148 | ||
149 | // First we'll seek forward to our offset | |
150 | if offset > 0 { | |
151 | for i = 0; i < len(sub); { | |
152 | d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true) | |
153 | i += d | |
154 | pos++ | |
155 | if pos == offset { | |
156 | break | |
157 | } | |
158 | if i >= len(in) { | |
159 | return cty.StringVal(""), nil | |
160 | } | |
161 | } | |
162 | ||
163 | sub = sub[i:] | |
164 | } | |
165 | ||
166 | if length < 0 { | |
167 | // Taking the remainder of the string is a fast path since | |
168 | // we can just return the rest of the buffer verbatim. | |
169 | return cty.StringVal(string(sub)), nil | |
170 | } | |
171 | ||
172 | // Otherwise we need to start seeking forward again until we | |
173 | // reach the length we want. | |
174 | pos = 0 | |
175 | for i = 0; i < len(sub); { | |
176 | d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true) | |
177 | i += d | |
178 | pos++ | |
179 | if pos == length { | |
180 | break | |
181 | } | |
182 | } | |
183 | ||
184 | sub = sub[:i] | |
185 | ||
186 | return cty.StringVal(string(sub)), nil | |
187 | }, | |
188 | }) | |
189 | ||
190 | // Upper is a Function that converts a given string to uppercase. | |
191 | func Upper(str cty.Value) (cty.Value, error) { | |
192 | return UpperFunc.Call([]cty.Value{str}) | |
193 | } | |
194 | ||
195 | // Lower is a Function that converts a given string to lowercase. | |
196 | func Lower(str cty.Value) (cty.Value, error) { | |
197 | return LowerFunc.Call([]cty.Value{str}) | |
198 | } | |
199 | ||
200 | // Reverse is a Function that reverses the order of the characters in the | |
201 | // given string. | |
202 | // | |
203 | // As usual, "character" for the sake of this function is a grapheme cluster, | |
204 | // so combining diacritics (for example) will be considered together as a | |
205 | // single character. | |
206 | func Reverse(str cty.Value) (cty.Value, error) { | |
207 | return ReverseFunc.Call([]cty.Value{str}) | |
208 | } | |
209 | ||
210 | // Strlen is a Function that returns the length of the given string in | |
211 | // characters. | |
212 | // | |
213 | // As usual, "character" for the sake of this function is a grapheme cluster, | |
214 | // so combining diacritics (for example) will be considered together as a | |
215 | // single character. | |
216 | func Strlen(str cty.Value) (cty.Value, error) { | |
217 | return StrlenFunc.Call([]cty.Value{str}) | |
218 | } | |
219 | ||
220 | // Substr is a Function that extracts a sequence of characters from another | |
221 | // string and creates a new string. | |
222 | // | |
223 | // As usual, "character" for the sake of this function is a grapheme cluster, | |
224 | // so combining diacritics (for example) will be considered together as a | |
225 | // single character. | |
226 | // | |
227 | // The "offset" index may be negative, in which case it is relative to the | |
228 | // end of the given string. | |
229 | // | |
230 | // The "length" may be -1, in which case the remainder of the string after | |
231 | // the given offset will be returned. | |
232 | func Substr(str cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) { | |
233 | return SubstrFunc.Call([]cty.Value{str, offset, length}) | |
234 | } |