dnscrypt-proxy/vendor/github.com/BurntSushi/toml/error.go

280 lines
8.1 KiB
Go
Raw Normal View History

2022-01-13 09:22:22 +01:00
package toml
import (
"fmt"
"strings"
)
2022-10-24 10:20:25 +02:00
// ParseError is returned when there is an error parsing the TOML syntax such as
// invalid syntax, duplicate keys, etc.
2022-01-13 09:22:22 +01:00
//
// In addition to the error message itself, you can also print detailed location
2022-10-24 10:20:25 +02:00
// information with context by using [ErrorWithPosition]:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// toml: error: Key 'fruit' was already created and cannot be used as an array.
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// At line 4, column 2-7:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// 2 | fruit = []
// 3 |
// 4 | [[fruit]] # Not allowed
// ^^^^^
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// [ErrorWithUsage] can be used to print the above with some more detailed usage
// guidance:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// toml: error: newlines not allowed within inline tables
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// At line 1, column 18:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// 1 | x = [{ key = 42 #
// ^
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// Error help:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// Inline tables must always be on a single line:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// table = {key = 42, second = 43}
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// It is invalid to split them over multiple lines like so:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// # INVALID
// table = {
// key = 42,
// second = 43
// }
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// Use regular for this:
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// [table]
// key = 42
// second = 43
2022-01-13 09:22:22 +01:00
type ParseError struct {
Message string // Short technical message.
Usage string // Longer message with usage guidance; may be blank.
Position Position // Position of the error
LastKey string // Last parsed key, may be blank.
2022-10-24 10:20:25 +02:00
// Line the error occurred.
//
// Deprecated: use [Position].
Line int
2022-01-13 09:22:22 +01:00
err error
input string
}
// Position of an error.
type Position struct {
Line int // Line number, starting at 1.
Start int // Start of error, as byte offset starting at 0.
Len int // Lenght in bytes.
}
func (pe ParseError) Error() string {
msg := pe.Message
if msg == "" { // Error from errorf()
msg = pe.err.Error()
}
if pe.LastKey == "" {
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
}
return fmt.Sprintf("toml: line %d (last key %q): %s",
pe.Position.Line, pe.LastKey, msg)
}
2023-06-01 11:32:10 +02:00
// ErrorWithPosition returns the error with detailed location context.
2022-01-13 09:22:22 +01:00
//
2022-10-24 10:20:25 +02:00
// See the documentation on [ParseError].
2022-01-13 09:22:22 +01:00
func (pe ParseError) ErrorWithPosition() string {
if pe.input == "" { // Should never happen, but just in case.
return pe.Error()
}
var (
lines = strings.Split(pe.input, "\n")
col = pe.column(lines)
b = new(strings.Builder)
)
msg := pe.Message
if msg == "" {
msg = pe.err.Error()
}
// TODO: don't show control characters as literals? This may not show up
// well everywhere.
if pe.Position.Len == 1 {
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
msg, pe.Position.Line, col+1)
} else {
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
msg, pe.Position.Line, col, col+pe.Position.Len)
}
if pe.Position.Line > 2 {
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
}
if pe.Position.Line > 1 {
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
}
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
return b.String()
}
2023-06-01 11:32:10 +02:00
// ErrorWithUsage returns the error with detailed location context and usage
2022-01-13 09:22:22 +01:00
// guidance.
//
2022-10-24 10:20:25 +02:00
// See the documentation on [ParseError].
2022-01-13 09:22:22 +01:00
func (pe ParseError) ErrorWithUsage() string {
m := pe.ErrorWithPosition()
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
2022-07-21 19:02:28 +02:00
lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
for i := range lines {
if lines[i] != "" {
lines[i] = " " + lines[i]
}
}
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
2022-01-13 09:22:22 +01:00
}
return m
}
func (pe ParseError) column(lines []string) int {
var pos, col int
for i := range lines {
ll := len(lines[i]) + 1 // +1 for the removed newline
if pos+ll >= pe.Position.Start {
col = pe.Position.Start - pos
if col < 0 { // Should never happen, but just in case.
col = 0
}
break
}
pos += ll
}
return col
}
type (
errLexControl struct{ r rune }
errLexEscape struct{ r rune }
errLexUTF8 struct{ b byte }
errLexInvalidNum struct{ v string }
errLexInvalidDate struct{ v string }
errLexInlineTableNL struct{}
errLexStringNL struct{}
2022-07-21 19:02:28 +02:00
errParseRange struct {
i interface{} // int or float
size string // "int64", "uint16", etc.
}
errParseDuration struct{ d string }
2022-01-13 09:22:22 +01:00
)
func (e errLexControl) Error() string {
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
}
func (e errLexControl) Usage() string { return "" }
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
func (e errLexEscape) Usage() string { return usageEscape }
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
func (e errLexUTF8) Usage() string { return "" }
func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) }
func (e errLexInvalidNum) Usage() string { return "" }
func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) }
func (e errLexInvalidDate) Usage() string { return "" }
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
func (e errLexStringNL) Usage() string { return usageStringNewline }
2022-07-21 19:02:28 +02:00
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
func (e errParseRange) Usage() string { return usageIntOverflow }
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
func (e errParseDuration) Usage() string { return usageDuration }
2022-01-13 09:22:22 +01:00
const usageEscape = `
A '\' inside a "-delimited string is interpreted as an escape character.
The following escape sequences are supported:
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
To prevent a '\' from being recognized as an escape character, use either:
- a ' or '''-delimited string; escape characters aren't processed in them; or
- write two backslashes to get a single backslash: '\\'.
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
instead of '\' will usually also work: "C:/Users/martin".
`
const usageInlineNewline = `
Inline tables must always be on a single line:
table = {key = 42, second = 43}
It is invalid to split them over multiple lines like so:
# INVALID
table = {
key = 42,
second = 43
}
Use regular for this:
[table]
key = 42
second = 43
`
const usageStringNewline = `
Strings must always be on a single line, and cannot span more than one line:
# INVALID
string = "Hello,
world!"
Instead use """ or ''' to split strings over multiple lines:
string = """Hello,
world!"""
`
2022-07-21 19:02:28 +02:00
const usageIntOverflow = `
This number is too large; this may be an error in the TOML, but it can also be a
bug in the program that uses too small of an integer.
The maximum and minimum values are:
size lowest highest
int8 -128 127
int16 -32,768 32,767
int32 -2,147,483,648 2,147,483,647
int64 -9.2 × 10¹ 9.2 × 10¹
uint8 0 255
uint16 0 65535
uint32 0 4294967295
uint64 0 1.8 × 10¹
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
`
const usageDuration = `
A duration must be as "number<unit>", without any spaces. Valid units are:
ns nanoseconds (billionth of a second)
us, µs microseconds (millionth of a second)
ms milliseconds (thousands of a second)
s seconds
m minutes
h hours
You can combine multiple units; for example "5m10s" for 5 minutes and 10
seconds.
`