Compare commits

..

11 Commits

Author SHA1 Message Date
Thomas Pelletier 7baa23f493 Decode: error on array table mismatched type (#804)
Prevent the decoder from continuing if it encounters a type it cannot decode an
array table into.

Fixes #799
2022-08-15 16:38:07 -04:00
Thomas Pelletier 2d8433b69e Encode: don't inherit omitempty (#803)
Fixes #786.
2022-08-15 11:29:46 -04:00
Thomas Pelletier 67bc5422f3 Go 1.19 (#802) 2022-08-15 10:56:33 -04:00
Thomas Pelletier fb6d1d6c2b Marshal: define and fix newlines behavior when using omitempty (#798)
Ref #786
2022-07-24 15:40:20 -04:00
dependabot[bot] d017a6dc89 build(deps): bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (#795) 2022-06-29 09:51:28 -04:00
dependabot[bot] d6d3196163 build(deps): bump github.com/stretchr/testify from 1.7.4 to 1.7.5 (#794) 2022-06-24 12:49:56 -04:00
dependabot[bot] 41718a6db3 build(deps): bump github.com/stretchr/testify from 1.7.2 to 1.7.4 (#793)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.2 to 1.7.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.2...v1.7.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-21 08:32:13 -04:00
Thomas Pelletier 216628222f Build arm + arm64 binaries for linux and windows (#790)
* Build arm + arm64 binaries for linux and windows

* Type MaxInt64 to avoid overflow on 32 bits arch

On a 32 bits arch, math.MaxIn64 is interpreted as an int, and therefore
overflows. This causes compilation to build on those platforms.
2022-06-08 18:05:42 -04:00
dependabot[bot] 322e0b15d2 build(deps): bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#788)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-08 17:44:59 -04:00
Thomas Pelletier 85bfc0ed51 Encode: add bound check for uint64 > math.Int64 (#785)
As brought up on #782, there is an asymetry between numbers go-toml can encode
and decode. Specifically, unsigned numbers larger than `math.Int64` could be
encoded but not decoded.

We considered two options: allow decoding of those numbers, or prevent those
numbers to be decoded.

Ultimately we decided to narrow the range of numbers to be encoded. The TOML
specification only requires parsers to support at least the 64 bits integer
range. Allowing larger numbers would create non-standard TOML documents, which
may not be readable (at best) by other implementations. It is a safer default to
generate documents valid by default. People who wish to deal with larger numbers
can provide a custom type implementing `encoding.TextMarshaler`.

Refs #781
2022-05-31 22:10:41 -04:00
dependabot[bot] 295a720dfb build(deps): bump goreleaser/goreleaser-action from 2 to 3 (#783)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-23 15:05:00 +02:00
21 changed files with 441 additions and 177 deletions
+1 -1
View File
@@ -15,6 +15,6 @@ jobs:
- name: Setup go - name: Setup go
uses: actions/setup-go@master uses: actions/setup-go@master
with: with:
go-version: 1.18 go-version: 1.19
- name: Run tests with coverage - name: Run tests with coverage
run: ./ci.sh coverage -d "${GITHUB_BASE_REF-HEAD}" run: ./ci.sh coverage -d "${GITHUB_BASE_REF-HEAD}"
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.18 go-version: 1.19
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
@@ -30,7 +30,7 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v3
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest'] os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest']
go: [ '1.17', '1.18' ] go: [ '1.18', '1.19' ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.go }}/${{ matrix.os }} name: ${{ matrix.go }}/${{ matrix.os }}
steps: steps:
+12
View File
@@ -16,7 +16,11 @@ builds:
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64
- linux_arm
- windows_amd64 - windows_amd64
- windows_arm64
- windows_arm
- darwin_amd64 - darwin_amd64
- darwin_arm64 - darwin_arm64
- id: tomljson - id: tomljson
@@ -31,7 +35,11 @@ builds:
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64
- linux_arm
- windows_amd64 - windows_amd64
- windows_arm64
- windows_arm
- darwin_amd64 - darwin_amd64
- darwin_arm64 - darwin_arm64
- id: jsontoml - id: jsontoml
@@ -46,7 +54,11 @@ builds:
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64
- linux_arm
- windows_amd64 - windows_amd64
- windows_arm64
- windows_arm
- darwin_amd64 - darwin_amd64
- darwin_arm64 - darwin_arm64
universal_binaries: universal_binaries:
+2 -2
View File
@@ -1,6 +1,6 @@
// Package jsontoml is a program that converts JSON to TOML. // Package jsontoml is a program that converts JSON to TOML.
// //
// Usage // # Usage
// //
// Reading from stdin: // Reading from stdin:
// //
@@ -10,7 +10,7 @@
// //
// jsontoml file.json > file.toml // jsontoml file.json > file.toml
// //
// Installation // # Installation
// //
// Using Go: // Using Go:
// //
-1
View File
@@ -26,7 +26,6 @@ func TestConvert(t *testing.T) {
}`, }`,
expected: `[mytoml] expected: `[mytoml]
a = 42.0 a = 42.0
`, `,
}, },
{ {
+2 -2
View File
@@ -1,6 +1,6 @@
// Package tomljson is a program that converts TOML to JSON. // Package tomljson is a program that converts TOML to JSON.
// //
// Usage // # Usage
// //
// Reading from stdin: // Reading from stdin:
// //
@@ -10,7 +10,7 @@
// //
// tomljson file.toml > file.json // tomljson file.toml > file.json
// //
// Installation // # Installation
// //
// Using Go: // Using Go:
// //
+2 -2
View File
@@ -1,6 +1,6 @@
// Package tomll is a linter program for TOML. // Package tomll is a linter program for TOML.
// //
// Usage // # Usage
// //
// Reading from stdin, writing to stdout: // Reading from stdin, writing to stdout:
// //
@@ -10,7 +10,7 @@
// //
// tomll a.toml b.toml c.toml // tomll a.toml b.toml c.toml
// //
// Installation // # Installation
// //
// Using Go: // Using Go:
// //
-1
View File
@@ -23,7 +23,6 @@ mytoml.a = 42.0
`, `,
expected: `[mytoml] expected: `[mytoml]
a = 42.0 a = 42.0
`, `,
}, },
{ {
+1
View File
@@ -103,6 +103,7 @@ func (e *DecodeError) Key() Key {
// //
// The function copies all bytes used in DecodeError, so that document and // The function copies all bytes used in DecodeError, so that document and
// 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 {
offset := danger.SubsliceOffset(document, de.highlight) offset := danger.SubsliceOffset(document, de.highlight)
+1 -2
View File
@@ -1,5 +1,4 @@
//go:build go1.18 //go:build go1.18 || go1.19
// +build go1.18
package toml_test package toml_test
+1 -1
View File
@@ -2,4 +2,4 @@ module github.com/pelletier/go-toml/v2
go 1.16 go 1.16
require github.com/stretchr/testify v1.7.1 require github.com/stretchr/testify v1.8.0
+7 -3
View File
@@ -1,11 +1,15 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -67,6 +67,7 @@ func TestDocMarshal(t *testing.T) {
} }
marshalTestToml := `title = 'TOML Marshal Testing' marshalTestToml := `title = 'TOML Marshal Testing'
[basic_lists] [basic_lists]
floats = [12.3, 45.6, 78.9] floats = [12.3, 45.6, 78.9]
bools = [true, false, true] bools = [true, false, true]
@@ -89,7 +90,6 @@ name = 'Second'
[subdoc.first] [subdoc.first]
name = 'First' name = 'First'
[basic] [basic]
uint = 5001 uint = 5001
bool = true bool = true
@@ -101,9 +101,9 @@ date = 1979-05-27T07:32:00Z
[[subdoclist]] [[subdoclist]]
name = 'List.First' name = 'List.First'
[[subdoclist]] [[subdoclist]]
name = 'List.Second' name = 'List.Second'
` `
result, err := toml.Marshal(docData) result, err := toml.Marshal(docData)
@@ -117,14 +117,15 @@ func TestBasicMarshalQuotedKey(t *testing.T) {
expected := `'Z.string-àéù' = 'Hello' expected := `'Z.string-àéù' = 'Hello'
'Yfloat-𝟘' = 3.5 'Yfloat-𝟘' = 3.5
['Xsubdoc-àéù'] ['Xsubdoc-àéù']
String2 = 'One' String2 = 'One'
[['W.sublist-𝟘']] [['W.sublist-𝟘']]
String2 = 'Two' String2 = 'Two'
[['W.sublist-𝟘']] [['W.sublist-𝟘']]
String2 = 'Three' String2 = 'Three'
` `
require.Equal(t, string(expected), string(result)) require.Equal(t, string(expected), string(result))
@@ -159,8 +160,8 @@ bool = false
int = 0 int = 0
string = '' string = ''
stringlist = [] stringlist = []
[map]
[map]
` `
require.Equal(t, string(expected), string(result)) require.Equal(t, string(expected), string(result))
@@ -151,6 +151,7 @@ type quotedKeyMarshalTestStruct struct {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{
String: "Hello", String: "Hello",
@@ -160,6 +161,7 @@ var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5
"Z.string-àéù" = "Hello" "Z.string-àéù" = "Hello"
@@ -272,6 +274,7 @@ var docData = testDoc{
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var mapTestDoc = testMapDoc{ var mapTestDoc = testMapDoc{
Title: "TOML Marshal Testing", Title: "TOML Marshal Testing",
@@ -559,10 +562,12 @@ func (c customMarshaler) MarshalTOML() ([]byte, error) {
var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"}
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var customMarshalerToml = []byte(`Sally Fields`) var customMarshalerToml = []byte(`Sally Fields`)
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var nestedCustomMarshalerData = customMarshalerParent{ var nestedCustomMarshalerData = customMarshalerParent{
Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"},
@@ -570,6 +575,7 @@ var nestedCustomMarshalerData = customMarshalerParent{
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"]
me = "Maiku Suteda" me = "Maiku Suteda"
@@ -611,6 +617,7 @@ func TestUnmarshalTextMarshaler(t *testing.T) {
} }
// TODO: Remove nolint once type and methods are used by a test // TODO: Remove nolint once type and methods are used by a test
//
//nolint:unused //nolint:unused
type precedentMarshaler struct { type precedentMarshaler struct {
FirstName string FirstName string
@@ -629,6 +636,7 @@ func (m precedentMarshaler) MarshalTOML() ([]byte, error) {
} }
// TODO: Remove nolint once type and method are used by a test // TODO: Remove nolint once type and method are used by a test
//
//nolint:unused //nolint:unused
type customPointerMarshaler struct { type customPointerMarshaler struct {
FirstName string FirstName string
@@ -641,6 +649,7 @@ func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) {
} }
// TODO: Remove nolint once type and method are used by a test // TODO: Remove nolint once type and method are used by a test
//
//nolint:unused //nolint:unused
type textPointerMarshaler struct { type textPointerMarshaler struct {
FirstName string FirstName string
@@ -653,6 +662,7 @@ func (m *textPointerMarshaler) MarshalText() ([]byte, error) {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var commentTestToml = []byte(` var commentTestToml = []byte(`
# it's a comment on type # it's a comment on type
@@ -690,6 +700,7 @@ type mapsTestStruct struct {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var mapsTestData = mapsTestStruct{ var mapsTestData = mapsTestStruct{
Simple: map[string]string{ Simple: map[string]string{
@@ -713,6 +724,7 @@ var mapsTestData = mapsTestStruct{
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var mapsTestToml = []byte(` var mapsTestToml = []byte(`
[Other] [Other]
@@ -735,6 +747,7 @@ var mapsTestToml = []byte(`
`) `)
// TODO: Remove nolint once type is used by a test // TODO: Remove nolint once type is used by a test
//
//nolint:deadcode,unused //nolint:deadcode,unused
type structArrayNoTag struct { type structArrayNoTag struct {
A struct { A struct {
@@ -744,6 +757,7 @@ type structArrayNoTag struct {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var customTagTestToml = []byte(` var customTagTestToml = []byte(`
[postgres] [postgres]
@@ -758,6 +772,7 @@ var customTagTestToml = []byte(`
`) `)
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var customCommentTagTestToml = []byte(` var customCommentTagTestToml = []byte(`
# db connection # db connection
@@ -771,6 +786,7 @@ var customCommentTagTestToml = []byte(`
`) `)
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var customCommentedTagTestToml = []byte(` var customCommentedTagTestToml = []byte(`
[postgres] [postgres]
@@ -825,6 +841,7 @@ func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var customMultilineTagTestToml = []byte(`int_slice = [ var customMultilineTagTestToml = []byte(`int_slice = [
1, 1,
@@ -834,6 +851,7 @@ var customMultilineTagTestToml = []byte(`int_slice = [
`) `)
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var testDocBasicToml = []byte(` var testDocBasicToml = []byte(`
[document] [document]
@@ -846,12 +864,14 @@ var testDocBasicToml = []byte(`
`) `)
// TODO: Remove nolint once type is used by a test // TODO: Remove nolint once type is used by a test
//
//nolint:deadcode //nolint:deadcode
type testDocCustomTag struct { type testDocCustomTag struct {
Doc testDocBasicsCustomTag `file:"document"` Doc testDocBasicsCustomTag `file:"document"`
} }
// TODO: Remove nolint once type is used by a test // TODO: Remove nolint once type is used by a test
//
//nolint:deadcode //nolint:deadcode
type testDocBasicsCustomTag struct { type testDocBasicsCustomTag struct {
Bool bool `file:"bool_val"` Bool bool `file:"bool_val"`
@@ -864,6 +884,7 @@ type testDocBasicsCustomTag struct {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,varcheck //nolint:deadcode,varcheck
var testDocCustomTagData = testDocCustomTag{ var testDocCustomTagData = testDocCustomTag{
Doc: testDocBasicsCustomTag{ Doc: testDocBasicsCustomTag{
@@ -966,6 +987,7 @@ func TestUnmarshalInvalidPointerKind(t *testing.T) {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused //nolint:deadcode,unused
type testDuration struct { type testDuration struct {
Nanosec time.Duration `toml:"nanosec"` Nanosec time.Duration `toml:"nanosec"`
@@ -980,6 +1002,7 @@ type testDuration struct {
} }
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var testDurationToml = []byte(` var testDurationToml = []byte(`
nanosec = "1ns" nanosec = "1ns"
@@ -994,6 +1017,7 @@ a_string = "15s"
`) `)
// TODO: Remove nolint once var is used by a test // TODO: Remove nolint once var is used by a test
//
//nolint:deadcode,unused,varcheck //nolint:deadcode,unused,varcheck
var testDurationToml2 = []byte(`a_string = "15s" var testDurationToml2 = []byte(`a_string = "15s"
hour = "1h0m0s" hour = "1h0m0s"
@@ -1007,6 +1031,7 @@ sec = "1s"
`) `)
// TODO: Remove nolint once type is used by a test // TODO: Remove nolint once type is used by a test
//
//nolint:deadcode,unused //nolint:deadcode,unused
type testBadDuration struct { type testBadDuration struct {
Val time.Duration `toml:"val"` Val time.Duration `toml:"val"`
+90 -12
View File
@@ -54,7 +54,7 @@ func NewEncoder(w io.Writer) *Encoder {
// This behavior can be controlled on an individual struct field basis with the // This behavior can be controlled on an individual struct field basis with the
// inline tag: // inline tag:
// //
// MyField `inline:"true"` // MyField `toml:",inline"`
func (enc *Encoder) SetTablesInline(inline bool) *Encoder { func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
enc.tablesInline = inline enc.tablesInline = inline
return enc return enc
@@ -89,7 +89,7 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
// //
// If v cannot be represented to TOML it returns an error. // If v cannot be represented to TOML it returns an error.
// //
// Encoding rules // # Encoding rules
// //
// A top level slice containing only maps or structs is encoded as [[table // A top level slice containing only maps or structs is encoded as [[table
// array]]. // array]].
@@ -107,10 +107,30 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
// a newline character or a single quote. In that case they are emitted as // a newline character or a single quote. In that case they are emitted as
// quoted strings. // quoted strings.
// //
// Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so
// results in an error. This rule exists because the TOML specification only
// requires parsers to support at least the 64 bits integer range. Allowing
// larger numbers would create non-standard TOML documents, which may not be
// readable (at best) by other implementations. To encode such numbers, a
// solution is a custom type that implements encoding.TextMarshaler.
//
// When encoding structs, fields are encoded in order of definition, with their // When encoding structs, fields are encoded in order of definition, with their
// exact name. // exact name.
// //
// Struct tags // Tables and array tables are separated by empty lines. However, consecutive
// subtables definitions are not. For example:
//
// [top1]
//
// [top2]
// [top2.child1]
//
// [[array]]
//
// [[array]]
// [array.child2]
//
// # Struct tags
// //
// The encoding of each public struct field can be customized by the format // The encoding of each public struct field can be customized by the format
// string in the "toml" key of the struct field's tag. This follows // string in the "toml" key of the struct field's tag. This follows
@@ -303,7 +323,11 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
b = append(b, "false"...) b = append(b, "false"...)
} }
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
b = strconv.AppendUint(b, v.Uint(), 10) x := v.Uint()
if x > uint64(math.MaxInt64) {
return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))
}
b = strconv.AppendUint(b, x, 10)
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
b = strconv.AppendInt(b, v.Int(), 10) b = strconv.AppendInt(b, v.Int(), 10)
default: default:
@@ -322,13 +346,13 @@ func isNil(v reflect.Value) bool {
} }
} }
func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
return options.omitempty && isEmptyValue(v)
}
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) { func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
var err error var err error
if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
return b, nil
}
if !ctx.inline { if !ctx.inline {
b = enc.encodeComment(ctx.indent, options.comment, b) b = enc.encodeComment(ctx.indent, options.comment, b)
} }
@@ -354,6 +378,8 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
func isEmptyValue(v reflect.Value) bool { func isEmptyValue(v reflect.Value) bool {
switch v.Kind() { switch v.Kind() {
case reflect.Struct:
return isEmptyStruct(v)
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0 return v.Len() == 0
case reflect.Bool: case reflect.Bool:
@@ -370,6 +396,34 @@ func isEmptyValue(v reflect.Value) bool {
return false return false
} }
func isEmptyStruct(v reflect.Value) bool {
// TODO: merge with walkStruct and cache.
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
fieldType := typ.Field(i)
// only consider exported fields
if fieldType.PkgPath != "" {
continue
}
tag := fieldType.Tag.Get("toml")
// special field name to skip field
if tag == "-" {
continue
}
f := v.Field(i)
if !isEmptyValue(f) {
return false
}
}
return true
}
const literalQuote = '\'' const literalQuote = '\''
func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte { func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
@@ -399,7 +453,6 @@ func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
return b return b
} }
//nolint:cyclop
func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte { func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
stringQuote := `"` stringQuote := `"`
@@ -746,7 +799,13 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
} }
ctx.skipTableHeader = false ctx.skipTableHeader = false
hasNonEmptyKV := false
for _, kv := range t.kvs { for _, kv := range t.kvs {
if shouldOmitEmpty(kv.Options, kv.Value) {
continue
}
hasNonEmptyKV = true
ctx.setKey(kv.Key) ctx.setKey(kv.Key)
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
@@ -757,7 +816,20 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
b = append(b, '\n') b = append(b, '\n')
} }
first := true
for _, table := range t.tables { for _, table := range t.tables {
if shouldOmitEmpty(table.Options, table.Value) {
continue
}
if first {
first = false
if hasNonEmptyKV {
b = append(b, '\n')
}
} else {
b = append(b, "\n"...)
}
ctx.setKey(table.Key) ctx.setKey(table.Key)
ctx.options = table.Options ctx.options = table.Options
@@ -766,8 +838,6 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
b = append(b, '\n')
} }
return b, nil return b, nil
@@ -780,6 +850,10 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
first := true first := true
for _, kv := range t.kvs { for _, kv := range t.kvs {
if shouldOmitEmpty(kv.Options, kv.Value) {
continue
}
if first { if first {
first = false first = false
} else { } else {
@@ -795,7 +869,7 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
} }
if len(t.tables) > 0 { if len(t.tables) > 0 {
panic("inline table cannot contain nested tables, online key-values") panic("inline table cannot contain nested tables, only key-values")
} }
b = append(b, "}"...) b = append(b, "}"...)
@@ -894,6 +968,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
b = enc.encodeComment(ctx.indent, ctx.options.comment, b) b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
for i := 0; i < v.Len(); i++ { for i := 0; i < v.Len(); i++ {
if i != 0 {
b = append(b, "\n"...)
}
b = append(b, scratch...) b = append(b, scratch...)
var err error var err error
+181 -93
View File
@@ -39,21 +39,21 @@ func TestMarshal(t *testing.T) {
v: map[string]string{ v: map[string]string{
"hello": "world", "hello": "world",
}, },
expected: "hello = 'world'", expected: "hello = 'world'\n",
}, },
{ {
desc: "map with new line in key", desc: "map with new line in key",
v: map[string]string{ v: map[string]string{
"hel\nlo": "world", "hel\nlo": "world",
}, },
expected: `"hel\nlo" = 'world'`, expected: "\"hel\\nlo\" = 'world'\n",
}, },
{ {
desc: `map with " in key`, desc: `map with " in key`,
v: map[string]string{ v: map[string]string{
`hel"lo`: "world", `hel"lo`: "world",
}, },
expected: `'hel"lo' = 'world'`, expected: "'hel\"lo' = 'world'\n",
}, },
{ {
desc: "map in map and string", desc: "map in map and string",
@@ -62,9 +62,9 @@ func TestMarshal(t *testing.T) {
"hello": "world", "hello": "world",
}, },
}, },
expected: ` expected: `[table]
[table] hello = 'world'
hello = 'world'`, `,
}, },
{ {
desc: "map in map in map and string", desc: "map in map in map and string",
@@ -75,10 +75,10 @@ hello = 'world'`,
}, },
}, },
}, },
expected: ` expected: `[this]
[this]
[this.is] [this.is]
a = 'test'`, a = 'test'
`,
}, },
{ {
desc: "map in map in map and string with values", desc: "map in map in map and string with values",
@@ -90,18 +90,20 @@ a = 'test'`,
"also": "that", "also": "that",
}, },
}, },
expected: ` expected: `[this]
[this]
also = 'that' also = 'that'
[this.is] [this.is]
a = 'test'`, a = 'test'
`,
}, },
{ {
desc: "simple string array", desc: "simple string array",
v: map[string][]string{ v: map[string][]string{
"array": {"one", "two", "three"}, "array": {"one", "two", "three"},
}, },
expected: `array = ['one', 'two', 'three']`, expected: `array = ['one', 'two', 'three']
`,
}, },
{ {
desc: "empty string array", desc: "empty string array",
@@ -118,14 +120,16 @@ a = 'test'`,
v: map[string][][]string{ v: map[string][][]string{
"array": {{"one", "two"}, {"three"}}, "array": {{"one", "two"}, {"three"}},
}, },
expected: `array = [['one', 'two'], ['three']]`, expected: `array = [['one', 'two'], ['three']]
`,
}, },
{ {
desc: "mixed strings and nested string arrays", desc: "mixed strings and nested string arrays",
v: map[string][]interface{}{ v: map[string][]interface{}{
"array": {"a string", []string{"one", "two"}, "last"}, "array": {"a string", []string{"one", "two"}, "last"},
}, },
expected: `array = ['a string', ['one', 'two'], 'last']`, expected: `array = ['a string', ['one', 'two'], 'last']
`,
}, },
{ {
desc: "array of maps", desc: "array of maps",
@@ -135,9 +139,9 @@ a = 'test'`,
{"map2.1": "v2.1"}, {"map2.1": "v2.1"},
}, },
}, },
expected: ` expected: `[[top]]
[[top]]
'map1.1' = 'v1.1' 'map1.1' = 'v1.1'
[[top]] [[top]]
'map2.1' = 'v2.1' 'map2.1' = 'v2.1'
`, `,
@@ -148,9 +152,9 @@ a = 'test'`,
"key1": "value1", "key1": "value1",
"key2": "value2", "key2": "value2",
}, },
expected: ` expected: `key1 = 'value1'
key1 = 'value1' key2 = 'value2'
key2 = 'value2'`, `,
}, },
{ {
desc: "simple struct", desc: "simple struct",
@@ -159,7 +163,8 @@ key2 = 'value2'`,
}{ }{
A: "foo", A: "foo",
}, },
expected: `A = 'foo'`, expected: `A = 'foo'
`,
}, },
{ {
desc: "one level of structs within structs", desc: "one level of structs within structs",
@@ -174,8 +179,7 @@ key2 = 'value2'`,
K2: "v2", K2: "v2",
}, },
}, },
expected: ` expected: `[A]
[A]
K1 = 'v1' K1 = 'v1'
K2 = 'v2' K2 = 'v2'
`, `,
@@ -190,10 +194,10 @@ K2 = 'v2'
}, },
}, },
}, },
expected: ` expected: `[root]
[root]
[[root.nested]] [[root.nested]]
name = 'Bob' name = 'Bob'
[[root.nested]] [[root.nested]]
name = 'Alice' name = 'Alice'
`, `,
@@ -203,49 +207,53 @@ name = 'Alice'
v: map[string]interface{}{ v: map[string]interface{}{
"a": "'\b\f\r\t\"\\", "a": "'\b\f\r\t\"\\",
}, },
expected: `a = "'\b\f\r\t\"\\"`, expected: `a = "'\b\f\r\t\"\\"
`,
}, },
{ {
desc: "string utf8 low", desc: "string utf8 low",
v: map[string]interface{}{ v: map[string]interface{}{
"a": "'Ę", "a": "'Ę",
}, },
expected: `a = "'Ę"`, expected: `a = "'Ę"
`,
}, },
{ {
desc: "string utf8 low 2", desc: "string utf8 low 2",
v: map[string]interface{}{ v: map[string]interface{}{
"a": "'\u10A85", "a": "'\u10A85",
}, },
expected: "a = \"'\u10A85\"", expected: "a = \"'\u10A85\"\n",
}, },
{ {
desc: "string utf8 low 2", desc: "string utf8 low 2",
v: map[string]interface{}{ v: map[string]interface{}{
"a": "'\u10A85", "a": "'\u10A85",
}, },
expected: "a = \"'\u10A85\"", expected: "a = \"'\u10A85\"\n",
}, },
{ {
desc: "emoji", desc: "emoji",
v: map[string]interface{}{ v: map[string]interface{}{
"a": "'😀", "a": "'😀",
}, },
expected: "a = \"'😀\"", expected: "a = \"'😀\"\n",
}, },
{ {
desc: "control char", desc: "control char",
v: map[string]interface{}{ v: map[string]interface{}{
"a": "'\u001A", "a": "'\u001A",
}, },
expected: `a = "'\u001A"`, expected: `a = "'\u001A"
`,
}, },
{ {
desc: "multi-line string", desc: "multi-line string",
v: map[string]interface{}{ v: map[string]interface{}{
"a": "hello\nworld", "a": "hello\nworld",
}, },
expected: `a = "hello\nworld"`, expected: `a = "hello\nworld"
`,
}, },
{ {
desc: "multi-line forced", desc: "multi-line forced",
@@ -256,7 +264,8 @@ name = 'Alice'
}, },
expected: `A = """ expected: `A = """
hello hello
world"""`, world"""
`,
}, },
{ {
desc: "inline field", desc: "inline field",
@@ -271,8 +280,8 @@ world"""`,
"isinline": "no", "isinline": "no",
}, },
}, },
expected: ` expected: `A = {isinline = 'yes'}
A = {isinline = 'yes'}
[B] [B]
isinline = 'no' isinline = 'no'
`, `,
@@ -286,8 +295,7 @@ isinline = 'no'
A: []int{1, 2, 3, 4}, A: []int{1, 2, 3, 4},
B: []int{1, 2, 3, 4}, B: []int{1, 2, 3, 4},
}, },
expected: ` expected: `A = [
A = [
1, 1,
2, 2,
3, 3,
@@ -303,8 +311,7 @@ B = [1, 2, 3, 4]
}{ }{
A: [][]int{{1, 2}, {3, 4}}, A: [][]int{{1, 2}, {3, 4}},
}, },
expected: ` expected: `A = [
A = [
[1, 2], [1, 2],
[3, 4] [3, 4]
] ]
@@ -329,7 +336,8 @@ A = [
}{ }{
A: []*int{nil}, A: []*int{nil},
}, },
expected: `A = [0]`, expected: `A = [0]
`,
}, },
{ {
desc: "nil pointer in slice uses zero value", desc: "nil pointer in slice uses zero value",
@@ -338,7 +346,8 @@ A = [
}{ }{
A: []*int{nil}, A: []*int{nil},
}, },
expected: `A = [0]`, expected: `A = [0]
`,
}, },
{ {
desc: "pointer in slice", desc: "pointer in slice",
@@ -347,7 +356,8 @@ A = [
}{ }{
A: []*int{&someInt}, A: []*int{&someInt},
}, },
expected: `A = [42]`, expected: `A = [42]
`,
}, },
{ {
desc: "inline table in inline table", desc: "inline table in inline table",
@@ -358,23 +368,25 @@ A = [
}, },
}, },
}, },
expected: `A = {A = {A = 'hello'}}`, expected: `A = {A = {A = 'hello'}}
`,
}, },
{ {
desc: "empty slice in map", desc: "empty slice in map",
v: map[string][]string{ v: map[string][]string{
"a": {}, "a": {},
}, },
expected: `a = []`, expected: `a = []
`,
}, },
{ {
desc: "map in slice", desc: "map in slice",
v: map[string][]map[string]string{ v: map[string][]map[string]string{
"a": {{"hello": "world"}}, "a": {{"hello": "world"}},
}, },
expected: ` expected: `[[a]]
[[a]] hello = 'world'
hello = 'world'`, `,
}, },
{ {
desc: "newline in map in slice", desc: "newline in map in slice",
@@ -382,7 +394,8 @@ hello = 'world'`,
"a\n": {{"hello": "world"}}, "a\n": {{"hello": "world"}},
}, },
expected: `[["a\n"]] expected: `[["a\n"]]
hello = 'world'`, hello = 'world'
`,
}, },
{ {
desc: "newline in map in slice", desc: "newline in map in slice",
@@ -398,7 +411,8 @@ hello = 'world'`,
}{ }{
A: []struct{}{}, A: []struct{}{},
}, },
expected: `A = []`, expected: `A = []
`,
}, },
{ {
desc: "nil field is ignored", desc: "nil field is ignored",
@@ -418,7 +432,8 @@ hello = 'world'`,
Public: "shown", Public: "shown",
private: "hidden", private: "hidden",
}, },
expected: `Public = 'shown'`, expected: `Public = 'shown'
`,
}, },
{ {
desc: "fields tagged - are ignored", desc: "fields tagged - are ignored",
@@ -442,7 +457,8 @@ hello = 'world'`,
v: map[string]interface{}{ v: map[string]interface{}{
"hello\nworld": 42, "hello\nworld": 42,
}, },
expected: `"hello\nworld" = 42`, expected: `"hello\nworld" = 42
`,
}, },
{ {
desc: "new line in parent of nested table key", desc: "new line in parent of nested table key",
@@ -452,7 +468,8 @@ hello = 'world'`,
}, },
}, },
expected: `["hello\nworld"] expected: `["hello\nworld"]
inner = 42`, inner = 42
`,
}, },
{ {
desc: "new line in nested table key", desc: "new line in nested table key",
@@ -465,7 +482,8 @@ inner = 42`,
}, },
expected: `[parent] expected: `[parent]
[parent."in\ner"] [parent."in\ner"]
foo = 42`, foo = 42
`,
}, },
{ {
desc: "invalid map key", desc: "invalid map key",
@@ -488,7 +506,8 @@ foo = 42`,
}{ }{
T: time.Time{}, T: time.Time{},
}, },
expected: `T = 0001-01-01T00:00:00Z`, expected: `T = 0001-01-01T00:00:00Z
`,
}, },
{ {
desc: "time nano", desc: "time nano",
@@ -497,7 +516,8 @@ foo = 42`,
}{ }{
T: time.Date(1979, time.May, 27, 0, 32, 0, 999999000, time.UTC), T: time.Date(1979, time.May, 27, 0, 32, 0, 999999000, time.UTC),
}, },
expected: `T = 1979-05-27T00:32:00.999999Z`, expected: `T = 1979-05-27T00:32:00.999999Z
`,
}, },
{ {
desc: "bool", desc: "bool",
@@ -508,9 +528,9 @@ foo = 42`,
A: false, A: false,
B: true, B: true,
}, },
expected: ` expected: `A = false
A = false B = true
B = true`, `,
}, },
{ {
desc: "numbers", desc: "numbers",
@@ -541,8 +561,7 @@ B = true`,
K: 42, K: 42,
L: 2.2, L: 2.2,
}, },
expected: ` expected: `A = 1.1
A = 1.1
B = 42 B = 42
C = 42 C = 42
D = 42 D = 42
@@ -553,7 +572,8 @@ H = 42
I = 42 I = 42
J = 42 J = 42
K = 42 K = 42
L = 2.2`, L = 2.2
`,
}, },
{ {
desc: "comments", desc: "comments",
@@ -566,8 +586,7 @@ L = 2.2`,
Three: []int{1, 2, 3}, Three: []int{1, 2, 3},
}, },
}, },
expected: ` expected: `# Before table
# Before table
[Table] [Table]
One = 1 One = 1
# Before kv # Before kv
@@ -589,7 +608,7 @@ Three = [1, 2, 3]
} }
require.NoError(t, err) require.NoError(t, err)
equalStringsIgnoreNewlines(t, e.expected, string(b)) assert.Equal(t, e.expected, string(b))
// make sure the output is always valid TOML // make sure the output is always valid TOML
defaultMap := map[string]interface{}{} defaultMap := map[string]interface{}{}
@@ -664,12 +683,6 @@ func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t
} }
} }
func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
t.Helper()
cutset := "\n"
assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset))
}
func TestMarshalFloats(t *testing.T) { func TestMarshalFloats(t *testing.T) {
v := map[string]float32{ v := map[string]float32{
"nan": float32(math.NaN()), "nan": float32(math.NaN()),
@@ -709,7 +722,8 @@ func TestMarshalIndentTables(t *testing.T) {
v: map[string]interface{}{ v: map[string]interface{}{
"foo": "bar", "foo": "bar",
}, },
expected: `foo = 'bar'`, expected: `foo = 'bar'
`,
}, },
{ {
desc: "one level table", desc: "one level table",
@@ -719,8 +733,7 @@ func TestMarshalIndentTables(t *testing.T) {
"two": "value2", "two": "value2",
}, },
}, },
expected: ` expected: `[foo]
[foo]
one = 'value1' one = 'value1'
two = 'value2' two = 'value2'
`, `,
@@ -736,10 +749,11 @@ func TestMarshalIndentTables(t *testing.T) {
}, },
}, },
}, },
expected: ` expected: `root = 'value0'
root = 'value0'
[level1] [level1]
one = 'value1' one = 'value1'
[level1.level2] [level1.level2]
two = 'value2' two = 'value2'
`, `,
@@ -754,7 +768,7 @@ root = 'value0'
enc.SetIndentTables(true) enc.SetIndentTables(true)
err := enc.Encode(e.v) err := enc.Encode(e.v)
require.NoError(t, err) require.NoError(t, err)
equalStringsIgnoreNewlines(t, e.expected, buf.String()) assert.Equal(t, e.expected, buf.String())
}) })
} }
} }
@@ -799,7 +813,7 @@ func TestMarshalTextMarshaler(t *testing.T) {
m := map[string]interface{}{"a": &customTextMarshaler{value: 2}} m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
r, err := toml.Marshal(m) r, err := toml.Marshal(m)
require.NoError(t, err) require.NoError(t, err)
equalStringsIgnoreNewlines(t, "a = '::2'", string(r)) assert.Equal(t, "a = '::2'\n", string(r))
} }
type brokenWriter struct{} type brokenWriter struct{}
@@ -822,10 +836,10 @@ func TestEncoderSetIndentSymbol(t *testing.T) {
enc.SetIndentSymbol(">>>") enc.SetIndentSymbol(">>>")
err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}}) err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}})
require.NoError(t, err) require.NoError(t, err)
expected := ` expected := `[parent]
[parent] >>>hello = 'world'
>>>hello = 'world'` `
equalStringsIgnoreNewlines(t, expected, w.String()) assert.Equal(t, expected, w.String())
} }
func TestEncoderOmitempty(t *testing.T) { func TestEncoderOmitempty(t *testing.T) {
@@ -856,9 +870,9 @@ func TestEncoderOmitempty(t *testing.T) {
b, err := toml.Marshal(d) b, err := toml.Marshal(d)
require.NoError(t, err) require.NoError(t, err)
expected := `[Struct]` expected := ``
equalStringsIgnoreNewlines(t, expected, string(b)) assert.Equal(t, expected, string(b))
} }
func TestEncoderTagFieldName(t *testing.T) { func TestEncoderTagFieldName(t *testing.T) {
@@ -873,13 +887,12 @@ func TestEncoderTagFieldName(t *testing.T) {
b, err := toml.Marshal(d) b, err := toml.Marshal(d)
require.NoError(t, err) require.NoError(t, err)
expected := ` expected := `hello = 'world'
hello = 'world'
'#' = '' '#' = ''
Bad = '' Bad = ''
` `
equalStringsIgnoreNewlines(t, expected, string(b)) assert.Equal(t, expected, string(b))
} }
func TestIssue436(t *testing.T) { func TestIssue436(t *testing.T) {
@@ -893,12 +906,11 @@ func TestIssue436(t *testing.T) {
err = toml.NewEncoder(&buf).Encode(v) err = toml.NewEncoder(&buf).Encode(v)
require.NoError(t, err) require.NoError(t, err)
expected := ` expected := `[[a]]
[[a]]
[a.b] [a.b]
c = 'd' c = 'd'
` `
equalStringsIgnoreNewlines(t, expected, buf.String()) assert.Equal(t, expected, buf.String())
} }
func TestIssue424(t *testing.T) { func TestIssue424(t *testing.T) {
@@ -980,7 +992,7 @@ func TestIssue678(t *testing.T) {
out, err := toml.Marshal(cfg) out, err := toml.Marshal(cfg)
require.NoError(t, err) require.NoError(t, err)
equalStringsIgnoreNewlines(t, "BigInt = '123'", string(out)) assert.Equal(t, "BigInt = '123'\n", string(out))
cfg2 := &Config{} cfg2 := &Config{}
err = toml.Unmarshal(out, cfg2) err = toml.Unmarshal(out, cfg2)
@@ -1020,6 +1032,69 @@ Name = ''
require.Equal(t, expected, string(out)) require.Equal(t, expected, string(out))
} }
func TestIssue786(t *testing.T) {
type Dependencies struct {
Dependencies []string `toml:"dependencies,multiline,omitempty"`
BuildDependencies []string `toml:"buildDependencies,multiline,omitempty"`
OptionalDependencies []string `toml:"optionalDependencies,multiline,omitempty"`
}
type Test struct {
Dependencies Dependencies `toml:"dependencies,omitempty"`
}
x := Test{}
b, err := toml.Marshal(x)
require.NoError(t, err)
require.Equal(t, "", string(b))
type General struct {
From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"`
Randomize bool `toml:"randomize" json:"randomize" comment:"randomize starting time with [0,step)"`
}
type Custom struct {
Name string `toml:"name" json:"name,omitempty" comment:"names for generator, braces are expanded like in shell"`
Type string `toml:"type,omitempty" json:"type,omitempty" comment:"type of generator"`
General
}
type Config struct {
General
Custom []Custom `toml:"custom,omitempty" json:"custom,omitempty" comment:"generators with custom parameters can be specified separately"`
}
buf := new(bytes.Buffer)
config := &Config{General: General{From: "-2d", Randomize: true}}
config.Custom = []Custom{{Name: "omit", General: General{Randomize: false}}}
config.Custom = append(config.Custom, Custom{Name: "present", General: General{From: "-2d", Randomize: true}})
encoder := toml.NewEncoder(buf)
encoder.Encode(config)
expected := `# from in graphite-web format, the local TZ is used
from = '-2d'
# randomize starting time with [0,step)
randomize = true
# generators with custom parameters can be specified separately
[[custom]]
# names for generator, braces are expanded like in shell
name = 'omit'
# randomize starting time with [0,step)
randomize = false
[[custom]]
# names for generator, braces are expanded like in shell
name = 'present'
# from in graphite-web format, the local TZ is used
from = '-2d'
# randomize starting time with [0,step)
randomize = true
`
require.Equal(t, expected, buf.String())
}
func TestMarshalNestedAnonymousStructs(t *testing.T) { func TestMarshalNestedAnonymousStructs(t *testing.T) {
type Embedded struct { type Embedded struct {
Value string `toml:"value" json:"value"` Value string `toml:"value" json:"value"`
@@ -1041,6 +1116,7 @@ func TestMarshalNestedAnonymousStructs(t *testing.T) {
} }
expected := `value = '' expected := `value = ''
[top] [top]
value = '' value = ''
@@ -1049,7 +1125,6 @@ value = ''
[anonymous] [anonymous]
value = '' value = ''
` `
result, err := toml.Marshal(doc) result, err := toml.Marshal(doc)
@@ -1073,9 +1148,9 @@ func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
doc.Value = "shadows" doc.Value = "shadows"
expected := `value = 'shadows' expected := `value = 'shadows'
[top] [top]
value = '' value = ''
` `
result, err := toml.Marshal(doc) result, err := toml.Marshal(doc)
@@ -1086,7 +1161,7 @@ value = ''
func TestLocalTime(t *testing.T) { func TestLocalTime(t *testing.T) {
v := map[string]toml.LocalTime{ v := map[string]toml.LocalTime{
"a": toml.LocalTime{ "a": {
Hour: 1, Hour: 1,
Minute: 2, Minute: 2,
Second: 3, Second: 3,
@@ -1102,6 +1177,19 @@ func TestLocalTime(t *testing.T) {
require.Equal(t, expected, string(out)) require.Equal(t, expected, string(out))
} }
func TestMarshalUint64Overflow(t *testing.T) {
// The TOML spec only requires implementation to provide support for the
// int64 range. To avoid generating TOML documents that would not be
// supported by standard-compliant parsers, uint64 > max int64 cannot be
// marshaled.
x := map[string]interface{}{
"foo": uint64(math.MaxInt64) + 1,
}
_, err := toml.Marshal(x)
require.Error(t, err)
}
func ExampleMarshal() { func ExampleMarshal() {
type MyConfig struct { type MyConfig struct {
Version int Version int
+3 -3
View File
@@ -79,7 +79,7 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
// strict mode and a field is missing, a `toml.StrictMissingError` is // strict mode and a field is missing, a `toml.StrictMissingError` is
// returned. In any other case, this function returns a standard Go error. // returned. In any other case, this function returns a standard Go error.
// //
// Type mapping // # Type mapping
// //
// List of supported TOML types and their associated accepted Go types: // List of supported TOML types and their associated accepted Go types:
// //
@@ -344,9 +344,9 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val
elem := v.Index(idx) elem := v.Index(idx)
_, err := d.handleArrayTable(key, elem) _, err := d.handleArrayTable(key, elem)
return v, err return v, err
default:
return reflect.Value{}, fmt.Errorf("toml: cannot decode array table into a %s", v.Type())
} }
return d.handleArrayTable(key, v)
} }
// When parsing an array table expression, each part of the key needs to be // When parsing an array table expression, each part of the key needs to be
+78 -20
View File
@@ -1735,6 +1735,28 @@ B = "data"`,
} }
}, },
}, },
{
desc: "kv that points to a slice",
input: "a.b.c = 'foo'",
gen: func() test {
doc := map[string][]string{}
return test{
target: &doc,
err: true,
}
},
},
{
desc: "kv that points to a pointer to a slice",
input: "a.b.c = 'foo'",
gen: func() test {
doc := map[string]*[]string{}
return test{
target: &doc,
err: true,
}
},
},
} }
for _, e := range examples { for _, e := range examples {
@@ -1876,8 +1898,7 @@ key2 = "missing2"
key3 = "missing3" key3 = "missing3"
key4 = "value4" key4 = "value4"
`, `,
expected: ` expected: `2| key1 = "value1"
2| key1 = "value1"
3| key2 = "missing2" 3| key2 = "missing2"
| ~~~~ missing field | ~~~~ missing field
4| key3 = "missing3" 4| key3 = "missing3"
@@ -1887,8 +1908,7 @@ key4 = "value4"
3| key2 = "missing2" 3| key2 = "missing2"
4| key3 = "missing3" 4| key3 = "missing3"
| ~~~~ missing field | ~~~~ missing field
5| key4 = "value4" 5| key4 = "value4"`,
`,
target: &struct { target: &struct {
Key1 string Key1 string
Key4 string Key4 string
@@ -1897,10 +1917,8 @@ key4 = "value4"
{ {
desc: "multi-part key", desc: "multi-part key",
input: `a.short.key="foo"`, input: `a.short.key="foo"`,
expected: ` expected: `1| a.short.key="foo"
1| a.short.key="foo" | ~~~~~~~~~~~ missing field`,
| ~~~~~~~~~~~ missing field
`,
}, },
{ {
desc: "missing table", desc: "missing table",
@@ -1908,24 +1926,19 @@ key4 = "value4"
[foo] [foo]
bar = 42 bar = 42
`, `,
expected: ` expected: `2| [foo]
2| [foo]
| ~~~ missing table | ~~~ missing table
3| bar = 42 3| bar = 42`,
`,
}, },
{ {
desc: "missing array table", desc: "missing array table",
input: ` input: `
[[foo]] [[foo]]
bar = 42 bar = 42`,
`, expected: `2| [[foo]]
expected: `
2| [[foo]]
| ~~~ missing table | ~~~ missing table
3| bar = 42 3| bar = 42`,
`,
}, },
} }
@@ -1944,7 +1957,7 @@ bar = 42
var tsm *toml.StrictMissingError var tsm *toml.StrictMissingError
if errors.As(err, &tsm) { if errors.As(err, &tsm) {
equalStringsIgnoreNewlines(t, e.expected, tsm.String()) assert.Equal(t, e.expected, tsm.String())
} else { } else {
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)
} }
@@ -2417,12 +2430,27 @@ func TestIssue774(t *testing.T) {
expected := `# Array of Secure Copy Configurations expected := `# Array of Secure Copy Configurations
[[scp]] [[scp]]
Host = 'main.domain.com' Host = 'main.domain.com'
` `
require.Equal(t, expected, string(b)) require.Equal(t, expected, string(b))
} }
func TestIssue799(t *testing.T) {
const testTOML = `
# notice the double brackets
[[test]]
answer = 42
`
var s struct {
// should be []map[string]int
Test map[string]int `toml:"test"`
}
err := toml.Unmarshal([]byte(testTOML), &s)
require.Error(t, err)
}
func TestUnmarshalDecodeErrors(t *testing.T) { func TestUnmarshalDecodeErrors(t *testing.T) {
examples := []struct { examples := []struct {
desc string desc string
@@ -2874,6 +2902,36 @@ world'`,
} }
} }
func TestOmitEmpty(t *testing.T) {
type inner struct {
private string
Skip string `toml:"-"`
V string
}
type elem struct {
Foo string `toml:",omitempty"`
Bar string `toml:",omitempty"`
Inner inner `toml:",omitempty"`
}
type doc struct {
X []elem `toml:",inline"`
}
d := doc{X: []elem{elem{
Foo: "test",
Inner: inner{
V: "alue",
},
}}}
b, err := toml.Marshal(d)
require.NoError(t, err)
require.Equal(t, "X = [{Foo = 'test', Inner = {V = 'alue'}}]\n", string(b))
}
func TestUnmarshalTags(t *testing.T) { func TestUnmarshalTags(t *testing.T) {
type doc struct { type doc struct {
Dash string `toml:"-,"` Dash string `toml:"-,"`