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  `