aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/function/stdlib/format.go
diff options
context:
space:
mode:
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.go496
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 @@
1package stdlib
2
3import (
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
20var 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
47var 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.
239func 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.
254func 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
261type 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.
283func 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
327func 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
357func 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
372func 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
394func 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
409func 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
456func 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.
485func 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}