a646ffd9fa
Thread byte offset information through all error creation sites, eliminating the need for SubsliceOffset to recover position from pointer comparison. Changes: - Add Offset field to ParserError struct - Add offset parameter to NewParserError - Add Parser.offsetOf helper for suffix-length arithmetic - Thread base offset through scanner functions (scanComment, scanBasicString, scanMultilineBasicString, scanLiteralString, scanMultilineLiteralString, scanWindowsNewline) - Thread base offset through standalone functions (expect, hexToRune) - Thread base offset through all decode functions (parseInteger, parseFloat, parseLocalDate, parseLocalTime, parseLocalDateTime, parseDateTime, checkAndRemoveUnderscores*) - Update all unmarshaler call sites to pass value.Raw.Offset - Update localtime.go UnmarshalText methods with base=0 - Update strict.go to populate Offset from key ranges - Change wrapDecodeError to read de.Offset directly - Change Utf8TomlValidAlreadyEscaped to return int index (-1 if valid) instead of a byte subslice - Unexport SubsliceOffset (now only used internally by Range()) This makes error positions self-describing: each ParserError carries its own byte offset, so callers no longer need the original document slice and address arithmetic to determine where an error occurred. Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
123 lines
3.3 KiB
Go
123 lines
3.3 KiB
Go
package toml
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pelletier/go-toml/v2/unstable"
|
|
)
|
|
|
|
// LocalDate represents a calendar day in no specific timezone.
|
|
type LocalDate struct {
|
|
Year int
|
|
Month int
|
|
Day int
|
|
}
|
|
|
|
// AsTime converts d into a specific time instance at midnight in zone.
|
|
func (d LocalDate) AsTime(zone *time.Location) time.Time {
|
|
return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, zone)
|
|
}
|
|
|
|
// String returns RFC 3339 representation of d.
|
|
func (d LocalDate) String() string {
|
|
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
|
|
}
|
|
|
|
// MarshalText returns RFC 3339 representation of d.
|
|
func (d LocalDate) MarshalText() ([]byte, error) {
|
|
return []byte(d.String()), nil
|
|
}
|
|
|
|
// UnmarshalText parses b using RFC 3339 to fill d.
|
|
func (d *LocalDate) UnmarshalText(b []byte) error {
|
|
res, err := parseLocalDate(b, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*d = res
|
|
return nil
|
|
}
|
|
|
|
// LocalTime represents a time of day of no specific day in no specific
|
|
// timezone.
|
|
type LocalTime struct {
|
|
Hour int // Hour of the day: [0; 24[
|
|
Minute int // Minute of the hour: [0; 60[
|
|
Second int // Second of the minute: [0; 59]
|
|
Nanosecond int // Nanoseconds within the second: [0, 1000000000[
|
|
Precision int // Number of digits to display for Nanosecond.
|
|
}
|
|
|
|
// String returns RFC 3339 representation of d.
|
|
// If d.Nanosecond and d.Precision are zero, the time won't have a nanosecond
|
|
// component. If d.Nanosecond > 0 but d.Precision = 0, then the minimum number
|
|
// of digits for nanoseconds is provided.
|
|
func (d LocalTime) String() string {
|
|
s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second)
|
|
|
|
if d.Precision > 0 {
|
|
s += fmt.Sprintf(".%09d", d.Nanosecond)[:d.Precision+1]
|
|
} else if d.Nanosecond > 0 {
|
|
// Nanoseconds are specified, but precision is not provided. Use the
|
|
// minimum.
|
|
s += strings.Trim(fmt.Sprintf(".%09d", d.Nanosecond), "0")
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// MarshalText returns RFC 3339 representation of d.
|
|
func (d LocalTime) MarshalText() ([]byte, error) {
|
|
return []byte(d.String()), nil
|
|
}
|
|
|
|
// UnmarshalText parses b using RFC 3339 to fill d.
|
|
func (d *LocalTime) UnmarshalText(b []byte) error {
|
|
res, left, err := parseLocalTime(b, 0)
|
|
if err == nil && len(left) != 0 {
|
|
err = unstable.NewParserError(left, len(b)-len(left), "extra characters")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*d = res
|
|
return nil
|
|
}
|
|
|
|
// LocalDateTime represents a time of a specific day in no specific timezone.
|
|
type LocalDateTime struct {
|
|
LocalDate
|
|
LocalTime
|
|
}
|
|
|
|
// AsTime converts d into a specific time instance in zone.
|
|
func (d LocalDateTime) AsTime(zone *time.Location) time.Time {
|
|
return time.Date(d.Year, time.Month(d.Month), d.Day, d.Hour, d.Minute, d.Second, d.Nanosecond, zone)
|
|
}
|
|
|
|
// String returns RFC 3339 representation of d.
|
|
func (d LocalDateTime) String() string {
|
|
return d.LocalDate.String() + "T" + d.LocalTime.String()
|
|
}
|
|
|
|
// MarshalText returns RFC 3339 representation of d.
|
|
func (d LocalDateTime) MarshalText() ([]byte, error) {
|
|
return []byte(d.String()), nil
|
|
}
|
|
|
|
// UnmarshalText parses b using RFC 3339 to fill d.
|
|
func (d *LocalDateTime) UnmarshalText(data []byte) error {
|
|
res, left, err := parseLocalDateTime(data, 0)
|
|
if err == nil && len(left) != 0 {
|
|
err = unstable.NewParserError(left, len(data)-len(left), "extra characters")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*d = res
|
|
return nil
|
|
}
|