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