podman
279 строк · 8.1 Кб
1package toml
2
3import (
4"fmt"
5"strings"
6)
7
8// ParseError is returned when there is an error parsing the TOML syntax such as
9// invalid syntax, duplicate keys, etc.
10//
11// In addition to the error message itself, you can also print detailed location
12// information with context by using [ErrorWithPosition]:
13//
14// toml: error: Key 'fruit' was already created and cannot be used as an array.
15//
16// At line 4, column 2-7:
17//
18// 2 | fruit = []
19// 3 |
20// 4 | [[fruit]] # Not allowed
21// ^^^^^
22//
23// [ErrorWithUsage] can be used to print the above with some more detailed usage
24// guidance:
25//
26// toml: error: newlines not allowed within inline tables
27//
28// At line 1, column 18:
29//
30// 1 | x = [{ key = 42 #
31// ^
32//
33// Error help:
34//
35// Inline tables must always be on a single line:
36//
37// table = {key = 42, second = 43}
38//
39// It is invalid to split them over multiple lines like so:
40//
41// # INVALID
42// table = {
43// key = 42,
44// second = 43
45// }
46//
47// Use regular for this:
48//
49// [table]
50// key = 42
51// second = 43
52type ParseError struct {
53Message string // Short technical message.
54Usage string // Longer message with usage guidance; may be blank.
55Position Position // Position of the error
56LastKey string // Last parsed key, may be blank.
57
58// Line the error occurred.
59//
60// Deprecated: use [Position].
61Line int
62
63err error
64input string
65}
66
67// Position of an error.
68type Position struct {
69Line int // Line number, starting at 1.
70Start int // Start of error, as byte offset starting at 0.
71Len int // Lenght in bytes.
72}
73
74func (pe ParseError) Error() string {
75msg := pe.Message
76if msg == "" { // Error from errorf()
77msg = pe.err.Error()
78}
79
80if pe.LastKey == "" {
81return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
82}
83return fmt.Sprintf("toml: line %d (last key %q): %s",
84pe.Position.Line, pe.LastKey, msg)
85}
86
87// ErrorWithPosition returns the error with detailed location context.
88//
89// See the documentation on [ParseError].
90func (pe ParseError) ErrorWithPosition() string {
91if pe.input == "" { // Should never happen, but just in case.
92return pe.Error()
93}
94
95var (
96lines = strings.Split(pe.input, "\n")
97col = pe.column(lines)
98b = new(strings.Builder)
99)
100
101msg := pe.Message
102if msg == "" {
103msg = pe.err.Error()
104}
105
106// TODO: don't show control characters as literals? This may not show up
107// well everywhere.
108
109if pe.Position.Len == 1 {
110fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
111msg, pe.Position.Line, col+1)
112} else {
113fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
114msg, pe.Position.Line, col, col+pe.Position.Len)
115}
116if pe.Position.Line > 2 {
117fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
118}
119if pe.Position.Line > 1 {
120fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
121}
122fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
123fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
124return b.String()
125}
126
127// ErrorWithUsage returns the error with detailed location context and usage
128// guidance.
129//
130// See the documentation on [ParseError].
131func (pe ParseError) ErrorWithUsage() string {
132m := pe.ErrorWithPosition()
133if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
134lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
135for i := range lines {
136if lines[i] != "" {
137lines[i] = " " + lines[i]
138}
139}
140return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
141}
142return m
143}
144
145func (pe ParseError) column(lines []string) int {
146var pos, col int
147for i := range lines {
148ll := len(lines[i]) + 1 // +1 for the removed newline
149if pos+ll >= pe.Position.Start {
150col = pe.Position.Start - pos
151if col < 0 { // Should never happen, but just in case.
152col = 0
153}
154break
155}
156pos += ll
157}
158
159return col
160}
161
162type (
163errLexControl struct{ r rune }
164errLexEscape struct{ r rune }
165errLexUTF8 struct{ b byte }
166errLexInvalidNum struct{ v string }
167errLexInvalidDate struct{ v string }
168errLexInlineTableNL struct{}
169errLexStringNL struct{}
170errParseRange struct {
171i interface{} // int or float
172size string // "int64", "uint16", etc.
173}
174errParseDuration struct{ d string }
175)
176
177func (e errLexControl) Error() string {
178return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
179}
180func (e errLexControl) Usage() string { return "" }
181
182func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
183func (e errLexEscape) Usage() string { return usageEscape }
184func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
185func (e errLexUTF8) Usage() string { return "" }
186func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) }
187func (e errLexInvalidNum) Usage() string { return "" }
188func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) }
189func (e errLexInvalidDate) Usage() string { return "" }
190func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
191func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
192func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
193func (e errLexStringNL) Usage() string { return usageStringNewline }
194func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
195func (e errParseRange) Usage() string { return usageIntOverflow }
196func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
197func (e errParseDuration) Usage() string { return usageDuration }
198
199const usageEscape = `
200A '\' inside a "-delimited string is interpreted as an escape character.
201
202The following escape sequences are supported:
203\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
204
205To prevent a '\' from being recognized as an escape character, use either:
206
207- a ' or '''-delimited string; escape characters aren't processed in them; or
208- write two backslashes to get a single backslash: '\\'.
209
210If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
211instead of '\' will usually also work: "C:/Users/martin".
212`
213
214const usageInlineNewline = `
215Inline tables must always be on a single line:
216
217table = {key = 42, second = 43}
218
219It is invalid to split them over multiple lines like so:
220
221# INVALID
222table = {
223key = 42,
224second = 43
225}
226
227Use regular for this:
228
229[table]
230key = 42
231second = 43
232`
233
234const usageStringNewline = `
235Strings must always be on a single line, and cannot span more than one line:
236
237# INVALID
238string = "Hello,
239world!"
240
241Instead use """ or ''' to split strings over multiple lines:
242
243string = """Hello,
244world!"""
245`
246
247const usageIntOverflow = `
248This number is too large; this may be an error in the TOML, but it can also be a
249bug in the program that uses too small of an integer.
250
251The maximum and minimum values are:
252
253size │ lowest │ highest
254───────┼────────────────┼──────────
255int8 │ -128 │ 127
256int16 │ -32,768 │ 32,767
257int32 │ -2,147,483,648 │ 2,147,483,647
258int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
259uint8 │ 0 │ 255
260uint16 │ 0 │ 65535
261uint32 │ 0 │ 4294967295
262uint64 │ 0 │ 1.8 × 10¹⁸
263
264int refers to int32 on 32-bit systems and int64 on 64-bit systems.
265`
266
267const usageDuration = `
268A duration must be as "number<unit>", without any spaces. Valid units are:
269
270ns nanoseconds (billionth of a second)
271us, µs microseconds (millionth of a second)
272ms milliseconds (thousands of a second)
273s seconds
274m minutes
275h hours
276
277You can combine multiple units; for example "5m10s" for 5 minutes and 10
278seconds.
279`
280