Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc72d75f3e | |||
| f77775b59e | |||
| b52f6c9823 | |||
| 12244064bb | |||
| 6430ee0bfa | |||
| cf530eba46 | |||
| 64fe47161f | |||
| 4dff8eaa4d | |||
| 2dbd29a565 | |||
| f27a07d31a | |||
| 644515958c | |||
| 8683be35f6 | |||
| dc1740d473 | |||
| 11f789ef11 | |||
| 74d21b367f | |||
| 6617e7e73d | |||
| 3dbca20bc9 | |||
| 85c0658984 | |||
| 772d169b52 | |||
| b4ec220f7e | |||
| 3694ae88f6 |
@@ -321,7 +321,6 @@ type benchmarkDoc struct {
|
||||
Key1 []int64
|
||||
Key2 []string
|
||||
Key3 [][]int64
|
||||
// TODO: Key4 not supported by go-toml's Unmarshal
|
||||
Key4 []interface{}
|
||||
Key5 []int64
|
||||
Key6 []int64
|
||||
|
||||
@@ -43,8 +43,7 @@ type testsCollection struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
const srcTemplate = "// +build testsuite\n\n" +
|
||||
"// Generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}\n" +
|
||||
const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}\n" +
|
||||
"package toml_test\n" +
|
||||
" import (\n" +
|
||||
" \"testing\"\n" +
|
||||
|
||||
@@ -35,13 +35,22 @@ func parseLocalDate(b []byte) (LocalDate, error) {
|
||||
return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD")
|
||||
}
|
||||
|
||||
date.Year = parseDecimalDigits(b[0:4])
|
||||
var err error
|
||||
|
||||
v := parseDecimalDigits(b[5:7])
|
||||
date.Year, err = parseDecimalDigits(b[0:4])
|
||||
if err != nil {
|
||||
return LocalDate{}, err
|
||||
}
|
||||
|
||||
date.Month = v
|
||||
date.Month, err = parseDecimalDigits(b[5:7])
|
||||
if err != nil {
|
||||
return LocalDate{}, err
|
||||
}
|
||||
|
||||
date.Day = parseDecimalDigits(b[8:10])
|
||||
date.Day, err = parseDecimalDigits(b[8:10])
|
||||
if err != nil {
|
||||
return LocalDate{}, err
|
||||
}
|
||||
|
||||
if !isValidDate(date.Year, date.Month, date.Day) {
|
||||
return LocalDate{}, newDecodeError(b, "impossible date")
|
||||
@@ -50,15 +59,18 @@ func parseLocalDate(b []byte) (LocalDate, error) {
|
||||
return date, nil
|
||||
}
|
||||
|
||||
func parseDecimalDigits(b []byte) int {
|
||||
func parseDecimalDigits(b []byte) (int, error) {
|
||||
v := 0
|
||||
|
||||
for _, c := range b {
|
||||
for i, c := range b {
|
||||
if c < '0' || c > '9' {
|
||||
return 0, newDecodeError(b[i:i+1], "expected digit (0-9)")
|
||||
}
|
||||
v *= 10
|
||||
v += int(c - '0')
|
||||
}
|
||||
|
||||
return v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseDateTime(b []byte) (time.Time, error) {
|
||||
@@ -159,7 +171,13 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||
return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
|
||||
}
|
||||
|
||||
t.Hour = parseDecimalDigits(b[0:2])
|
||||
var err error
|
||||
|
||||
t.Hour, err = parseDecimalDigits(b[0:2])
|
||||
if err != nil {
|
||||
return t, nil, err
|
||||
}
|
||||
|
||||
if t.Hour > 23 {
|
||||
return t, nil, newDecodeError(b[0:2], "hour cannot be greater 23")
|
||||
}
|
||||
@@ -167,7 +185,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||
return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes")
|
||||
}
|
||||
|
||||
t.Minute = parseDecimalDigits(b[3:5])
|
||||
t.Minute, err = parseDecimalDigits(b[3:5])
|
||||
if err != nil {
|
||||
return t, nil, err
|
||||
}
|
||||
if t.Minute > 59 {
|
||||
return t, nil, newDecodeError(b[3:5], "minutes cannot be greater 59")
|
||||
}
|
||||
@@ -175,7 +196,11 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||
return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds")
|
||||
}
|
||||
|
||||
t.Second = parseDecimalDigits(b[6:8])
|
||||
t.Second, err = parseDecimalDigits(b[6:8])
|
||||
if err != nil {
|
||||
return t, nil, err
|
||||
}
|
||||
|
||||
if t.Second > 59 {
|
||||
return t, nil, newDecodeError(b[3:5], "seconds cannot be greater 59")
|
||||
}
|
||||
@@ -204,6 +229,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||
digits++
|
||||
}
|
||||
|
||||
if digits == 0 {
|
||||
return t, nil, newDecodeError(b[minLengthWithFrac-1:minLengthWithFrac], "nanoseconds need at least one digit")
|
||||
}
|
||||
|
||||
t.Nanosecond = frac * nspow[digits]
|
||||
t.Precision = digits
|
||||
|
||||
@@ -405,6 +434,9 @@ func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {
|
||||
if !before {
|
||||
return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores")
|
||||
}
|
||||
if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') {
|
||||
return nil, newDecodeError(b[i+1:i+2], "cannot have underscore before exponent")
|
||||
}
|
||||
before = false
|
||||
case 'e', 'E':
|
||||
if i < len(b)-1 && b[i+1] == '_' {
|
||||
|
||||
@@ -136,7 +136,6 @@ func (n *Node) Key() Iterator {
|
||||
// Guaranteed to be non-nil.
|
||||
// Panics if not called on a KeyValue node, or if the Children are malformed.
|
||||
func (n *Node) Value() *Node {
|
||||
assertKind(KeyValue, *n)
|
||||
return n.Child()
|
||||
}
|
||||
|
||||
@@ -144,9 +143,3 @@ func (n *Node) Value() *Node {
|
||||
func (n *Node) Children() Iterator {
|
||||
return Iterator{node: n.Child()}
|
||||
}
|
||||
|
||||
func assertKind(k Kind, n Node) {
|
||||
if n.Kind != k {
|
||||
panic(fmt.Errorf("method was expecting a %s, not a %s", k, n.Kind))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,3 +63,14 @@ func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer {
|
||||
// https://github.com/golang/go/issues/40481
|
||||
return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset))
|
||||
}
|
||||
|
||||
type Slice struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
|
||||
type iface struct {
|
||||
typ unsafe.Pointer
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package danger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func ExtendSlice(t reflect.Type, s *Slice, n int) Slice {
|
||||
arrayType := reflect.ArrayOf(n, t.Elem())
|
||||
arrayData := reflect.New(arrayType)
|
||||
reflect.Copy(arrayData.Elem(), reflect.NewAt(t, unsafe.Pointer(s)).Elem())
|
||||
return Slice{
|
||||
Data: unsafe.Pointer(arrayData.Pointer()),
|
||||
Len: s.Len,
|
||||
Cap: n,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
package danger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:linkname unsafe_NewArray reflect.unsafe_NewArray
|
||||
func unsafe_NewArray(rtype unsafe.Pointer, length int) unsafe.Pointer
|
||||
|
||||
//go:linkname typedslicecopy reflect.typedslicecopy
|
||||
//go:noescape
|
||||
func typedslicecopy(elemType unsafe.Pointer, dst, src Slice) int
|
||||
|
||||
func ExtendSlice(t reflect.Type, s *Slice, n int) Slice {
|
||||
elemTypeRef := t.Elem()
|
||||
elemTypePtr := ((*iface)(unsafe.Pointer(&elemTypeRef))).ptr
|
||||
|
||||
d := Slice{
|
||||
Data: unsafe_NewArray(elemTypePtr, n),
|
||||
Len: s.Len,
|
||||
Cap: n,
|
||||
}
|
||||
|
||||
typedslicecopy(elemTypePtr, d, *s)
|
||||
return d
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package danger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// typeID is used as key in encoder and decoder caches to enable using
|
||||
// the optimize runtime.mapaccess2_fast64 function instead of the more
|
||||
// expensive lookup if we were to use reflect.Type as map key.
|
||||
//
|
||||
// typeID holds the pointer to the reflect.Type value, which is unique
|
||||
// in the program.
|
||||
//
|
||||
// https://github.com/segmentio/encoding/blob/master/json/codec.go#L59-L61
|
||||
type TypeID unsafe.Pointer
|
||||
|
||||
func MakeTypeID(t reflect.Type) TypeID {
|
||||
// reflect.Type has the fields:
|
||||
// typ unsafe.Pointer
|
||||
// ptr unsafe.Pointer
|
||||
return TypeID((*[2]unsafe.Pointer)(unsafe.Pointer(&t))[1])
|
||||
}
|
||||
+65
-20
@@ -65,7 +65,7 @@ type entry struct {
|
||||
explicit bool
|
||||
}
|
||||
|
||||
// Remove all descendent of node at position idx.
|
||||
// Remove all descendants of node at position idx.
|
||||
func (s *SeenTracker) clear(idx int) {
|
||||
p := s.entries[idx].id
|
||||
rest := clear(p, s.entries[idx+1:])
|
||||
@@ -102,19 +102,21 @@ func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit
|
||||
return idx
|
||||
}
|
||||
|
||||
// CheckExpression takes a top-level node and checks that it does not contain keys
|
||||
// that have been seen in previous calls, and validates that types are consistent.
|
||||
// CheckExpression takes a top-level node and checks that it does not contain
|
||||
// keys that have been seen in previous calls, and validates that types are
|
||||
// consistent.
|
||||
func (s *SeenTracker) CheckExpression(node *ast.Node) error {
|
||||
if s.entries == nil {
|
||||
// Skip ID = 0 to remove the confusion between nodes whose parent has
|
||||
// id 0 and root nodes (parent id is 0 because it's the zero value).
|
||||
// Skip ID = 0 to remove the confusion between nodes whose
|
||||
// parent has id 0 and root nodes (parent id is 0 because it's
|
||||
// the zero value).
|
||||
s.nextID = 1
|
||||
// Start unscoped, so idx is negative.
|
||||
s.currentIdx = -1
|
||||
}
|
||||
switch node.Kind {
|
||||
case ast.KeyValue:
|
||||
return s.checkKeyValue(node)
|
||||
return s.checkKeyValue(s.currentIdx, node)
|
||||
case ast.Table:
|
||||
return s.checkTable(node)
|
||||
case ast.ArrayTable:
|
||||
@@ -206,39 +208,82 @@ func (s *SeenTracker) checkArrayTable(node *ast.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SeenTracker) checkKeyValue(node *ast.Node) error {
|
||||
func (s *SeenTracker) checkKeyValue(parentIdx int, node *ast.Node) error {
|
||||
it := node.Key()
|
||||
|
||||
parentIdx := s.currentIdx
|
||||
|
||||
for it.Next() {
|
||||
k := it.Node().Data
|
||||
|
||||
idx := s.find(parentIdx, k)
|
||||
|
||||
if idx >= 0 {
|
||||
if s.entries[idx].kind != tableKind {
|
||||
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), s.entries[idx].kind)
|
||||
}
|
||||
if s.entries[idx].explicit {
|
||||
if idx < 0 {
|
||||
idx = s.create(parentIdx, k, tableKind, false)
|
||||
} else {
|
||||
entry := s.entries[idx]
|
||||
if it.IsLast() {
|
||||
return fmt.Errorf("toml: key %s is already defined", string(k))
|
||||
} else if entry.kind != tableKind {
|
||||
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
|
||||
} else if entry.explicit {
|
||||
return fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))
|
||||
}
|
||||
} else {
|
||||
idx = s.create(parentIdx, k, tableKind, false)
|
||||
}
|
||||
|
||||
parentIdx = idx
|
||||
}
|
||||
|
||||
kind := valueKind
|
||||
s.entries[parentIdx].kind = valueKind
|
||||
|
||||
if node.Value().Kind == ast.InlineTable {
|
||||
kind = tableKind
|
||||
value := node.Value()
|
||||
|
||||
switch value.Kind {
|
||||
case ast.InlineTable:
|
||||
return s.checkInlineTable(parentIdx, value)
|
||||
case ast.Array:
|
||||
return s.checkArray(parentIdx, value)
|
||||
}
|
||||
s.entries[parentIdx].kind = kind
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SeenTracker) checkArray(parentIdx int, node *ast.Node) error {
|
||||
set := false
|
||||
it := node.Children()
|
||||
for it.Next() {
|
||||
if set {
|
||||
s.clear(parentIdx)
|
||||
}
|
||||
n := it.Node()
|
||||
switch n.Kind {
|
||||
case ast.InlineTable:
|
||||
err := s.checkInlineTable(parentIdx, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set = true
|
||||
case ast.Array:
|
||||
err := s.checkArray(parentIdx, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SeenTracker) checkInlineTable(parentIdx int, node *ast.Node) error {
|
||||
it := node.Children()
|
||||
for it.Next() {
|
||||
n := it.Node()
|
||||
err := s.checkKeyValue(parentIdx, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SeenTracker) id(idx int) int {
|
||||
if idx >= 0 {
|
||||
return s.entries[idx].id
|
||||
|
||||
+8
-4
@@ -54,8 +54,9 @@ func NewEncoder(w io.Writer) *Encoder {
|
||||
// inline tag:
|
||||
//
|
||||
// MyField `inline:"true"`
|
||||
func (enc *Encoder) SetTablesInline(inline bool) {
|
||||
func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
|
||||
enc.tablesInline = inline
|
||||
return enc
|
||||
}
|
||||
|
||||
// SetArraysMultiline forces the encoder to emit all arrays with one element per
|
||||
@@ -64,20 +65,23 @@ func (enc *Encoder) SetTablesInline(inline bool) {
|
||||
// This behavior can be controlled on an individual struct field basis with the multiline tag:
|
||||
//
|
||||
// MyField `multiline:"true"`
|
||||
func (enc *Encoder) SetArraysMultiline(multiline bool) {
|
||||
func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder {
|
||||
enc.arraysMultiline = multiline
|
||||
return enc
|
||||
}
|
||||
|
||||
// SetIndentSymbol defines the string that should be used for indentation. The
|
||||
// provided string is repeated for each indentation level. Defaults to two
|
||||
// spaces.
|
||||
func (enc *Encoder) SetIndentSymbol(s string) {
|
||||
func (enc *Encoder) SetIndentSymbol(s string) *Encoder {
|
||||
enc.indentSymbol = s
|
||||
return enc
|
||||
}
|
||||
|
||||
// SetIndentTables forces the encoder to intent tables and array tables.
|
||||
func (enc *Encoder) SetIndentTables(indent bool) {
|
||||
func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
||||
enc.indentTables = indent
|
||||
return enc
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of v to the stream.
|
||||
|
||||
+1
-1
@@ -551,7 +551,7 @@ K = 42`,
|
||||
|
||||
type flagsSetters []struct {
|
||||
name string
|
||||
f func(enc *toml.Encoder, flag bool)
|
||||
f func(enc *toml.Encoder, flag bool) *toml.Encoder
|
||||
}
|
||||
|
||||
var allFlags = flagsSetters{
|
||||
|
||||
@@ -549,7 +549,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er
|
||||
startIdx := i
|
||||
endIdx := len(token) - len(`"""`)
|
||||
|
||||
if escaped < 0 {
|
||||
if !escaped {
|
||||
str := token[startIdx:endIdx]
|
||||
verr := utf8TomlValidAlreadyEscaped(str)
|
||||
if verr.Zero() {
|
||||
@@ -692,10 +692,6 @@ func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) {
|
||||
// simple-key = quoted-key / unquoted-key
|
||||
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
||||
// quoted-key = basic-string / literal-string
|
||||
if len(b) == 0 {
|
||||
return nil, nil, nil, newDecodeError(b, "key is incomplete")
|
||||
}
|
||||
|
||||
switch {
|
||||
case b[0] == '\'':
|
||||
return p.parseLiteralString(b)
|
||||
@@ -736,7 +732,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
|
||||
// Fast path. If there is no escape sequence, the string should just be
|
||||
// an UTF-8 encoded string, which is the same as Go. In that case,
|
||||
// validate the string and return a direct reference to the buffer.
|
||||
if escaped < 0 {
|
||||
if !escaped {
|
||||
str := token[startIdx:endIdx]
|
||||
verr := utf8TomlValidAlreadyEscaped(str)
|
||||
if verr.Zero() {
|
||||
@@ -884,6 +880,8 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err
|
||||
if idx == 2 && c == ':' || (idx == 4 && c == '-') {
|
||||
return p.scanDateTime(b)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return p.scanIntOrFloat(b)
|
||||
@@ -970,7 +968,7 @@ byteLoop:
|
||||
func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) {
|
||||
i := 0
|
||||
|
||||
if len(b) > 2 && b[0] == '0' && b[1] != '.' && b[1] != 'e' {
|
||||
if len(b) > 2 && b[0] == '0' && b[1] != '.' && b[1] != 'e' && b[1] != 'E' {
|
||||
var isValidRune validRuneFn
|
||||
|
||||
switch b[1] {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/ast"
|
||||
@@ -371,6 +373,23 @@ func BenchmarkParseBasicStringWithUnicode(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParseBasicStringsEasy(b *testing.B) {
|
||||
p := &parser{}
|
||||
|
||||
for _, size := range []int{1, 4, 8, 16, 21} {
|
||||
b.Run(strconv.Itoa(size), func(b *testing.B) {
|
||||
input := []byte(`"` + strings.Repeat("A", size) + `"`)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(input)))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.parseBasicString(input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_AST_DateTimes(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
|
||||
+13
-39
@@ -149,6 +149,12 @@ func scanComment(b []byte) ([]byte, []byte, error) {
|
||||
if b[i] == '\n' {
|
||||
return b[:i], b[i:], nil
|
||||
}
|
||||
if b[i] == '\r' {
|
||||
if i+1 < len(b) && b[i+1] == '\n' {
|
||||
return b[:i+1], b[i+1:], nil
|
||||
}
|
||||
return nil, nil, newDecodeError(b[i:i+1], "invalid character in comment")
|
||||
}
|
||||
size := utf8ValidNext(b[i:])
|
||||
if size == 0 {
|
||||
return nil, nil, newDecodeError(b[i:i+1], "invalid character in comment")
|
||||
@@ -160,42 +166,26 @@ func scanComment(b []byte) ([]byte, []byte, error) {
|
||||
return b, b[len(b):], nil
|
||||
}
|
||||
|
||||
func scanBasicString(b []byte) ([]byte, int, []byte, error) {
|
||||
func scanBasicString(b []byte) ([]byte, bool, []byte, error) {
|
||||
// basic-string = quotation-mark *basic-char quotation-mark
|
||||
// quotation-mark = %x22 ; "
|
||||
// basic-char = basic-unescaped / escaped
|
||||
// basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
// escaped = escape escape-seq-char
|
||||
escaped := -1 // index of the first \. -1 means no escape character in there.
|
||||
escaped := false
|
||||
i := 1
|
||||
|
||||
loop:
|
||||
for ; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '"':
|
||||
return b[:i+1], escaped, b[i+1:], nil
|
||||
case '\n':
|
||||
return nil, escaped, nil, newDecodeError(b[i:i+1], "basic strings cannot have new lines")
|
||||
case '\\':
|
||||
if len(b) < i+2 {
|
||||
return nil, escaped, nil, newDecodeError(b[i:i+1], "need a character after \\")
|
||||
}
|
||||
escaped = i
|
||||
i += 2 // skip the next character
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
for ; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '"':
|
||||
return b[:i+1], escaped, b[i+1:], nil
|
||||
case '\n':
|
||||
case '\n', '\r':
|
||||
return nil, escaped, nil, newDecodeError(b[i:i+1], "basic strings cannot have new lines")
|
||||
case '\\':
|
||||
if len(b) < i+2 {
|
||||
return nil, escaped, nil, newDecodeError(b[i:i+1], "need a character after \\")
|
||||
}
|
||||
escaped = true
|
||||
i++ // skip the next character
|
||||
}
|
||||
}
|
||||
@@ -203,7 +193,7 @@ loop:
|
||||
return nil, escaped, nil, newDecodeError(b[len(b):], `basic string not terminated by "`)
|
||||
}
|
||||
|
||||
func scanMultilineBasicString(b []byte) ([]byte, int, []byte, error) {
|
||||
func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) {
|
||||
// ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body
|
||||
// ml-basic-string-delim
|
||||
// ml-basic-string-delim = 3quotation-mark
|
||||
@@ -215,10 +205,9 @@ func scanMultilineBasicString(b []byte) ([]byte, int, []byte, error) {
|
||||
// mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
|
||||
// mlb-escaped-nl = escape ws newline *( wschar / newline )
|
||||
|
||||
escaped := -1
|
||||
escaped := false
|
||||
i := 3
|
||||
|
||||
loop:
|
||||
for ; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '"':
|
||||
@@ -251,22 +240,7 @@ loop:
|
||||
if len(b) < i+2 {
|
||||
return nil, escaped, nil, newDecodeError(b[len(b):], "need a character after \\")
|
||||
}
|
||||
escaped = i
|
||||
i += 2 // skip the next character
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
for ; i < len(b); i++ {
|
||||
switch b[i] {
|
||||
case '"':
|
||||
if scanFollowsMultilineBasicStringDelimiter(b[i:]) {
|
||||
return b[:i+3], escaped, b[i+3:], nil
|
||||
}
|
||||
case '\\':
|
||||
if len(b) < i+2 {
|
||||
return nil, escaped, nil, newDecodeError(b[len(b):], "need a character after \\")
|
||||
}
|
||||
escaped = true
|
||||
i++ // skip the next character
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func DecodeStdin() error {
|
||||
j := json.NewEncoder(os.Stdout)
|
||||
j.SetIndent("", " ")
|
||||
if err := j.Encode(addTag("", decoded)); err != nil {
|
||||
fmt.Errorf("Error encoding JSON: %s", err)
|
||||
return fmt.Errorf("Error encoding JSON: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+11
-1
@@ -1,4 +1,4 @@
|
||||
// Generated by tomltestgen for toml-test ref master on 2021-09-30T20:29:36-05:00
|
||||
// Generated by tomltestgen for toml-test ref master on 2021-11-08T22:33:24-05:00
|
||||
package toml_test
|
||||
|
||||
import (
|
||||
@@ -375,6 +375,11 @@ func TestTOMLTest_Invalid_Float_TrailingPoint(t *testing.T) {
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_Float_TrailingUsExp(t *testing.T) {
|
||||
input := "# trailing underscore in integer part is not allowed\ntrailing-us-exp = 1_e2\n# trailing underscore in float part is not allowed\ntrailing-us-exp2 = 1.2_e2\n"
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_Float_TrailingUs(t *testing.T) {
|
||||
input := "trailing-us = 1.2_\n"
|
||||
testgenInvalid(t, input)
|
||||
@@ -395,6 +400,11 @@ func TestTOMLTest_Invalid_InlineTable_DoubleComma(t *testing.T) {
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_InlineTable_DuplicateKey(t *testing.T) {
|
||||
input := "# Duplicate keys within an inline table are invalid\na={b=1, b=2}\n"
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_InlineTable_Empty(t *testing.T) {
|
||||
input := "t = {,}\n"
|
||||
testgenInvalid(t, input)
|
||||
|
||||
@@ -11,3 +11,4 @@ var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
|
||||
var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
|
||||
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
|
||||
var sliceInterfaceType = reflect.TypeOf([]interface{}{})
|
||||
var stringType = reflect.TypeOf("")
|
||||
|
||||
+214
-117
@@ -9,10 +9,12 @@ import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/ast"
|
||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
||||
"github.com/pelletier/go-toml/v2/internal/tracker"
|
||||
)
|
||||
|
||||
@@ -47,8 +49,9 @@ func NewDecoder(r io.Reader) *Decoder {
|
||||
// that could not be set on the target value. In that case, the decoder returns
|
||||
// a StrictMissingError that can be used to retrieve the individual errors as
|
||||
// well as generate a human readable description of the missing fields.
|
||||
func (d *Decoder) SetStrict(strict bool) {
|
||||
func (d *Decoder) SetStrict(strict bool) *Decoder {
|
||||
d.strict = strict
|
||||
return d
|
||||
}
|
||||
|
||||
// Decode the whole content of r into v.
|
||||
@@ -384,12 +387,14 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle
|
||||
elem = v.Elem()
|
||||
return d.handleKeyPart(key, elem, nextFn, makeFn)
|
||||
case reflect.Map:
|
||||
|
||||
// Create the key for the map element. For now assume it's a string.
|
||||
mk := reflect.ValueOf(string(key.Node().Data))
|
||||
|
||||
// If the map does not exist, create it.
|
||||
if v.IsNil() {
|
||||
v = reflect.MakeMap(v.Type())
|
||||
vt := v.Type()
|
||||
v = reflect.MakeMap(vt)
|
||||
rv = v
|
||||
}
|
||||
|
||||
@@ -401,7 +406,8 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle
|
||||
// map[string]interface{} or a []interface{} depending on whether
|
||||
// this is the last part of the array table key.
|
||||
|
||||
t := v.Type().Elem()
|
||||
vt := v.Type()
|
||||
t := vt.Elem()
|
||||
if t.Kind() == reflect.Interface {
|
||||
mv = makeFn()
|
||||
} else {
|
||||
@@ -415,7 +421,8 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle
|
||||
}
|
||||
set = true
|
||||
} else if !mv.CanAddr() {
|
||||
t := v.Type().Elem()
|
||||
vt := v.Type()
|
||||
t := vt.Elem()
|
||||
oldmv := mv
|
||||
mv = reflect.New(t).Elem()
|
||||
mv.Set(oldmv)
|
||||
@@ -453,7 +460,7 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle
|
||||
if v.Elem().IsValid() {
|
||||
v = v.Elem()
|
||||
} else {
|
||||
v = reflect.MakeMap(mapStringInterfaceType)
|
||||
v = makeMapStringInterface()
|
||||
}
|
||||
|
||||
x, err := d.handleKeyPart(key, v, nextFn, makeFn)
|
||||
@@ -614,62 +621,128 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.MakeSlice(v.Type(), 0, 16))
|
||||
} else {
|
||||
v.SetLen(0)
|
||||
}
|
||||
case reflect.Array:
|
||||
// arrays are always initialized
|
||||
case reflect.Interface:
|
||||
elem := v.Elem()
|
||||
if !elem.IsValid() {
|
||||
elem = reflect.New(sliceInterfaceType).Elem()
|
||||
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
|
||||
} else if elem.Kind() == reflect.Slice {
|
||||
if elem.Type() != sliceInterfaceType {
|
||||
elem = reflect.New(sliceInterfaceType).Elem()
|
||||
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
|
||||
} else if !elem.CanSet() {
|
||||
nelem := reflect.New(sliceInterfaceType).Elem()
|
||||
nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap()))
|
||||
reflect.Copy(nelem, elem)
|
||||
elem = nelem
|
||||
}
|
||||
}
|
||||
err := d.unmarshalArray(array, elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(elem)
|
||||
return nil
|
||||
default:
|
||||
// TODO: use newDecodeError, but first the parser needs to fill
|
||||
// array.Data.
|
||||
return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind())
|
||||
type unmarshalArrayFn func(d *decoder, array *ast.Node, v reflect.Value) error
|
||||
|
||||
var globalUnmarshalArrayFnCache atomic.Value // map[danger.TypeID]unmarshalArrayFn
|
||||
|
||||
func unmarshalArrayFnForSlice(vt reflect.Type) unmarshalArrayFn {
|
||||
tid := danger.MakeTypeID(vt)
|
||||
|
||||
cache, _ := globalUnmarshalArrayFnCache.Load().(map[danger.TypeID]unmarshalArrayFn)
|
||||
fn, ok := cache[tid]
|
||||
|
||||
if ok {
|
||||
return fn
|
||||
}
|
||||
|
||||
elemType := v.Type().Elem()
|
||||
elemType := vt.Elem()
|
||||
elemSize := elemType.Size()
|
||||
|
||||
fn = func(d *decoder, array *ast.Node, v reflect.Value) error {
|
||||
sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr()))
|
||||
|
||||
sp.Len = 0
|
||||
|
||||
it := array.Children()
|
||||
idx := 0
|
||||
for it.Next() {
|
||||
n := it.Node()
|
||||
|
||||
// TODO: optimize
|
||||
if v.Kind() == reflect.Slice {
|
||||
elem := reflect.New(elemType).Elem()
|
||||
idx := sp.Len
|
||||
|
||||
if sp.Len == sp.Cap {
|
||||
c := sp.Cap
|
||||
if c == 0 {
|
||||
c = 16
|
||||
} else {
|
||||
c *= 2
|
||||
}
|
||||
*sp = danger.ExtendSlice(vt, sp, c)
|
||||
}
|
||||
|
||||
datap := unsafe.Pointer(sp.Data)
|
||||
elemp := danger.Stride(datap, elemSize, idx)
|
||||
elem := reflect.NewAt(elemType, elemp).Elem()
|
||||
|
||||
err := d.handleValue(n, elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Set(reflect.Append(v, elem))
|
||||
} else { // array
|
||||
sp.Len++
|
||||
}
|
||||
|
||||
if sp.Data == nil {
|
||||
*sp = danger.ExtendSlice(vt, sp, 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
newCache := make(map[danger.TypeID]unmarshalArrayFn, len(cache)+1)
|
||||
newCache[tid] = fn
|
||||
for k, v := range cache {
|
||||
newCache[k] = v
|
||||
}
|
||||
globalUnmarshalArrayFnCache.Store(newCache)
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
func unmarshalArraySliceInterface(d *decoder, array *ast.Node, v reflect.Value) error {
|
||||
sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr()))
|
||||
|
||||
sp.Len = 0
|
||||
|
||||
var x interface{}
|
||||
|
||||
it := array.Children()
|
||||
for it.Next() {
|
||||
n := it.Node()
|
||||
|
||||
idx := sp.Len
|
||||
|
||||
if sp.Len == sp.Cap {
|
||||
c := sp.Cap
|
||||
if c == 0 {
|
||||
c = 16
|
||||
} else {
|
||||
c *= 2
|
||||
}
|
||||
*sp = danger.ExtendSlice(sliceInterfaceType, sp, c)
|
||||
}
|
||||
|
||||
datap := unsafe.Pointer(sp.Data)
|
||||
elemp := danger.Stride(datap, unsafe.Sizeof(x), idx)
|
||||
elem := reflect.NewAt(sliceInterfaceType.Elem(), elemp).Elem()
|
||||
|
||||
err := d.handleValue(n, elem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sp.Len++
|
||||
}
|
||||
|
||||
if sp.Data == nil {
|
||||
*sp = danger.ExtendSlice(sliceInterfaceType, sp, 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Slice:
|
||||
fn := unmarshalArrayFnForSlice(v.Type())
|
||||
return fn(d, array, v)
|
||||
case reflect.Array:
|
||||
// arrays are always initialized
|
||||
|
||||
it := array.Children()
|
||||
idx := 0
|
||||
for it.Next() {
|
||||
n := it.Node()
|
||||
|
||||
if idx >= v.Len() {
|
||||
return nil
|
||||
}
|
||||
@@ -680,6 +753,39 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
|
||||
}
|
||||
idx++
|
||||
}
|
||||
case reflect.Interface:
|
||||
elemIsSliceInterface := false
|
||||
elem := v.Elem()
|
||||
if !elem.IsValid() {
|
||||
s := make([]interface{}, 0, 16)
|
||||
elem = reflect.ValueOf(&s).Elem()
|
||||
elemIsSliceInterface = true
|
||||
} else if elem.Kind() == reflect.Slice {
|
||||
if elem.Type() != sliceInterfaceType {
|
||||
s := make([]interface{}, 0, 16)
|
||||
elem = reflect.ValueOf(&s).Elem()
|
||||
} else if !elem.CanSet() {
|
||||
s := make([]interface{}, elem.Len(), elem.Cap())
|
||||
nelem := reflect.ValueOf(&s).Elem()
|
||||
reflect.Copy(nelem, elem)
|
||||
elem = nelem
|
||||
}
|
||||
elemIsSliceInterface = true
|
||||
}
|
||||
|
||||
var err error
|
||||
if elemIsSliceInterface {
|
||||
err = unmarshalArraySliceInterface(d, array, elem)
|
||||
} else {
|
||||
err = d.unmarshalArray(array, elem)
|
||||
}
|
||||
|
||||
v.Set(elem)
|
||||
return err
|
||||
default:
|
||||
// TODO: use newDecodeError, but first the parser needs to fill
|
||||
// array.Data.
|
||||
return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind())
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -697,7 +803,7 @@ func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error
|
||||
case reflect.Interface:
|
||||
elem := v.Elem()
|
||||
if !elem.IsValid() {
|
||||
elem = reflect.MakeMap(mapStringInterfaceType)
|
||||
elem = makeMapStringInterface()
|
||||
v.Set(elem)
|
||||
}
|
||||
return d.unmarshalInlineTable(itable, elem)
|
||||
@@ -953,12 +1059,15 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec
|
||||
// There is no guarantee over what it could be.
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
mk := reflect.ValueOf(string(key.Node().Data))
|
||||
vt := v.Type()
|
||||
|
||||
keyType := v.Type().Key()
|
||||
if !mk.Type().AssignableTo(keyType) {
|
||||
if !mk.Type().ConvertibleTo(keyType) {
|
||||
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", mk.Type(), keyType)
|
||||
mk := reflect.ValueOf(string(key.Node().Data))
|
||||
mkt := stringType
|
||||
|
||||
keyType := vt.Key()
|
||||
if !mkt.AssignableTo(keyType) {
|
||||
if !mkt.ConvertibleTo(keyType) {
|
||||
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", mkt, keyType)
|
||||
}
|
||||
|
||||
mk = mk.Convert(keyType)
|
||||
@@ -966,7 +1075,7 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec
|
||||
|
||||
// If the map does not exist, create it.
|
||||
if v.IsNil() {
|
||||
v = reflect.MakeMap(v.Type())
|
||||
v = reflect.MakeMap(vt)
|
||||
rv = v
|
||||
}
|
||||
|
||||
@@ -1013,11 +1122,12 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec
|
||||
case reflect.Interface:
|
||||
v = v.Elem()
|
||||
|
||||
// Following encoding/toml: decoding an object into an interface{}, it
|
||||
// needs to always hold a map[string]interface{}. This is for the types
|
||||
// to be consistent whether a previous value was set or not.
|
||||
// Following encoding/json: decoding an object into an
|
||||
// interface{}, it needs to always hold a
|
||||
// map[string]interface{}. This is for the types to be
|
||||
// consistent whether a previous value was set or not.
|
||||
if !v.IsValid() || v.Type() != mapStringInterfaceType {
|
||||
v = reflect.MakeMap(mapStringInterfaceType)
|
||||
v = makeMapStringInterface()
|
||||
}
|
||||
|
||||
x, err := d.handleKeyValuePart(key, value, v)
|
||||
@@ -1064,70 +1174,30 @@ func initAndDereferencePointer(v reflect.Value) reflect.Value {
|
||||
|
||||
type fieldPathsMap = map[string][]int
|
||||
|
||||
type fieldPathsCache struct {
|
||||
m map[reflect.Type]fieldPathsMap
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *fieldPathsCache) get(t reflect.Type) (fieldPathsMap, bool) {
|
||||
c.l.RLock()
|
||||
paths, ok := c.m[t]
|
||||
c.l.RUnlock()
|
||||
|
||||
return paths, ok
|
||||
}
|
||||
|
||||
func (c *fieldPathsCache) set(t reflect.Type, m fieldPathsMap) {
|
||||
c.l.Lock()
|
||||
c.m[t] = m
|
||||
c.l.Unlock()
|
||||
}
|
||||
|
||||
var globalFieldPathsCache = fieldPathsCache{
|
||||
m: map[reflect.Type]fieldPathsMap{},
|
||||
l: sync.RWMutex{},
|
||||
}
|
||||
var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap
|
||||
|
||||
func structField(v reflect.Value, name string) (reflect.Value, bool) {
|
||||
//nolint:godox
|
||||
// TODO: cache this, and reduce allocations
|
||||
fieldPaths, ok := globalFieldPathsCache.get(v.Type())
|
||||
t := v.Type()
|
||||
tid := danger.MakeTypeID(t)
|
||||
|
||||
cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap)
|
||||
fieldPaths, ok := cache[tid]
|
||||
|
||||
if !ok {
|
||||
fieldPaths = map[string][]int{}
|
||||
|
||||
path := make([]int, 0, 16)
|
||||
|
||||
var walk func(reflect.Value)
|
||||
walk = func(v reflect.Value) {
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
l := len(path)
|
||||
path = append(path, i)
|
||||
f := t.Field(i)
|
||||
|
||||
if f.Anonymous {
|
||||
walk(v.Field(i))
|
||||
} else if f.PkgPath == "" {
|
||||
// only consider exported fields
|
||||
fieldName, ok := f.Tag.Lookup("toml")
|
||||
if !ok {
|
||||
fieldName = f.Name
|
||||
}
|
||||
|
||||
pathCopy := make([]int, len(path))
|
||||
copy(pathCopy, path)
|
||||
|
||||
fieldPaths[fieldName] = pathCopy
|
||||
forEachField(t, nil, func(name string, path []int) {
|
||||
fieldPaths[name] = path
|
||||
// extra copy for the case-insensitive match
|
||||
fieldPaths[strings.ToLower(fieldName)] = pathCopy
|
||||
}
|
||||
path = path[:l]
|
||||
}
|
||||
}
|
||||
fieldPaths[strings.ToLower(name)] = path
|
||||
})
|
||||
|
||||
walk(v)
|
||||
|
||||
globalFieldPathsCache.set(v.Type(), fieldPaths)
|
||||
newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1)
|
||||
newCache[tid] = fieldPaths
|
||||
for k, v := range cache {
|
||||
newCache[k] = v
|
||||
}
|
||||
globalFieldPathsCache.Store(newCache)
|
||||
}
|
||||
|
||||
path, ok := fieldPaths[name]
|
||||
@@ -1141,3 +1211,30 @@ func structField(v reflect.Value, name string) (reflect.Value, bool) {
|
||||
|
||||
return v.FieldByIndex(path), true
|
||||
}
|
||||
|
||||
func forEachField(t reflect.Type, path []int, do func(name string, path []int)) {
|
||||
n := t.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
if !f.Anonymous && f.PkgPath != "" {
|
||||
// only consider exported fields.
|
||||
continue
|
||||
}
|
||||
|
||||
fieldPath := append(path, i)
|
||||
fieldPath = fieldPath[:len(fieldPath):len(fieldPath)]
|
||||
|
||||
if f.Anonymous {
|
||||
forEachField(f.Type, fieldPath, do)
|
||||
continue
|
||||
}
|
||||
|
||||
name, ok := f.Tag.Lookup("toml")
|
||||
if !ok {
|
||||
name = f.Name
|
||||
}
|
||||
|
||||
do(name, fieldPath)
|
||||
}
|
||||
}
|
||||
|
||||
+459
-331
@@ -16,6 +16,66 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ExampleDecoder_SetStrict() {
|
||||
type S struct {
|
||||
Key1 string
|
||||
Key3 string
|
||||
}
|
||||
doc := `
|
||||
key1 = "value1"
|
||||
key2 = "value2"
|
||||
key3 = "value3"
|
||||
`
|
||||
r := strings.NewReader(doc)
|
||||
d := toml.NewDecoder(r)
|
||||
d.SetStrict(true)
|
||||
s := S{}
|
||||
err := d.Decode(&s)
|
||||
|
||||
fmt.Println(err.Error())
|
||||
|
||||
var details *toml.StrictMissingError
|
||||
if !errors.As(err, &details) {
|
||||
panic(fmt.Sprintf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err))
|
||||
}
|
||||
|
||||
fmt.Println(details.String())
|
||||
// Output:
|
||||
// strict mode: fields in the document are missing in the target struct
|
||||
// 2| key1 = "value1"
|
||||
// 3| key2 = "value2"
|
||||
// | ~~~~ missing field
|
||||
// 4| key3 = "value3"
|
||||
}
|
||||
|
||||
func ExampleUnmarshal() {
|
||||
type MyConfig struct {
|
||||
Version int
|
||||
Name string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
doc := `
|
||||
version = 2
|
||||
name = "go-toml"
|
||||
tags = ["go", "toml"]
|
||||
`
|
||||
|
||||
var cfg MyConfig
|
||||
err := toml.Unmarshal([]byte(doc), &cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("version:", cfg.Version)
|
||||
fmt.Println("name:", cfg.Name)
|
||||
fmt.Println("tags:", cfg.Tags)
|
||||
|
||||
// Output:
|
||||
// version: 2
|
||||
// name: go-toml
|
||||
// tags: [go toml]
|
||||
}
|
||||
|
||||
type badReader struct{}
|
||||
|
||||
func (r *badReader) Read([]byte) (int, error) {
|
||||
@@ -155,6 +215,11 @@ func TestUnmarshal_Floats(t *testing.T) {
|
||||
input: `0e0`,
|
||||
expected: 0.0,
|
||||
},
|
||||
{
|
||||
desc: "float upper exponent zero",
|
||||
input: `0E0`,
|
||||
expected: 0.0,
|
||||
},
|
||||
{
|
||||
desc: "float fractional with exponent",
|
||||
input: `6.626e-34`,
|
||||
@@ -204,6 +269,16 @@ func TestUnmarshal_Floats(t *testing.T) {
|
||||
assert.True(t, math.IsNaN(v))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "underscore after integer part",
|
||||
input: `1_e2`,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "underscore after integer part",
|
||||
input: `1.0_e2`,
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
type doc struct {
|
||||
@@ -215,12 +290,16 @@ func TestUnmarshal_Floats(t *testing.T) {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
doc := doc{}
|
||||
err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
if e.testFn != nil {
|
||||
e.testFn(t, doc.A)
|
||||
} else {
|
||||
assert.Equal(t, e.expected, doc.A)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1023,6 +1102,19 @@ B = "data"`,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "unexported struct fields are ignored",
|
||||
input: `foo = "bar"`,
|
||||
gen: func() test {
|
||||
type doc struct {
|
||||
foo string
|
||||
}
|
||||
return test{
|
||||
target: &doc{},
|
||||
expected: &doc{},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "array table into nil ptr",
|
||||
input: `[[foo]]
|
||||
@@ -1543,6 +1635,42 @@ B = "data"`,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "comment with CRLF",
|
||||
input: "# foo\r\na=2",
|
||||
gen: func() test {
|
||||
doc := map[string]interface{}{}
|
||||
|
||||
return test{
|
||||
target: &doc,
|
||||
expected: &map[string]interface{}{"a": int64(2)},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "comment that looks like a date",
|
||||
input: "a=19#9-",
|
||||
gen: func() test {
|
||||
doc := map[string]interface{}{}
|
||||
|
||||
return test{
|
||||
target: &doc,
|
||||
expected: &map[string]interface{}{"a": int64(19)},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "comment that looks like a date",
|
||||
input: "a=199#-",
|
||||
gen: func() test {
|
||||
doc := map[string]interface{}{}
|
||||
|
||||
return test{
|
||||
target: &doc,
|
||||
expected: &map[string]interface{}{"a": int64(199)},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
@@ -1655,6 +1783,174 @@ func TestUnmarshalFloat32(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecoderStrict(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
expected string
|
||||
target interface{}
|
||||
}{
|
||||
{
|
||||
desc: "multiple missing root keys",
|
||||
input: `
|
||||
key1 = "value1"
|
||||
key2 = "missing2"
|
||||
key3 = "missing3"
|
||||
key4 = "value4"
|
||||
`,
|
||||
expected: `
|
||||
2| key1 = "value1"
|
||||
3| key2 = "missing2"
|
||||
| ~~~~ missing field
|
||||
4| key3 = "missing3"
|
||||
5| key4 = "value4"
|
||||
---
|
||||
2| key1 = "value1"
|
||||
3| key2 = "missing2"
|
||||
4| key3 = "missing3"
|
||||
| ~~~~ missing field
|
||||
5| key4 = "value4"
|
||||
`,
|
||||
target: &struct {
|
||||
Key1 string
|
||||
Key4 string
|
||||
}{},
|
||||
},
|
||||
{
|
||||
desc: "multi-part key",
|
||||
input: `a.short.key="foo"`,
|
||||
expected: `
|
||||
1| a.short.key="foo"
|
||||
| ~~~~~~~~~~~ missing field
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "missing table",
|
||||
input: `
|
||||
[foo]
|
||||
bar = 42
|
||||
`,
|
||||
expected: `
|
||||
2| [foo]
|
||||
| ~~~ missing table
|
||||
3| bar = 42
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "missing array table",
|
||||
input: `
|
||||
[[foo]]
|
||||
bar = 42
|
||||
`,
|
||||
expected: `
|
||||
2| [[foo]]
|
||||
| ~~~ missing table
|
||||
3| bar = 42
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
e := e
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
t.Run("strict", func(t *testing.T) {
|
||||
r := strings.NewReader(e.input)
|
||||
d := toml.NewDecoder(r)
|
||||
d.SetStrict(true)
|
||||
x := e.target
|
||||
if x == nil {
|
||||
x = &struct{}{}
|
||||
}
|
||||
err := d.Decode(x)
|
||||
|
||||
var tsm *toml.StrictMissingError
|
||||
if errors.As(err, &tsm) {
|
||||
equalStringsIgnoreNewlines(t, e.expected, tsm.String())
|
||||
} else {
|
||||
t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
r := strings.NewReader(e.input)
|
||||
d := toml.NewDecoder(r)
|
||||
d.SetStrict(false)
|
||||
x := e.target
|
||||
if x == nil {
|
||||
x = &struct{}{}
|
||||
}
|
||||
err := d.Decode(x)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue252(t *testing.T) {
|
||||
type config struct {
|
||||
Val1 string `toml:"val1"`
|
||||
Val2 string `toml:"val2"`
|
||||
}
|
||||
|
||||
configFile := []byte(
|
||||
`
|
||||
val1 = "test1"
|
||||
`)
|
||||
|
||||
cfg := &config{
|
||||
Val2: "test2",
|
||||
}
|
||||
|
||||
err := toml.Unmarshal(configFile, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test2", cfg.Val2)
|
||||
}
|
||||
|
||||
func TestIssue287(t *testing.T) {
|
||||
b := `y=[[{}]]`
|
||||
v := map[string]interface{}{}
|
||||
err := toml.Unmarshal([]byte(b), &v)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"y": []interface{}{
|
||||
[]interface{}{
|
||||
map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, expected, v)
|
||||
}
|
||||
|
||||
type (
|
||||
Map458 map[string]interface{}
|
||||
Slice458 []interface{}
|
||||
)
|
||||
|
||||
func (m Map458) A(s string) Slice458 {
|
||||
return m[s].([]interface{})
|
||||
}
|
||||
|
||||
func TestIssue458(t *testing.T) {
|
||||
s := []byte(`[[package]]
|
||||
dependencies = ["regex"]
|
||||
name = "decode"
|
||||
version = "0.1.0"`)
|
||||
m := Map458{}
|
||||
err := toml.Unmarshal(s, &m)
|
||||
require.NoError(t, err)
|
||||
a := m.A("package")
|
||||
expected := Slice458{
|
||||
map[string]interface{}{
|
||||
"dependencies": []interface{}{"regex"},
|
||||
"name": "decode",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, a)
|
||||
}
|
||||
|
||||
type Integer484 struct {
|
||||
Value int
|
||||
}
|
||||
@@ -1688,54 +1984,6 @@ func TestIssue484(t *testing.T) {
|
||||
}, cfg)
|
||||
}
|
||||
|
||||
type (
|
||||
Map458 map[string]interface{}
|
||||
Slice458 []interface{}
|
||||
)
|
||||
|
||||
func (m Map458) A(s string) Slice458 {
|
||||
return m[s].([]interface{})
|
||||
}
|
||||
|
||||
func TestIssue458(t *testing.T) {
|
||||
s := []byte(`[[package]]
|
||||
dependencies = ["regex"]
|
||||
name = "decode"
|
||||
version = "0.1.0"`)
|
||||
m := Map458{}
|
||||
err := toml.Unmarshal(s, &m)
|
||||
require.NoError(t, err)
|
||||
a := m.A("package")
|
||||
expected := Slice458{
|
||||
map[string]interface{}{
|
||||
"dependencies": []interface{}{"regex"},
|
||||
"name": "decode",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, a)
|
||||
}
|
||||
|
||||
func TestIssue252(t *testing.T) {
|
||||
type config struct {
|
||||
Val1 string `toml:"val1"`
|
||||
Val2 string `toml:"val2"`
|
||||
}
|
||||
|
||||
configFile := []byte(
|
||||
`
|
||||
val1 = "test1"
|
||||
`)
|
||||
|
||||
cfg := &config{
|
||||
Val2: "test2",
|
||||
}
|
||||
|
||||
err := toml.Unmarshal(configFile, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test2", cfg.Val2)
|
||||
}
|
||||
|
||||
func TestIssue494(t *testing.T) {
|
||||
data := `
|
||||
foo = 2021-04-08
|
||||
@@ -1751,6 +1999,23 @@ bar = 2021-04-08
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIssue508(t *testing.T) {
|
||||
type head struct {
|
||||
Title string `toml:"title"`
|
||||
}
|
||||
|
||||
type text struct {
|
||||
head
|
||||
}
|
||||
|
||||
b := []byte(`title = "This is a title"`)
|
||||
|
||||
t1 := text{}
|
||||
err := toml.Unmarshal(b, &t1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "This is a title", t1.head.Title)
|
||||
}
|
||||
|
||||
func TestIssue507(t *testing.T) {
|
||||
data := []byte{'0', '=', '\n', '0', 'a', 'm', 'e'}
|
||||
m := map[string]interface{}{}
|
||||
@@ -1758,6 +2023,84 @@ func TestIssue507(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type uuid [16]byte
|
||||
|
||||
func (u *uuid) UnmarshalText(text []byte) (err error) {
|
||||
// Note: the original reported issue had a more complex implementation
|
||||
// of this function. But the important part is to verify that a
|
||||
// non-struct type implementing UnmarshalText works with the unmarshal
|
||||
// process.
|
||||
placeholder := bytes.Repeat([]byte{0xAA}, 16)
|
||||
copy(u[:], placeholder)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestIssue564(t *testing.T) {
|
||||
type Config struct {
|
||||
ID uuid
|
||||
}
|
||||
|
||||
var config Config
|
||||
|
||||
err := toml.Unmarshal([]byte(`id = "0818a52b97b94768941ba1172c76cf6c"`), &config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uuid{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, config.ID)
|
||||
}
|
||||
|
||||
func TestIssue575(t *testing.T) {
|
||||
b := []byte(`
|
||||
[pkg.cargo]
|
||||
version = "0.55.0 (5ae8d74b3 2021-06-22)"
|
||||
git_commit_hash = "a178d0322ce20e33eac124758e837cbd80a6f633"
|
||||
[pkg.cargo.target.aarch64-apple-darwin]
|
||||
available = true
|
||||
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.gz"
|
||||
hash = "7bac3901d8eb6a4191ffeebe75b29c78bcb270158ec901addb31f588d965d35d"
|
||||
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz"
|
||||
xz_hash = "5207644fd6379f3e5b8ae60016b854efa55a381b0c363bff7f9b2f25bfccc430"
|
||||
|
||||
[pkg.cargo.target.aarch64-pc-windows-msvc]
|
||||
available = true
|
||||
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.gz"
|
||||
hash = "eb8ccd9b1f6312b06dc749c17896fa4e9c163661c273dcb61cd7a48376227f6d"
|
||||
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz"
|
||||
xz_hash = "1a48f723fea1f17d786ce6eadd9d00914d38062d28fd9c455ed3c3801905b388"
|
||||
`)
|
||||
|
||||
type target struct {
|
||||
XZ_URL string
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
Target map[string]target
|
||||
}
|
||||
|
||||
type doc struct {
|
||||
Pkg map[string]pkg
|
||||
}
|
||||
|
||||
var dist doc
|
||||
err := toml.Unmarshal(b, &dist)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := doc{
|
||||
Pkg: map[string]pkg{
|
||||
"cargo": pkg{
|
||||
Target: map[string]target{
|
||||
"aarch64-apple-darwin": {
|
||||
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz",
|
||||
},
|
||||
"aarch64-pc-windows-msvc": {
|
||||
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, expected, dist)
|
||||
}
|
||||
|
||||
func TestIssue579(t *testing.T) {
|
||||
var v interface{}
|
||||
err := toml.Unmarshal([]byte(`[foo`), &v)
|
||||
@@ -1770,6 +2113,12 @@ func TestIssue581(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIssue585(t *testing.T) {
|
||||
var v interface{}
|
||||
err := toml.Unmarshal([]byte(`a=1979-05127T 0`), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIssue586(t *testing.T) {
|
||||
var v interface{}
|
||||
err := toml.Unmarshal([]byte(`a={ `), &v)
|
||||
@@ -1782,12 +2131,6 @@ func TestIssue588(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIssue585(t *testing.T) {
|
||||
var v interface{}
|
||||
err := toml.Unmarshal([]byte(`a=1979-05127T 0`), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// Support lowercase 'T' and 'Z'
|
||||
func TestIssue600(t *testing.T) {
|
||||
var v interface{}
|
||||
@@ -1823,28 +2166,28 @@ foo = "bar"`
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type uuid [16]byte
|
||||
|
||||
func (u *uuid) UnmarshalText(text []byte) (err error) {
|
||||
// Note: the original reported issue had a more complex implementation
|
||||
// of this function. But the important part is to verify that a
|
||||
// non-struct type implementing UnmarshalText works with the unmarshal
|
||||
// process.
|
||||
placeholder := bytes.Repeat([]byte{0xAA}, 16)
|
||||
copy(u[:], placeholder)
|
||||
return nil
|
||||
func TestIssue631(t *testing.T) {
|
||||
v := map[string]interface{}{}
|
||||
err := toml.Unmarshal([]byte("\"\\b\u007f\"= 2"), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIssue564(t *testing.T) {
|
||||
type Config struct {
|
||||
ID uuid
|
||||
func TestIssue658(t *testing.T) {
|
||||
var v map[string]interface{}
|
||||
err := toml.Unmarshal([]byte("e={b=1,b=4}"), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
func TestIssue662(t *testing.T) {
|
||||
var v map[string]interface{}
|
||||
err := toml.Unmarshal([]byte("a=[{b=1,b=2}]"), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
err := toml.Unmarshal([]byte(`id = "0818a52b97b94768941ba1172c76cf6c"`), &config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uuid{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, config.ID)
|
||||
func TestIssue666(t *testing.T) {
|
||||
var v map[string]interface{}
|
||||
err := toml.Unmarshal([]byte("a={}\na={}"), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
@@ -1988,7 +2331,7 @@ world'`,
|
||||
{
|
||||
desc: "invalid minutes value",
|
||||
data: `a=1979-05-27T23:+2:99`,
|
||||
msg: `minutes cannot be greater 59`,
|
||||
msg: `expected digit (0-9)`,
|
||||
},
|
||||
{
|
||||
desc: "invalid seconds value",
|
||||
@@ -2169,6 +2512,50 @@ world'`,
|
||||
data: `A = 2021-02-29T23:59:00`,
|
||||
msg: `impossible date`,
|
||||
},
|
||||
{
|
||||
desc: `missing minute digit`,
|
||||
data: `a=17:4::01`,
|
||||
},
|
||||
{
|
||||
desc: `invalid space in year`,
|
||||
data: `i=19 7-12-21T10:32:00`,
|
||||
},
|
||||
{
|
||||
desc: `missing nanoseconds digits`,
|
||||
data: `a=17:45:56.`,
|
||||
},
|
||||
{
|
||||
desc: `minutes over 60`,
|
||||
data: `a=17:99:00`,
|
||||
},
|
||||
{
|
||||
desc: `invalid second`,
|
||||
data: `a=17:00::0`,
|
||||
},
|
||||
{
|
||||
desc: `invalid hour`,
|
||||
data: `a=1::00:00`,
|
||||
},
|
||||
{
|
||||
desc: `invalid month`,
|
||||
data: `a=2021-0--29`,
|
||||
},
|
||||
{
|
||||
desc: `carriage return inside basic key`,
|
||||
data: "\"\r\"=42",
|
||||
},
|
||||
{
|
||||
desc: `carriage return inside basic string`,
|
||||
data: "A = \"\r\"",
|
||||
},
|
||||
{
|
||||
desc: `carriage return in comment`,
|
||||
data: "# this is a test\ra=1",
|
||||
},
|
||||
{
|
||||
desc: `backspace in comment`,
|
||||
data: "# this is a test\ba=1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
@@ -2334,205 +2721,6 @@ func TestLocalDateTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue287(t *testing.T) {
|
||||
b := `y=[[{}]]`
|
||||
v := map[string]interface{}{}
|
||||
err := toml.Unmarshal([]byte(b), &v)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"y": []interface{}{
|
||||
[]interface{}{
|
||||
map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, expected, v)
|
||||
}
|
||||
|
||||
func TestIssue508(t *testing.T) {
|
||||
type head struct {
|
||||
Title string `toml:"title"`
|
||||
}
|
||||
|
||||
type text struct {
|
||||
head
|
||||
}
|
||||
|
||||
b := []byte(`title = "This is a title"`)
|
||||
|
||||
t1 := text{}
|
||||
err := toml.Unmarshal(b, &t1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "This is a title", t1.head.Title)
|
||||
}
|
||||
|
||||
func TestIssue575(t *testing.T) {
|
||||
b := []byte(`
|
||||
[pkg.cargo]
|
||||
version = "0.55.0 (5ae8d74b3 2021-06-22)"
|
||||
git_commit_hash = "a178d0322ce20e33eac124758e837cbd80a6f633"
|
||||
[pkg.cargo.target.aarch64-apple-darwin]
|
||||
available = true
|
||||
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.gz"
|
||||
hash = "7bac3901d8eb6a4191ffeebe75b29c78bcb270158ec901addb31f588d965d35d"
|
||||
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz"
|
||||
xz_hash = "5207644fd6379f3e5b8ae60016b854efa55a381b0c363bff7f9b2f25bfccc430"
|
||||
|
||||
[pkg.cargo.target.aarch64-pc-windows-msvc]
|
||||
available = true
|
||||
url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.gz"
|
||||
hash = "eb8ccd9b1f6312b06dc749c17896fa4e9c163661c273dcb61cd7a48376227f6d"
|
||||
xz_url = "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz"
|
||||
xz_hash = "1a48f723fea1f17d786ce6eadd9d00914d38062d28fd9c455ed3c3801905b388"
|
||||
`)
|
||||
|
||||
type target struct {
|
||||
XZ_URL string
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
Target map[string]target
|
||||
}
|
||||
|
||||
type doc struct {
|
||||
Pkg map[string]pkg
|
||||
}
|
||||
|
||||
var dist doc
|
||||
err := toml.Unmarshal(b, &dist)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := doc{
|
||||
Pkg: map[string]pkg{
|
||||
"cargo": pkg{
|
||||
Target: map[string]target{
|
||||
"aarch64-apple-darwin": {
|
||||
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-apple-darwin.tar.xz",
|
||||
},
|
||||
"aarch64-pc-windows-msvc": {
|
||||
XZ_URL: "https://static.rust-lang.org/dist/2021-07-29/cargo-1.54.0-aarch64-pc-windows-msvc.tar.xz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, expected, dist)
|
||||
}
|
||||
|
||||
func TestIssue631(t *testing.T) {
|
||||
v := map[string]interface{}{}
|
||||
|
||||
err := toml.Unmarshal([]byte("\"\\b\u007f\"= 2"), &v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func TestDecoderStrict(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
expected string
|
||||
target interface{}
|
||||
}{
|
||||
{
|
||||
desc: "multiple missing root keys",
|
||||
input: `
|
||||
key1 = "value1"
|
||||
key2 = "missing2"
|
||||
key3 = "missing3"
|
||||
key4 = "value4"
|
||||
`,
|
||||
expected: `
|
||||
2| key1 = "value1"
|
||||
3| key2 = "missing2"
|
||||
| ~~~~ missing field
|
||||
4| key3 = "missing3"
|
||||
5| key4 = "value4"
|
||||
---
|
||||
2| key1 = "value1"
|
||||
3| key2 = "missing2"
|
||||
4| key3 = "missing3"
|
||||
| ~~~~ missing field
|
||||
5| key4 = "value4"
|
||||
`,
|
||||
target: &struct {
|
||||
Key1 string
|
||||
Key4 string
|
||||
}{},
|
||||
},
|
||||
{
|
||||
desc: "multi-part key",
|
||||
input: `a.short.key="foo"`,
|
||||
expected: `
|
||||
1| a.short.key="foo"
|
||||
| ~~~~~~~~~~~ missing field
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "missing table",
|
||||
input: `
|
||||
[foo]
|
||||
bar = 42
|
||||
`,
|
||||
expected: `
|
||||
2| [foo]
|
||||
| ~~~ missing table
|
||||
3| bar = 42
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "missing array table",
|
||||
input: `
|
||||
[[foo]]
|
||||
bar = 42
|
||||
`,
|
||||
expected: `
|
||||
2| [[foo]]
|
||||
| ~~~ missing table
|
||||
3| bar = 42
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
e := e
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
t.Run("strict", func(t *testing.T) {
|
||||
r := strings.NewReader(e.input)
|
||||
d := toml.NewDecoder(r)
|
||||
d.SetStrict(true)
|
||||
x := e.target
|
||||
if x == nil {
|
||||
x = &struct{}{}
|
||||
}
|
||||
err := d.Decode(x)
|
||||
|
||||
var tsm *toml.StrictMissingError
|
||||
if errors.As(err, &tsm) {
|
||||
equalStringsIgnoreNewlines(t, e.expected, tsm.String())
|
||||
} else {
|
||||
t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
r := strings.NewReader(e.input)
|
||||
d := toml.NewDecoder(r)
|
||||
d.SetStrict(false)
|
||||
x := e.target
|
||||
if x == nil {
|
||||
x = &struct{}{}
|
||||
}
|
||||
err := d.Decode(x)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal_RecursiveTable(t *testing.T) {
|
||||
type Foo struct {
|
||||
I int
|
||||
@@ -2703,63 +2891,3 @@ func TestUnmarshal_RecursiveTableArray(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleDecoder_SetStrict() {
|
||||
type S struct {
|
||||
Key1 string
|
||||
Key3 string
|
||||
}
|
||||
doc := `
|
||||
key1 = "value1"
|
||||
key2 = "value2"
|
||||
key3 = "value3"
|
||||
`
|
||||
r := strings.NewReader(doc)
|
||||
d := toml.NewDecoder(r)
|
||||
d.SetStrict(true)
|
||||
s := S{}
|
||||
err := d.Decode(&s)
|
||||
|
||||
fmt.Println(err.Error())
|
||||
|
||||
var details *toml.StrictMissingError
|
||||
if !errors.As(err, &details) {
|
||||
panic(fmt.Sprintf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err))
|
||||
}
|
||||
|
||||
fmt.Println(details.String())
|
||||
// Output:
|
||||
// strict mode: fields in the document are missing in the target struct
|
||||
// 2| key1 = "value1"
|
||||
// 3| key2 = "value2"
|
||||
// | ~~~~ missing field
|
||||
// 4| key3 = "value3"
|
||||
}
|
||||
|
||||
func ExampleUnmarshal() {
|
||||
type MyConfig struct {
|
||||
Version int
|
||||
Name string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
doc := `
|
||||
version = 2
|
||||
name = "go-toml"
|
||||
tags = ["go", "toml"]
|
||||
`
|
||||
|
||||
var cfg MyConfig
|
||||
err := toml.Unmarshal([]byte(doc), &cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("version:", cfg.Version)
|
||||
fmt.Println("name:", cfg.Name)
|
||||
fmt.Println("tags:", cfg.Tags)
|
||||
|
||||
// Output:
|
||||
// version: 2
|
||||
// name: go-toml
|
||||
// tags: [go toml]
|
||||
}
|
||||
|
||||
@@ -140,8 +140,45 @@ func utf8ValidNext(p []byte) int {
|
||||
return size
|
||||
}
|
||||
|
||||
var invalidAsciiTable = [256]bool{
|
||||
0x00: true,
|
||||
0x01: true,
|
||||
0x02: true,
|
||||
0x03: true,
|
||||
0x04: true,
|
||||
0x05: true,
|
||||
0x06: true,
|
||||
0x07: true,
|
||||
0x08: true,
|
||||
// 0x09 TAB
|
||||
// 0x0A LF
|
||||
0x0B: true,
|
||||
0x0C: true,
|
||||
// 0x0D CR
|
||||
0x0E: true,
|
||||
0x0F: true,
|
||||
0x10: true,
|
||||
0x11: true,
|
||||
0x12: true,
|
||||
0x13: true,
|
||||
0x14: true,
|
||||
0x15: true,
|
||||
0x16: true,
|
||||
0x17: true,
|
||||
0x18: true,
|
||||
0x19: true,
|
||||
0x1A: true,
|
||||
0x1B: true,
|
||||
0x1C: true,
|
||||
0x1D: true,
|
||||
0x1E: true,
|
||||
0x1F: true,
|
||||
// 0x20 - 0x7E Printable ASCII characters
|
||||
0x7F: true,
|
||||
}
|
||||
|
||||
func invalidAscii(b byte) bool {
|
||||
return b <= 0x08 || (b > 0x0A && b < 0x0D) || (b > 0x0D && b <= 0x1F) || b == 0x7F
|
||||
return invalidAsciiTable[b]
|
||||
}
|
||||
|
||||
// acceptRange gives the range of valid values for the second byte in a UTF-8
|
||||
|
||||
Reference in New Issue
Block a user