Compare commits
14 Commits
v2.0.0-beta.7
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a422e3dbd | |||
| 627dade0c7 | |||
| b2e0231cc9 | |||
| ba95863cd3 | |||
| db679df765 | |||
| c5ca2c682b | |||
| ed80712cb4 | |||
| b24772942d | |||
| 9501a05ed7 | |||
| 171a592663 | |||
| 5aaf5ef13b | |||
| adacebd8c7 | |||
| 8bbb673431 | |||
| 2377ac4bc0 |
@@ -1,3 +1,4 @@
|
||||
* text=auto
|
||||
|
||||
benchmark/benchmark.toml text eol=lf
|
||||
testdata/** text eol=lf
|
||||
|
||||
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: report
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup go
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
go-version: 1.18
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.go }}/${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup go ${{ matrix.go }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 - 2021 Thomas Pelletier, Eric Anderton
|
||||
Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -4,17 +4,6 @@ Go library for the [TOML](https://toml.io/en/) format.
|
||||
|
||||
This library supports [TOML v1.0.0](https://toml.io/en/v1.0.0).
|
||||
|
||||
## Development status
|
||||
|
||||
This is the upcoming major version of go-toml. It is currently in active
|
||||
development. As of release v2.0.0-beta.1, the library has reached feature parity
|
||||
with v1, and fixes a lot known bugs and performance issues along the way.
|
||||
|
||||
If you do not need the advanced document editing features of v1, you are
|
||||
encouraged to try out this version.
|
||||
|
||||
[👉 Roadmap for v2](https://github.com/pelletier/go-toml/discussions/506)
|
||||
|
||||
[🐞 Bug Reports](https://github.com/pelletier/go-toml/issues)
|
||||
|
||||
[💬 Anything else](https://github.com/pelletier/go-toml/discussions)
|
||||
@@ -49,7 +38,7 @@ operations should not be shockingly slow. See [benchmarks](#benchmarks).
|
||||
### Strict mode
|
||||
|
||||
`Decoder` can be set to "strict mode", which makes it error when some parts of
|
||||
the TOML document was not prevent in the target structure. This is a great way
|
||||
the TOML document was not present in the target structure. This is a great way
|
||||
to check for typos. [See example in the documentation][strict].
|
||||
|
||||
[strict]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Decoder.DisallowUnknownFields
|
||||
@@ -161,11 +150,11 @@ Execution time speedup compared to other Go TOML libraries:
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>1.9x</td></tr>
|
||||
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>1.9x</td></tr>
|
||||
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.4x</td><td>2.6x</td></tr>
|
||||
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.5x</td></tr>
|
||||
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.7x</td><td>2.6x</td></tr>
|
||||
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.8x</td><td>5.1x</td></tr>
|
||||
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>1.8x</td></tr>
|
||||
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>2.5x</td></tr>
|
||||
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.9x</td></tr>
|
||||
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.9x</td></tr>
|
||||
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.4x</td><td>5.3x</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<details><summary>See more</summary>
|
||||
@@ -178,17 +167,17 @@ provided for completeness.</p>
|
||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.7x</td><td>2.1x</td></tr>
|
||||
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.5x</td><td>2.8x</td></tr>
|
||||
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>4.1x</td><td>3.1x</td></tr>
|
||||
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>6.4x</td><td>4.3x</td></tr>
|
||||
<tr><td>UnmarshalDataset/example-2</td><td>3.4x</td><td>3.2x</td></tr>
|
||||
<tr><td>UnmarshalDataset/code-2</td><td>2.2x</td><td>2.5x</td></tr>
|
||||
<tr><td>UnmarshalDataset/twitter-2</td><td>2.8x</td><td>2.7x</td></tr>
|
||||
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.2x</td><td>2.0x</td></tr>
|
||||
<tr><td>UnmarshalDataset/canada-2</td><td>1.8x</td><td>1.4x</td></tr>
|
||||
<tr><td>UnmarshalDataset/config-2</td><td>4.4x</td><td>2.9x</td></tr>
|
||||
<tr><td>[Geo mean]</td><td>2.8x</td><td>2.6x</td></tr>
|
||||
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.9x</td></tr>
|
||||
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>4.2x</td></tr>
|
||||
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>4.5x</td><td>3.1x</td></tr>
|
||||
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>6.2x</td><td>3.9x</td></tr>
|
||||
<tr><td>UnmarshalDataset/example-2</td><td>3.1x</td><td>3.5x</td></tr>
|
||||
<tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>3.1x</td></tr>
|
||||
<tr><td>UnmarshalDataset/twitter-2</td><td>2.5x</td><td>2.6x</td></tr>
|
||||
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.1x</td><td>2.2x</td></tr>
|
||||
<tr><td>UnmarshalDataset/canada-2</td><td>1.6x</td><td>1.3x</td></tr>
|
||||
<tr><td>UnmarshalDataset/config-2</td><td>4.3x</td><td>3.2x</td></tr>
|
||||
<tr><td>[Geo mean]</td><td>2.7x</td><td>2.8x</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
|
||||
@@ -551,6 +540,13 @@ complete solutions exist out there.
|
||||
[query]: https://github.com/pelletier/go-toml/tree/f99d6bbca119636aeafcf351ee52b3d202782627/query
|
||||
[dasel]: https://github.com/TomWright/dasel
|
||||
|
||||
## Versioning
|
||||
|
||||
Go-toml follows [Semantic Versioning](http://semver.org/). The supported version
|
||||
of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of
|
||||
this document. The last two major versions of Go are supported
|
||||
(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)).
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT). Read [LICENSE](LICENSE).
|
||||
|
||||
@@ -76,7 +76,8 @@ cover() {
|
||||
fi
|
||||
|
||||
pushd "$dir"
|
||||
go test -covermode=atomic -coverprofile=coverage.out ./...
|
||||
go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./...
|
||||
cat coverage.out.tmp | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out
|
||||
go tool cover -func=coverage.out
|
||||
popd
|
||||
|
||||
@@ -103,8 +104,8 @@ coverage() {
|
||||
|
||||
echo ""
|
||||
|
||||
target_pct="$(cat ${target_out} |sed -E 's/.*total.*\t([0-9.]+)%/\1/;t;d')"
|
||||
head_pct="$(cat ${head_out} |sed -E 's/.*total.*\t([0-9.]+)%/\1/;t;d')"
|
||||
target_pct="$(tail -n2 ${target_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%.*/\1/')"
|
||||
head_pct="$(tail -n2 ${head_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%/\1/')"
|
||||
echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%"
|
||||
|
||||
delta_pct=$(echo "$head_pct - $target_pct" | bc -l)
|
||||
@@ -112,6 +113,13 @@ coverage() {
|
||||
|
||||
if [[ $delta_pct = \-* ]]; then
|
||||
echo "Regression!";
|
||||
|
||||
target_diff="${output_dir}/target.diff.txt"
|
||||
head_diff="${output_dir}/head.diff.txt"
|
||||
cat "${target_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${target_diff}"
|
||||
cat "${head_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${head_diff}"
|
||||
|
||||
diff --side-by-side --suppress-common-lines "${target_diff}" "${head_diff}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
||||
@@ -130,7 +130,11 @@ func parseDateTime(b []byte) (time.Time, error) {
|
||||
}
|
||||
|
||||
seconds := direction * (hours*3600 + minutes*60)
|
||||
if seconds == 0 {
|
||||
zone = time.UTC
|
||||
} else {
|
||||
zone = time.FixedZone("", seconds)
|
||||
}
|
||||
b = b[dateTimeByteLen:]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package toml_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func FuzzUnmarshal(f *testing.F) {
|
||||
file, err := ioutil.ReadFile("benchmark/benchmark.toml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.Add(file)
|
||||
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
if strings.Contains(string(b), "nan") {
|
||||
// Current limitation of testify.
|
||||
// https://github.com/stretchr/testify/issues/624
|
||||
t.Skip("can't compare NaNs")
|
||||
}
|
||||
|
||||
t.Log("INITIAL DOCUMENT ===========================")
|
||||
t.Log(string(b))
|
||||
|
||||
var v interface{}
|
||||
err := toml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("DECODED VALUE ===========================")
|
||||
t.Logf("%#+v", v)
|
||||
|
||||
encoded, err := toml.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot marshal unmarshaled document: %s", err)
|
||||
}
|
||||
|
||||
t.Log("ENCODED DOCUMENT ===========================")
|
||||
t.Log(string(encoded))
|
||||
|
||||
var v2 interface{}
|
||||
err = toml.Unmarshal(encoded, &v2)
|
||||
if err != nil {
|
||||
t.Fatalf("failed round trip: %s", err)
|
||||
}
|
||||
require.Equal(t, v, v2)
|
||||
})
|
||||
}
|
||||
@@ -11,5 +11,6 @@ func TestEntrySize(t *testing.T) {
|
||||
// Validate no regression on the size of entry{}. This is a critical bit for
|
||||
// performance of unmarshaling documents. Should only be increased with care
|
||||
// and a very good reason.
|
||||
require.LessOrEqual(t, 48, int(unsafe.Sizeof(entry{})))
|
||||
maxExpectedEntrySize := 48
|
||||
require.LessOrEqual(t, int(unsafe.Sizeof(entry{})), maxExpectedEntrySize)
|
||||
}
|
||||
|
||||
+79
-49
@@ -128,7 +128,8 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
||||
//
|
||||
// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
|
||||
// a TOML comment before the value being annotated. Comments are ignored inside
|
||||
// inline tables.
|
||||
// inline tables. For array tables, the comment is only present before the first
|
||||
// element of the array.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
var (
|
||||
b []byte
|
||||
@@ -208,11 +209,20 @@ func (ctx *encoderCtx) isRoot() bool {
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||
if !v.IsZero() {
|
||||
i, ok := v.Interface().(time.Time)
|
||||
if ok {
|
||||
return i.AppendFormat(b, time.RFC3339), nil
|
||||
i := v.Interface()
|
||||
|
||||
switch x := i.(type) {
|
||||
case time.Time:
|
||||
if x.Nanosecond() > 0 {
|
||||
return x.AppendFormat(b, time.RFC3339Nano), nil
|
||||
}
|
||||
return x.AppendFormat(b, time.RFC3339), nil
|
||||
case LocalTime:
|
||||
return append(b, x.String()...), nil
|
||||
case LocalDate:
|
||||
return append(b, x.String()...), nil
|
||||
case LocalDateTime:
|
||||
return append(b, x.String()...), nil
|
||||
}
|
||||
|
||||
hasTextMarshaler := v.Type().Implements(textMarshalerType)
|
||||
@@ -260,16 +270,31 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
||||
case reflect.String:
|
||||
b = enc.encodeString(b, v.String(), ctx.options)
|
||||
case reflect.Float32:
|
||||
if math.Trunc(v.Float()) == v.Float() {
|
||||
b = strconv.AppendFloat(b, v.Float(), 'f', 1, 32)
|
||||
f := v.Float()
|
||||
|
||||
if math.IsNaN(f) {
|
||||
b = append(b, "nan"...)
|
||||
} else if f > math.MaxFloat32 {
|
||||
b = append(b, "inf"...)
|
||||
} else if f < -math.MaxFloat32 {
|
||||
b = append(b, "-inf"...)
|
||||
} else if math.Trunc(f) == f {
|
||||
b = strconv.AppendFloat(b, f, 'f', 1, 32)
|
||||
} else {
|
||||
b = strconv.AppendFloat(b, v.Float(), 'f', -1, 32)
|
||||
b = strconv.AppendFloat(b, f, 'f', -1, 32)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if math.Trunc(v.Float()) == v.Float() {
|
||||
b = strconv.AppendFloat(b, v.Float(), 'f', 1, 64)
|
||||
f := v.Float()
|
||||
if math.IsNaN(f) {
|
||||
b = append(b, "nan"...)
|
||||
} else if f > math.MaxFloat64 {
|
||||
b = append(b, "inf"...)
|
||||
} else if f < -math.MaxFloat64 {
|
||||
b = append(b, "-inf"...)
|
||||
} else if math.Trunc(f) == f {
|
||||
b = strconv.AppendFloat(b, f, 'f', 1, 64)
|
||||
} else {
|
||||
b = strconv.AppendFloat(b, v.Float(), 'f', -1, 64)
|
||||
b = strconv.AppendFloat(b, f, 'f', -1, 64)
|
||||
}
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
@@ -300,10 +325,6 @@ func isNil(v reflect.Value) bool {
|
||||
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
if !ctx.hasKey {
|
||||
panic("caller of encodeKv should have set the key in the context")
|
||||
}
|
||||
|
||||
if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
|
||||
return b, nil
|
||||
}
|
||||
@@ -313,12 +334,7 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
|
||||
}
|
||||
|
||||
b = enc.indent(ctx.indent, b)
|
||||
|
||||
b, err = enc.encodeKey(b, ctx.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b = enc.encodeKey(b, ctx.key)
|
||||
b = append(b, " = "...)
|
||||
|
||||
// create a copy of the context because the value of a KV shouldn't
|
||||
@@ -365,7 +381,13 @@ func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byt
|
||||
}
|
||||
|
||||
func needsQuoting(v string) bool {
|
||||
return strings.ContainsAny(v, "'\b\f\n\r\t")
|
||||
// TODO: vectorize
|
||||
for _, b := range []byte(v) {
|
||||
if b == '\'' || b == '\r' || b == '\n' || invalidAscii(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// caller should have checked that the string does not contain new lines or ' .
|
||||
@@ -437,7 +459,7 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt
|
||||
return b
|
||||
}
|
||||
|
||||
// called should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
|
||||
// caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
|
||||
func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
|
||||
return append(b, v...)
|
||||
}
|
||||
@@ -453,20 +475,11 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
||||
|
||||
b = append(b, '[')
|
||||
|
||||
var err error
|
||||
|
||||
b, err = enc.encodeKey(b, ctx.parentKey[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = enc.encodeKey(b, ctx.parentKey[0])
|
||||
|
||||
for _, k := range ctx.parentKey[1:] {
|
||||
b = append(b, '.')
|
||||
|
||||
b, err = enc.encodeKey(b, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = enc.encodeKey(b, k)
|
||||
}
|
||||
|
||||
b = append(b, "]\n"...)
|
||||
@@ -475,19 +488,19 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) {
|
||||
func (enc *Encoder) encodeKey(b []byte, k string) []byte {
|
||||
needsQuotation := false
|
||||
cannotUseLiteral := false
|
||||
|
||||
if len(k) == 0 {
|
||||
return append(b, "''"...)
|
||||
}
|
||||
|
||||
for _, c := range k {
|
||||
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
|
||||
continue
|
||||
}
|
||||
|
||||
if c == '\n' {
|
||||
return nil, fmt.Errorf("toml: new line characters in keys are not supported")
|
||||
}
|
||||
|
||||
if c == literalQuote {
|
||||
cannotUseLiteral = true
|
||||
}
|
||||
@@ -495,13 +508,17 @@ func (enc *Encoder) encodeKey(b []byte, k string) ([]byte, error) {
|
||||
needsQuotation = true
|
||||
}
|
||||
|
||||
if needsQuotation && needsQuoting(k) {
|
||||
cannotUseLiteral = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case cannotUseLiteral:
|
||||
return enc.encodeQuotedString(false, b, k), nil
|
||||
return enc.encodeQuotedString(false, b, k)
|
||||
case needsQuotation:
|
||||
return enc.encodeLiteralString(b, k), nil
|
||||
return enc.encodeLiteralString(b, k)
|
||||
default:
|
||||
return enc.encodeUnquotedKey(b, k), nil
|
||||
return enc.encodeUnquotedKey(b, k)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,7 +617,9 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
||||
|
||||
if k == "" {
|
||||
if fieldType.Anonymous {
|
||||
if fieldType.Type.Kind() == reflect.Struct {
|
||||
walkStruct(ctx, t, f)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
k = fieldType.Name
|
||||
@@ -634,10 +653,19 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
|
||||
}
|
||||
|
||||
func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
|
||||
if comment != "" {
|
||||
for len(comment) > 0 {
|
||||
var line string
|
||||
idx := strings.IndexByte(comment, '\n')
|
||||
if idx >= 0 {
|
||||
line = comment[:idx]
|
||||
comment = comment[idx+1:]
|
||||
} else {
|
||||
line = comment
|
||||
comment = ""
|
||||
}
|
||||
b = enc.indent(indent, b)
|
||||
b = append(b, "# "...)
|
||||
b = append(b, comment...)
|
||||
b = append(b, line...)
|
||||
b = append(b, '\n')
|
||||
}
|
||||
return b
|
||||
@@ -801,6 +829,9 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
|
||||
}
|
||||
|
||||
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
|
||||
if ctx.insideKv {
|
||||
return false
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
if t.Kind() == reflect.Interface {
|
||||
@@ -846,7 +877,6 @@ func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]by
|
||||
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||
ctx.shiftKey()
|
||||
|
||||
var err error
|
||||
scratch := make([]byte, 0, 64)
|
||||
scratch = append(scratch, "[["...)
|
||||
|
||||
@@ -855,18 +885,18 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
||||
scratch = append(scratch, '.')
|
||||
}
|
||||
|
||||
scratch, err = enc.encodeKey(scratch, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scratch = enc.encodeKey(scratch, k)
|
||||
}
|
||||
|
||||
scratch = append(scratch, "]]\n"...)
|
||||
ctx.skipTableHeader = true
|
||||
|
||||
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
b = append(b, scratch...)
|
||||
|
||||
var err error
|
||||
b, err = enc.encode(b, ctx, v.Index(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+97
-6
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -45,7 +46,7 @@ func TestMarshal(t *testing.T) {
|
||||
v: map[string]string{
|
||||
"hel\nlo": "world",
|
||||
},
|
||||
err: true,
|
||||
expected: `"hel\nlo" = 'world'`,
|
||||
},
|
||||
{
|
||||
desc: `map with " in key`,
|
||||
@@ -380,7 +381,8 @@ hello = 'world'`,
|
||||
v: map[string][]map[string]string{
|
||||
"a\n": {{"hello": "world"}},
|
||||
},
|
||||
err: true,
|
||||
expected: `[["a\n"]]
|
||||
hello = 'world'`,
|
||||
},
|
||||
{
|
||||
desc: "newline in map in slice",
|
||||
@@ -440,7 +442,7 @@ hello = 'world'`,
|
||||
v: map[string]interface{}{
|
||||
"hello\nworld": 42,
|
||||
},
|
||||
err: true,
|
||||
expected: `"hello\nworld" = 42`,
|
||||
},
|
||||
{
|
||||
desc: "new line in parent of nested table key",
|
||||
@@ -449,7 +451,8 @@ hello = 'world'`,
|
||||
"inner": 42,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
expected: `["hello\nworld"]
|
||||
inner = 42`,
|
||||
},
|
||||
{
|
||||
desc: "new line in nested table key",
|
||||
@@ -460,7 +463,9 @@ hello = 'world'`,
|
||||
},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
expected: `[parent]
|
||||
[parent."in\ner"]
|
||||
foo = 42`,
|
||||
},
|
||||
{
|
||||
desc: "invalid map key",
|
||||
@@ -483,7 +488,16 @@ hello = 'world'`,
|
||||
}{
|
||||
T: time.Time{},
|
||||
},
|
||||
expected: `T = '0001-01-01T00:00:00Z'`,
|
||||
expected: `T = 0001-01-01T00:00:00Z`,
|
||||
},
|
||||
{
|
||||
desc: "time nano",
|
||||
v: struct {
|
||||
T time.Time
|
||||
}{
|
||||
T: time.Date(1979, time.May, 27, 0, 32, 0, 999999000, time.UTC),
|
||||
},
|
||||
expected: `T = 1979-05-27T00:32:00.999999Z`,
|
||||
},
|
||||
{
|
||||
desc: "bool",
|
||||
@@ -656,6 +670,33 @@ func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
|
||||
assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset))
|
||||
}
|
||||
|
||||
func TestMarshalFloats(t *testing.T) {
|
||||
v := map[string]float32{
|
||||
"nan": float32(math.NaN()),
|
||||
"+inf": float32(math.Inf(1)),
|
||||
"-inf": float32(math.Inf(-1)),
|
||||
}
|
||||
|
||||
expected := `'+inf' = inf
|
||||
-inf = -inf
|
||||
nan = nan
|
||||
`
|
||||
|
||||
actual, err := toml.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(actual))
|
||||
|
||||
v64 := map[string]float64{
|
||||
"nan": math.NaN(),
|
||||
"+inf": math.Inf(1),
|
||||
"-inf": math.Inf(-1),
|
||||
}
|
||||
|
||||
actual, err = toml.Marshal(v64)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(actual))
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func TestMarshalIndentTables(t *testing.T) {
|
||||
examples := []struct {
|
||||
@@ -947,6 +988,38 @@ func TestIssue678(t *testing.T) {
|
||||
require.Equal(t, cfg, cfg2)
|
||||
}
|
||||
|
||||
func TestIssue752(t *testing.T) {
|
||||
type Fooer interface {
|
||||
Foo() string
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Fooer
|
||||
}
|
||||
|
||||
c := Container{}
|
||||
|
||||
out, err := toml.Marshal(c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", string(out))
|
||||
}
|
||||
|
||||
func TestIssue768(t *testing.T) {
|
||||
type cfg struct {
|
||||
Name string `comment:"This is a multiline comment.\nThis is line 2."`
|
||||
}
|
||||
|
||||
out, err := toml.Marshal(&cfg{})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := `# This is a multiline comment.
|
||||
# This is line 2.
|
||||
Name = ''
|
||||
`
|
||||
|
||||
require.Equal(t, expected, string(out))
|
||||
}
|
||||
|
||||
func TestMarshalNestedAnonymousStructs(t *testing.T) {
|
||||
type Embedded struct {
|
||||
Value string `toml:"value" json:"value"`
|
||||
@@ -1011,6 +1084,24 @@ value = ''
|
||||
require.Equal(t, expected, string(result))
|
||||
}
|
||||
|
||||
func TestLocalTime(t *testing.T) {
|
||||
v := map[string]toml.LocalTime{
|
||||
"a": toml.LocalTime{
|
||||
Hour: 1,
|
||||
Minute: 2,
|
||||
Second: 3,
|
||||
Nanosecond: 4,
|
||||
},
|
||||
}
|
||||
|
||||
expected := `a = 01:02:03.000000004
|
||||
`
|
||||
|
||||
out, err := toml.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(out))
|
||||
}
|
||||
|
||||
func ExampleMarshal() {
|
||||
type MyConfig struct {
|
||||
Version int
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=0000-01-01 00:00:00")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\"\\n\"=\"\"")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("''=0")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=0000-01-01")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=\"\"\"\\U00000000\"\"\"")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=[[{}]]")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\"\\b\"=\"\"")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=inf")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=0000-01-01 00:00:00+00:00")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=[{}]")
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0=nan")
|
||||
+29
-11
@@ -322,10 +322,12 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val
|
||||
return v, nil
|
||||
case reflect.Slice:
|
||||
elemType := v.Type().Elem()
|
||||
var elem reflect.Value
|
||||
if elemType.Kind() == reflect.Interface {
|
||||
elemType = mapStringInterfaceType
|
||||
elem = makeMapStringInterface()
|
||||
} else {
|
||||
elem = reflect.New(elemType).Elem()
|
||||
}
|
||||
elem := reflect.New(elemType).Elem()
|
||||
elem2, err := d.handleArrayTable(key, elem)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
@@ -864,12 +866,27 @@ func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error {
|
||||
const (
|
||||
const (
|
||||
maxInt = int64(^uint(0) >> 1)
|
||||
minInt = -maxInt - 1
|
||||
)
|
||||
)
|
||||
|
||||
// Maximum value of uint for decoding. Currently the decoder parses the integer
|
||||
// into an int64. As a result, on architectures where uint is 64 bits, the
|
||||
// effective maximum uint we can decode is the maximum of int64. On
|
||||
// architectures where uint is 32 bits, the maximum value we can decode is
|
||||
// lower: the maximum of uint32. I didn't find a way to figure out this value at
|
||||
// compile time, so it is computed during initialization.
|
||||
var maxUint int64 = math.MaxInt64
|
||||
|
||||
func init() {
|
||||
m := uint64(^uint(0))
|
||||
if m < uint64(maxUint) {
|
||||
maxUint = int64(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error {
|
||||
i, err := parseInteger(value.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -930,7 +947,7 @@ func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error {
|
||||
|
||||
r = reflect.ValueOf(uint8(i))
|
||||
case reflect.Uint:
|
||||
if i < 0 {
|
||||
if i < 0 || i > maxUint {
|
||||
return fmt.Errorf("toml: negative number %d does not fit in an uint", i)
|
||||
}
|
||||
|
||||
@@ -1165,11 +1182,6 @@ func forEachField(t reflect.Type, path []int, do func(name string, path []int))
|
||||
fieldPath := append(path, i)
|
||||
fieldPath = fieldPath[:len(fieldPath):len(fieldPath)]
|
||||
|
||||
if f.Anonymous {
|
||||
forEachField(f.Type, fieldPath, do)
|
||||
continue
|
||||
}
|
||||
|
||||
name := f.Tag.Get("toml")
|
||||
if name == "-" {
|
||||
continue
|
||||
@@ -1178,6 +1190,12 @@ func forEachField(t reflect.Type, path []int, do func(name string, path []int))
|
||||
if i := strings.IndexByte(name, ','); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
|
||||
if f.Anonymous && name == "" {
|
||||
forEachField(f.Type, fieldPath, do)
|
||||
continue
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = f.Name
|
||||
}
|
||||
|
||||
+45
-2
@@ -971,7 +971,7 @@ B = "data"`,
|
||||
"Name": "Hammer",
|
||||
"Sku": int64(738594937),
|
||||
},
|
||||
map[string]interface{}(nil),
|
||||
map[string]interface{}{},
|
||||
map[string]interface{}{
|
||||
"Name": "Nail",
|
||||
"Sku": int64(284758393),
|
||||
@@ -1505,7 +1505,7 @@ B = "data"`,
|
||||
target: &map[string]interface{}{},
|
||||
expected: &map[string]interface{}{
|
||||
"products": []interface{}{
|
||||
map[string]interface{}(nil),
|
||||
map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -2380,6 +2380,49 @@ func TestIssue714(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIssue772(t *testing.T) {
|
||||
type FileHandling struct {
|
||||
FilePattern string `toml:"pattern"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
FileHandling `toml:"filehandling"`
|
||||
}
|
||||
|
||||
var defaultConfigFile = []byte(`
|
||||
[filehandling]
|
||||
pattern = "reach-masterdev-"`)
|
||||
|
||||
config := Config{}
|
||||
err := toml.Unmarshal(defaultConfigFile, &config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "reach-masterdev-", config.FileHandling.FilePattern)
|
||||
}
|
||||
|
||||
func TestIssue774(t *testing.T) {
|
||||
type ScpData struct {
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
type GenConfig struct {
|
||||
SCP []ScpData `toml:"scp" comment:"Array of Secure Copy Configurations"`
|
||||
}
|
||||
|
||||
c := &GenConfig{}
|
||||
c.SCP = []ScpData{{Host: "main.domain.com"}}
|
||||
|
||||
b, err := toml.Marshal(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := `# Array of Secure Copy Configurations
|
||||
[[scp]]
|
||||
Host = 'main.domain.com'
|
||||
|
||||
`
|
||||
|
||||
require.Equal(t, expected, string(b))
|
||||
}
|
||||
|
||||
func TestUnmarshalDecodeErrors(t *testing.T) {
|
||||
examples := []struct {
|
||||
desc string
|
||||
|
||||
Reference in New Issue
Block a user