diff options
Diffstat (limited to 'vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go')
-rw-r--r-- | vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go new file mode 100644 index 0000000..fb24f20 --- /dev/null +++ b/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go | |||
@@ -0,0 +1,496 @@ | |||
1 | package stdlib | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "math/big" | ||
7 | "strings" | ||
8 | |||
9 | "github.com/apparentlymart/go-textseg/textseg" | ||
10 | |||
11 | "github.com/zclconf/go-cty/cty" | ||
12 | "github.com/zclconf/go-cty/cty/convert" | ||
13 | "github.com/zclconf/go-cty/cty/function" | ||
14 | "github.com/zclconf/go-cty/cty/json" | ||
15 | ) | ||
16 | |||
17 | //go:generate ragel -Z format_fsm.rl | ||
18 | //go:generate gofmt -w format_fsm.go | ||
19 | |||
20 | var FormatFunc = function.New(&function.Spec{ | ||
21 | Params: []function.Parameter{ | ||
22 | { | ||
23 | Name: "format", | ||
24 | Type: cty.String, | ||
25 | }, | ||
26 | }, | ||
27 | VarParam: &function.Parameter{ | ||
28 | Name: "args", | ||
29 | Type: cty.DynamicPseudoType, | ||
30 | AllowNull: true, | ||
31 | }, | ||
32 | Type: function.StaticReturnType(cty.String), | ||
33 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
34 | for _, arg := range args[1:] { | ||
35 | if !arg.IsWhollyKnown() { | ||
36 | // We require all nested values to be known because the only | ||
37 | // thing we can do for a collection/structural type is print | ||
38 | // it as JSON and that requires it to be wholly known. | ||
39 | return cty.UnknownVal(cty.String), nil | ||
40 | } | ||
41 | } | ||
42 | str, err := formatFSM(args[0].AsString(), args[1:]) | ||
43 | return cty.StringVal(str), err | ||
44 | }, | ||
45 | }) | ||
46 | |||
47 | var FormatListFunc = function.New(&function.Spec{ | ||
48 | Params: []function.Parameter{ | ||
49 | { | ||
50 | Name: "format", | ||
51 | Type: cty.String, | ||
52 | }, | ||
53 | }, | ||
54 | VarParam: &function.Parameter{ | ||
55 | Name: "args", | ||
56 | Type: cty.DynamicPseudoType, | ||
57 | AllowNull: true, | ||
58 | AllowUnknown: true, | ||
59 | }, | ||
60 | Type: function.StaticReturnType(cty.List(cty.String)), | ||
61 | Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { | ||
62 | fmtVal := args[0] | ||
63 | args = args[1:] | ||
64 | |||
65 | if len(args) == 0 { | ||
66 | // With no arguments, this function is equivalent to Format, but | ||
67 | // returning a single-element list result. | ||
68 | result, err := Format(fmtVal, args...) | ||
69 | return cty.ListVal([]cty.Value{result}), err | ||
70 | } | ||
71 | |||
72 | fmtStr := fmtVal.AsString() | ||
73 | |||
74 | // Each of our arguments will be dealt with either as an iterator | ||
75 | // or as a single value. Iterators are used for sequence-type values | ||
76 | // (lists, sets, tuples) while everything else is treated as a | ||
77 | // single value. The sequences we iterate over are required to be | ||
78 | // all the same length. | ||
79 | iterLen := -1 | ||
80 | lenChooser := -1 | ||
81 | iterators := make([]cty.ElementIterator, len(args)) | ||
82 | singleVals := make([]cty.Value, len(args)) | ||
83 | for i, arg := range args { | ||
84 | argTy := arg.Type() | ||
85 | switch { | ||
86 | case (argTy.IsListType() || argTy.IsSetType() || argTy.IsTupleType()) && !arg.IsNull(): | ||
87 | thisLen := arg.LengthInt() | ||
88 | if iterLen == -1 { | ||
89 | iterLen = thisLen | ||
90 | lenChooser = i | ||
91 | } else { | ||
92 | if thisLen != iterLen { | ||
93 | return cty.NullVal(cty.List(cty.String)), function.NewArgErrorf( | ||
94 | i+1, | ||
95 | "argument %d has length %d, which is inconsistent with argument %d of length %d", | ||
96 | i+1, thisLen, | ||
97 | lenChooser+1, iterLen, | ||
98 | ) | ||
99 | } | ||
100 | } | ||
101 | iterators[i] = arg.ElementIterator() | ||
102 | default: | ||
103 | singleVals[i] = arg | ||
104 | } | ||
105 | } | ||
106 | |||
107 | if iterLen == 0 { | ||
108 | // If our sequences are all empty then our result must be empty. | ||
109 | return cty.ListValEmpty(cty.String), nil | ||
110 | } | ||
111 | |||
112 | if iterLen == -1 { | ||
113 | // If we didn't encounter any iterables at all then we're going | ||
114 | // to just do one iteration with items from singleVals. | ||
115 | iterLen = 1 | ||
116 | } | ||
117 | |||
118 | ret := make([]cty.Value, 0, iterLen) | ||
119 | fmtArgs := make([]cty.Value, len(iterators)) | ||
120 | Results: | ||
121 | for iterIdx := 0; iterIdx < iterLen; iterIdx++ { | ||
122 | |||
123 | // Construct our arguments for a single format call | ||
124 | for i := range fmtArgs { | ||
125 | switch { | ||
126 | case iterators[i] != nil: | ||
127 | iterator := iterators[i] | ||
128 | iterator.Next() | ||
129 | _, val := iterator.Element() | ||
130 | fmtArgs[i] = val | ||
131 | default: | ||
132 | fmtArgs[i] = singleVals[i] | ||
133 | } | ||
134 | |||
135 | // If any of the arguments to this call would be unknown then | ||
136 | // this particular result is unknown, but we'll keep going | ||
137 | // to see if any other iterations can produce known values. | ||
138 | if !fmtArgs[i].IsWhollyKnown() { | ||
139 | // We require all nested values to be known because the only | ||
140 | // thing we can do for a collection/structural type is print | ||
141 | // it as JSON and that requires it to be wholly known. | ||
142 | ret = append(ret, cty.UnknownVal(cty.String)) | ||
143 | continue Results | ||
144 | } | ||
145 | } | ||
146 | |||
147 | str, err := formatFSM(fmtStr, fmtArgs) | ||
148 | if err != nil { | ||
149 | return cty.NullVal(cty.List(cty.String)), fmt.Errorf( | ||
150 | "error on format iteration %d: %s", iterIdx, err, | ||
151 | ) | ||
152 | } | ||
153 | |||
154 | ret = append(ret, cty.StringVal(str)) | ||
155 | } | ||
156 | |||
157 | return cty.ListVal(ret), nil | ||
158 | }, | ||
159 | }) | ||
160 | |||
161 | // Format produces a string representation of zero or more values using a | ||
162 | // format string similar to the "printf" function in C. | ||
163 | // | ||
164 | // It supports the following "verbs": | ||
165 | // | ||
166 | // %% Literal percent sign, consuming no value | ||
167 | // %v A default formatting of the value based on type, as described below. | ||
168 | // %#v JSON serialization of the value | ||
169 | // %t Converts to boolean and then produces "true" or "false" | ||
170 | // %b Converts to number, requires integer, produces binary representation | ||
171 | // %d Converts to number, requires integer, produces decimal representation | ||
172 | // %o Converts to number, requires integer, produces octal representation | ||
173 | // %x Converts to number, requires integer, produces hexadecimal representation | ||
174 | // with lowercase letters | ||
175 | // %X Like %x but with uppercase letters | ||
176 | // %e Converts to number, produces scientific notation like -1.234456e+78 | ||
177 | // %E Like %e but with an uppercase "E" representing the exponent | ||
178 | // %f Converts to number, produces decimal representation with fractional | ||
179 | // part but no exponent, like 123.456 | ||
180 | // %g %e for large exponents or %f otherwise | ||
181 | // %G %E for large exponents or %f otherwise | ||
182 | // %s Converts to string and produces the string's characters | ||
183 | // %q Converts to string and produces JSON-quoted string representation, | ||
184 | // like %v. | ||
185 | // | ||
186 | // The default format selections made by %v are: | ||
187 | // | ||
188 | // string %s | ||
189 | // number %g | ||
190 | // bool %t | ||
191 | // other %#v | ||
192 | // | ||
193 | // Null values produce the literal keyword "null" for %v and %#v, and produce | ||
194 | // an error otherwise. | ||
195 | // | ||
196 | // Width is specified by an optional decimal number immediately preceding the | ||
197 | // verb letter. If absent, the width is whatever is necessary to represent the | ||
198 | // value. Precision is specified after the (optional) width by a period | ||
199 | // followed by a decimal number. If no period is present, a default precision | ||
200 | // is used. A period with no following number is invalid. | ||
201 | // For examples: | ||
202 | // | ||
203 | // %f default width, default precision | ||
204 | // %9f width 9, default precision | ||
205 | // %.2f default width, precision 2 | ||
206 | // %9.2f width 9, precision 2 | ||
207 | // | ||
208 | // Width and precision are measured in unicode characters (grapheme clusters). | ||
209 | // | ||
210 | // For most values, width is the minimum number of characters to output, | ||
211 | // padding the formatted form with spaces if necessary. | ||
212 | // | ||
213 | // For strings, precision limits the length of the input to be formatted (not | ||
214 | // the size of the output), truncating if necessary. | ||
215 | // | ||
216 | // For numbers, width sets the minimum width of the field and precision sets | ||
217 | // the number of places after the decimal, if appropriate, except that for | ||
218 | // %g/%G precision sets the total number of significant digits. | ||
219 | // | ||
220 | // The following additional symbols can be used immediately after the percent | ||
221 | // introducer as flags: | ||
222 | // | ||
223 | // (a space) leave a space where the sign would be if number is positive | ||
224 | // + Include a sign for a number even if it is positive (numeric only) | ||
225 | // - Pad with spaces on the left rather than the right | ||
226 | // 0 Pad with zeros rather than spaces. | ||
227 | // | ||
228 | // Flag characters are ignored for verbs that do not support them. | ||
229 | // | ||
230 | // By default, % sequences consume successive arguments starting with the first. | ||
231 | // Introducing a [n] sequence immediately before the verb letter, where n is a | ||
232 | // decimal integer, explicitly chooses a particular value argument by its | ||
233 | // one-based index. Subsequent calls without an explicit index will then | ||
234 | // proceed with n+1, n+2, etc. | ||
235 | // | ||
236 | // An error is produced if the format string calls for an impossible conversion | ||
237 | // or accesses more values than are given. An error is produced also for | ||
238 | // an unsupported format verb. | ||
239 | func Format(format cty.Value, vals ...cty.Value) (cty.Value, error) { | ||
240 | args := make([]cty.Value, 0, len(vals)+1) | ||
241 | args = append(args, format) | ||
242 | args = append(args, vals...) | ||
243 | return FormatFunc.Call(args) | ||
244 | } | ||
245 | |||
246 | // FormatList applies the same formatting behavior as Format, but accepts | ||
247 | // a mixture of list and non-list values as arguments. Any list arguments | ||
248 | // passed must have the same length, which dictates the length of the | ||
249 | // resulting list. | ||
250 | // | ||
251 | // Any non-list arguments are used repeatedly for each iteration over the | ||
252 | // list arguments. The list arguments are iterated in order by key, so | ||
253 | // corresponding items are formatted together. | ||
254 | func FormatList(format cty.Value, vals ...cty.Value) (cty.Value, error) { | ||
255 | args := make([]cty.Value, 0, len(vals)+1) | ||
256 | args = append(args, format) | ||
257 | args = append(args, vals...) | ||
258 | return FormatListFunc.Call(args) | ||
259 | } | ||
260 | |||
261 | type formatVerb struct { | ||
262 | Raw string | ||
263 | Offset int | ||
264 | |||
265 | ArgNum int | ||
266 | Mode rune | ||
267 | |||
268 | Zero bool | ||
269 | Sharp bool | ||
270 | Plus bool | ||
271 | Minus bool | ||
272 | Space bool | ||
273 | |||
274 | HasPrec bool | ||
275 | Prec int | ||
276 | |||
277 | HasWidth bool | ||
278 | Width int | ||
279 | } | ||
280 | |||
281 | // formatAppend is called by formatFSM (generated by format_fsm.rl) for each | ||
282 | // formatting sequence that is encountered. | ||
283 | func formatAppend(verb *formatVerb, buf *bytes.Buffer, args []cty.Value) error { | ||
284 | argIdx := verb.ArgNum - 1 | ||
285 | if argIdx >= len(args) { | ||
286 | return fmt.Errorf( | ||
287 | "not enough arguments for %q at %d: need index %d but have %d total", | ||
288 | verb.Raw, verb.Offset, | ||
289 | verb.ArgNum, len(args), | ||
290 | ) | ||
291 | } | ||
292 | arg := args[argIdx] | ||
293 | |||
294 | if verb.Mode != 'v' && arg.IsNull() { | ||
295 | return fmt.Errorf("unsupported value for %q at %d: null value cannot be formatted", verb.Raw, verb.Offset) | ||
296 | } | ||
297 | |||
298 | // Normalize to make some things easier for downstream formatters | ||
299 | if !verb.HasWidth { | ||
300 | verb.Width = -1 | ||
301 | } | ||
302 | if !verb.HasPrec { | ||
303 | verb.Prec = -1 | ||
304 | } | ||
305 | |||
306 | // For our first pass we'll ensure the verb is supported and then fan | ||
307 | // out to other functions based on what conversion is needed. | ||
308 | switch verb.Mode { | ||
309 | |||
310 | case 'v': | ||
311 | return formatAppendAsIs(verb, buf, arg) | ||
312 | |||
313 | case 't': | ||
314 | return formatAppendBool(verb, buf, arg) | ||
315 | |||
316 | case 'b', 'd', 'o', 'x', 'X', 'e', 'E', 'f', 'g', 'G': | ||
317 | return formatAppendNumber(verb, buf, arg) | ||
318 | |||
319 | case 's', 'q': | ||
320 | return formatAppendString(verb, buf, arg) | ||
321 | |||
322 | default: | ||
323 | return fmt.Errorf("unsupported format verb %q in %q at offset %d", verb.Mode, verb.Raw, verb.Offset) | ||
324 | } | ||
325 | } | ||
326 | |||
327 | func formatAppendAsIs(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { | ||
328 | |||
329 | if !verb.Sharp && !arg.IsNull() { | ||
330 | // Unless the caller overrode it with the sharp flag, we'll try some | ||
331 | // specialized formats before we fall back on JSON. | ||
332 | switch arg.Type() { | ||
333 | case cty.String: | ||
334 | fmted := arg.AsString() | ||
335 | fmted = formatPadWidth(verb, fmted) | ||
336 | buf.WriteString(fmted) | ||
337 | return nil | ||
338 | case cty.Number: | ||
339 | bf := arg.AsBigFloat() | ||
340 | fmted := bf.Text('g', -1) | ||
341 | fmted = formatPadWidth(verb, fmted) | ||
342 | buf.WriteString(fmted) | ||
343 | return nil | ||
344 | } | ||
345 | } | ||
346 | |||
347 | jb, err := json.Marshal(arg, arg.Type()) | ||
348 | if err != nil { | ||
349 | return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) | ||
350 | } | ||
351 | fmted := formatPadWidth(verb, string(jb)) | ||
352 | buf.WriteString(fmted) | ||
353 | |||
354 | return nil | ||
355 | } | ||
356 | |||
357 | func formatAppendBool(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { | ||
358 | var err error | ||
359 | arg, err = convert.Convert(arg, cty.Bool) | ||
360 | if err != nil { | ||
361 | return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) | ||
362 | } | ||
363 | |||
364 | if arg.True() { | ||
365 | buf.WriteString("true") | ||
366 | } else { | ||
367 | buf.WriteString("false") | ||
368 | } | ||
369 | return nil | ||
370 | } | ||
371 | |||
372 | func formatAppendNumber(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { | ||
373 | var err error | ||
374 | arg, err = convert.Convert(arg, cty.Number) | ||
375 | if err != nil { | ||
376 | return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) | ||
377 | } | ||
378 | |||
379 | switch verb.Mode { | ||
380 | case 'b', 'd', 'o', 'x', 'X': | ||
381 | return formatAppendInteger(verb, buf, arg) | ||
382 | default: | ||
383 | bf := arg.AsBigFloat() | ||
384 | |||
385 | // For floats our format syntax is a subset of Go's, so it's | ||
386 | // safe for us to just lean on the existing Go implementation. | ||
387 | fmtstr := formatStripIndexSegment(verb.Raw) | ||
388 | fmted := fmt.Sprintf(fmtstr, bf) | ||
389 | buf.WriteString(fmted) | ||
390 | return nil | ||
391 | } | ||
392 | } | ||
393 | |||
394 | func formatAppendInteger(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { | ||
395 | bf := arg.AsBigFloat() | ||
396 | bi, acc := bf.Int(nil) | ||
397 | if acc != big.Exact { | ||
398 | return fmt.Errorf("unsupported value for %q at %d: an integer is required", verb.Raw, verb.Offset) | ||
399 | } | ||
400 | |||
401 | // For integers our format syntax is a subset of Go's, so it's | ||
402 | // safe for us to just lean on the existing Go implementation. | ||
403 | fmtstr := formatStripIndexSegment(verb.Raw) | ||
404 | fmted := fmt.Sprintf(fmtstr, bi) | ||
405 | buf.WriteString(fmted) | ||
406 | return nil | ||
407 | } | ||
408 | |||
409 | func formatAppendString(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { | ||
410 | var err error | ||
411 | arg, err = convert.Convert(arg, cty.String) | ||
412 | if err != nil { | ||
413 | return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) | ||
414 | } | ||
415 | |||
416 | // We _cannot_ directly use the Go fmt.Sprintf implementation for strings | ||
417 | // because it measures widths and precisions in runes rather than grapheme | ||
418 | // clusters. | ||
419 | |||
420 | str := arg.AsString() | ||
421 | if verb.Prec > 0 { | ||
422 | strB := []byte(str) | ||
423 | pos := 0 | ||
424 | wanted := verb.Prec | ||
425 | for i := 0; i < wanted; i++ { | ||
426 | next := strB[pos:] | ||
427 | if len(next) == 0 { | ||
428 | // ran out of characters before we hit our max width | ||
429 | break | ||
430 | } | ||
431 | d, _, _ := textseg.ScanGraphemeClusters(strB[pos:], true) | ||
432 | pos += d | ||
433 | } | ||
434 | str = str[:pos] | ||
435 | } | ||
436 | |||
437 | switch verb.Mode { | ||
438 | case 's': | ||
439 | fmted := formatPadWidth(verb, str) | ||
440 | buf.WriteString(fmted) | ||
441 | case 'q': | ||
442 | jb, err := json.Marshal(cty.StringVal(str), cty.String) | ||
443 | if err != nil { | ||
444 | // Should never happen, since we know this is a known, non-null string | ||
445 | panic(fmt.Errorf("failed to marshal %#v as JSON: %s", arg, err)) | ||
446 | } | ||
447 | fmted := formatPadWidth(verb, string(jb)) | ||
448 | buf.WriteString(fmted) | ||
449 | default: | ||
450 | // Should never happen because formatAppend should've already validated | ||
451 | panic(fmt.Errorf("invalid string formatting mode %q", verb.Mode)) | ||
452 | } | ||
453 | return nil | ||
454 | } | ||
455 | |||
456 | func formatPadWidth(verb *formatVerb, fmted string) string { | ||
457 | if verb.Width < 0 { | ||
458 | return fmted | ||
459 | } | ||
460 | |||
461 | // Safe to ignore errors because ScanGraphemeClusters cannot produce errors | ||
462 | givenLen, _ := textseg.TokenCount([]byte(fmted), textseg.ScanGraphemeClusters) | ||
463 | wantLen := verb.Width | ||
464 | if givenLen >= wantLen { | ||
465 | return fmted | ||
466 | } | ||
467 | |||
468 | padLen := wantLen - givenLen | ||
469 | padChar := " " | ||
470 | if verb.Zero { | ||
471 | padChar = "0" | ||
472 | } | ||
473 | pads := strings.Repeat(padChar, padLen) | ||
474 | |||
475 | if verb.Minus { | ||
476 | return fmted + pads | ||
477 | } | ||
478 | return pads + fmted | ||
479 | } | ||
480 | |||
481 | // formatStripIndexSegment strips out any [nnn] segment present in a verb | ||
482 | // string so that we can pass it through to Go's fmt.Sprintf with a single | ||
483 | // argument. This is used in cases where we're just leaning on Go's formatter | ||
484 | // because it's a superset of ours. | ||
485 | func formatStripIndexSegment(rawVerb string) string { | ||
486 | // We assume the string has already been validated here, since we should | ||
487 | // only be using this function with strings that were accepted by our | ||
488 | // scanner in formatFSM. | ||
489 | start := strings.Index(rawVerb, "[") | ||
490 | end := strings.Index(rawVerb, "]") | ||
491 | if start == -1 || end == -1 { | ||
492 | return rawVerb | ||
493 | } | ||
494 | |||
495 | return rawVerb[:start] + rawVerb[end+1:] | ||
496 | } | ||