...
1 package toml
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7
8 "github.com/pelletier/go-toml/v2/internal/danger"
9 "github.com/pelletier/go-toml/v2/unstable"
10 )
11
12
13
14
15
16
17
18 type DecodeError struct {
19 message string
20 line int
21 column int
22 key Key
23
24 human string
25 }
26
27
28
29
30
31
32 type StrictMissingError struct {
33
34 Errors []DecodeError
35 }
36
37
38 func (s *StrictMissingError) Error() string {
39 return "strict mode: fields in the document are missing in the target struct"
40 }
41
42
43 func (s *StrictMissingError) String() string {
44 var buf strings.Builder
45
46 for i, e := range s.Errors {
47 if i > 0 {
48 buf.WriteString("\n---\n")
49 }
50
51 buf.WriteString(e.String())
52 }
53
54 return buf.String()
55 }
56
57 type Key []string
58
59
60 func (e *DecodeError) Error() string {
61 return "toml: " + e.message
62 }
63
64
65 func (e *DecodeError) String() string {
66 return e.human
67 }
68
69
70
71 func (e *DecodeError) Position() (row int, column int) {
72 return e.line, e.column
73 }
74
75
76
77 func (e *DecodeError) Key() Key {
78 return e.key
79 }
80
81
82
83
84
85
86
87
88
89
90 func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
91 offset := danger.SubsliceOffset(document, de.Highlight)
92
93 errMessage := de.Error()
94 errLine, errColumn := positionAtEnd(document[:offset])
95 before, after := linesOfContext(document, de.Highlight, offset, 3)
96
97 var buf strings.Builder
98
99 maxLine := errLine + len(after) - 1
100 lineColumnWidth := len(strconv.Itoa(maxLine))
101
102
103 for i := len(before) - 1; i > 0; i-- {
104 line := errLine - i
105 buf.WriteString(formatLineNumber(line, lineColumnWidth))
106 buf.WriteString("|")
107
108 if len(before[i]) > 0 {
109 buf.WriteString(" ")
110 buf.Write(before[i])
111 }
112
113 buf.WriteRune('\n')
114 }
115
116
117
118 buf.WriteString(formatLineNumber(errLine, lineColumnWidth))
119 buf.WriteString("| ")
120
121 if len(before) > 0 {
122 buf.Write(before[0])
123 }
124
125 buf.Write(de.Highlight)
126
127 if len(after) > 0 {
128 buf.Write(after[0])
129 }
130
131 buf.WriteRune('\n')
132
133
134
135
136 buf.WriteString(strings.Repeat(" ", lineColumnWidth))
137 buf.WriteString("| ")
138
139 if len(before) > 0 {
140 buf.WriteString(strings.Repeat(" ", len(before[0])))
141 }
142
143 buf.WriteString(strings.Repeat("~", len(de.Highlight)))
144
145 if len(errMessage) > 0 {
146 buf.WriteString(" ")
147 buf.WriteString(errMessage)
148 }
149
150
151
152 for i := 1; i < len(after); i++ {
153 buf.WriteRune('\n')
154 line := errLine + i
155 buf.WriteString(formatLineNumber(line, lineColumnWidth))
156 buf.WriteString("|")
157
158 if len(after[i]) > 0 {
159 buf.WriteString(" ")
160 buf.Write(after[i])
161 }
162 }
163
164 return &DecodeError{
165 message: errMessage,
166 line: errLine,
167 column: errColumn,
168 key: de.Key,
169 human: buf.String(),
170 }
171 }
172
173 func formatLineNumber(line int, width int) string {
174 format := "%" + strconv.Itoa(width) + "d"
175
176 return fmt.Sprintf(format, line)
177 }
178
179 func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) {
180 return beforeLines(document, offset, linesAround), afterLines(document, highlight, offset, linesAround)
181 }
182
183 func beforeLines(document []byte, offset int, linesAround int) [][]byte {
184 var beforeLines [][]byte
185
186
187
188 rest := document[:offset]
189 backward:
190 for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; {
191 switch {
192 case rest[o] == '\n':
193
194 beforeLines = append(beforeLines, rest[o+1:])
195 rest = rest[:o]
196 o = len(rest) - 1
197 case o == 0:
198
199 beforeLines = append(beforeLines, rest)
200
201 break backward
202 default:
203 o--
204 }
205 }
206
207 return beforeLines
208 }
209
210 func afterLines(document []byte, highlight []byte, offset int, linesAround int) [][]byte {
211 var afterLines [][]byte
212
213
214
215 rest := document[offset+len(highlight):]
216 forward:
217 for o := 0; o < len(rest) && len(afterLines) <= linesAround; {
218 switch {
219 case rest[o] == '\n':
220
221 afterLines = append(afterLines, rest[:o])
222 rest = rest[o+1:]
223 o = 0
224
225 case o == len(rest)-1:
226
227 afterLines = append(afterLines, rest)
228
229 break forward
230 default:
231 o++
232 }
233 }
234
235 return afterLines
236 }
237
238 func positionAtEnd(b []byte) (row int, column int) {
239 row = 1
240 column = 1
241
242 for _, c := range b {
243 if c == '\n' {
244 row++
245 column = 1
246 } else {
247 column++
248 }
249 }
250
251 return
252 }
253
View as plain text