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