-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathformatter.go
216 lines (194 loc) · 5.22 KB
/
formatter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package errors
// Attribution: portions of the below code and documentation are modeled
// directly on the github.com/dominikh/go-tools/blob/master/printf
// package, used with the permission available under the software
// license (MIT):
// https://github.com/dominikh/go-tools/blob/master/LICENSE
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
// Errorf is a shorthand for:
//
// fmt.Errorf("some msg: %w", errors.WithFrame(err))
//
// It is made available to support the best practice of adding a call
// stack frame to the error context alongside a message when building a
// chain. When possible, prefer using the full syntax instead of this
// shorthand for clarity.
//
// Similar to fmt.Errorf, this function supports multiple `%w` verbs to
// generate a multierror: each wrapped error will have a frame attached
// to it.
func Errorf(format string, values ...interface{}) error {
verbs, err := parseFormatString(format, len(values))
if err != nil {
return errors.New(`%!e(errors.Errorf=failed: ` + err.Error() + `)`)
}
var numWrapped int
for _, v := range verbs {
if v.letter != 'w' {
continue
}
numWrapped++
}
// Allow no %w verbs, and when 0 or 1, then we wrap the created error
// with a frame. This allows the received error to handle %+v formatting
// correctly.
if numWrapped <= 1 {
return &withFrames{
error: fmt.Errorf(format, values...),
frames: frames{getFrame(3)},
}
}
// Interpose and wrap errors with framer if the associated verb is `%w`,
// when there is more than one value, creating a multierror where each
// error has a reference to the frame.
for _, v := range verbs {
if v.letter != 'w' {
continue
}
if wrappedErr, ok := values[v.idx].(error); ok {
if wrappedErr != nil {
numWrapped++
values[v.idx] = &withFrames{
error: wrappedErr,
frames: frames{getFrame(3)},
}
}
}
}
return fmt.Errorf(format, values...)
}
type fmtVerb struct {
letter rune
flags string
// Which value in the argument list the verb uses:
// * -1 denotes the next argument,
// * >0 denote explicit arguments,
// * 0 denotes that no argument is consumed, ie: %%. This will not be returned.
value int
// Similar to above: take into account argument indices used in either
// place. When a literal will be 0.
width, prec int
// The 0-indexed argument this verb is associated with.
idx int
raw string
}
// parseFormatString parses f and returns a list of actions.
// An action may either be a literal string, or a Verb.
//
// This may break down when doing some more abstract things, like using
// argument indices with star precisions. If there is a problem, please
// don't use Errorf.
func parseFormatString(f string, numValues int) (verbs []fmtVerb, err error) {
var nextValueIndex int
for len(f) > 0 {
if f[0] == '%' {
v, n, err := parseVerb(f)
if err != nil {
return nil, err
}
f = f[n:]
if v.value != 0 {
if v.width > numValues {
return nil, errors.New("invalid format string: not enough arguments")
}
if v.prec > numValues {
return nil, errors.New("invalid format string: not enough arguments")
}
if v.value == -1 {
v.idx = nextValueIndex
nextValueIndex++
} else {
// printf argument index is one-indexed, so we can always subtract 1 here.
v.idx = v.value - 1
nextValueIndex = v.value
}
if v.idx >= numValues {
return nil, errors.New("invalid format string: not enough arguments")
}
verbs = append(verbs, v)
}
} else {
n := strings.IndexByte(f, '%')
if n > -1 {
f = f[n:]
} else {
f = ""
}
}
}
return verbs, nil
}
func atoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
// parseVerb parses the verb at the beginning of f. It returns the verb,
// how much of the input was consumed, and an error, if any.
func parseVerb(f string) (fmtVerb, int, error) {
if len(f) < 2 {
return fmtVerb{}, 0, errors.New("invalid format string")
}
const (
flags = 1
widthStar = 3
widthIndex = 5
dot = 6
precStar = 8
precIndex = 10
verbIndex = 11
verb = 12
)
m := re.FindStringSubmatch(f)
if m == nil {
return fmtVerb{}, 0, errors.New("invalid format string")
}
v := fmtVerb{
letter: []rune(m[verb])[0],
flags: m[flags],
raw: m[0],
}
if m[widthStar] != "" {
if m[widthIndex] != "" {
v.width = atoi(m[widthIndex])
} else {
v.width = -1
}
}
if m[dot] != "" && m[precStar] != "" {
if m[precIndex] != "" {
v.prec = atoi(m[precIndex])
} else {
v.prec = -1
}
}
if m[verb] == "%" {
v.value = 0
} else if m[verbIndex] != "" {
idx := atoi(m[verbIndex])
if idx <= 0 || idx > 128 {
return fmtVerb{}, 0, errors.New("invalid format string: bad argument index")
}
v.value = idx
} else {
v.value = -1
}
return v, len(m[0]), nil
}
const (
flags = `([+#0 -]*)`
verb = `([a-zA-Z%])`
index = `(?:\[([0-9]+)\])`
star = `((` + index + `)?\*)`
width1 = `([0-9]+)`
width2 = star
width = `(?:` + width1 + `|` + width2 + `)`
precision = width
widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
)
var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)