+4
-4
@@ -24,7 +24,7 @@ enable = [
|
|||||||
# "exhaustivestruct",
|
# "exhaustivestruct",
|
||||||
"exportloopref",
|
"exportloopref",
|
||||||
"forbidigo",
|
"forbidigo",
|
||||||
"forcetypeassert",
|
# "forcetypeassert",
|
||||||
"funlen",
|
"funlen",
|
||||||
"gci",
|
"gci",
|
||||||
# "gochecknoglobals",
|
# "gochecknoglobals",
|
||||||
@@ -35,7 +35,7 @@ enable = [
|
|||||||
"gocyclo",
|
"gocyclo",
|
||||||
"godot",
|
"godot",
|
||||||
"godox",
|
"godox",
|
||||||
"goerr113",
|
# "goerr113",
|
||||||
"gofmt",
|
"gofmt",
|
||||||
"gofumpt",
|
"gofumpt",
|
||||||
"goheader",
|
"goheader",
|
||||||
@@ -57,7 +57,7 @@ enable = [
|
|||||||
"nakedret",
|
"nakedret",
|
||||||
"nestif",
|
"nestif",
|
||||||
"nilerr",
|
"nilerr",
|
||||||
"nlreturn",
|
# "nlreturn",
|
||||||
"noctx",
|
"noctx",
|
||||||
"nolintlint",
|
"nolintlint",
|
||||||
"paralleltest",
|
"paralleltest",
|
||||||
@@ -80,5 +80,5 @@ enable = [
|
|||||||
"wastedassign",
|
"wastedassign",
|
||||||
"whitespace",
|
"whitespace",
|
||||||
# "wrapcheck",
|
# "wrapcheck",
|
||||||
"wsl"
|
# "wsl"
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-11
@@ -51,17 +51,17 @@ Want to contribute a patch? Very happy to hear that!
|
|||||||
|
|
||||||
First, some high-level rules:
|
First, some high-level rules:
|
||||||
|
|
||||||
* A short proposal with some POC code is better than a lengthy piece of text
|
- A short proposal with some POC code is better than a lengthy piece of text
|
||||||
with no code. Code speaks louder than words. That being said, bigger changes
|
with no code. Code speaks louder than words. That being said, bigger changes
|
||||||
should probably start with a [discussion][discussions].
|
should probably start with a [discussion][discussions].
|
||||||
* No backward-incompatible patch will be accepted unless discussed. Sometimes
|
- No backward-incompatible patch will be accepted unless discussed. Sometimes
|
||||||
it's hard, but we try not to break people's programs unless we absolutely have
|
it's hard, but we try not to break people's programs unless we absolutely have
|
||||||
to.
|
to.
|
||||||
* If you are writing a new feature or extending an existing one, make sure to
|
- If you are writing a new feature or extending an existing one, make sure to
|
||||||
write some documentation.
|
write some documentation.
|
||||||
* Bug fixes need to be accompanied with regression tests.
|
- Bug fixes need to be accompanied with regression tests.
|
||||||
* New code needs to be tested.
|
- New code needs to be tested.
|
||||||
* Your commit messages need to explain why the change is needed, even if already
|
- Your commit messages need to explain why the change is needed, even if already
|
||||||
included in the PR description.
|
included in the PR description.
|
||||||
|
|
||||||
It does sound like a lot, but those best practices are here to save time overall
|
It does sound like a lot, but those best practices are here to save time overall
|
||||||
@@ -130,12 +130,14 @@ Benchmark results should be compared against each other with
|
|||||||
4. Run `benchstat old.txt new.txt` to check that time/op does not go up in any
|
4. Run `benchstat old.txt new.txt` to check that time/op does not go up in any
|
||||||
test.
|
test.
|
||||||
|
|
||||||
|
On Unix you can use `./ci.sh benchmark -d v2` to verify how your code impacts
|
||||||
|
performance.
|
||||||
|
|
||||||
It is highly encouraged to add the benchstat results to your pull request
|
It is highly encouraged to add the benchstat results to your pull request
|
||||||
description. Pull requests that lower performance will receive more scrutiny.
|
description. Pull requests that lower performance will receive more scrutiny.
|
||||||
|
|
||||||
[benchstat]: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
|
[benchstat]: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
|
||||||
|
|
||||||
|
|
||||||
### Style
|
### Style
|
||||||
|
|
||||||
Try to look around and follow the same format and structure as the rest of the
|
Try to look around and follow the same format and structure as the rest of the
|
||||||
@@ -149,10 +151,10 @@ code. We enforce using `go fmt` on the whole code base.
|
|||||||
|
|
||||||
Checklist:
|
Checklist:
|
||||||
|
|
||||||
* Passing CI.
|
- Passing CI.
|
||||||
* Does not introduce backward-incompatible changes (unless discussed).
|
- Does not introduce backward-incompatible changes (unless discussed).
|
||||||
* Has relevant doc changes.
|
- Has relevant doc changes.
|
||||||
* Benchstat does not show performance regression.
|
- Benchstat does not show performance regression.
|
||||||
|
|
||||||
1. Merge using "squash and merge".
|
1. Merge using "squash and merge".
|
||||||
2. Make sure to edit the commit message to keep all the useful information
|
2. Make sure to edit the commit message to keep all the useful information
|
||||||
|
|||||||
@@ -25,6 +25,20 @@ USAGE
|
|||||||
|
|
||||||
COMMANDS
|
COMMANDS
|
||||||
|
|
||||||
|
benchmark [OPTIONS...] [BRANCH]
|
||||||
|
|
||||||
|
Run benchmarks.
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
|
||||||
|
BRANCH Optional. Defines which Git branch to use when running
|
||||||
|
benchmarks.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
|
||||||
|
-d Compare benchmarks of HEAD with BRANCH using benchstats. In
|
||||||
|
this form the BRANCH argument is required.
|
||||||
|
|
||||||
coverage [OPTIONS...] [BRANCH]
|
coverage [OPTIONS...] [BRANCH]
|
||||||
|
|
||||||
Generates code coverage.
|
Generates code coverage.
|
||||||
@@ -101,7 +115,48 @@ coverage() {
|
|||||||
cover "${1-HEAD}"
|
cover "${1-HEAD}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bench() {
|
||||||
|
branch="${1}"
|
||||||
|
out="${2}"
|
||||||
|
dir="$(mktemp -d)"
|
||||||
|
|
||||||
|
stderr "Executing benchmark for ${branch} at ${dir}"
|
||||||
|
|
||||||
|
if [ "${branch}" = "HEAD" ]; then
|
||||||
|
cp -r . "${dir}/"
|
||||||
|
else
|
||||||
|
git worktree add "$dir" "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd "$dir"
|
||||||
|
go test -bench=. -count=10 ./... | tee "${out}"
|
||||||
|
popd
|
||||||
|
|
||||||
|
if [ "${branch}" != "HEAD" ]; then
|
||||||
|
git worktree remove --force "$dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark() {
|
||||||
|
case "$1" in
|
||||||
|
-d)
|
||||||
|
shift
|
||||||
|
target="${1?Need to provide a target branch argument}"
|
||||||
|
old=`mktemp`
|
||||||
|
bench "${target}" "${old}"
|
||||||
|
|
||||||
|
new=`mktemp`
|
||||||
|
bench HEAD "${new}"
|
||||||
|
benchstat "${old}" "${new}"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
bench "${1-HEAD}" `mktemp`
|
||||||
|
}
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
coverage) shift; coverage $@;;
|
coverage) shift; coverage $@;;
|
||||||
|
benchmark) shift; benchmark $@;;
|
||||||
*) usage "bad argument $1";;
|
*) usage "bad argument $1";;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -16,7 +17,7 @@ func parseInteger(b []byte) (int64, error) {
|
|||||||
case 'o':
|
case 'o':
|
||||||
return parseIntOct(b)
|
return parseIntOct(b)
|
||||||
default:
|
default:
|
||||||
return 0, newDecodeError(b[1:2], "invalid base: '%c'", b[1])
|
panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,41 +35,26 @@ func parseLocalDate(b []byte) (LocalDate, error) {
|
|||||||
return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD")
|
return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
date.Year = parseDecimalDigits(b[0:4])
|
||||||
|
|
||||||
date.Year, err = parseDecimalDigits(b[0:4])
|
v := parseDecimalDigits(b[5:7])
|
||||||
if err != nil {
|
|
||||||
return date, err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := parseDecimalDigits(b[5:7])
|
|
||||||
if err != nil {
|
|
||||||
return date, err
|
|
||||||
}
|
|
||||||
|
|
||||||
date.Month = time.Month(v)
|
date.Month = time.Month(v)
|
||||||
|
|
||||||
date.Day, err = parseDecimalDigits(b[8:10])
|
date.Day = parseDecimalDigits(b[8:10])
|
||||||
if err != nil {
|
|
||||||
return date, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return date, nil
|
return date, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDecimalDigits(b []byte) (int, error) {
|
func parseDecimalDigits(b []byte) int {
|
||||||
v := 0
|
v := 0
|
||||||
|
|
||||||
for i, c := range b {
|
for _, c := range b {
|
||||||
if !isDigit(c) {
|
|
||||||
return 0, newDecodeError(b[i:i+1], "should be a digit (0-9)")
|
|
||||||
}
|
|
||||||
|
|
||||||
v *= 10
|
v *= 10
|
||||||
v += int(c - '0')
|
v += int(c - '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDateTime(b []byte) (time.Time, error) {
|
func parseDateTime(b []byte) (time.Time, error) {
|
||||||
@@ -77,8 +63,6 @@ func parseDateTime(b []byte) (time.Time, error) {
|
|||||||
// time-offset = "Z" / time-numoffset
|
// time-offset = "Z" / time-numoffset
|
||||||
// time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
|
// time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
|
||||||
|
|
||||||
originalBytes := b
|
|
||||||
|
|
||||||
dt, b, err := parseLocalDateTime(b)
|
dt, b, err := parseLocalDateTime(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
@@ -87,7 +71,8 @@ func parseDateTime(b []byte) (time.Time, error) {
|
|||||||
var zone *time.Location
|
var zone *time.Location
|
||||||
|
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return time.Time{}, newDecodeError(originalBytes, "date-time is missing timezone")
|
// parser should have checked that when assigning the date time node
|
||||||
|
panic("date time should have a timezone")
|
||||||
}
|
}
|
||||||
|
|
||||||
if b[0] == 'Z' {
|
if b[0] == 'Z' {
|
||||||
@@ -99,18 +84,15 @@ func parseDateTime(b []byte) (time.Time, error) {
|
|||||||
return time.Time{}, newDecodeError(b, "invalid date-time timezone")
|
return time.Time{}, newDecodeError(b, "invalid date-time timezone")
|
||||||
}
|
}
|
||||||
direction := 1
|
direction := 1
|
||||||
switch b[0] {
|
if b[0] == '-' {
|
||||||
case '+':
|
|
||||||
case '-':
|
|
||||||
direction = -1
|
direction = -1
|
||||||
default:
|
|
||||||
return time.Time{}, newDecodeError(b[0:1], "invalid timezone offset character")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hours := digitsToInt(b[1:3])
|
hours := digitsToInt(b[1:3])
|
||||||
minutes := digitsToInt(b[4:6])
|
minutes := digitsToInt(b[4:6])
|
||||||
seconds := direction * (hours*3600 + minutes*60)
|
seconds := direction * (hours*3600 + minutes*60)
|
||||||
zone = time.FixedZone("", seconds)
|
zone = time.FixedZone("", seconds)
|
||||||
|
b = b[dateTimeByteLen:]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b) > 0 {
|
if len(b) > 0 {
|
||||||
@@ -161,7 +143,6 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
|
|||||||
// parseLocalTime is a bit different because it also returns the remaining
|
// parseLocalTime is a bit different because it also returns the remaining
|
||||||
// []byte that is didn't need. This is to allow parseDateTime to parse those
|
// []byte that is didn't need. This is to allow parseDateTime to parse those
|
||||||
// remaining bytes as a timezone.
|
// remaining bytes as a timezone.
|
||||||
//nolint:cyclop,funlen
|
|
||||||
func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||||
var (
|
var (
|
||||||
nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0}
|
nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0}
|
||||||
@@ -173,46 +154,26 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
|||||||
return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
|
return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
t.Hour = parseDecimalDigits(b[0:2])
|
||||||
|
|
||||||
t.Hour, err = parseDecimalDigits(b[0:2])
|
|
||||||
if err != nil {
|
|
||||||
return t, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b[2] != ':' {
|
if b[2] != ':' {
|
||||||
return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes")
|
return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Minute, err = parseDecimalDigits(b[3:5])
|
t.Minute = parseDecimalDigits(b[3:5])
|
||||||
if err != nil {
|
|
||||||
return t, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b[5] != ':' {
|
if b[5] != ':' {
|
||||||
return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds")
|
return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Second, err = parseDecimalDigits(b[6:8])
|
t.Second = parseDecimalDigits(b[6:8])
|
||||||
if err != nil {
|
|
||||||
return t, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) >= 9 && b[8] == '.' {
|
const minLengthWithFrac = 9
|
||||||
|
if len(b) >= minLengthWithFrac && b[minLengthWithFrac-1] == '.' {
|
||||||
frac := 0
|
frac := 0
|
||||||
digits := 0
|
digits := 0
|
||||||
|
|
||||||
for i, c := range b[9:] {
|
for i, c := range b[minLengthWithFrac:] {
|
||||||
if !isDigit(c) {
|
const maxFracPrecision = 9
|
||||||
if i == 0 {
|
if i >= maxFracPrecision {
|
||||||
return t, nil, newDecodeError(b[i:i+1], "need at least one digit after fraction point")
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gomnd
|
|
||||||
if i >= 9 {
|
|
||||||
return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond")
|
return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +192,6 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
|||||||
|
|
||||||
//nolint:cyclop
|
//nolint:cyclop
|
||||||
func parseFloat(b []byte) (float64, error) {
|
func parseFloat(b []byte) (float64, error) {
|
||||||
//nolint:godox
|
|
||||||
// TODO: inefficient
|
|
||||||
if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' {
|
if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' {
|
||||||
return math.NaN(), nil
|
return math.NaN(), nil
|
||||||
}
|
}
|
||||||
@@ -252,7 +211,7 @@ func parseFloat(b []byte) (float64, error) {
|
|||||||
|
|
||||||
f, err := strconv.ParseFloat(string(cleaned), 64)
|
f, err := strconv.ParseFloat(string(cleaned), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, newDecodeError(b, "coudn't parse float: %w", err)
|
return 0, newDecodeError(b, "unable to parse float: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return f, nil
|
return f, nil
|
||||||
@@ -315,10 +274,6 @@ func parseIntDec(b []byte) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkAndRemoveUnderscores(b []byte) ([]byte, error) {
|
func checkAndRemoveUnderscores(b []byte) ([]byte, error) {
|
||||||
if len(b) == 0 {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if b[0] == '_' {
|
if b[0] == '_' {
|
||||||
return nil, newDecodeError(b[0:1], "number cannot start with underscore")
|
return nil, newDecodeError(b[0:1], "number cannot start with underscore")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
/*
|
// Package toml is a library to read and write TOML documents.
|
||||||
Package toml is a library to read and write TOML documents.
|
|
||||||
*/
|
|
||||||
package toml
|
package toml
|
||||||
|
|||||||
@@ -105,13 +105,9 @@ func (e *DecodeError) Key() Key {
|
|||||||
// highlight can be freely deallocated.
|
// highlight can be freely deallocated.
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
|
func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
|
||||||
if de == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := unsafe.SubsliceOffset(document, de.highlight)
|
offset := unsafe.SubsliceOffset(document, de.highlight)
|
||||||
|
|
||||||
errMessage := de.message
|
errMessage := de.Error()
|
||||||
errLine, errColumn := positionAtEnd(document[:offset])
|
errLine, errColumn := positionAtEnd(document[:offset])
|
||||||
before, after := linesOfContext(document, de.highlight, offset, 3)
|
before, after := linesOfContext(document, de.highlight, offset, 3)
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -181,6 +181,24 @@ line 5`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodeError_Accessors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
e := DecodeError{
|
||||||
|
message: "foo",
|
||||||
|
line: 1,
|
||||||
|
column: 2,
|
||||||
|
key: []string{"one", "two"},
|
||||||
|
human: "bar",
|
||||||
|
}
|
||||||
|
assert.Equal(t, "toml: foo", e.Error())
|
||||||
|
r, c := e.Position()
|
||||||
|
assert.Equal(t, 1, r)
|
||||||
|
assert.Equal(t, 2, c)
|
||||||
|
assert.Equal(t, Key{"one", "two"}, e.Key())
|
||||||
|
assert.Equal(t, "bar", e.String())
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleDecodeError() {
|
func ExampleDecodeError() {
|
||||||
doc := `name = 123__456`
|
doc := `name = 123__456`
|
||||||
|
|
||||||
@@ -189,14 +207,15 @@ func ExampleDecodeError() {
|
|||||||
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
||||||
|
//nolint:errorlint
|
||||||
de := err.(*DecodeError)
|
de := err.(*DecodeError)
|
||||||
fmt.Println(de.String())
|
fmt.Println(de.String())
|
||||||
|
|
||||||
row, col := de.Position()
|
row, col := de.Position()
|
||||||
fmt.Println("error occured at row", row, "column", col)
|
fmt.Println("error occurred at row", row, "column", col)
|
||||||
// Output:
|
// Output:
|
||||||
// toml: number must have at least one digit between underscores
|
// toml: number must have at least one digit between underscores
|
||||||
// 1| name = 123__456
|
// 1| name = 123__456
|
||||||
// | ~~ number must have at least one digit between underscores
|
// | ~~ number must have at least one digit between underscores
|
||||||
// error occured at row 1 column 11
|
// error occurred at row 1 column 11
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-63
@@ -127,6 +127,10 @@ func (enc *Encoder) Encode(v interface{}) error {
|
|||||||
|
|
||||||
ctx.inline = enc.tablesInline
|
ctx.inline = enc.tablesInline
|
||||||
|
|
||||||
|
if v == nil {
|
||||||
|
return fmt.Errorf("toml: cannot encode a nil interface")
|
||||||
|
}
|
||||||
|
|
||||||
b, err := enc.encode(b, ctx, reflect.ValueOf(v))
|
b, err := enc.encode(b, ctx, reflect.ValueOf(v))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -193,10 +197,12 @@ func (ctx *encoderCtx) isRoot() bool {
|
|||||||
|
|
||||||
//nolint:cyclop,funlen
|
//nolint:cyclop,funlen
|
||||||
func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
|
if !v.IsZero() {
|
||||||
i, ok := v.Interface().(time.Time)
|
i, ok := v.Interface().(time.Time)
|
||||||
if ok {
|
if ok {
|
||||||
return i.AppendFormat(b, time.RFC3339), nil
|
return i.AppendFormat(b, time.RFC3339), nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if v.Type().Implements(textMarshalerType) {
|
if v.Type().Implements(textMarshalerType) {
|
||||||
if ctx.isRoot() {
|
if ctx.isRoot() {
|
||||||
@@ -273,11 +279,6 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
|
|||||||
if !ctx.hasKey {
|
if !ctx.hasKey {
|
||||||
panic("caller of encodeKv should have set the key in the context")
|
panic("caller of encodeKv should have set the key in the context")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNil(v) {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b = enc.indent(ctx.indent, b)
|
b = enc.indent(ctx.indent, b)
|
||||||
|
|
||||||
b, err = enc.encodeKey(b, ctx.key)
|
b, err = enc.encodeKey(b, ctx.key)
|
||||||
@@ -470,12 +471,7 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := willConvertToTableOrArrayTable(ctx, v)
|
if willConvertToTableOrArrayTable(ctx, v) {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if table {
|
|
||||||
t.pushTable(k, v, emptyValueOptions)
|
t.pushTable(k, v, emptyValueOptions)
|
||||||
} else {
|
} else {
|
||||||
t.pushKV(k, v, emptyValueOptions)
|
t.pushKV(k, v, emptyValueOptions)
|
||||||
@@ -543,18 +539,13 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
willConvert, err := willConvertToTableOrArrayTable(ctx, f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
options := valueOptions{
|
options := valueOptions{
|
||||||
multiline: fieldBoolTag(fieldType, "multiline"),
|
multiline: fieldBoolTag(fieldType, "multiline"),
|
||||||
}
|
}
|
||||||
|
|
||||||
inline := fieldBoolTag(fieldType, "inline")
|
inline := fieldBoolTag(fieldType, "inline")
|
||||||
|
|
||||||
if inline || !willConvert {
|
if inline || !willConvertToTableOrArrayTable(ctx, f) {
|
||||||
t.pushKV(k, f, options)
|
t.pushKV(k, f, options)
|
||||||
} else {
|
} else {
|
||||||
t.pushTable(k, f, options)
|
t.pushTable(k, f, options)
|
||||||
@@ -640,21 +631,8 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, table := range t.tables {
|
if len(t.tables) > 0 {
|
||||||
if first {
|
panic("inline table cannot contain nested tables, online key-values")
|
||||||
first = false
|
|
||||||
} else {
|
|
||||||
b = append(b, `, `...)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.setKey(table.Key)
|
|
||||||
|
|
||||||
b, err = enc.encode(b, ctx, table.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = append(b, '\n')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b = append(b, "}"...)
|
b = append(b, "}"...)
|
||||||
@@ -664,61 +642,50 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
|
|||||||
|
|
||||||
var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
|
var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
|
||||||
|
|
||||||
func willConvertToTable(ctx encoderCtx, v reflect.Value) (bool, error) {
|
func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
|
||||||
if v.Type() == timeType || v.Type().Implements(textMarshalerType) {
|
if v.Type() == timeType || v.Type().Implements(textMarshalerType) {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Map, reflect.Struct:
|
case reflect.Map, reflect.Struct:
|
||||||
return !ctx.inline, nil
|
return !ctx.inline
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
if v.IsNil() {
|
|
||||||
return false, fmt.Errorf("toml: encoding a nil interface is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
return willConvertToTable(ctx, v.Elem())
|
return willConvertToTable(ctx, v.Elem())
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return willConvertToTable(ctx, v.Elem())
|
return willConvertToTable(ctx, v.Elem())
|
||||||
default:
|
default:
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) (bool, error) {
|
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
|
|
||||||
if t.Kind() == reflect.Interface {
|
if t.Kind() == reflect.Interface {
|
||||||
if v.IsNil() {
|
|
||||||
return false, fmt.Errorf("toml: encoding a nil interface is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
return willConvertToTableOrArrayTable(ctx, v.Elem())
|
return willConvertToTableOrArrayTable(ctx, v.Elem())
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Kind() == reflect.Slice {
|
if t.Kind() == reflect.Slice {
|
||||||
if v.Len() == 0 {
|
if v.Len() == 0 {
|
||||||
// An empty slice should be a kv = [].
|
// An empty slice should be a kv = [].
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
t, err := willConvertToTable(ctx, v.Index(i))
|
t := willConvertToTable(ctx, v.Index(i))
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !t {
|
if !t {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return willConvertToTable(ctx, v)
|
return willConvertToTable(ctx, v)
|
||||||
@@ -731,12 +698,7 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
allTables, err := willConvertToTableOrArrayTable(ctx, v)
|
if willConvertToTableOrArrayTable(ctx, v) {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if allTables {
|
|
||||||
return enc.encodeSliceAsArrayTable(b, ctx, v)
|
return enc.encodeSliceAsArrayTable(b, ctx, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,10 +708,6 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by
|
|||||||
// caller should have checked that v is a slice that only contains values that
|
// caller should have checked that v is a slice that only contains values that
|
||||||
// encode into tables.
|
// encode into tables.
|
||||||
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
if v.Len() == 0 {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.shiftKey()
|
ctx.shiftKey()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ import (
|
|||||||
func TestMarshal(t *testing.T) {
|
func TestMarshal(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
someInt := 42
|
||||||
|
|
||||||
|
type structInline struct {
|
||||||
|
A interface{} `inline:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
examples := []struct {
|
examples := []struct {
|
||||||
desc string
|
desc string
|
||||||
v interface{}
|
v interface{}
|
||||||
@@ -298,6 +304,213 @@ A = [
|
|||||||
]
|
]
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "nil interface not supported at root",
|
||||||
|
v: nil,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nil interface not supported in slice",
|
||||||
|
v: map[string]interface{}{
|
||||||
|
"a": []interface{}{"a", nil, 2},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nil pointer in slice uses zero value",
|
||||||
|
v: struct {
|
||||||
|
A []*int
|
||||||
|
}{
|
||||||
|
A: []*int{nil},
|
||||||
|
},
|
||||||
|
expected: `A = [0]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nil pointer in slice uses zero value",
|
||||||
|
v: struct {
|
||||||
|
A []*int
|
||||||
|
}{
|
||||||
|
A: []*int{nil},
|
||||||
|
},
|
||||||
|
expected: `A = [0]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "pointer in slice",
|
||||||
|
v: struct {
|
||||||
|
A []*int
|
||||||
|
}{
|
||||||
|
A: []*int{&someInt},
|
||||||
|
},
|
||||||
|
expected: `A = [42]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "inline table in inline table",
|
||||||
|
v: structInline{
|
||||||
|
A: structInline{
|
||||||
|
A: structInline{
|
||||||
|
A: "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: `A = {A = {A = 'hello'}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty slice in map",
|
||||||
|
v: map[string][]string{
|
||||||
|
"a": {},
|
||||||
|
},
|
||||||
|
expected: `a = []`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "map in slice",
|
||||||
|
v: map[string][]map[string]string{
|
||||||
|
"a": {{"hello": "world"}},
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
[[a]]
|
||||||
|
hello = 'world'`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "newline in map in slice",
|
||||||
|
v: map[string][]map[string]string{
|
||||||
|
"a\n": {{"hello": "world"}},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "newline in map in slice",
|
||||||
|
v: map[string][]map[string]*customTextMarshaler{
|
||||||
|
"a": {{"hello": &customTextMarshaler{1}}},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty slice of empty struct",
|
||||||
|
v: struct {
|
||||||
|
A []struct{}
|
||||||
|
}{
|
||||||
|
A: []struct{}{},
|
||||||
|
},
|
||||||
|
expected: `A = []`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nil field is ignored",
|
||||||
|
v: struct {
|
||||||
|
A interface{}
|
||||||
|
}{
|
||||||
|
A: nil,
|
||||||
|
},
|
||||||
|
expected: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "private fields are ignored",
|
||||||
|
v: struct {
|
||||||
|
Public string
|
||||||
|
private string
|
||||||
|
}{
|
||||||
|
Public: "shown",
|
||||||
|
private: "hidden",
|
||||||
|
},
|
||||||
|
expected: `Public = 'shown'`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "fields tagged - are ignored",
|
||||||
|
v: struct {
|
||||||
|
Public string `toml:"-"`
|
||||||
|
private string
|
||||||
|
}{
|
||||||
|
Public: "hidden",
|
||||||
|
},
|
||||||
|
expected: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nil value in map is ignored",
|
||||||
|
v: map[string]interface{}{
|
||||||
|
"A": nil,
|
||||||
|
},
|
||||||
|
expected: ``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "new line in table key",
|
||||||
|
v: map[string]interface{}{
|
||||||
|
"hello\nworld": 42,
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "new line in parent of nested table key",
|
||||||
|
v: map[string]interface{}{
|
||||||
|
"hello\nworld": map[string]interface{}{
|
||||||
|
"inner": 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "new line in nested table key",
|
||||||
|
v: map[string]interface{}{
|
||||||
|
"parent": map[string]interface{}{
|
||||||
|
"in\ner": map[string]interface{}{
|
||||||
|
"foo": 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid map key",
|
||||||
|
v: map[int]interface{}{},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "unhandled type",
|
||||||
|
v: struct {
|
||||||
|
A chan int
|
||||||
|
}{
|
||||||
|
A: make(chan int),
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "numbers",
|
||||||
|
v: struct {
|
||||||
|
A float32
|
||||||
|
B uint64
|
||||||
|
C uint32
|
||||||
|
D uint16
|
||||||
|
E uint8
|
||||||
|
F uint
|
||||||
|
G int64
|
||||||
|
H int32
|
||||||
|
I int16
|
||||||
|
J int8
|
||||||
|
K int
|
||||||
|
}{
|
||||||
|
A: 1.1,
|
||||||
|
B: 42,
|
||||||
|
C: 42,
|
||||||
|
D: 42,
|
||||||
|
E: 42,
|
||||||
|
F: 42,
|
||||||
|
G: 42,
|
||||||
|
H: 42,
|
||||||
|
I: 42,
|
||||||
|
J: 42,
|
||||||
|
K: 42,
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
A = 1.1
|
||||||
|
B = 42
|
||||||
|
C = 42
|
||||||
|
D = 42
|
||||||
|
E = 42
|
||||||
|
F = 42
|
||||||
|
G = 42
|
||||||
|
H = 42
|
||||||
|
I = 42
|
||||||
|
J = 42
|
||||||
|
K = 42`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range examples {
|
for _, e := range examples {
|
||||||
@@ -460,6 +673,85 @@ root = 'value0'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type customTextMarshaler struct {
|
||||||
|
value int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *customTextMarshaler) MarshalText() ([]byte, error) {
|
||||||
|
if c.value == 1 {
|
||||||
|
return nil, fmt.Errorf("cannot represent 1 because this is a silly test")
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf("::%d", c.value)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextMarshaler_NoRoot(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := customTextMarshaler{}
|
||||||
|
_, err := toml.Marshal(&c)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextMarshaler_Error(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
|
||||||
|
_, err := toml.Marshal(m)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type s struct {
|
||||||
|
A map[string]interface{} `inline:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
d := s{
|
||||||
|
A: map[string]interface{}{"a": &customTextMarshaler{value: 1}},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := toml.Marshal(d)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextMarshaler(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
|
||||||
|
r, err := toml.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
equalStringsIgnoreNewlines(t, "a = '::2'", string(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
type brokenWriter struct{}
|
||||||
|
|
||||||
|
func (b *brokenWriter) Write([]byte) (int, error) {
|
||||||
|
return 0, fmt.Errorf("dead")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeToBrokenWriter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
w := brokenWriter{}
|
||||||
|
enc := toml.NewEncoder(&w)
|
||||||
|
err := enc.Encode(map[string]string{"hello": "world"})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncoderSetIndentSymbol(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var w strings.Builder
|
||||||
|
enc := toml.NewEncoder(&w)
|
||||||
|
enc.SetIndentTables(true)
|
||||||
|
enc.SetIndentSymbol(">>>")
|
||||||
|
err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected := `
|
||||||
|
[parent]
|
||||||
|
>>>hello = 'world'`
|
||||||
|
equalStringsIgnoreNewlines(t, expected, w.String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue436(t *testing.T) {
|
func TestIssue436(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package toml
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/ast"
|
"github.com/pelletier/go-toml/v2/internal/ast"
|
||||||
@@ -77,7 +76,6 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) {
|
|||||||
|
|
||||||
if b[0] == '\r' {
|
if b[0] == '\r' {
|
||||||
_, rest, err := scanWindowsNewline(b)
|
_, rest, err := scanWindowsNewline(b)
|
||||||
|
|
||||||
return rest, err
|
return rest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +204,10 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) {
|
|||||||
|
|
||||||
b = p.parseWhitespace(b)
|
b = p.parseWhitespace(b)
|
||||||
|
|
||||||
|
if len(b) == 0 {
|
||||||
|
return ast.Reference{}, nil, newDecodeError(b, "expected = after a key, but the document ends there")
|
||||||
|
}
|
||||||
|
|
||||||
b, err = expect('=', b)
|
b, err = expect('=', b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ast.Reference{}, nil, err
|
return ast.Reference{}, nil, err
|
||||||
@@ -304,6 +306,7 @@ func atmost(b []byte, n int) []byte {
|
|||||||
if n >= len(b) {
|
if n >= len(b) {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
return b[:n]
|
return b[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,8 +400,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
//nolint:godox
|
return parent, nil, newDecodeError(b, "array is incomplete")
|
||||||
return parent, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if b[0] == ']' {
|
if b[0] == ']' {
|
||||||
@@ -562,7 +564,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) {
|
|||||||
case 't':
|
case 't':
|
||||||
builder.WriteByte('\t')
|
builder.WriteByte('\t')
|
||||||
case 'u':
|
case 'u':
|
||||||
x, err := hexToString(token[i+3:len(token)-3], 4)
|
x, err := hexToString(atmost(token[i+1:], 4), 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -570,7 +572,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) {
|
|||||||
builder.WriteString(x)
|
builder.WriteString(x)
|
||||||
i += 4
|
i += 4
|
||||||
case 'U':
|
case 'U':
|
||||||
x, err := hexToString(token[i+3:len(token)-3], 8)
|
x, err := hexToString(atmost(token[i+1:], 8), 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -610,12 +612,7 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
|
|||||||
for {
|
for {
|
||||||
b = p.parseWhitespace(b)
|
b = p.parseWhitespace(b)
|
||||||
if len(b) > 0 && b[0] == '.' {
|
if len(b) > 0 && b[0] == '.' {
|
||||||
b, err = expect('.', b)
|
b = p.parseWhitespace(b[1:])
|
||||||
if err != nil {
|
|
||||||
return ref, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = p.parseWhitespace(b)
|
|
||||||
|
|
||||||
key, b, err = p.parseSimpleKey(b)
|
key, b, err = p.parseSimpleKey(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -639,8 +636,7 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) {
|
|||||||
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
||||||
// quoted-key = basic-string / literal-string
|
// quoted-key = basic-string / literal-string
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
//nolint:godox
|
return nil, nil, newDecodeError(b, "key is incomplete")
|
||||||
return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -649,10 +645,10 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) {
|
|||||||
case b[0] == '"':
|
case b[0] == '"':
|
||||||
return p.parseBasicString(b)
|
return p.parseBasicString(b)
|
||||||
case isUnquotedKeyChar(b[0]):
|
case isUnquotedKeyChar(b[0]):
|
||||||
return scanUnquotedKey(b)
|
key, rest = scanUnquotedKey(b)
|
||||||
|
return key, rest, nil
|
||||||
default:
|
default:
|
||||||
//nolint:godox
|
return nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0])
|
||||||
return nil, nil, unexpectedCharacter{b: b} // TODO: should be unexpected EOF
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,11 +821,14 @@ byteLoop:
|
|||||||
c := b[i]
|
c := b[i]
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case isDigit(c) || c == '-':
|
case isDigit(c):
|
||||||
|
case c == '-':
|
||||||
|
const offsetOfTz = 19
|
||||||
|
if i == offsetOfTz {
|
||||||
|
hasTz = true
|
||||||
|
}
|
||||||
case c == 'T' || c == ':' || c == '.':
|
case c == 'T' || c == ':' || c == '.':
|
||||||
hasTime = true
|
hasTime = true
|
||||||
|
|
||||||
continue byteLoop
|
|
||||||
case c == '+' || c == '-' || c == 'Z':
|
case c == '+' || c == '-' || c == 'Z':
|
||||||
hasTz = true
|
hasTz = true
|
||||||
case c == ' ':
|
case c == ' ':
|
||||||
@@ -854,9 +853,6 @@ byteLoop:
|
|||||||
kind = ast.LocalDateTime
|
kind = ast.LocalDateTime
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if hasTz {
|
|
||||||
return ast.Reference{}, nil, newDecodeError(b, "date-time has timezone but not time component")
|
|
||||||
}
|
|
||||||
kind = ast.LocalDate
|
kind = ast.LocalDate
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -977,26 +973,9 @@ func isValidBinaryRune(r byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func expect(x byte, b []byte) ([]byte, error) {
|
func expect(x byte, b []byte) ([]byte, error) {
|
||||||
if len(b) == 0 {
|
|
||||||
return nil, newDecodeError(b[:0], "expecting %#U", x)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b[0] != x {
|
if b[0] != x {
|
||||||
return nil, newDecodeError(b[0:1], "expected character %U", x)
|
return nil, newDecodeError(b[0:1], "expected character %U", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b[1:], nil
|
return b[1:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type unexpectedCharacter struct {
|
|
||||||
r byte
|
|
||||||
b []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u unexpectedCharacter) Error() string {
|
|
||||||
if len(u.b) == 0 {
|
|
||||||
return fmt.Sprintf("expected %#U, not EOF", u.r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("expected %#U, not %#U", u.r, u.b[0])
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-3
@@ -30,15 +30,15 @@ func scanFollowsNan(b []byte) bool {
|
|||||||
return scanFollows(b, `nan`)
|
return scanFollows(b, `nan`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanUnquotedKey(b []byte) ([]byte, []byte, error) {
|
func scanUnquotedKey(b []byte) ([]byte, []byte) {
|
||||||
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
|
||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
if !isUnquotedKeyChar(b[i]) {
|
if !isUnquotedKeyChar(b[i]) {
|
||||||
return b[:i], b[i:], nil
|
return b[:i], b[i:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, b[len(b):], nil
|
return b, b[len(b):]
|
||||||
}
|
}
|
||||||
|
|
||||||
func isUnquotedKeyChar(r byte) bool {
|
func isUnquotedKeyChar(r byte) bool {
|
||||||
|
|||||||
+5
-9
@@ -70,19 +70,19 @@ func (t interfaceTarget) set(v reflect.Value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t interfaceTarget) setString(v string) {
|
func (t interfaceTarget) setString(v string) {
|
||||||
t.x.setString(v)
|
panic("interface targets should always go through set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t interfaceTarget) setBool(v bool) {
|
func (t interfaceTarget) setBool(v bool) {
|
||||||
t.x.setBool(v)
|
panic("interface targets should always go through set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t interfaceTarget) setInt64(v int64) {
|
func (t interfaceTarget) setInt64(v int64) {
|
||||||
t.x.setInt64(v)
|
panic("interface targets should always go through set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t interfaceTarget) setFloat64(v float64) {
|
func (t interfaceTarget) setFloat64(v float64) {
|
||||||
t.x.setFloat64(v)
|
panic("interface targets should always go through set")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapTarget targets a specific key of a map.
|
// mapTarget targets a specific key of a map.
|
||||||
@@ -115,7 +115,6 @@ func (t mapTarget) setFloat64(v float64) {
|
|||||||
t.set(reflect.ValueOf(v))
|
t.set(reflect.ValueOf(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:cyclop
|
|
||||||
// makes sure that the value pointed at by t is indexable (Slice, Array), or
|
// makes sure that the value pointed at by t is indexable (Slice, Array), or
|
||||||
// dereferences to an indexable (Ptr, Interface).
|
// dereferences to an indexable (Ptr, Interface).
|
||||||
func ensureValueIndexable(t target) error {
|
func ensureValueIndexable(t target) error {
|
||||||
@@ -193,7 +192,7 @@ const (
|
|||||||
minInt = -maxInt - 1
|
minInt = -maxInt - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen,gocognit,cyclop,gocyclo
|
//nolint:funlen,gocognit,cyclop
|
||||||
func setInt64(t target, v int64) error {
|
func setInt64(t target, v int64) error {
|
||||||
f := t.get()
|
f := t.get()
|
||||||
|
|
||||||
@@ -285,7 +284,6 @@ func setFloat64(t target, v float64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:cyclop
|
|
||||||
// Returns the element at idx of the value pointed at by target, or an error if
|
// Returns the element at idx of the value pointed at by target, or an error if
|
||||||
// t does not point to an indexable.
|
// t does not point to an indexable.
|
||||||
// If the target points to an Array and idx is out of bounds, it returns
|
// If the target points to an Array and idx is out of bounds, it returns
|
||||||
@@ -311,7 +309,6 @@ func elementAt(t target, idx int) target {
|
|||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
// This function is called after ensureValueIndexable, so it's
|
// This function is called after ensureValueIndexable, so it's
|
||||||
// guaranteed that f contains an initialized slice.
|
// guaranteed that f contains an initialized slice.
|
||||||
|
|
||||||
ifaceElem := f.Elem()
|
ifaceElem := f.Elem()
|
||||||
idx := ifaceElem.Len()
|
idx := ifaceElem.Len()
|
||||||
newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
|
newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
|
||||||
@@ -326,7 +323,6 @@ func elementAt(t target, idx int) target {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:cyclop
|
|
||||||
func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (target, bool, error) {
|
func (d *decoder) scopeTableTarget(shouldAppend bool, t target, name string) (target, bool, error) {
|
||||||
x := t.get()
|
x := t.get()
|
||||||
|
|
||||||
|
|||||||
+22
-2
@@ -541,6 +541,27 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special work around when unmarshaling into an array.
|
||||||
|
// If the array is not addressable, for example when stored as a value in a
|
||||||
|
// map, calling elementAt in the inner function would fail.
|
||||||
|
// Instead, we allocate a new array that will be filled then inserted into
|
||||||
|
// the container.
|
||||||
|
// This problem does not exist with slices because they are addressable.
|
||||||
|
// There may be a better way of doing this, but it is not obvious to me
|
||||||
|
// with the target system.
|
||||||
|
if x.get().Kind() == reflect.Array {
|
||||||
|
container := x
|
||||||
|
newArrayPtr := reflect.New(x.get().Type())
|
||||||
|
x = valueTarget(newArrayPtr.Elem())
|
||||||
|
defer func() {
|
||||||
|
container.set(newArrayPtr.Elem())
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.unmarshalArrayInner(x, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) unmarshalArrayInner(x target, node ast.Node) error {
|
||||||
idx := 0
|
idx := 0
|
||||||
|
|
||||||
it := node.Children()
|
it := node.Children()
|
||||||
@@ -555,14 +576,13 @@ func (d *decoder) unmarshalArray(x target, node ast.Node) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.unmarshalValue(v, n)
|
err := d.unmarshalValue(v, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// nolint:funlen
|
||||||
func TestUnmarshal_Integers(t *testing.T) {
|
func TestUnmarshal_Integers(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -239,6 +240,34 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "time.time with negative zone",
|
||||||
|
input: `a = 1979-05-27T00:32:00-07:00 `, // space intentional
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]time.Time
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]time.Time{
|
||||||
|
"a": time.Date(1979, 5, 27, 0, 32, 0, 0, time.FixedZone("", -7*3600)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "time.time with positive zone",
|
||||||
|
input: `a = 1979-05-27T00:32:00+07:00`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]time.Time
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]time.Time{
|
||||||
|
"a": time.Date(1979, 5, 27, 0, 32, 0, 0, time.FixedZone("", 7*3600)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "issue 475 - space between dots in key",
|
desc: "issue 475 - space between dots in key",
|
||||||
input: `fruit. color = "yellow"
|
input: `fruit. color = "yellow"
|
||||||
@@ -288,6 +317,73 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "multiline literal string with windows newline",
|
||||||
|
input: "A = '''\r\nTest'''",
|
||||||
|
gen: func() test {
|
||||||
|
type doc struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &doc{},
|
||||||
|
expected: &doc{A: "Test"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "multiline basic string with windows newline",
|
||||||
|
input: "A = \"\"\"\r\nTest\"\"\"",
|
||||||
|
gen: func() test {
|
||||||
|
type doc struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &doc{},
|
||||||
|
expected: &doc{A: "Test"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "multiline basic string escapes",
|
||||||
|
input: `A = """
|
||||||
|
\\\b\f\n\r\t\uffff\U0001D11E"""`,
|
||||||
|
gen: func() test {
|
||||||
|
type doc struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &doc{},
|
||||||
|
expected: &doc{A: "\\\b\f\n\r\t\uffff\U0001D11E"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "basic string escapes",
|
||||||
|
input: `A = "\\\b\f\n\r\t\uffff\U0001D11E"`,
|
||||||
|
gen: func() test {
|
||||||
|
type doc struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &doc{},
|
||||||
|
expected: &doc{A: "\\\b\f\n\r\t\uffff\U0001D11E"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "spaces around dotted keys",
|
||||||
|
input: "a . b = 1",
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string]map[string]interface{}{},
|
||||||
|
expected: &map[string]map[string]interface{}{"a": {"b": int64(1)}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "kv bool true",
|
desc: "kv bool true",
|
||||||
input: `A = true`,
|
input: `A = true`,
|
||||||
@@ -721,6 +817,197 @@ B = "data"`,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "interface holding a string",
|
||||||
|
input: `A = "Hello"`,
|
||||||
|
gen: func() test {
|
||||||
|
type doc struct {
|
||||||
|
A interface{}
|
||||||
|
}
|
||||||
|
return test{
|
||||||
|
target: &doc{},
|
||||||
|
expected: &doc{
|
||||||
|
A: "Hello",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "map of bools",
|
||||||
|
input: `A = true`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string]bool{},
|
||||||
|
expected: &map[string]bool{"A": true},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "map of int64",
|
||||||
|
input: `A = 42`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string]int64{},
|
||||||
|
expected: &map[string]int64{"A": 42},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "map of float64",
|
||||||
|
input: `A = 4.2`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string]float64{},
|
||||||
|
expected: &map[string]float64{"A": 4.2},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "array of int in map",
|
||||||
|
input: `A = [1,2,3]`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string][3]int{},
|
||||||
|
expected: &map[string][3]int{"A": {1, 2, 3}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "array of int in map with too many elements",
|
||||||
|
input: `A = [1,2,3,4,5]`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string][3]int{},
|
||||||
|
expected: &map[string][3]int{"A": {1, 2, 3}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "array of int in map with invalid element",
|
||||||
|
input: `A = [1,2,false]`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string][3]int{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nested arrays",
|
||||||
|
input: `
|
||||||
|
[[A]]
|
||||||
|
[[A.B]]
|
||||||
|
C = 1
|
||||||
|
[[A]]
|
||||||
|
[[A.B]]
|
||||||
|
C = 2`,
|
||||||
|
gen: func() test {
|
||||||
|
type leaf struct {
|
||||||
|
C int
|
||||||
|
}
|
||||||
|
type inner struct {
|
||||||
|
B [2]leaf
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
A [2]inner
|
||||||
|
}
|
||||||
|
return test{
|
||||||
|
target: &s{},
|
||||||
|
expected: &s{A: [2]inner{
|
||||||
|
{B: [2]leaf{
|
||||||
|
{C: 1},
|
||||||
|
}},
|
||||||
|
{B: [2]leaf{
|
||||||
|
{C: 2},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nested arrays too many",
|
||||||
|
input: `
|
||||||
|
[[A]]
|
||||||
|
[[A.B]]
|
||||||
|
C = 1
|
||||||
|
[[A.B]]
|
||||||
|
C = 2`,
|
||||||
|
gen: func() test {
|
||||||
|
type leaf struct {
|
||||||
|
C int
|
||||||
|
}
|
||||||
|
type inner struct {
|
||||||
|
B [1]leaf
|
||||||
|
}
|
||||||
|
type s struct {
|
||||||
|
A [1]inner
|
||||||
|
}
|
||||||
|
return test{
|
||||||
|
target: &s{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map with invalid key type",
|
||||||
|
input: `A = "hello"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[int]string{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map with convertible key type",
|
||||||
|
input: `A = "hello"`,
|
||||||
|
gen: func() test {
|
||||||
|
type foo string
|
||||||
|
return test{
|
||||||
|
target: &map[foo]string{},
|
||||||
|
expected: &map[foo]string{
|
||||||
|
"A": "hello",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "array of int in struct",
|
||||||
|
input: `A = [1,2,3]`,
|
||||||
|
gen: func() test {
|
||||||
|
type s struct {
|
||||||
|
A [3]int
|
||||||
|
}
|
||||||
|
return test{
|
||||||
|
target: &s{},
|
||||||
|
expected: &s{A: [3]int{1, 2, 3}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "array of int in struct",
|
||||||
|
input: `[A]
|
||||||
|
b = 42`,
|
||||||
|
gen: func() test {
|
||||||
|
type s struct {
|
||||||
|
A *map[string]interface{}
|
||||||
|
}
|
||||||
|
return test{
|
||||||
|
target: &s{},
|
||||||
|
expected: &s{A: &map[string]interface{}{"b": int64(42)}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "assign bool to float",
|
||||||
|
input: `A = true`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[string]float64{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "interface holding a struct",
|
desc: "interface holding a struct",
|
||||||
input: `[A]
|
input: `[A]
|
||||||
@@ -877,6 +1164,82 @@ B = "data"`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalOverflows(t *testing.T) {
|
||||||
|
examples := []struct {
|
||||||
|
t interface{}
|
||||||
|
errors []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
t: &map[string]int32{},
|
||||||
|
errors: []string{`-2147483649`, `2147483649`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]int16{},
|
||||||
|
errors: []string{`-2147483649`, `2147483649`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]int8{},
|
||||||
|
errors: []string{`-2147483649`, `2147483649`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]int{},
|
||||||
|
errors: []string{`-19223372036854775808`, `9223372036854775808`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]uint64{},
|
||||||
|
errors: []string{`-1`, `18446744073709551616`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]uint32{},
|
||||||
|
errors: []string{`-1`, `18446744073709551616`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]uint16{},
|
||||||
|
errors: []string{`-1`, `18446744073709551616`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]uint8{},
|
||||||
|
errors: []string{`-1`, `18446744073709551616`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
t: &map[string]uint{},
|
||||||
|
errors: []string{`-1`, `18446744073709551616`},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range examples {
|
||||||
|
e := e
|
||||||
|
for _, v := range e.errors {
|
||||||
|
v := v
|
||||||
|
t.Run(fmt.Sprintf("%T %s", e.t, v), func(t *testing.T) {
|
||||||
|
doc := "A = " + v
|
||||||
|
err := toml.Unmarshal([]byte(doc), e.t)
|
||||||
|
t.Log("input:", doc)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Run(fmt.Sprintf("%T ok", e.t), func(t *testing.T) {
|
||||||
|
doc := "A = 1"
|
||||||
|
err := toml.Unmarshal([]byte(doc), e.t)
|
||||||
|
t.Log("input:", doc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalFloat32(t *testing.T) {
|
||||||
|
t.Run("fits", func(t *testing.T) {
|
||||||
|
doc := "A = 1.2"
|
||||||
|
err := toml.Unmarshal([]byte(doc), &map[string]float32{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
t.Run("overflows", func(t *testing.T) {
|
||||||
|
doc := "A = 4.40282346638528859811704183484516925440e+38"
|
||||||
|
err := toml.Unmarshal([]byte(doc), &map[string]float32{})
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type Integer484 struct {
|
type Integer484 struct {
|
||||||
Value int
|
Value int
|
||||||
}
|
}
|
||||||
@@ -999,10 +1362,66 @@ func TestUnmarshalDecodeErrors(t *testing.T) {
|
|||||||
data string
|
data string
|
||||||
msg string
|
msg string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
desc: "local date with invalid digit",
|
||||||
|
data: `a = 20x1-05-21`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "local time with fractional",
|
||||||
|
data: `a = 11:22:33.x`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "local time frac precision too large",
|
||||||
|
data: `a = 2021-05-09T11:22:33.99999999999`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wrong time offset separator",
|
||||||
|
data: `a = 1979-05-27T00:32:00T07:00`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wrong time offset separator",
|
||||||
|
data: `a = 1979-05-27T00:32:00Z07:00`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "float with double _",
|
||||||
|
data: `flt8 = 224_617.445_991__228`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "float with double _",
|
||||||
|
data: `flt8 = 1..2`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "int with wrong base",
|
desc: "int with wrong base",
|
||||||
data: `a = 0f2`,
|
data: `a = 0f2`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "int hex with double underscore",
|
||||||
|
data: `a = 0xFFF__FFF`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "int hex very large",
|
||||||
|
data: `a = 0xFFFFFFFFFFFFFFFFF`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "int oct with double underscore",
|
||||||
|
data: `a = 0o777__77`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "int oct very large",
|
||||||
|
data: `a = 0o77777777777777777777777`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "int bin with double underscore",
|
||||||
|
data: `a = 0b111__111`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "int bin very large",
|
||||||
|
data: `a = 0b11111111111111111111111111111111111111111111111111111111111111111111111111111`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "int dec very large",
|
||||||
|
data: `a = 999999999999999999999999`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "literal string with new lines",
|
desc: "literal string with new lines",
|
||||||
data: `a = 'hello
|
data: `a = 'hello
|
||||||
@@ -1065,6 +1484,102 @@ world'`,
|
|||||||
data: `a = 2021-03-30 21:312:0`,
|
data: `a = 2021-03-30 21:312:0`,
|
||||||
msg: `expecting colon between minutes and seconds`,
|
msg: `expecting colon between minutes and seconds`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: `binary with invalid digit`,
|
||||||
|
data: `a = 0bf`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid i in dec`,
|
||||||
|
data: `a = 0i`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid n in dec`,
|
||||||
|
data: `a = 0n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid unquoted key`,
|
||||||
|
data: `a`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "dt with tz has no time",
|
||||||
|
data: `a = 2021-03-30TZ`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid end of array table",
|
||||||
|
data: `[[a}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid end of array table two",
|
||||||
|
data: `[[a]}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "eof after equal",
|
||||||
|
data: `a =`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid true boolean",
|
||||||
|
data: `a = trois`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid false boolean",
|
||||||
|
data: `a = faux`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "inline table with incorrect separator",
|
||||||
|
data: `a = {b=1;}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "inline table with invalid value",
|
||||||
|
data: `a = {b=faux}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `incomplete array after whitespace`,
|
||||||
|
data: `a = [ `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `array with comma first`,
|
||||||
|
data: `a = [ ,]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `array staring with incomplete newline`,
|
||||||
|
data: "a = [\r]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `array with incomplete newline after comma`,
|
||||||
|
data: "a = [1,\r]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `array with incomplete newline after value`,
|
||||||
|
data: "a = [1\r]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid unicode in basic multiline string`,
|
||||||
|
data: `A = """\u123"""`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid long unicode in basic multiline string`,
|
||||||
|
data: `A = """\U0001D11"""`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid unicode in basic string`,
|
||||||
|
data: `A = "\u123"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid long unicode in basic string`,
|
||||||
|
data: `A = "\U0001D11"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid escape char basic multiline string`,
|
||||||
|
data: `A = """\z"""`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid inf`,
|
||||||
|
data: `A = ick`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: `invalid nan`,
|
||||||
|
data: `A = non`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range examples {
|
for _, e := range examples {
|
||||||
@@ -1270,6 +1785,7 @@ bar = 42
|
|||||||
t.Run(e.desc, func(t *testing.T) {
|
t.Run(e.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("strict", func(t *testing.T) {
|
||||||
r := strings.NewReader(e.input)
|
r := strings.NewReader(e.input)
|
||||||
d := toml.NewDecoder(r)
|
d := toml.NewDecoder(r)
|
||||||
d.SetStrict(true)
|
d.SetStrict(true)
|
||||||
@@ -1286,6 +1802,19 @@ bar = 42
|
|||||||
t.Fatalf("err should have been a *toml.StrictMissingError, but got %s (%T)", err, err)
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user