Compare commits

...

34 Commits

Author SHA1 Message Date
Thomas Pelletier ee07c9203b Update to go 1.24 (#982) 2025-04-07 07:11:38 -04:00
Alex Mikitik 014204cfb7 Replace stretchr/testify with an internal test suite (#981)
As recommended, an `internal/assert` package was added with a reduced set of assertions. All tests were then refactored to use the internal assertions. When more complex assertions were used, they have been rewritten using logic and the simplified assertions.

Fancy formatting for failures was omitted. The `internal/assert/assertions.diff` function could be overwritten for better formatting. That is where diff libraries are used in other test suites.

Refs: #872

Co-authored-by: Alex Mikitik <alex.mikitik@oracle.com>
2025-04-07 06:36:37 -04:00
Oleksandr Redko 923b2ab478 Fix typos in comments and tests (#972) 2024-11-16 11:30:13 -05:00
Thomas Pelletier af236b689f Fix goreleaser deprecated attribute name (#964)
https://goreleaser.com/deprecations/#snapshotname_template
2024-08-23 13:56:48 -04:00
Thomas Pelletier b730b2be5d Bump testing to go 1.23 (#961) 2024-08-17 16:26:05 -04:00
vito a437caafe5 Fix reflect.Pointer backward compatibility (#956) 2024-08-17 16:07:56 -04:00
guoguangwu be6c57be30 Fix readme typo(#951) 2024-08-17 15:56:40 -04:00
Daniel Weiße d55304782e Allow int, uint, and floats as map keys (#958)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2024-08-17 15:44:21 -04:00
Daniel Weiße 0977c05dd5 Update goreleaser action to v6 and set goreleaser binary to v2 (#959)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2024-08-17 15:40:55 -04:00
Daniel Martí bccd6e48f4 allocate unstable.Parser as part of decoder (#953)
This way, calls to Unmarshal or Decoder.Decode allocate once
at the start rather than twice.

                                │    old     │               new                │
                                │ allocs/op  │ allocs/op   vs base              │
    Unmarshal/HugoFrontMatter-8   141.0 ± 0%   140.0 ± 0%  -0.71% (p=0.002 n=6)
2024-05-24 14:49:06 -04:00
Daniel Martí 9b890cf9c5 go.mod: bump minimum and language to 1.21 (#949)
* go.mod: bump minimum and language to 1.21

CI only tests Go 1.21 and 1.22, and older versions of Go are no longer
getting any bug or security fixes, so advertise that we only support
Go 1.21 or later via go.mod.

While here, ensure the module is tidy and resolve deprecation warnings,
and remove now-unnecessary Go version build tags.

* replace sort.Slice with slices.SortFunc

The latter is more efficient, and allocates less, since sort.Slice
needs to go through sort.Interface which causes allocations.

    goos: linux
    goarch: amd64
    pkg: github.com/pelletier/go-toml/v2/benchmark
    cpu: AMD Ryzen 7 PRO 5850U with Radeon Graphics
                              │     old     │                new                 │
                              │   sec/op    │   sec/op     vs base               │
    Marshal/HugoFrontMatter-8   7.612µ ± 1%   6.730µ ± 1%  -11.59% (p=0.002 n=6)

                              │     old      │                 new                 │
                              │     B/s      │     B/s       vs base               │
    Marshal/HugoFrontMatter-8   65.52Mi ± 1%   74.11Mi ± 1%  +13.11% (p=0.002 n=6)

                              │     old      │                new                 │
                              │     B/op     │     B/op      vs base              │
    Marshal/HugoFrontMatter-8   5.672Ki ± 0%   5.266Ki ± 0%  -7.16% (p=0.002 n=6)

                              │    old     │                new                │
                              │ allocs/op  │ allocs/op   vs base               │
    Marshal/HugoFrontMatter-8   85.00 ± 0%   73.00 ± 0%  -14.12% (p=0.002 n=6)
2024-05-24 10:58:39 -04:00
大可 a3d5a0bb53 fix: sync pool race condition (#947) 2024-04-29 06:02:54 -04:00
Daniel Weiße d00d2cca6e Fix indentation of custom type arrays (#944)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2024-04-12 10:42:12 -04:00
dependabot[bot] 86608d7fca build(deps): bump github/codeql-action from 2 to 3 (#919)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-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>
2024-03-19 13:24:46 -04:00
dependabot[bot] 4a1877957a build(deps): bump actions/setup-go from 4 to 5 (#916)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  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>
2024-03-19 13:24:37 -04:00
dependabot[bot] 3021d6d033 build(deps): bump actions/upload-artifact from 3 to 4 (#920)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  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>
2024-03-19 13:24:24 -04:00
Thomas Pelletier 32788f26f8 Update release instructions (#941) 2024-03-19 12:47:39 -04:00
rszyma 8ed6d131eb Decode: unstable/Unmarshal interface (#940)
Co-authored-by: Pavlos Karakalidis <pkarakal@pkarakal.com>
Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
2024-03-19 12:33:12 -04:00
dependabot[bot] 7dad87762a build(deps): bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#936)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 09:06:45 -04:00
Thomas Pelletier 2b69615b5d Go 1.22 support (#935) 2024-02-27 15:30:13 -05:00
Thomas Pelletier 06fb30bf2e Decode: fix reuse of slice for array tables (#934)
When decoding into a non-empty slice, it needs to be emptied so that only the
tables contained in the document are present in the resulting value.

Arrays are not impacted because their unmarshal offset is tracked separately.

Fixes #931
2024-02-27 15:28:49 -05:00
Thomas Pelletier 2e087bdf5f Run tests on macos/M1 (#929)
https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/
2024-01-30 19:15:50 -05:00
Thomas Pelletier caeb9f9631 Fix marshaler typos (#927) 2024-01-30 19:01:55 -05:00
Rdbo e7223fb40e fix: odd indentation in README (#928) 2024-01-30 19:01:43 -05:00
Jakub Wilk 05bedf36d8 Fix README typo (#925) 2024-01-25 15:21:33 -08:00
Daniel Graña f5486d590f Support encoding json.Number type (#923)
Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
2024-01-25 15:21:02 -08:00
Daniel Graña 2ca21fb7b4 Support encoding of pointers to embedded structs (#924) 2024-01-23 13:06:33 -05:00
Thomas Pelletier 34765b4a9e Fix unmarshaling of nested non-exported struct (#917)
Fixes #915
2023-12-11 14:17:49 -05:00
Moritz Poldrack 358c8d2c23 Use toml-test to generate tests (#911)
Fixes: #909
2023-10-26 12:05:02 -06:00
Martin Tournoij fd8d0bf4d9 Add cmd/gotoml-test-encoder (#907) 2023-10-23 14:40:44 -06:00
Thomas Pelletier a76e18e8c5 Fix benchmark script (#905) 2023-10-02 13:49:01 -04:00
dependabot[bot] dff0c128d0 build(deps): bump docker/login-action from 2 to 3 (#901)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-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>
2023-09-17 18:11:24 -04:00
Thomas Pelletier 3573ce3770 Update SECURITY.md
Remove placeholder.
2023-09-04 09:43:32 -04:00
dependabot[bot] ae933f2e2a build(deps): bump actions/checkout from 3 to 4 (#896)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  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>
2023-09-04 09:42:37 -04:00
44 changed files with 2964 additions and 991 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
dry-run: false dry-run: false
language: go language: go
- name: Upload Crash - name: Upload Crash
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success' if: failure() && steps.build.outcome == 'success'
with: with:
name: artifacts name: artifacts
+4 -4
View File
@@ -35,11 +35,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # 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). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -64,4 +64,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3
+3 -3
View File
@@ -9,12 +9,12 @@ jobs:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
name: report name: report
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup go - name: Setup go
uses: actions/setup-go@v4 uses: actions/setup-go@v5
with: with:
go-version: "1.21" go-version: "1.24"
- 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}"
+7 -7
View File
@@ -16,24 +16,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v4 uses: actions/setup-go@v5
with: with:
go-version: "1.21" go-version: "1.24"
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: '~> v2'
args: release ${{ inputs.args }} --rm-dist args: release ${{ inputs.args }} --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -11,22 +11,22 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest' ] os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest', 'macos-14' ]
go: [ '1.20', '1.21' ] go: [ '1.23', '1.24' ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.go }}/${{ matrix.os }} name: ${{ matrix.go }}/${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup go ${{ matrix.go }} - name: Setup go ${{ matrix.go }}
uses: actions/setup-go@v4 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Run unit tests - name: Run unit tests
run: go test -race ./... run: go test -race ./...
release-check: release-check:
if: ${{ github.ref != 'refs/heads/v2' }} if: ${{ github.ref != 'refs/heads/v2' }}
uses: pelletier/go-toml/.github/workflows/release.yml@v2 uses: ./.github/workflows/release.yml
with: with:
args: --snapshot args: --snapshot
+2 -1
View File
@@ -3,4 +3,5 @@ fuzz/
cmd/tomll/tomll cmd/tomll/tomll
cmd/tomljson/tomljson cmd/tomljson/tomljson
cmd/tomltestgen/tomltestgen cmd/tomltestgen/tomltestgen
dist dist
tests/
+2 -1
View File
@@ -1,3 +1,4 @@
version: 2
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
@@ -112,7 +113,7 @@ dockers:
checksum: checksum:
name_template: 'sha256sums.txt' name_template: 'sha256sums.txt'
snapshot: snapshot:
name_template: "{{ incpatch .Version }}-next" version_template: "{{ incpatch .Version }}-next"
release: release:
github: github:
owner: pelletier owner: pelletier
+14 -17
View File
@@ -165,25 +165,22 @@ Checklist:
### New release ### New release
1. Decide on the next version number. Use semver. 1. Decide on the next version number. Use semver. Review commits since last
2. Generate release notes using [`gh`][gh]. Example: version to assess.
2. Tag release. For example:
``` ```
$ gh api -X POST \ git checkout v2
-F tag_name='v2.0.0-beta.5' \ git pull
-F target_commitish='v2' \ git tag v2.2.0
-F previous_tag_name='v2.0.0-beta.4' \ git push --tags
--jq '.body' \
repos/pelletier/go-toml/releases/generate-notes
``` ```
3. Look for "Other changes". That would indicate a pull request not labeled 3. CI automatically builds a draft Github release. Review it and edit as
properly. Tweak labels and pull request titles until changelog looks good for necessary. Look for "Other changes". That would indicate a pull request not
users. labeled properly. Tweak labels and pull request titles until changelog looks
4. [Draft new release][new-release]. good for users.
5. Fill tag and target with the same value used to generate the changelog. 4. Check "create discussion" box, in the "Releases" category.
6. Set title to the new tag value. 5. If new version is an alpha or beta only, check pre-release box.
7. Paste the generated changelog.
8. Check "create discussion", in the "Releases" category.
9. Check pre-release if new version is an alpha or beta.
[issues-tracker]: https://github.com/pelletier/go-toml/issues [issues-tracker]: https://github.com/pelletier/go-toml/issues
[bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md [bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md
+59 -58
View File
@@ -98,9 +98,9 @@ Given the following struct, let's see how to read it and write it as TOML:
```go ```go
type MyConfig struct { type MyConfig struct {
Version int Version int
Name string Name string
Tags []string Tags []string
} }
``` ```
@@ -119,7 +119,7 @@ tags = ["go", "toml"]
var cfg MyConfig var cfg MyConfig
err := toml.Unmarshal([]byte(doc), &cfg) err := toml.Unmarshal([]byte(doc), &cfg)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println("version:", cfg.Version) fmt.Println("version:", cfg.Version)
fmt.Println("name:", cfg.Name) fmt.Println("name:", cfg.Name)
@@ -140,14 +140,14 @@ as a TOML document:
```go ```go
cfg := MyConfig{ cfg := MyConfig{
Version: 2, Version: 2,
Name: "go-toml", Name: "go-toml",
Tags: []string{"go", "toml"}, Tags: []string{"go", "toml"},
} }
b, err := toml.Marshal(cfg) b, err := toml.Marshal(cfg)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(string(b)) fmt.Println(string(b))
@@ -175,17 +175,17 @@ the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable.
Execution time speedup compared to other Go TOML libraries: Execution time speedup compared to other Go TOML libraries:
<table> <table>
<thead> <thead>
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr> <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>1.9x</td></tr> <tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>2.2x</td></tr>
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>1.8x</td></tr> <tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>2.1x</td></tr>
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>2.5x</td></tr> <tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>3.0x</td></tr>
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.9x</td></tr> <tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.7x</td></tr>
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.9x</td></tr> <tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.7x</td></tr>
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.4x</td><td>5.3x</td></tr> <tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.6x</td><td>5.1x</td></tr>
</tbody> </tbody>
</table> </table>
<details><summary>See more</summary> <details><summary>See more</summary>
<p>The table above has the results of the most common use-cases. The table below <p>The table above has the results of the most common use-cases. The table below
@@ -193,22 +193,22 @@ contains the results of all benchmarks, including unrealistic ones. It is
provided for completeness.</p> provided for completeness.</p>
<table> <table>
<thead> <thead>
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr> <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.9x</td></tr> <tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.7x</td></tr>
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>4.2x</td></tr> <tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>3.8x</td></tr>
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>4.5x</td><td>3.1x</td></tr> <tr><td>Unmarshal/SimpleDocument/map-2</td><td>3.8x</td><td>3.0x</td></tr>
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>6.2x</td><td>3.9x</td></tr> <tr><td>Unmarshal/SimpleDocument/struct-2</td><td>5.6x</td><td>4.1x</td></tr>
<tr><td>UnmarshalDataset/example-2</td><td>3.1x</td><td>3.5x</td></tr> <tr><td>UnmarshalDataset/example-2</td><td>3.0x</td><td>3.2x</td></tr>
<tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>3.1x</td></tr> <tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>2.9x</td></tr>
<tr><td>UnmarshalDataset/twitter-2</td><td>2.5x</td><td>2.6x</td></tr> <tr><td>UnmarshalDataset/twitter-2</td><td>2.6x</td><td>2.7x</td></tr>
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.1x</td><td>2.2x</td></tr> <tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.2x</td><td>2.3x</td></tr>
<tr><td>UnmarshalDataset/canada-2</td><td>1.6x</td><td>1.3x</td></tr> <tr><td>UnmarshalDataset/canada-2</td><td>1.8x</td><td>1.5x</td></tr>
<tr><td>UnmarshalDataset/config-2</td><td>4.3x</td><td>3.2x</td></tr> <tr><td>UnmarshalDataset/config-2</td><td>4.1x</td><td>2.9x</td></tr>
<tr><td>[Geo mean]</td><td>2.7x</td><td>2.8x</td></tr> <tr><td>geomean</td><td>2.7x</td><td>2.8x</td></tr>
</tbody> </tbody>
</table> </table>
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p> <p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
</details> </details>
@@ -233,24 +233,24 @@ Go-toml provides three handy command line tools:
* `tomljson`: Reads a TOML file and outputs its JSON representation. * `tomljson`: Reads a TOML file and outputs its JSON representation.
``` ```
$ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest $ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest
$ tomljson --help $ tomljson --help
``` ```
* `jsontoml`: Reads a JSON file and outputs a TOML representation. * `jsontoml`: Reads a JSON file and outputs a TOML representation.
``` ```
$ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest $ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest
$ jsontoml --help $ jsontoml --help
``` ```
* `tomll`: Lints and reformats a TOML file. * `tomll`: Lints and reformats a TOML file.
``` ```
$ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest $ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest
$ tomll --help $ tomll --help
``` ```
### Docker image ### Docker image
@@ -261,7 +261,7 @@ Those tools are also available as a [Docker image][docker]. For example, to use
docker run -i ghcr.io/pelletier/go-toml:v2 tomljson < example.toml docker run -i ghcr.io/pelletier/go-toml:v2 tomljson < example.toml
``` ```
Multiple versions are availble on [ghcr.io][docker]. Multiple versions are available on [ghcr.io][docker].
[docker]: https://github.com/pelletier/go-toml/pkgs/container/go-toml [docker]: https://github.com/pelletier/go-toml/pkgs/container/go-toml
@@ -293,16 +293,16 @@ element in the interface to decode the object. For example:
```go ```go
type inner struct { type inner struct {
B interface{} B interface{}
} }
type doc struct { type doc struct {
A interface{} A interface{}
} }
d := doc{ d := doc{
A: inner{ A: inner{
B: "Before", B: "Before",
}, },
} }
data := ` data := `
@@ -341,7 +341,7 @@ contained in the doc is superior to the capacity of the array. For example:
```go ```go
type doc struct { type doc struct {
A [2]string A [2]string
} }
d := doc{} d := doc{}
err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d)
@@ -565,10 +565,11 @@ complete solutions exist out there.
## Versioning ## Versioning
Go-toml follows [Semantic Versioning](https://semver.org). The supported version Expect for parts explicitly marked otherwise, go-toml follows [Semantic
of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of Versioning](https://semver.org). The supported version of
this document. The last two major versions of Go are supported [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of this
(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)). document. The last two major versions of Go are supported (see [Go Release
Policy](https://golang.org/doc/devel/release.html#policy)).
## License ## License
-3
View File
@@ -2,9 +2,6 @@
## Supported Versions ## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported | | Version | Supported |
| ---------- | ------------------ | | ---------- | ------------------ |
| Latest 2.x | :white_check_mark: | | Latest 2.x | :white_check_mark: |
+8 -8
View File
@@ -9,7 +9,7 @@ import (
"testing" "testing"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
var bench_inputs = []struct { var bench_inputs = []struct {
@@ -35,11 +35,11 @@ func TestUnmarshalDatasetCode(t *testing.T) {
buf := fixture(t, tc.name) buf := fixture(t, tc.name)
var v interface{} var v interface{}
require.NoError(t, toml.Unmarshal(buf, &v)) assert.NoError(t, toml.Unmarshal(buf, &v))
b, err := json.Marshal(v) b, err := json.Marshal(v)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, len(b), tc.jsonLen) assert.Equal(t, len(b), tc.jsonLen)
}) })
} }
} }
@@ -53,7 +53,7 @@ func BenchmarkUnmarshalDataset(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var v interface{} var v interface{}
require.NoError(b, toml.Unmarshal(buf, &v)) assert.NoError(b, toml.Unmarshal(buf, &v))
} }
}) })
} }
@@ -68,13 +68,13 @@ func fixture(tb testing.TB, path string) []byte {
if os.IsNotExist(err) { if os.IsNotExist(err) {
tb.Skip("benchmark fixture not found:", file) tb.Skip("benchmark fixture not found:", file)
} }
require.NoError(tb, err) assert.NoError(tb, err)
defer f.Close() defer f.Close()
gz, err := gzip.NewReader(f) gz, err := gzip.NewReader(f)
require.NoError(tb, err) assert.NoError(tb, err)
buf, err := ioutil.ReadAll(gz) buf, err := ioutil.ReadAll(gz)
require.NoError(tb, err) assert.NoError(tb, err)
return buf return buf
} }
+4 -4
View File
@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func TestUnmarshalSimple(t *testing.T) { func TestUnmarshalSimple(t *testing.T) {
@@ -345,10 +345,10 @@ type benchmarkDoc struct {
func TestUnmarshalReferenceFile(t *testing.T) { func TestUnmarshalReferenceFile(t *testing.T) {
bytes, err := ioutil.ReadFile("benchmark.toml") bytes, err := ioutil.ReadFile("benchmark.toml")
require.NoError(t, err) assert.NoError(t, err)
d := benchmarkDoc{} d := benchmarkDoc{}
err = toml.Unmarshal(bytes, &d) err = toml.Unmarshal(bytes, &d)
require.NoError(t, err) assert.NoError(t, err)
expected := benchmarkDoc{ expected := benchmarkDoc{
Table: struct { Table: struct {
@@ -627,7 +627,7 @@ trimmed in raw strings.
}, },
} }
require.Equal(t, expected, d) assert.Equal(t, expected, d)
} }
var hugoFrontMatterbytes = []byte(` var hugoFrontMatterbytes = []byte(`
+13 -9
View File
@@ -77,7 +77,7 @@ cover() {
pushd "$dir" pushd "$dir"
go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./... go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./...
cat coverage.out.tmp | grep -v fuzz | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out grep -Ev '(fuzz|testsuite|tomltestgen|gotoml-test-decoder|gotoml-test-encoder)' coverage.out.tmp > coverage.out
go tool cover -func=coverage.out go tool cover -func=coverage.out
echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2 echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2
popd popd
@@ -152,7 +152,7 @@ bench() {
fi fi
export GOMAXPROCS=2 export GOMAXPROCS=2
nice -n -19 taskset --cpu-list 0,1 go test '-bench=^Benchmark(Un)?[mM]arshal' -count=5 -run=Nothing ./... | tee "${out}" go test '-bench=^Benchmark(Un)?[mM]arshal' -count=10 -run=Nothing ./... | tee "${out}"
popd popd
if [ "${branch}" != "HEAD" ]; then if [ "${branch}" != "HEAD" ]; then
@@ -161,10 +161,12 @@ bench() {
} }
fmktemp() { fmktemp() {
if mktemp --version|grep GNU >/dev/null; then if mktemp --version &> /dev/null; then
mktemp --suffix=-$1; # GNU
mktemp --suffix=-$1
else else
mktemp -t $1; # BSD
mktemp -t $1
fi fi
} }
@@ -184,12 +186,14 @@ with open(sys.argv[1]) as f:
lines.append(line.split(',')) lines.append(line.split(','))
results = [] results = []
for line in reversed(lines[1:]): for line in reversed(lines[2:]):
if len(line) < 8 or line[0] == "":
continue
v2 = float(line[1]) v2 = float(line[1])
results.append([ results.append([
line[0].replace("-32", ""), line[0].replace("-32", ""),
"%.1fx" % (float(line[3])/v2), # v1 "%.1fx" % (float(line[3])/v2), # v1
"%.1fx" % (float(line[5])/v2), # bs "%.1fx" % (float(line[7])/v2), # bs
]) ])
# move geomean to the end # move geomean to the end
results.append(results[0]) results.append(results[0])
@@ -260,10 +264,10 @@ benchmark() {
if [ "$1" = "-html" ]; then if [ "$1" = "-html" ]; then
tmpcsv=`fmktemp csv` tmpcsv=`fmktemp csv`
benchstat -csv -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv benchstat -format csv go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv
benchstathtml $tmpcsv benchstathtml $tmpcsv
else else
benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt benchstat go-toml-v2.txt go-toml-v1.txt bs-toml.txt
fi fi
rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt
@@ -0,0 +1,30 @@
package main
import (
"flag"
"log"
"os"
"path"
"github.com/pelletier/go-toml/v2/internal/testsuite"
)
func main() {
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
if flag.NArg() != 0 {
flag.Usage()
}
err := testsuite.EncodeStdin()
if err != nil {
log.Fatal(err)
}
}
func usage() {
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
flag.PrintDefaults()
os.Exit(1)
}
+12 -1
View File
@@ -19,6 +19,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"flag"
"io" "io"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
@@ -33,7 +34,11 @@ Reading from a file:
jsontoml file.json > file.toml jsontoml file.json > file.toml
` `
var useJsonNumber bool
func main() { func main() {
flag.BoolVar(&useJsonNumber, "use-json-number", false, "unmarshal numbers into `json.Number` type instead of as `float64`")
p := cli.Program{ p := cli.Program{
Usage: usage, Usage: usage,
Fn: convert, Fn: convert,
@@ -45,11 +50,17 @@ func convert(r io.Reader, w io.Writer) error {
var v interface{} var v interface{}
d := json.NewDecoder(r) d := json.NewDecoder(r)
e := toml.NewEncoder(w)
if useJsonNumber {
d.UseNumber()
e.SetMarshalJsonNumbers(true)
}
err := d.Decode(&v) err := d.Decode(&v)
if err != nil { if err != nil {
return err return err
} }
e := toml.NewEncoder(w)
return e.Encode(v) return e.Encode(v)
} }
+21 -7
View File
@@ -5,16 +5,16 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
) )
func TestConvert(t *testing.T) { func TestConvert(t *testing.T) {
examples := []struct { examples := []struct {
name string name string
input string input string
expected string expected string
errors bool errors bool
useJsonNumber bool
}{ }{
{ {
name: "valid json", name: "valid json",
@@ -26,6 +26,19 @@ func TestConvert(t *testing.T) {
}`, }`,
expected: `[mytoml] expected: `[mytoml]
a = 42.0 a = 42.0
`,
},
{
name: "use json number",
useJsonNumber: true,
input: `
{
"mytoml": {
"a": 42
}
}`,
expected: `[mytoml]
a = 42
`, `,
}, },
{ {
@@ -37,9 +50,10 @@ a = 42.0
for _, e := range examples { for _, e := range examples {
b := new(bytes.Buffer) b := new(bytes.Buffer)
useJsonNumber = e.useJsonNumber
err := convert(strings.NewReader(e.input), b) err := convert(strings.NewReader(e.input), b)
if e.errors { if e.errors {
require.Error(t, err) assert.Error(t, err)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, e.expected, b.String()) assert.Equal(t, e.expected, b.String())
+2 -3
View File
@@ -7,8 +7,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
) )
func TestConvert(t *testing.T) { func TestConvert(t *testing.T) {
@@ -46,7 +45,7 @@ a = 42`),
b := new(bytes.Buffer) b := new(bytes.Buffer)
err := convert(e.input, b) err := convert(e.input, b)
if e.errors { if e.errors {
require.Error(t, err) assert.Error(t, err)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, e.expected, b.String()) assert.Equal(t, e.expected, b.String())
+2 -3
View File
@@ -5,8 +5,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
) )
func TestConvert(t *testing.T) { func TestConvert(t *testing.T) {
@@ -36,7 +35,7 @@ a = 42.0
b := new(bytes.Buffer) b := new(bytes.Buffer)
err := convert(strings.NewReader(e.input), b) err := convert(strings.NewReader(e.input), b)
if e.errors { if e.errors {
require.Error(t, err) assert.Error(t, err)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, e.expected, b.String()) assert.Equal(t, e.expected, b.String())
+43 -88
View File
@@ -7,17 +7,13 @@
package main package main
import ( import (
"archive/zip"
"bytes" "bytes"
"flag" "flag"
"fmt" "fmt"
"go/format" "go/format"
"io"
"io/ioutil"
"log" "log"
"net/http"
"os" "os"
"regexp" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@@ -64,30 +60,6 @@ const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on {
"}\n" + "}\n" +
"{{end}}\n" "{{end}}\n"
func downloadTmpFile(url string) string {
log.Println("starting to download file from", url)
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
tmpfile, err := ioutil.TempFile("", "toml-test-*.zip")
if err != nil {
panic(err)
}
defer tmpfile.Close()
copiedLen, err := io.Copy(tmpfile, resp.Body)
if err != nil {
panic(err)
}
if resp.ContentLength > 0 && copiedLen != resp.ContentLength {
panic(fmt.Errorf("copied %d bytes, request body had %d", copiedLen, resp.ContentLength))
}
return tmpfile.Name()
}
func kebabToCamel(kebab string) string { func kebabToCamel(kebab string) string {
camel := "" camel := ""
nextUpper := true nextUpper := true
@@ -107,19 +79,6 @@ func kebabToCamel(kebab string) string {
return camel return camel
} }
func readFileFromZip(f *zip.File) string {
reader, err := f.Open()
if err != nil {
panic(err)
}
defer reader.Close()
bytes, err := ioutil.ReadAll(reader)
if err != nil {
panic(err)
}
return string(bytes)
}
func templateGoStr(input string) string { func templateGoStr(input string) string {
return strconv.Quote(input) return strconv.Quote(input)
} }
@@ -138,61 +97,57 @@ func main() {
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
url := "https://codeload.github.com/BurntSushi/toml-test/zip/" + *ref
resultFile := downloadTmpFile(url)
defer os.Remove(resultFile)
log.Println("file written to", resultFile)
zipReader, err := zip.OpenReader(resultFile)
if err != nil {
panic(err)
}
defer zipReader.Close()
collection := testsCollection{ collection := testsCollection{
Ref: *ref, Ref: *ref,
Timestamp: time.Now().Format(time.RFC3339), Timestamp: time.Now().Format(time.RFC3339),
} }
zipFilesMap := map[string]*zip.File{} dirContent, _ := filepath.Glob("tests/invalid/**/*.toml")
for _, f := range dirContent {
filename := strings.TrimPrefix(f, "tests/valid/")
name := kebabToCamel(strings.TrimSuffix(filename, ".toml"))
for _, f := range zipReader.File { log.Printf("> [%s] %s\n", "invalid", name)
zipFilesMap[f.Name] = f
tomlContent, err := os.ReadFile(f)
if err != nil {
fmt.Printf("failed to read test file: %s\n", err)
os.Exit(1)
}
collection.Invalid = append(collection.Invalid, invalid{
Name: name,
Input: string(tomlContent),
})
collection.Count++
} }
testFileRegexp := regexp.MustCompile(`([^/]+/tests/(valid|invalid)/(.+))\.(toml)`) dirContent, _ = filepath.Glob("tests/valid/**/*.toml")
for _, f := range zipReader.File { for _, f := range dirContent {
groups := testFileRegexp.FindStringSubmatch(f.Name) filename := strings.TrimPrefix(f, "tests/valid/")
if len(groups) > 0 { name := kebabToCamel(strings.TrimSuffix(filename, ".toml"))
name := kebabToCamel(groups[3])
testType := groups[2]
log.Printf("> [%s] %s\n", testType, name) log.Printf("> [%s] %s\n", "valid", name)
tomlContent := readFileFromZip(f) tomlContent, err := os.ReadFile(f)
if err != nil {
switch testType { fmt.Printf("failed reading test file: %s\n", err)
case "invalid": os.Exit(1)
collection.Invalid = append(collection.Invalid, invalid{
Name: name,
Input: tomlContent,
})
collection.Count++
case "valid":
baseFilePath := groups[1]
jsonFilePath := baseFilePath + ".json"
jsonContent := readFileFromZip(zipFilesMap[jsonFilePath])
collection.Valid = append(collection.Valid, valid{
Name: name,
Input: tomlContent,
JsonRef: jsonContent,
})
collection.Count++
default:
panic(fmt.Sprintf("unknown test type: %s", testType))
}
} }
filename = strings.TrimSuffix(f, ".toml")
jsonContent, err := os.ReadFile(filename + ".json")
if err != nil {
fmt.Printf("failed reading validation json: %s\n", err)
os.Exit(1)
}
collection.Valid = append(collection.Valid, valid{
Name: name,
Input: string(tomlContent),
JsonRef: string(jsonContent),
})
collection.Count++
} }
log.Printf("Collected %d tests from toml-test\n", collection.Count) log.Printf("Collected %d tests from toml-test\n", collection.Count)
@@ -202,7 +157,7 @@ func main() {
} }
t := template.Must(template.New("src").Funcs(funcMap).Parse(srcTemplate)) t := template.Must(template.New("src").Funcs(funcMap).Parse(srcTemplate))
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err = t.Execute(buf, collection) err := t.Execute(buf, collection)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -216,7 +171,7 @@ func main() {
return return
} }
err = os.WriteFile(*out, outputBytes, 0644) err = os.WriteFile(*out, outputBytes, 0o644)
if err != nil { if err != nil {
panic(err) panic(err)
} }
+1 -1
View File
@@ -7,8 +7,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/pelletier/go-toml/v2/internal/assert"
"github.com/pelletier/go-toml/v2/unstable" "github.com/pelletier/go-toml/v2/unstable"
"github.com/stretchr/testify/assert"
) )
//nolint:funlen //nolint:funlen
+15 -15
View File
@@ -4,28 +4,28 @@ import (
"testing" "testing"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func TestFastSimpleInt(t *testing.T) { func TestFastSimpleInt(t *testing.T) {
m := map[string]int64{} m := map[string]int64{}
err := toml.Unmarshal([]byte(`a = 42`), &m) err := toml.Unmarshal([]byte(`a = 42`), &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]int64{"a": 42}, m) assert.Equal(t, map[string]int64{"a": 42}, m)
} }
func TestFastSimpleFloat(t *testing.T) { func TestFastSimpleFloat(t *testing.T) {
m := map[string]float64{} m := map[string]float64{}
err := toml.Unmarshal([]byte("a = 42\nb = 1.1\nc = 12341234123412341234123412341234"), &m) err := toml.Unmarshal([]byte("a = 42\nb = 1.1\nc = 12341234123412341234123412341234"), &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]float64{"a": 42, "b": 1.1, "c": 1.2341234123412342e+31}, m) assert.Equal(t, map[string]float64{"a": 42, "b": 1.1, "c": 1.2341234123412342e+31}, m)
} }
func TestFastSimpleString(t *testing.T) { func TestFastSimpleString(t *testing.T) {
m := map[string]string{} m := map[string]string{}
err := toml.Unmarshal([]byte(`a = "hello"`), &m) err := toml.Unmarshal([]byte(`a = "hello"`), &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]string{"a": "hello"}, m) assert.Equal(t, map[string]string{"a": "hello"}, m)
} }
func TestFastSimpleInterface(t *testing.T) { func TestFastSimpleInterface(t *testing.T) {
@@ -33,8 +33,8 @@ func TestFastSimpleInterface(t *testing.T) {
err := toml.Unmarshal([]byte(` err := toml.Unmarshal([]byte(`
a = "hello" a = "hello"
b = 42`), &m) b = 42`), &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]interface{}{ assert.Equal(t, map[string]interface{}{
"a": "hello", "a": "hello",
"b": int64(42), "b": int64(42),
}, m) }, m)
@@ -46,8 +46,8 @@ func TestFastMultipartKeyInterface(t *testing.T) {
a.interim = "test" a.interim = "test"
a.b.c = "hello" a.b.c = "hello"
b = 42`), &m) b = 42`), &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]interface{}{ assert.Equal(t, map[string]interface{}{
"a": map[string]interface{}{ "a": map[string]interface{}{
"interim": "test", "interim": "test",
"b": map[string]interface{}{ "b": map[string]interface{}{
@@ -66,8 +66,8 @@ func TestFastExistingMap(t *testing.T) {
ints.one = 1 ints.one = 1
ints.two = 2 ints.two = 2
strings.yo = "hello"`), &m) strings.yo = "hello"`), &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]interface{}{ assert.Equal(t, map[string]interface{}{
"ints": map[string]interface{}{ "ints": map[string]interface{}{
"one": int64(1), "one": int64(1),
"two": int64(2), "two": int64(2),
@@ -90,9 +90,9 @@ func TestFastArrayTable(t *testing.T) {
m := map[string]interface{}{} m := map[string]interface{}{}
err := toml.Unmarshal(b, &m) err := toml.Unmarshal(b, &m)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, map[string]interface{}{ assert.Equal(t, map[string]interface{}{
"root": map[string]interface{}{ "root": map[string]interface{}{
"nested": []interface{}{ "nested": []interface{}{
map[string]interface{}{ map[string]interface{}{
+2 -5
View File
@@ -1,6 +1,3 @@
//go:build go1.18 || go1.19 || go1.20 || go1.21
// +build go1.18 go1.19 go1.20 go1.21
package toml_test package toml_test
import ( import (
@@ -9,7 +6,7 @@ import (
"testing" "testing"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func FuzzUnmarshal(f *testing.F) { func FuzzUnmarshal(f *testing.F) {
@@ -51,6 +48,6 @@ func FuzzUnmarshal(f *testing.F) {
if err != nil { if err != nil {
t.Fatalf("failed round trip: %s", err) t.Fatalf("failed round trip: %s", err)
} }
require.Equal(t, v, v2) assert.Equal(t, v, v2)
}) })
} }
+1 -3
View File
@@ -1,5 +1,3 @@
module github.com/pelletier/go-toml/v2 module github.com/pelletier/go-toml/v2
go 1.16 go 1.21.0
require github.com/stretchr/testify v1.8.4
-17
View File
@@ -1,17 +0,0 @@
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/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=
+135
View File
@@ -0,0 +1,135 @@
package assert
import (
"bytes"
"fmt"
"reflect"
"strings"
"testing"
)
// True asserts that an expression is true.
func True(t testing.TB, ok bool, msgAndArgs ...any) {
if ok {
return
}
t.Helper()
t.Fatal(formatMsgAndArgs("Expected expression to be true", msgAndArgs...))
}
// False asserts that an expression is false.
func False(t testing.TB, ok bool, msgAndArgs ...any) {
if !ok {
return
}
t.Helper()
t.Fatal(formatMsgAndArgs("Expected expression to be false", msgAndArgs...))
}
// Equal asserts that "expected" and "actual" are equal.
func Equal[T any](t testing.TB, expected, actual T, msgAndArgs ...any) {
if objectsAreEqual(expected, actual) {
return
}
t.Helper()
msg := formatMsgAndArgs("Expected values to be equal:", msgAndArgs...)
t.Fatalf("%s\n%s", msg, diff(expected, actual))
}
// Error asserts that an error is not nil.
func Error(t testing.TB, err error, msgAndArgs ...any) {
if err != nil {
return
}
t.Helper()
t.Fatal(formatMsgAndArgs("Expected an error", msgAndArgs...))
}
// NoError asserts that an error is nil.
func NoError(t testing.TB, err error, msgAndArgs ...any) {
if err == nil {
return
}
t.Helper()
msg := formatMsgAndArgs("Unexpected error:", msgAndArgs...)
t.Fatalf("%s\n%+v", msg, err)
}
// Panics asserts that the given function panics.
func Panics(t testing.TB, fn func(), msgAndArgs ...any) {
t.Helper()
defer func() {
if recover() == nil {
msg := formatMsgAndArgs("Expected function to panic", msgAndArgs...)
t.Fatal(msg)
}
}()
fn()
}
// Zero asserts that a value is its zero value.
func Zero[T any](t testing.TB, value T, msgAndArgs ...any) {
var zero T
if objectsAreEqual(value, zero) {
return
}
val := reflect.ValueOf(value)
if (val.Kind() == reflect.Slice || val.Kind() == reflect.Map || val.Kind() == reflect.Array) && val.Len() == 0 {
return
}
t.Helper()
msg := formatMsgAndArgs("Expected zero value but got:", msgAndArgs...)
t.Fatalf("%s\n%s", msg, fmt.Sprintf("%v", value))
}
func NotZero[T any](t testing.TB, value T, msgAndArgs ...any) {
var zero T
if !objectsAreEqual(value, zero) {
val := reflect.ValueOf(value)
if !((val.Kind() == reflect.Slice || val.Kind() == reflect.Map || val.Kind() == reflect.Array) && val.Len() == 0) {
return
}
}
t.Helper()
msg := formatMsgAndArgs("Unexpected zero value:", msgAndArgs...)
t.Fatalf("%s\n%s", msg, fmt.Sprintf("%v", value))
}
func formatMsgAndArgs(msg string, args ...any) string {
if len(args) == 0 {
return msg
}
format, ok := args[0].(string)
if !ok {
panic("message argument must be a fmt string")
}
return fmt.Sprintf(format, args[1:]...)
}
func diff(expected, actual any) string {
lines := []string{
"expected:",
fmt.Sprintf("%v", expected),
"actual:",
fmt.Sprintf("%v", actual),
}
return strings.Join(lines, "\n")
}
func objectsAreEqual(expected, actual any) bool {
if expected == nil || actual == nil {
return expected == actual
}
if exp, eok := expected.([]byte); eok {
if act, aok := actual.([]byte); aok {
return bytes.Equal(exp, act)
}
}
if exp, eok := expected.(string); eok {
if act, aok := actual.(string); aok {
return exp == act
}
}
return reflect.DeepEqual(expected, actual)
}
+184
View File
@@ -0,0 +1,184 @@
package assert
import (
"fmt"
"testing"
)
type Data struct {
Label string
Value int64
}
func TestBadMessage(t *testing.T) {
invalidMessage := func() { True(t, false, 1234) }
assertOk(t, "Non-fmt message value", func(t testing.TB) {
Panics(t, invalidMessage)
})
assertFail(t, "Non-fmt message value", func(t testing.TB) {
True(t, false, "example %s", "message")
})
}
func TestTrue(t *testing.T) {
assertOk(t, "Succeed", func(t testing.TB) {
True(t, 1 > 0)
})
assertFail(t, "Fail", func(t testing.TB) {
True(t, 1 < 0)
})
}
func TestFalse(t *testing.T) {
assertOk(t, "Succeed", func(t testing.TB) {
False(t, 1 < 0)
})
assertFail(t, "Fail", func(t testing.TB) {
False(t, 1 > 0)
})
}
func TestEqual(t *testing.T) {
assertOk(t, "Nil", func(t testing.TB) {
Equal(t, interface{}(nil), interface{}(nil))
})
assertOk(t, "Identical structs", func(t testing.TB) {
Equal(t, Data{"expected", 1234}, Data{"expected", 1234})
})
assertFail(t, "Different structs", func(t testing.TB) {
Equal(t, Data{"expected", 1234}, Data{"actual", 1234})
})
assertOk(t, "Identical numbers", func(t testing.TB) {
Equal(t, 1234, 1234)
})
assertFail(t, "Identical numbers", func(t testing.TB) {
Equal(t, 1234, 1324)
})
assertOk(t, "Zero-length byte arrays", func(t testing.TB) {
Equal(t, []byte(nil), []byte(""))
})
assertOk(t, "Identical byte arrays", func(t testing.TB) {
Equal(t, []byte{1, 2, 3, 4}, []byte{1, 2, 3, 4})
})
assertFail(t, "Different byte arrays", func(t testing.TB) {
Equal(t, []byte{1, 2, 3, 4}, []byte{1, 3, 2, 4})
})
assertOk(t, "Identical strings", func(t testing.TB) {
Equal(t, "example", "example")
})
assertFail(t, "Identical strings", func(t testing.TB) {
Equal(t, "example", "elpmaxe")
})
}
func TestError(t *testing.T) {
assertOk(t, "Error", func(t testing.TB) {
Error(t, fmt.Errorf("example"))
})
assertFail(t, "Nil", func(t testing.TB) {
Error(t, nil)
})
}
func TestNoError(t *testing.T) {
assertFail(t, "Error", func(t testing.TB) {
NoError(t, fmt.Errorf("example"))
})
assertOk(t, "Nil", func(t testing.TB) {
NoError(t, nil)
})
}
func TestPanics(t *testing.T) {
willPanic := func() { panic("example") }
wontPanic := func() {}
assertOk(t, "Will panic", func(t testing.TB) {
Panics(t, willPanic)
})
assertFail(t, "Won't panic", func(t testing.TB) {
Panics(t, wontPanic)
})
}
func TestZero(t *testing.T) {
assertOk(t, "Empty struct", func(t testing.TB) {
Zero(t, Data{})
})
assertFail(t, "Non-empty struct", func(t testing.TB) {
Zero(t, Data{Label: "example"})
})
assertOk(t, "Nil slice", func(t testing.TB) {
var slice []int
Zero(t, slice)
})
assertFail(t, "Non-empty slice", func(t testing.TB) {
slice := []int{1, 2, 3, 4}
Zero(t, slice)
})
assertOk(t, "Zero-length slice", func(t testing.TB) {
slice := []int{}
Zero(t, slice)
})
}
func TestNotZero(t *testing.T) {
assertFail(t, "Empty struct", func(t testing.TB) {
zero := Data{}
NotZero(t, zero)
})
assertOk(t, "Non-empty struct", func(t testing.TB) {
notZero := Data{Label: "example"}
NotZero(t, notZero)
})
assertFail(t, "Nil slice", func(t testing.TB) {
var slice []int
NotZero(t, slice)
})
assertFail(t, "Zero-length slice", func(t testing.TB) {
slice := []int{}
NotZero(t, slice)
})
assertOk(t, "Non-empty slice", func(t testing.TB) {
slice := []int{1, 2, 3, 4}
NotZero(t, slice)
})
}
type testCase struct {
*testing.T
failed string
}
func (t *testCase) Fatal(args ...interface{}) {
t.failed = fmt.Sprint(args...)
}
func (t *testCase) Fatalf(message string, args ...interface{}) {
t.failed = fmt.Sprintf(message, args...)
}
func assertFail(t *testing.T, name string, fn func(t testing.TB)) {
t.Helper()
t.Run(name, func(t *testing.T) {
t.Helper()
test := &testCase{T: t}
fn(test)
if test.failed == "" {
t.Fatal("Test expected to fail but did not")
} else {
t.Log(test.failed)
}
})
}
func assertOk(t *testing.T, name string, fn func(t testing.TB)) {
t.Helper()
t.Run(name, func(t *testing.T) {
t.Helper()
test := &testCase{T: t}
fn(test)
if test.failed != "" {
t.Fatal("Test expected to succeed but did not:\n", test.failed)
}
})
}
+27 -28
View File
@@ -11,8 +11,7 @@ import (
"testing" "testing"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
) )
func processMain(args []string, input io.Reader, stdout, stderr io.Writer, f ConvertFn) int { func processMain(args []string, input io.Reader, stdout, stderr io.Writer, f ConvertFn) int {
@@ -30,8 +29,8 @@ func TestProcessMainStdin(t *testing.T) {
}) })
assert.Equal(t, 0, exit) assert.Equal(t, 0, exit)
assert.Empty(t, stdout.String()) assert.Zero(t, stdout.String())
assert.Empty(t, stderr.String()) assert.Zero(t, stderr.String())
} }
func TestProcessMainStdinErr(t *testing.T) { func TestProcessMainStdinErr(t *testing.T) {
@@ -44,8 +43,8 @@ func TestProcessMainStdinErr(t *testing.T) {
}) })
assert.Equal(t, -1, exit) assert.Equal(t, -1, exit)
assert.Empty(t, stdout.String()) assert.Zero(t, stdout.String())
assert.NotEmpty(t, stderr.String()) assert.NotZero(t, stderr.String())
} }
func TestProcessMainStdinDecodeErr(t *testing.T) { func TestProcessMainStdinDecodeErr(t *testing.T) {
@@ -59,16 +58,16 @@ func TestProcessMainStdinDecodeErr(t *testing.T) {
}) })
assert.Equal(t, -1, exit) assert.Equal(t, -1, exit)
assert.Empty(t, stdout.String()) assert.Zero(t, stdout.String())
assert.Contains(t, stderr.String(), "error occurred at") assert.True(t, strings.Contains(stderr.String(), "error occurred at"))
} }
func TestProcessMainFileExists(t *testing.T) { func TestProcessMainFileExists(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "example") tmpfile, err := ioutil.TempFile("", "example")
require.NoError(t, err) assert.NoError(t, err)
defer os.Remove(tmpfile.Name()) defer os.Remove(tmpfile.Name())
_, err = tmpfile.Write([]byte(`some data`)) _, err = tmpfile.Write([]byte(`some data`))
require.NoError(t, err) assert.NoError(t, err)
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
@@ -78,8 +77,8 @@ func TestProcessMainFileExists(t *testing.T) {
}) })
assert.Equal(t, 0, exit) assert.Equal(t, 0, exit)
assert.Empty(t, stdout.String()) assert.Zero(t, stdout.String())
assert.Empty(t, stderr.String()) assert.Zero(t, stderr.String())
} }
func TestProcessMainFileDoesNotExist(t *testing.T) { func TestProcessMainFileDoesNotExist(t *testing.T) {
@@ -91,22 +90,22 @@ func TestProcessMainFileDoesNotExist(t *testing.T) {
}) })
assert.Equal(t, -1, exit) assert.Equal(t, -1, exit)
assert.Empty(t, stdout.String()) assert.Zero(t, stdout.String())
assert.NotEmpty(t, stderr.String()) assert.NotZero(t, stderr.String())
} }
func TestProcessMainFilesInPlace(t *testing.T) { func TestProcessMainFilesInPlace(t *testing.T) {
dir, err := ioutil.TempDir("", "") dir, err := ioutil.TempDir("", "")
require.NoError(t, err) assert.NoError(t, err)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
path1 := path.Join(dir, "file1") path1 := path.Join(dir, "file1")
path2 := path.Join(dir, "file2") path2 := path.Join(dir, "file2")
err = ioutil.WriteFile(path1, []byte("content 1"), 0600) err = ioutil.WriteFile(path1, []byte("content 1"), 0600)
require.NoError(t, err) assert.NoError(t, err)
err = ioutil.WriteFile(path2, []byte("content 2"), 0600) err = ioutil.WriteFile(path2, []byte("content 2"), 0600)
require.NoError(t, err) assert.NoError(t, err)
p := Program{ p := Program{
Fn: dummyFileFn, Fn: dummyFileFn,
@@ -115,15 +114,15 @@ func TestProcessMainFilesInPlace(t *testing.T) {
exit := p.main([]string{path1, path2}, os.Stdin, os.Stdout, os.Stderr) exit := p.main([]string{path1, path2}, os.Stdin, os.Stdout, os.Stderr)
require.Equal(t, 0, exit) assert.Equal(t, 0, exit)
v1, err := ioutil.ReadFile(path1) v1, err := ioutil.ReadFile(path1)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "1", string(v1)) assert.Equal(t, "1", string(v1))
v2, err := ioutil.ReadFile(path2) v2, err := ioutil.ReadFile(path2)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "2", string(v2)) assert.Equal(t, "2", string(v2))
} }
func TestProcessMainFilesInPlaceErrRead(t *testing.T) { func TestProcessMainFilesInPlaceErrRead(t *testing.T) {
@@ -134,18 +133,18 @@ func TestProcessMainFilesInPlaceErrRead(t *testing.T) {
exit := p.main([]string{"/this/path/is/invalid"}, os.Stdin, os.Stdout, os.Stderr) exit := p.main([]string{"/this/path/is/invalid"}, os.Stdin, os.Stdout, os.Stderr)
require.Equal(t, -1, exit) assert.Equal(t, -1, exit)
} }
func TestProcessMainFilesInPlaceFailFn(t *testing.T) { func TestProcessMainFilesInPlaceFailFn(t *testing.T) {
dir, err := ioutil.TempDir("", "") dir, err := ioutil.TempDir("", "")
require.NoError(t, err) assert.NoError(t, err)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
path1 := path.Join(dir, "file1") path1 := path.Join(dir, "file1")
err = ioutil.WriteFile(path1, []byte("content 1"), 0600) err = ioutil.WriteFile(path1, []byte("content 1"), 0600)
require.NoError(t, err) assert.NoError(t, err)
p := Program{ p := Program{
Fn: func(io.Reader, io.Writer) error { return fmt.Errorf("oh no") }, Fn: func(io.Reader, io.Writer) error { return fmt.Errorf("oh no") },
@@ -154,11 +153,11 @@ func TestProcessMainFilesInPlaceFailFn(t *testing.T) {
exit := p.main([]string{path1}, os.Stdin, os.Stdout, os.Stderr) exit := p.main([]string{path1}, os.Stdin, os.Stdout, os.Stderr)
require.Equal(t, -1, exit) assert.Equal(t, -1, exit)
v1, err := ioutil.ReadFile(path1) v1, err := ioutil.ReadFile(path1)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "content 1", string(v1)) assert.Equal(t, "content 1", string(v1))
} }
func dummyFileFn(r io.Reader, w io.Writer) error { func dummyFileFn(r io.Reader, w io.Writer) error {
+6 -8
View File
@@ -4,9 +4,7 @@ import (
"testing" "testing"
"unsafe" "unsafe"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
) )
@@ -72,7 +70,7 @@ func TestSubsliceOffsetInvalid(t *testing.T) {
for _, e := range examples { for _, e := range examples {
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
d, s := e.test() d, s := e.test()
require.Panics(t, func() { assert.Panics(t, func() {
danger.SubsliceOffset(d, s) danger.SubsliceOffset(d, s)
}) })
}) })
@@ -83,9 +81,9 @@ func TestStride(t *testing.T) {
a := []byte{1, 2, 3, 4} a := []byte{1, 2, 3, 4}
x := &a[1] x := &a[1]
n := (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), 1)) n := (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), 1))
require.Equal(t, &a[2], n) assert.Equal(t, &a[2], n)
n = (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), -1)) n = (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), -1))
require.Equal(t, &a[0], n) assert.Equal(t, &a[0], n)
} }
func TestBytesRange(t *testing.T) { func TestBytesRange(t *testing.T) {
@@ -166,12 +164,12 @@ func TestBytesRange(t *testing.T) {
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
start, end := e.test() start, end := e.test()
if e.expected == nil { if e.expected == nil {
require.Panics(t, func() { assert.Panics(t, func() {
danger.BytesRange(start, end) danger.BytesRange(start, end)
}) })
} else { } else {
res := danger.BytesRange(start, end) res := danger.BytesRange(start, end)
require.Equal(t, e.expected, res) assert.Equal(t, e.expected, res)
} }
}) })
} }
@@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func TestDocMarshal(t *testing.T) { func TestDocMarshal(t *testing.T) {
@@ -107,13 +107,13 @@ name = 'List.Second'
` `
result, err := toml.Marshal(docData) result, err := toml.Marshal(docData)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, marshalTestToml, string(result)) assert.Equal(t, marshalTestToml, string(result))
} }
func TestBasicMarshalQuotedKey(t *testing.T) { func TestBasicMarshalQuotedKey(t *testing.T) {
result, err := toml.Marshal(quotedKeyMarshalTestData) result, err := toml.Marshal(quotedKeyMarshalTestData)
require.NoError(t, err) assert.NoError(t, err)
expected := `'Z.string-àéù' = 'Hello' expected := `'Z.string-àéù' = 'Hello'
'Yfloat-𝟘' = 3.5 'Yfloat-𝟘' = 3.5
@@ -128,7 +128,7 @@ String2 = 'Two'
String2 = 'Three' String2 = 'Three'
` `
require.Equal(t, string(expected), string(result)) assert.Equal(t, string(expected), string(result))
} }
@@ -153,7 +153,7 @@ func TestEmptyMarshal(t *testing.T) {
Map: map[string]string{}, Map: map[string]string{},
} }
result, err := toml.Marshal(doc) result, err := toml.Marshal(doc)
require.NoError(t, err) assert.NoError(t, err)
expected := `title = 'Placeholder' expected := `title = 'Placeholder'
bool = false bool = false
@@ -164,7 +164,7 @@ stringlist = []
[map] [map]
` `
require.Equal(t, string(expected), string(result)) assert.Equal(t, string(expected), string(result))
} }
type textMarshaler struct { type textMarshaler struct {
@@ -187,13 +187,13 @@ func TestTextMarshaler(t *testing.T) {
t.Run("at root", func(t *testing.T) { t.Run("at root", func(t *testing.T) {
_, err := toml.Marshal(m) _, err := toml.Marshal(m)
// in v2 we do not allow TextMarshaler at root // in v2 we do not allow TextMarshaler at root
require.Error(t, err) assert.Error(t, err)
}) })
t.Run("leaf", func(t *testing.T) { t.Run("leaf", func(t *testing.T) {
res, err := toml.Marshal(wrap{m}) res, err := toml.Marshal(wrap{m})
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "TM = 'Sally Fields'\n", string(res)) assert.Equal(t, "TM = 'Sally Fields'\n", string(res))
}) })
} }
@@ -16,8 +16,7 @@ import (
"time" "time"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
) )
type basicMarshalTestStruct struct { type basicMarshalTestStruct struct {
@@ -123,7 +122,7 @@ func TestInterface(t *testing.T) {
var config Conf var config Conf
config.Inter = &NestedStruct{} config.Inter = &NestedStruct{}
err := toml.Unmarshal(doc, &config) err := toml.Unmarshal(doc, &config)
require.NoError(t, err) assert.NoError(t, err)
expected := Conf{ expected := Conf{
Name: "rui", Name: "rui",
Age: 18, Age: 18,
@@ -139,8 +138,8 @@ func TestInterface(t *testing.T) {
func TestBasicUnmarshal(t *testing.T) { func TestBasicUnmarshal(t *testing.T) {
result := basicMarshalTestStruct{} result := basicMarshalTestStruct{}
err := toml.Unmarshal(basicTestToml, &result) err := toml.Unmarshal(basicTestToml, &result)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, basicTestData, result) assert.Equal(t, basicTestData, result)
} }
type quotedKeyMarshalTestStruct struct { type quotedKeyMarshalTestStruct struct {
@@ -300,7 +299,7 @@ func TestDocUnmarshal(t *testing.T) {
result := testDoc{} result := testDoc{}
err := toml.Unmarshal(marshalTestToml, &result) err := toml.Unmarshal(marshalTestToml, &result)
expected := docData expected := docData
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, result) assert.Equal(t, expected, result)
} }
@@ -340,7 +339,7 @@ shouldntBeHere = 2
func TestUnexportedUnmarshal(t *testing.T) { func TestUnexportedUnmarshal(t *testing.T) {
result := unexportedMarshalTestStruct{} result := unexportedMarshalTestStruct{}
err := toml.Unmarshal(unexportedTestToml, &result) err := toml.Unmarshal(unexportedTestToml, &result)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, unexportedTestData, result) assert.Equal(t, unexportedTestData, result)
} }
@@ -456,7 +455,7 @@ func TestEmptytomlUnmarshal(t *testing.T) {
result := emptyMarshalTestStruct{} result := emptyMarshalTestStruct{}
err := toml.Unmarshal(emptyTestToml, &result) err := toml.Unmarshal(emptyTestToml, &result)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, emptyTestData, result) assert.Equal(t, emptyTestData, result)
} }
@@ -504,7 +503,7 @@ Str = "Hello"
func TestPointerUnmarshal(t *testing.T) { func TestPointerUnmarshal(t *testing.T) {
result := pointerMarshalTestStruct{} result := pointerMarshalTestStruct{}
err := toml.Unmarshal(pointerTestToml, &result) err := toml.Unmarshal(pointerTestToml, &result)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, pointerTestData, result) assert.Equal(t, pointerTestData, result)
} }
@@ -540,7 +539,7 @@ StringPtr = [["Three", "Four"]]
func TestNestedUnmarshal(t *testing.T) { func TestNestedUnmarshal(t *testing.T) {
result := nestedMarshalTestStruct{} result := nestedMarshalTestStruct{}
err := toml.Unmarshal(nestedTestToml, &result) err := toml.Unmarshal(nestedTestToml, &result)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, nestedTestData, result) assert.Equal(t, nestedTestData, result)
} }
@@ -834,7 +833,7 @@ func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
result := Test{} result := Test{}
err := toml.Unmarshal(test.input, &result) err := toml.Unmarshal(test.input, &result)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, test.expected, result) assert.Equal(t, test.expected, result)
}) })
} }
@@ -963,7 +962,7 @@ func TestUnmarshalTypeTableHeader(t *testing.T) {
} }
expected := map[header]map[string]int{ expected := map[header]map[string]int{
"test": map[string]int{"a": 1}, "test": {"a": 1},
} }
if !reflect.DeepEqual(result, expected) { if !reflect.DeepEqual(result, expected) {
@@ -1090,7 +1089,7 @@ func TestUnmarshalCheckConversionFloatInt(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
err := toml.Unmarshal([]byte(test.input), &conversionCheck{}) err := toml.Unmarshal([]byte(test.input), &conversionCheck{})
require.Error(t, err) assert.Error(t, err)
}) })
} }
} }
@@ -1125,7 +1124,7 @@ func TestUnmarshalOverflow(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
err := toml.Unmarshal([]byte(test.input), &overflow{}) err := toml.Unmarshal([]byte(test.input), &overflow{})
require.Error(t, err) assert.Error(t, err)
}) })
} }
} }
@@ -1745,7 +1744,7 @@ Age = 23
} }
actual := OuterStruct{} actual := OuterStruct{}
err := toml.Unmarshal(doc, &actual) err := toml.Unmarshal(doc, &actual)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@@ -1830,7 +1829,7 @@ InnerField = "After4"
} }
err := toml.Unmarshal(doc, &actual) err := toml.Unmarshal(doc, &actual)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@@ -1879,7 +1878,7 @@ type arrayTooSmallStruct struct {
func TestUnmarshalSlice(t *testing.T) { func TestUnmarshalSlice(t *testing.T) {
var actual sliceStruct var actual sliceStruct
err := toml.Unmarshal(sliceTomlDemo, &actual) err := toml.Unmarshal(sliceTomlDemo, &actual)
require.NoError(t, err) assert.NoError(t, err)
expected := sliceStruct{ expected := sliceStruct{
Slice: []string{"Howdy", "Hey There"}, Slice: []string{"Howdy", "Hey There"},
SlicePtr: &[]string{"Howdy", "Hey There"}, SlicePtr: &[]string{"Howdy", "Hey There"},
@@ -1930,7 +1929,7 @@ func TestUnmarshalMixedTypeSlice(t *testing.T) {
}, },
} }
err := toml.Unmarshal(doc, &actual) err := toml.Unmarshal(doc, &actual)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@@ -1939,7 +1938,7 @@ func TestUnmarshalArray(t *testing.T) {
var actual arrayStruct var actual arrayStruct
err = toml.Unmarshal(sliceTomlDemo, &actual) err = toml.Unmarshal(sliceTomlDemo, &actual)
require.NoError(t, err) assert.NoError(t, err)
expected := arrayStruct{ expected := arrayStruct{
Slice: [4]string{"Howdy", "Hey There"}, Slice: [4]string{"Howdy", "Hey There"},
@@ -1998,8 +1997,13 @@ func TestDecoderStrict(t *testing.T) {
} }
err := strictDecoder(input).Decode(&doc) err := strictDecoder(input).Decode(&doc)
require.Error(t, err) assert.Error(t, err)
require.IsType(t, &toml.StrictMissingError{}, err)
assert.Equal(t,
reflect.TypeOf(err), reflect.TypeOf(&toml.StrictMissingError{}),
"Expected a *toml.StrictMissingError, got: %v", reflect.TypeOf(err),
)
se := err.(*toml.StrictMissingError) se := err.(*toml.StrictMissingError)
keys := []toml.Key{} keys := []toml.Key{}
@@ -2015,10 +2019,10 @@ func TestDecoderStrict(t *testing.T) {
{"undecoded", "array"}, {"undecoded", "array"},
} }
require.Equal(t, expectedKeys, keys) assert.Equal(t, expectedKeys, keys)
err = decoder(input).Decode(&doc) err = decoder(input).Decode(&doc)
require.NoError(t, err) assert.NoError(t, err)
var m map[string]interface{} var m map[string]interface{}
err = decoder(input).Decode(&m) err = decoder(input).Decode(&m)
@@ -2036,7 +2040,7 @@ func TestDecoderStrictValid(t *testing.T) {
} }
err := strictDecoder(input).Decode(&doc) err := strictDecoder(input).Decode(&doc)
require.NoError(t, err) assert.NoError(t, err)
} }
type docUnmarshalTOML struct { type docUnmarshalTOML struct {
@@ -2087,7 +2091,7 @@ func TestCustomUnmarshal(t *testing.T) {
var d parent var d parent
err := toml.Unmarshal([]byte(input), &d) err := toml.Unmarshal([]byte(input), &d)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "ok1", d.Doc.Decoded.Key) assert.Equal(t, "ok1", d.Doc.Decoded.Key)
assert.Equal(t, "ok2", d.DocPointer.Decoded.Key) assert.Equal(t, "ok2", d.DocPointer.Decoded.Key)
} }
@@ -2153,7 +2157,7 @@ Int = 21
Float = 2.0 Float = 2.0
` `
err := toml.Unmarshal([]byte(input), &doc) err := toml.Unmarshal([]byte(input), &doc)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 12, doc.UnixTime.Value) assert.Equal(t, 12, doc.UnixTime.Value)
assert.Equal(t, 42, doc.Version.Value) assert.Equal(t, 42, doc.Version.Value)
assert.Equal(t, 1, doc.Bool.Value) assert.Equal(t, 1, doc.Bool.Value)
@@ -2223,7 +2227,10 @@ func TestUnmarshalEmptyInterface(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
require.IsType(t, map[string]interface{}{}, v) assert.Equal(t,
reflect.TypeOf(map[string]interface{}{}), reflect.TypeOf(v),
"Expected map[string]interface{}{} type, got: %v", reflect.TypeOf(v),
)
x := v.(map[string]interface{}) x := v.(map[string]interface{})
assert.Equal(t, "pelletier", x["User"]) assert.Equal(t, "pelletier", x["User"])
+24 -5
View File
@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
"github.com/pelletier/go-toml/v2"
) )
// Remove JSON tags to a data structure as returned by toml-test. // Remove JSON tags to a data structure as returned by toml-test.
@@ -40,7 +42,7 @@ func rmTag(typedJson interface{}) (interface{}, error) {
} }
return m, nil return m, nil
// Array: remove tags from all itenm. // Array: remove tags from all items.
case []interface{}: case []interface{}:
a := make([]interface{}, len(v)) a := make([]interface{}, len(v))
for i := range v { for i := range v {
@@ -76,14 +78,31 @@ func untag(typed map[string]interface{}) (interface{}, error) {
return nil, fmt.Errorf("untag: %w", err) return nil, fmt.Errorf("untag: %w", err)
} }
return f, nil return f, nil
//toml.LocalDate{Year:2020, Month:12, Day:12}
case "datetime": case "datetime":
return parseTime(v, "2006-01-02T15:04:05.999999999Z07:00", false) return time.Parse("2006-01-02T15:04:05.999999999Z07:00", v)
case "datetime-local": case "datetime-local":
return parseTime(v, "2006-01-02T15:04:05.999999999", true) var t toml.LocalDateTime
err := t.UnmarshalText([]byte(v))
if err != nil {
return nil, fmt.Errorf("untag: %w", err)
}
return t, nil
case "date-local": case "date-local":
return parseTime(v, "2006-01-02", true) var t toml.LocalDate
err := t.UnmarshalText([]byte(v))
if err != nil {
return nil, fmt.Errorf("untag: %w", err)
}
return t, nil
case "time-local": case "time-local":
return parseTime(v, "15:04:05.999999999", true) var t toml.LocalTime
err := t.UnmarshalText([]byte(v))
if err != nil {
return nil, fmt.Errorf("untag: %w", err)
}
return t, nil
case "bool": case "bool":
switch v { switch v {
case "true": case "true":
+18
View File
@@ -48,3 +48,21 @@ func DecodeStdin() error {
return nil return nil
} }
// EncodeStdin is a helper function for the toml-test binary interface. Tagged
// JSON is read from STDIN and a resulting TOML representation is written to
// STDOUT.
func EncodeStdin() error {
var j interface{}
err := json.NewDecoder(os.Stdin).Decode(&j)
if err != nil {
return err
}
rm, err := rmTag(j)
if err != nil {
return fmt.Errorf("removing tags: %w", err)
}
return toml.NewEncoder(os.Stdout).Encode(rm)
}
+38 -36
View File
@@ -57,7 +57,11 @@ type SeenTracker struct {
currentIdx int currentIdx int
} }
var pool sync.Pool var pool = sync.Pool{
New: func() interface{} {
return &SeenTracker{}
},
}
func (s *SeenTracker) reset() { func (s *SeenTracker) reset() {
// Always contains a root element at index 0. // Always contains a root element at index 0.
@@ -149,8 +153,9 @@ func (s *SeenTracker) setExplicitFlag(parentIdx int) {
// CheckExpression takes a top-level node and checks that it does not contain // CheckExpression takes a top-level node and checks that it does not contain
// keys that have been seen in previous calls, and validates that types are // keys that have been seen in previous calls, and validates that types are
// consistent. // consistent. It returns true if it is the first time this node's key is seen.
func (s *SeenTracker) CheckExpression(node *unstable.Node) error { // Useful to clear array tables on first use.
func (s *SeenTracker) CheckExpression(node *unstable.Node) (bool, error) {
if s.entries == nil { if s.entries == nil {
s.reset() s.reset()
} }
@@ -166,7 +171,7 @@ func (s *SeenTracker) CheckExpression(node *unstable.Node) error {
} }
} }
func (s *SeenTracker) checkTable(node *unstable.Node) error { func (s *SeenTracker) checkTable(node *unstable.Node) (bool, error) {
if s.currentIdx >= 0 { if s.currentIdx >= 0 {
s.setExplicitFlag(s.currentIdx) s.setExplicitFlag(s.currentIdx)
} }
@@ -192,7 +197,7 @@ func (s *SeenTracker) checkTable(node *unstable.Node) error {
} else { } else {
entry := s.entries[idx] entry := s.entries[idx]
if entry.kind == valueKind { if entry.kind == valueKind {
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
} }
} }
parentIdx = idx parentIdx = idx
@@ -201,25 +206,27 @@ func (s *SeenTracker) checkTable(node *unstable.Node) error {
k := it.Node().Data k := it.Node().Data
idx := s.find(parentIdx, k) idx := s.find(parentIdx, k)
first := false
if idx >= 0 { if idx >= 0 {
kind := s.entries[idx].kind kind := s.entries[idx].kind
if kind != tableKind { if kind != tableKind {
return fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind) return false, fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind)
} }
if s.entries[idx].explicit { if s.entries[idx].explicit {
return fmt.Errorf("toml: table %s already exists", string(k)) return false, fmt.Errorf("toml: table %s already exists", string(k))
} }
s.entries[idx].explicit = true s.entries[idx].explicit = true
} else { } else {
idx = s.create(parentIdx, k, tableKind, true, false) idx = s.create(parentIdx, k, tableKind, true, false)
first = true
} }
s.currentIdx = idx s.currentIdx = idx
return nil return first, nil
} }
func (s *SeenTracker) checkArrayTable(node *unstable.Node) error { func (s *SeenTracker) checkArrayTable(node *unstable.Node) (bool, error) {
if s.currentIdx >= 0 { if s.currentIdx >= 0 {
s.setExplicitFlag(s.currentIdx) s.setExplicitFlag(s.currentIdx)
} }
@@ -242,7 +249,7 @@ func (s *SeenTracker) checkArrayTable(node *unstable.Node) error {
} else { } else {
entry := s.entries[idx] entry := s.entries[idx]
if entry.kind == valueKind { if entry.kind == valueKind {
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
} }
} }
@@ -252,22 +259,23 @@ func (s *SeenTracker) checkArrayTable(node *unstable.Node) error {
k := it.Node().Data k := it.Node().Data
idx := s.find(parentIdx, k) idx := s.find(parentIdx, k)
if idx >= 0 { firstTime := idx < 0
if firstTime {
idx = s.create(parentIdx, k, arrayTableKind, true, false)
} else {
kind := s.entries[idx].kind kind := s.entries[idx].kind
if kind != arrayTableKind { if kind != arrayTableKind {
return fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k)) return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k))
} }
s.clear(idx) s.clear(idx)
} else {
idx = s.create(parentIdx, k, arrayTableKind, true, false)
} }
s.currentIdx = idx s.currentIdx = idx
return nil return firstTime, nil
} }
func (s *SeenTracker) checkKeyValue(node *unstable.Node) error { func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) {
parentIdx := s.currentIdx parentIdx := s.currentIdx
it := node.Key() it := node.Key()
@@ -281,11 +289,11 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) error {
} else { } else {
entry := s.entries[idx] entry := s.entries[idx]
if it.IsLast() { if it.IsLast() {
return fmt.Errorf("toml: key %s is already defined", string(k)) return false, fmt.Errorf("toml: key %s is already defined", string(k))
} else if entry.kind != tableKind { } else if entry.kind != tableKind {
return fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
} else if entry.explicit { } else if entry.explicit {
return fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k)) return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))
} }
} }
@@ -303,45 +311,39 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) error {
return s.checkArray(value) return s.checkArray(value)
} }
return nil return false, nil
} }
func (s *SeenTracker) checkArray(node *unstable.Node) error { func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) {
it := node.Children() it := node.Children()
for it.Next() { for it.Next() {
n := it.Node() n := it.Node()
switch n.Kind { switch n.Kind {
case unstable.InlineTable: case unstable.InlineTable:
err := s.checkInlineTable(n) first, err = s.checkInlineTable(n)
if err != nil { if err != nil {
return err return false, err
} }
case unstable.Array: case unstable.Array:
err := s.checkArray(n) first, err = s.checkArray(n)
if err != nil { if err != nil {
return err return false, err
} }
} }
} }
return nil return first, nil
} }
func (s *SeenTracker) checkInlineTable(node *unstable.Node) error { func (s *SeenTracker) checkInlineTable(node *unstable.Node) (first bool, err error) {
if pool.New == nil {
pool.New = func() interface{} {
return &SeenTracker{}
}
}
s = pool.Get().(*SeenTracker) s = pool.Get().(*SeenTracker)
s.reset() s.reset()
it := node.Children() it := node.Children()
for it.Next() { for it.Next() {
n := it.Node() n := it.Node()
err := s.checkKeyValue(n) first, err = s.checkKeyValue(n)
if err != nil { if err != nil {
return err return false, err
} }
} }
@@ -352,5 +354,5 @@ func (s *SeenTracker) checkInlineTable(node *unstable.Node) error {
// redefinition of its keys: check* functions cannot walk into // redefinition of its keys: check* functions cannot walk into
// a value. // a value.
pool.Put(s) pool.Put(s)
return nil return first, nil
} }
+6 -2
View File
@@ -4,7 +4,7 @@ import (
"testing" "testing"
"unsafe" "unsafe"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func TestEntrySize(t *testing.T) { func TestEntrySize(t *testing.T) {
@@ -12,5 +12,9 @@ func TestEntrySize(t *testing.T) {
// performance of unmarshaling documents. Should only be increased with care // performance of unmarshaling documents. Should only be increased with care
// and a very good reason. // and a very good reason.
maxExpectedEntrySize := 48 maxExpectedEntrySize := 48
require.LessOrEqual(t, int(unsafe.Sizeof(entry{})), maxExpectedEntrySize) assert.True(t,
int(unsafe.Sizeof(entry{})) <= maxExpectedEntrySize,
"Expected entry to be less than or equal to %d, got: %d",
maxExpectedEntrySize, int(unsafe.Sizeof(entry{})),
)
} }
+28 -28
View File
@@ -5,73 +5,73 @@ import (
"time" "time"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func TestLocalDate_AsTime(t *testing.T) { func TestLocalDate_AsTime(t *testing.T) {
d := toml.LocalDate{2021, 6, 8} d := toml.LocalDate{2021, 6, 8}
cast := d.AsTime(time.UTC) cast := d.AsTime(time.UTC)
require.Equal(t, time.Date(2021, time.June, 8, 0, 0, 0, 0, time.UTC), cast) assert.Equal(t, time.Date(2021, time.June, 8, 0, 0, 0, 0, time.UTC), cast)
} }
func TestLocalDate_String(t *testing.T) { func TestLocalDate_String(t *testing.T) {
d := toml.LocalDate{2021, 6, 8} d := toml.LocalDate{2021, 6, 8}
require.Equal(t, "2021-06-08", d.String()) assert.Equal(t, "2021-06-08", d.String())
} }
func TestLocalDate_MarshalText(t *testing.T) { func TestLocalDate_MarshalText(t *testing.T) {
d := toml.LocalDate{2021, 6, 8} d := toml.LocalDate{2021, 6, 8}
b, err := d.MarshalText() b, err := d.MarshalText()
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, []byte("2021-06-08"), b) assert.Equal(t, []byte("2021-06-08"), b)
} }
func TestLocalDate_UnmarshalMarshalText(t *testing.T) { func TestLocalDate_UnmarshalMarshalText(t *testing.T) {
d := toml.LocalDate{} d := toml.LocalDate{}
err := d.UnmarshalText([]byte("2021-06-08")) err := d.UnmarshalText([]byte("2021-06-08"))
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, toml.LocalDate{2021, 6, 8}, d) assert.Equal(t, toml.LocalDate{2021, 6, 8}, d)
err = d.UnmarshalText([]byte("what")) err = d.UnmarshalText([]byte("what"))
require.Error(t, err) assert.Error(t, err)
} }
func TestLocalTime_String(t *testing.T) { func TestLocalTime_String(t *testing.T) {
d := toml.LocalTime{20, 12, 1, 2, 9} d := toml.LocalTime{20, 12, 1, 2, 9}
require.Equal(t, "20:12:01.000000002", d.String()) assert.Equal(t, "20:12:01.000000002", d.String())
d = toml.LocalTime{20, 12, 1, 0, 0} d = toml.LocalTime{20, 12, 1, 0, 0}
require.Equal(t, "20:12:01", d.String()) assert.Equal(t, "20:12:01", d.String())
d = toml.LocalTime{20, 12, 1, 0, 9} d = toml.LocalTime{20, 12, 1, 0, 9}
require.Equal(t, "20:12:01.000000000", d.String()) assert.Equal(t, "20:12:01.000000000", d.String())
d = toml.LocalTime{20, 12, 1, 100, 0} d = toml.LocalTime{20, 12, 1, 100, 0}
require.Equal(t, "20:12:01.0000001", d.String()) assert.Equal(t, "20:12:01.0000001", d.String())
} }
func TestLocalTime_MarshalText(t *testing.T) { func TestLocalTime_MarshalText(t *testing.T) {
d := toml.LocalTime{20, 12, 1, 2, 9} d := toml.LocalTime{20, 12, 1, 2, 9}
b, err := d.MarshalText() b, err := d.MarshalText()
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, []byte("20:12:01.000000002"), b) assert.Equal(t, []byte("20:12:01.000000002"), b)
} }
func TestLocalTime_UnmarshalMarshalText(t *testing.T) { func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
d := toml.LocalTime{} d := toml.LocalTime{}
err := d.UnmarshalText([]byte("20:12:01.000000002")) err := d.UnmarshalText([]byte("20:12:01.000000002"))
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, toml.LocalTime{20, 12, 1, 2, 9}, d) assert.Equal(t, toml.LocalTime{20, 12, 1, 2, 9}, d)
err = d.UnmarshalText([]byte("what")) err = d.UnmarshalText([]byte("what"))
require.Error(t, err) assert.Error(t, err)
err = d.UnmarshalText([]byte("20:12:01.000000002 bad")) err = d.UnmarshalText([]byte("20:12:01.000000002 bad"))
require.Error(t, err) assert.Error(t, err)
} }
func TestLocalTime_RoundTrip(t *testing.T) { func TestLocalTime_RoundTrip(t *testing.T) {
var d struct{ A toml.LocalTime } var d struct{ A toml.LocalTime }
err := toml.Unmarshal([]byte("a=20:12:01.500"), &d) err := toml.Unmarshal([]byte("a=20:12:01.500"), &d)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "20:12:01.500", d.A.String()) assert.Equal(t, "20:12:01.500", d.A.String())
} }
func TestLocalDateTime_AsTime(t *testing.T) { func TestLocalDateTime_AsTime(t *testing.T) {
@@ -80,7 +80,7 @@ func TestLocalDateTime_AsTime(t *testing.T) {
toml.LocalTime{20, 12, 1, 2, 9}, toml.LocalTime{20, 12, 1, 2, 9},
} }
cast := d.AsTime(time.UTC) cast := d.AsTime(time.UTC)
require.Equal(t, time.Date(2021, time.June, 8, 20, 12, 1, 2, time.UTC), cast) assert.Equal(t, time.Date(2021, time.June, 8, 20, 12, 1, 2, time.UTC), cast)
} }
func TestLocalDateTime_String(t *testing.T) { func TestLocalDateTime_String(t *testing.T) {
@@ -88,7 +88,7 @@ func TestLocalDateTime_String(t *testing.T) {
toml.LocalDate{2021, 6, 8}, toml.LocalDate{2021, 6, 8},
toml.LocalTime{20, 12, 1, 2, 9}, toml.LocalTime{20, 12, 1, 2, 9},
} }
require.Equal(t, "2021-06-08T20:12:01.000000002", d.String()) assert.Equal(t, "2021-06-08T20:12:01.000000002", d.String())
} }
func TestLocalDateTime_MarshalText(t *testing.T) { func TestLocalDateTime_MarshalText(t *testing.T) {
@@ -97,22 +97,22 @@ func TestLocalDateTime_MarshalText(t *testing.T) {
toml.LocalTime{20, 12, 1, 2, 9}, toml.LocalTime{20, 12, 1, 2, 9},
} }
b, err := d.MarshalText() b, err := d.MarshalText()
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, []byte("2021-06-08T20:12:01.000000002"), b) assert.Equal(t, []byte("2021-06-08T20:12:01.000000002"), b)
} }
func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) { func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) {
d := toml.LocalDateTime{} d := toml.LocalDateTime{}
err := d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002")) err := d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002"))
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, toml.LocalDateTime{ assert.Equal(t, toml.LocalDateTime{
toml.LocalDate{2021, 6, 8}, toml.LocalDate{2021, 6, 8},
toml.LocalTime{20, 12, 1, 2, 9}, toml.LocalTime{20, 12, 1, 2, 9},
}, d) }, d)
err = d.UnmarshalText([]byte("what")) err = d.UnmarshalText([]byte("what"))
require.Error(t, err) assert.Error(t, err)
err = d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002 bad")) err = d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002 bad"))
require.Error(t, err) assert.Error(t, err)
} }
+52 -9
View File
@@ -3,11 +3,12 @@ package toml
import ( import (
"bytes" "bytes"
"encoding" "encoding"
"encoding/json"
"fmt" "fmt"
"io" "io"
"math" "math"
"reflect" "reflect"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -37,10 +38,11 @@ type Encoder struct {
w io.Writer w io.Writer
// global settings // global settings
tablesInline bool tablesInline bool
arraysMultiline bool arraysMultiline bool
indentSymbol string indentSymbol string
indentTables bool indentTables bool
marshalJsonNumbers bool
} }
// NewEncoder returns a new Encoder that writes to w. // NewEncoder returns a new Encoder that writes to w.
@@ -87,6 +89,17 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
return enc return enc
} }
// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a
// float or integer instead of relying on TextMarshaler to emit a string.
//
// *Unstable:* This method does not follow the compatibility guarantees of
// semver. It can be changed or removed without a new major version being
// issued.
func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder {
enc.marshalJsonNumbers = indent
return enc
}
// Encode writes a TOML representation of v to the stream. // Encode writes a TOML representation of v to the stream.
// //
// If v cannot be represented to TOML it returns an error. // If v cannot be represented to TOML it returns an error.
@@ -252,10 +265,22 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
return append(b, x.String()...), nil return append(b, x.String()...), nil
case LocalDateTime: case LocalDateTime:
return append(b, x.String()...), nil return append(b, x.String()...), nil
case json.Number:
if enc.marshalJsonNumbers {
if x == "" { /// Useful zero value.
return append(b, "0"...), nil
} else if v, err := x.Int64(); err == nil {
return enc.encode(b, ctx, reflect.ValueOf(v))
} else if f, err := x.Float64(); err == nil {
return enc.encode(b, ctx, reflect.ValueOf(f))
} else {
return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x)
}
}
} }
hasTextMarshaler := v.Type().Implements(textMarshalerType) hasTextMarshaler := v.Type().Implements(textMarshalerType)
if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) { if hasTextMarshaler || (v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
if !hasTextMarshaler { if !hasTextMarshaler {
v = v.Addr() v = v.Addr()
} }
@@ -606,6 +631,18 @@ func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err) return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
} }
return string(keyB), nil return string(keyB), nil
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
return strconv.FormatInt(k.Int(), 10), nil
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
return strconv.FormatUint(k.Uint(), 10), nil
case keyType.Kind() == reflect.Float32:
return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil
case keyType.Kind() == reflect.Float64:
return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil
} }
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind()) return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
} }
@@ -643,8 +680,8 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte
} }
func sortEntriesByKey(e []entry) { func sortEntriesByKey(e []entry) {
sort.Slice(e, func(i, j int) bool { slices.SortFunc(e, func(a, b entry) int {
return e[i].Key < e[j].Key return strings.Compare(a.Key, b.Key)
}) })
} }
@@ -707,6 +744,8 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
if fieldType.Anonymous { if fieldType.Anonymous {
if fieldType.Type.Kind() == reflect.Struct { if fieldType.Type.Kind() == reflect.Struct {
walkStruct(ctx, t, f) walkStruct(ctx, t, f)
} else if fieldType.Type.Kind() == reflect.Ptr && !f.IsNil() && f.Elem().Kind() == reflect.Struct {
walkStruct(ctx, t, f.Elem())
} }
continue continue
} else { } else {
@@ -924,7 +963,7 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
if !v.IsValid() { if !v.IsValid() {
return false return false
} }
if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) { if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
return false return false
} }
@@ -998,6 +1037,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
scratch = enc.commented(ctx.commented, scratch) scratch = enc.commented(ctx.commented, scratch)
if enc.indentTables {
scratch = enc.indent(ctx.indent, scratch)
}
scratch = append(scratch, "[["...) scratch = append(scratch, "[["...)
for i, k := range ctx.parentKey { for i, k := range ctx.parentKey {
+242 -71
View File
@@ -6,13 +6,13 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/assert" "github.com/pelletier/go-toml/v2/internal/assert"
"github.com/stretchr/testify/require"
) )
type marshalTextKey struct { type marshalTextKey struct {
@@ -30,6 +30,27 @@ func (k marshalBadTextKey) MarshalText() ([]byte, error) {
return nil, fmt.Errorf("error") return nil, fmt.Errorf("error")
} }
func toFloat(x interface{}) float64 {
// Shortened version of testify/toFloat
var xf float64
switch xn := x.(type) {
case float32:
xf = float64(xn)
case float64:
xf = xn
}
return xf
}
func inDelta(t *testing.T, expected, actual interface{}, delta float64) {
dt := toFloat(expected) - toFloat(actual)
assert.True(t,
dt < -delta && dt < delta,
"Difference between %v and %v is %v, but difference was %v",
expected, actual, delta, dt,
)
}
func TestMarshal(t *testing.T) { func TestMarshal(t *testing.T) {
someInt := 42 someInt := 42
@@ -587,13 +608,69 @@ foo = 42
`, `,
}, },
{ {
desc: "invalid map key", desc: "int map key",
v: map[int]interface{}{1: "a"}, v: map[int]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "int8 map key",
v: map[int8]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "int64 map key",
v: map[int64]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "uint map key",
v: map[uint]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "uint8 map key",
v: map[uint8]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "uint64 map key",
v: map[uint64]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "float32 map key",
v: map[float32]interface{}{
1.1: "a",
1.0020: "b",
},
expected: `'1.002' = 'b'
'1.1' = 'a'
`,
},
{
desc: "float64 map key",
v: map[float64]interface{}{
1.1: "a",
1.0020: "b",
},
expected: `'1.002' = 'b'
'1.1' = 'a'
`,
},
{
desc: "invalid map key",
v: map[struct{ int }]interface{}{{1}: "a"},
err: true, err: true,
}, },
{ {
desc: "invalid map key but empty", desc: "invalid map key but empty",
v: map[int]interface{}{}, v: map[struct{ int }]interface{}{},
expected: "", expected: "",
}, },
{ {
@@ -708,18 +785,18 @@ Three = [1, 2, 3]
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
b, err := toml.Marshal(e.v) b, err := toml.Marshal(e.v)
if e.err { if e.err {
require.Error(t, err) assert.Error(t, err)
return return
} }
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(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{}{}
err = toml.Unmarshal(b, &defaultMap) err = toml.Unmarshal(b, &defaultMap)
require.NoError(t, err) assert.NoError(t, err)
testWithAllFlags(t, func(t *testing.T, flags int) { testWithAllFlags(t, func(t *testing.T, flags int) {
t.Helper() t.Helper()
@@ -729,13 +806,13 @@ Three = [1, 2, 3]
setFlags(enc, flags) setFlags(enc, flags)
err := enc.Encode(e.v) err := enc.Encode(e.v)
require.NoError(t, err) assert.NoError(t, err)
inlineMap := map[string]interface{}{} inlineMap := map[string]interface{}{}
err = toml.Unmarshal(buf.Bytes(), &inlineMap) err = toml.Unmarshal(buf.Bytes(), &inlineMap)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, defaultMap, inlineMap) assert.Equal(t, defaultMap, inlineMap)
}) })
}) })
} }
@@ -802,8 +879,8 @@ nan = nan
` `
actual, err := toml.Marshal(v) actual, err := toml.Marshal(v)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, expected, string(actual)) assert.Equal(t, expected, string(actual))
v64 := map[string]float64{ v64 := map[string]float64{
"nan": math.NaN(), "nan": math.NaN(),
@@ -812,8 +889,8 @@ nan = nan
} }
actual, err = toml.Marshal(v64) actual, err = toml.Marshal(v64)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, expected, string(actual)) assert.Equal(t, expected, string(actual))
} }
//nolint:funlen //nolint:funlen
@@ -873,7 +950,7 @@ func TestMarshalIndentTables(t *testing.T) {
enc := toml.NewEncoder(&buf) enc := toml.NewEncoder(&buf)
enc.SetIndentTables(true) enc.SetIndentTables(true)
err := enc.Encode(e.v) err := enc.Encode(e.v)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, e.expected, buf.String()) assert.Equal(t, e.expected, buf.String())
}) })
} }
@@ -893,13 +970,13 @@ func (c *customTextMarshaler) MarshalText() ([]byte, error) {
func TestMarshalTextMarshaler_NoRoot(t *testing.T) { func TestMarshalTextMarshaler_NoRoot(t *testing.T) {
c := customTextMarshaler{} c := customTextMarshaler{}
_, err := toml.Marshal(&c) _, err := toml.Marshal(&c)
require.Error(t, err) assert.Error(t, err)
} }
func TestMarshalTextMarshaler_Error(t *testing.T) { func TestMarshalTextMarshaler_Error(t *testing.T) {
m := map[string]interface{}{"a": &customTextMarshaler{value: 1}} m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
_, err := toml.Marshal(m) _, err := toml.Marshal(m)
require.Error(t, err) assert.Error(t, err)
} }
func TestMarshalTextMarshaler_ErrorInline(t *testing.T) { func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
@@ -912,13 +989,13 @@ func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
} }
_, err := toml.Marshal(d) _, err := toml.Marshal(d)
require.Error(t, err) assert.Error(t, err)
} }
func TestMarshalTextMarshaler(t *testing.T) { 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) assert.NoError(t, err)
assert.Equal(t, "a = '::2'\n", string(r)) assert.Equal(t, "a = '::2'\n", string(r))
} }
@@ -932,7 +1009,7 @@ func TestEncodeToBrokenWriter(t *testing.T) {
w := brokenWriter{} w := brokenWriter{}
enc := toml.NewEncoder(&w) enc := toml.NewEncoder(&w)
err := enc.Encode(map[string]string{"hello": "world"}) err := enc.Encode(map[string]string{"hello": "world"})
require.Error(t, err) assert.Error(t, err)
} }
func TestEncoderSetIndentSymbol(t *testing.T) { func TestEncoderSetIndentSymbol(t *testing.T) {
@@ -941,13 +1018,36 @@ func TestEncoderSetIndentSymbol(t *testing.T) {
enc.SetIndentTables(true) enc.SetIndentTables(true)
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) assert.NoError(t, err)
expected := `[parent] expected := `[parent]
>>>hello = 'world' >>>hello = 'world'
` `
assert.Equal(t, expected, w.String()) assert.Equal(t, expected, w.String())
} }
func TestEncoderSetMarshalJsonNumbers(t *testing.T) {
var w strings.Builder
enc := toml.NewEncoder(&w)
enc.SetMarshalJsonNumbers(true)
err := enc.Encode(map[string]interface{}{
"A": json.Number("1.1"),
"B": json.Number("42e-3"),
"C": json.Number("42"),
"D": json.Number("0"),
"E": json.Number("0.0"),
"F": json.Number(""),
})
assert.NoError(t, err)
expected := `A = 1.1
B = 0.042
C = 42
D = 0
E = 0.0
F = 0
`
assert.Equal(t, expected, w.String())
}
func TestEncoderOmitempty(t *testing.T) { func TestEncoderOmitempty(t *testing.T) {
type doc struct { type doc struct {
String string `toml:",omitempty,multiline"` String string `toml:",omitempty,multiline"`
@@ -974,7 +1074,7 @@ func TestEncoderOmitempty(t *testing.T) {
d := doc{} d := doc{}
b, err := toml.Marshal(d) b, err := toml.Marshal(d)
require.NoError(t, err) assert.NoError(t, err)
expected := `` expected := ``
@@ -991,7 +1091,7 @@ func TestEncoderTagFieldName(t *testing.T) {
d := doc{String: "world"} d := doc{String: "world"}
b, err := toml.Marshal(d) b, err := toml.Marshal(d)
require.NoError(t, err) assert.NoError(t, err)
expected := `hello = 'world' expected := `hello = 'world'
'#' = '' '#' = ''
@@ -1006,11 +1106,11 @@ func TestIssue436(t *testing.T) {
var v interface{} var v interface{}
err := json.Unmarshal(data, &v) err := json.Unmarshal(data, &v)
require.NoError(t, err) assert.NoError(t, err)
var buf bytes.Buffer var buf bytes.Buffer
err = toml.NewEncoder(&buf).Encode(v) err = toml.NewEncoder(&buf).Encode(v)
require.NoError(t, err) assert.NoError(t, err)
expected := `[[a]] expected := `[[a]]
[a.b] [a.b]
@@ -1032,27 +1132,30 @@ func TestIssue424(t *testing.T) {
msg2 := Message2{"Hello\\World"} msg2 := Message2{"Hello\\World"}
toml1, err := toml.Marshal(msg1) toml1, err := toml.Marshal(msg1)
require.NoError(t, err) assert.NoError(t, err)
toml2, err := toml.Marshal(msg2) toml2, err := toml.Marshal(msg2)
require.NoError(t, err) assert.NoError(t, err)
msg1parsed := Message1{} msg1parsed := Message1{}
err = toml.Unmarshal(toml1, &msg1parsed) err = toml.Unmarshal(toml1, &msg1parsed)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, msg1, msg1parsed) assert.Equal(t, msg1, msg1parsed)
msg2parsed := Message2{} msg2parsed := Message2{}
err = toml.Unmarshal(toml2, &msg2parsed) err = toml.Unmarshal(toml2, &msg2parsed)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, msg2, msg2parsed) assert.Equal(t, msg2, msg2parsed)
} }
func TestIssue567(t *testing.T) { func TestIssue567(t *testing.T) {
var m map[string]interface{} var m map[string]interface{}
err := toml.Unmarshal([]byte("A = 12:08:05"), &m) err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
require.NoError(t, err) assert.NoError(t, err)
require.IsType(t, m["A"], toml.LocalTime{}) assert.Equal(t,
reflect.TypeOf(m["A"]), reflect.TypeOf(toml.LocalTime{}),
"Expected type '%v', got: %v", reflect.TypeOf(m["A"]), reflect.TypeOf(toml.LocalTime{}),
)
} }
func TestIssue590(t *testing.T) { func TestIssue590(t *testing.T) {
@@ -1061,7 +1164,7 @@ func TestIssue590(t *testing.T) {
Option CustomType `toml:"option"` Option CustomType `toml:"option"`
} }
err := toml.Unmarshal([]byte("option = 42"), &cfg) err := toml.Unmarshal([]byte("option = 42"), &cfg)
require.NoError(t, err) assert.NoError(t, err)
} }
func TestIssue571(t *testing.T) { func TestIssue571(t *testing.T) {
@@ -1077,14 +1180,14 @@ func TestIssue571(t *testing.T) {
Float64: 43, Float64: 43,
} }
b, err := toml.Marshal(foo) b, err := toml.Marshal(foo)
require.NoError(t, err) assert.NoError(t, err)
var foo2 Foo var foo2 Foo
err = toml.Unmarshal(b, &foo2) err = toml.Unmarshal(b, &foo2)
require.NoError(t, err) assert.NoError(t, err)
assert.InDelta(t, 42, foo2.Float32, closeEnough) inDelta(t, 42, foo2.Float32, closeEnough)
assert.InDelta(t, 43, foo2.Float64, closeEnough) inDelta(t, 43, foo2.Float64, closeEnough)
} }
func TestIssue678(t *testing.T) { func TestIssue678(t *testing.T) {
@@ -1097,13 +1200,13 @@ func TestIssue678(t *testing.T) {
} }
out, err := toml.Marshal(cfg) out, err := toml.Marshal(cfg)
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "BigInt = '123'\n", string(out)) assert.Equal(t, "BigInt = '123'\n", string(out))
cfg2 := &Config{} cfg2 := &Config{}
err = toml.Unmarshal(out, cfg2) err = toml.Unmarshal(out, cfg2)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, cfg, cfg2) assert.Equal(t, cfg, cfg2)
} }
func TestIssue752(t *testing.T) { func TestIssue752(t *testing.T) {
@@ -1118,8 +1221,8 @@ func TestIssue752(t *testing.T) {
c := Container{} c := Container{}
out, err := toml.Marshal(c) out, err := toml.Marshal(c)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "", string(out)) assert.Equal(t, "", string(out))
} }
func TestIssue768(t *testing.T) { func TestIssue768(t *testing.T) {
@@ -1128,14 +1231,14 @@ func TestIssue768(t *testing.T) {
} }
out, err := toml.Marshal(&cfg{}) out, err := toml.Marshal(&cfg{})
require.NoError(t, err) assert.NoError(t, err)
expected := `# This is a multiline comment. expected := `# This is a multiline comment.
# This is line 2. # This is line 2.
Name = '' Name = ''
` `
require.Equal(t, expected, string(out)) assert.Equal(t, expected, string(out))
} }
func TestIssue786(t *testing.T) { func TestIssue786(t *testing.T) {
@@ -1151,9 +1254,9 @@ func TestIssue786(t *testing.T) {
x := Test{} x := Test{}
b, err := toml.Marshal(x) b, err := toml.Marshal(x)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, "", string(b)) assert.Equal(t, "", string(b))
type General struct { type General struct {
From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"` From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"`
@@ -1198,10 +1301,10 @@ from = '-2d'
randomize = true randomize = true
` `
require.Equal(t, expected, buf.String()) assert.Equal(t, expected, buf.String())
} }
func TestMarhsalIssue888(t *testing.T) { func TestMarshalIssue888(t *testing.T) {
type Thing struct { type Thing struct {
FieldA string `comment:"my field A"` FieldA string `comment:"my field A"`
FieldB string `comment:"my field B"` FieldB string `comment:"my field B"`
@@ -1237,7 +1340,7 @@ func TestMarhsalIssue888(t *testing.T) {
FieldB = 'field b 2' FieldB = 'field b 2'
` `
require.Equal(t, expected, buf.String()) assert.Equal(t, expected, buf.String())
} }
func TestMarshalNestedAnonymousStructs(t *testing.T) { func TestMarshalNestedAnonymousStructs(t *testing.T) {
@@ -1273,8 +1376,8 @@ value = ''
` `
result, err := toml.Marshal(doc) result, err := toml.Marshal(doc)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, expected, string(result)) assert.Equal(t, expected, string(result))
} }
func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) { func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
@@ -1299,9 +1402,41 @@ value = ''
` `
result, err := toml.Marshal(doc) result, err := toml.Marshal(doc)
require.NoError(t, err) assert.NoError(t, err)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, expected, string(result)) assert.Equal(t, expected, string(result))
}
func TestMarshalNestedAnonymousStructs_PointerEmbedded(t *testing.T) {
type Embedded struct {
Value string `toml:"value" json:"value"`
Omitted string `toml:"omitted,omitempty"`
Ptr *string `toml:"ptr"`
}
type Named struct {
Value string `toml:"value" json:"value"`
}
type Doc struct {
*Embedded
*Named `toml:"named" json:"named"`
Anonymous struct {
*Embedded
Value *string `toml:"value" json:"value"`
} `toml:"anonymous,omitempty" json:"anonymous,omitempty"`
}
doc := &Doc{
Embedded: &Embedded{Value: "foo"},
}
expected := `value = 'foo'
`
result, err := toml.Marshal(doc)
assert.NoError(t, err)
assert.Equal(t, expected, string(result))
} }
func TestLocalTime(t *testing.T) { func TestLocalTime(t *testing.T) {
@@ -1318,12 +1453,12 @@ func TestLocalTime(t *testing.T) {
` `
out, err := toml.Marshal(v) out, err := toml.Marshal(v)
require.NoError(t, err) assert.NoError(t, err)
require.Equal(t, expected, string(out)) assert.Equal(t, expected, string(out))
} }
func TestMarshalUint64Overflow(t *testing.T) { func TestMarshalUint64Overflow(t *testing.T) {
// The TOML spec only requires implementation to provide support for the // The TOML spec only asserts implementation to provide support for the
// int64 range. To avoid generating TOML documents that would not be // int64 range. To avoid generating TOML documents that would not be
// supported by standard-compliant parsers, uint64 > max int64 cannot be // supported by standard-compliant parsers, uint64 > max int64 cannot be
// marshaled. // marshaled.
@@ -1332,7 +1467,7 @@ func TestMarshalUint64Overflow(t *testing.T) {
} }
_, err := toml.Marshal(x) _, err := toml.Marshal(x)
require.Error(t, err) assert.Error(t, err)
} }
func TestIndentWithInlineTable(t *testing.T) { func TestIndentWithInlineTable(t *testing.T) {
@@ -1352,7 +1487,7 @@ func TestIndentWithInlineTable(t *testing.T) {
enc.SetIndentTables(true) enc.SetIndentTables(true)
enc.SetTablesInline(true) enc.SetTablesInline(true)
enc.SetArraysMultiline(true) enc.SetArraysMultiline(true)
require.NoError(t, enc.Encode(x)) assert.NoError(t, enc.Encode(x))
assert.Equal(t, expected, buf.String()) assert.Equal(t, expected, buf.String())
} }
@@ -1409,7 +1544,7 @@ func TestMarshalCommented(t *testing.T) {
} }
out, err := toml.Marshal(c) out, err := toml.Marshal(c)
require.NoError(t, err) assert.NoError(t, err)
expected := `# Int = 42 expected := `# Int = 42
# String = 'root' # String = 'root'
@@ -1441,7 +1576,44 @@ func TestMarshalCommented(t *testing.T) {
# Values = [4, 5, 6] # Values = [4, 5, 6]
` `
require.Equal(t, expected, string(out)) assert.Equal(t, expected, string(out))
}
func TestMarshalIndentedCustomTypeArray(t *testing.T) {
c := struct {
Nested struct {
NestedArray []struct {
Value int
}
}
}{
Nested: struct {
NestedArray []struct {
Value int
}
}{
NestedArray: []struct {
Value int
}{
{Value: 1},
{Value: 2},
},
},
}
expected := `[Nested]
[[Nested.NestedArray]]
Value = 1
[[Nested.NestedArray]]
Value = 2
`
var buf bytes.Buffer
enc := toml.NewEncoder(&buf)
enc.SetIndentTables(true)
assert.NoError(t, enc.Encode(c))
assert.Equal(t, expected, buf.String())
} }
func ExampleMarshal() { func ExampleMarshal() {
@@ -1473,7 +1645,6 @@ func ExampleMarshal() {
// configuration file that has commented out sections (example from // configuration file that has commented out sections (example from
// go-graphite/graphite-clickhouse). // go-graphite/graphite-clickhouse).
func ExampleMarshal_commented() { func ExampleMarshal_commented() {
type Common struct { type Common struct {
Listen string `toml:"listen" comment:"general listener"` Listen string `toml:"listen" comment:"general listener"`
PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"` PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`
@@ -1482,15 +1653,15 @@ func ExampleMarshal_commented() {
} }
type Costs struct { type Costs struct {
Cost *int `toml:"cost" comment:"default cost (for wildcarded equalence or matched with regex, or if no value cost set)"` Cost *int `toml:"cost" comment:"default cost (for wildcarded equivalence or matched with regex, or if no value cost set)"`
ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equalence without wildcards) (additional tuning, usually not needed)"` ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equivalence without wildcards) (additional tuning, usually not needed)"`
} }
type ClickHouse struct { type ClickHouse struct {
URL string `toml:"url" comment:"default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params"` URL string `toml:"url" comment:"default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params"`
RenderMaxQueries int `toml:"render-max-queries" comment:"Max queries to render queiries"` RenderMaxQueries int `toml:"render-max-queries" comment:"Max queries to render queries"`
RenderConcurrentQueries int `toml:"render-concurrent-queries" comment:"Concurrent queries to render queiries"` RenderConcurrentQueries int `toml:"render-concurrent-queries" comment:"Concurrent queries to render queries"`
TaggedCosts map[string]*Costs `toml:"tagged-costs,commented"` TaggedCosts map[string]*Costs `toml:"tagged-costs,commented"`
TreeTable string `toml:"tree-table,commented"` TreeTable string `toml:"tree-table,commented"`
ReverseTreeTable string `toml:"reverse-tree-table,commented"` ReverseTreeTable string `toml:"reverse-tree-table,commented"`
@@ -1564,9 +1735,9 @@ func ExampleMarshal_commented() {
// [clickhouse] // [clickhouse]
// # default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params // # default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params
// url = 'http://localhost:8123?cancel_http_readonly_queries_on_client_close=1' // url = 'http://localhost:8123?cancel_http_readonly_queries_on_client_close=1'
// # Max queries to render queiries // # Max queries to render queries
// render-max-queries = 0 // render-max-queries = 0
// # Concurrent queries to render queiries // # Concurrent queries to render queries
// render-concurrent-queries = 0 // render-concurrent-queries = 0
// # tree-table = '' // # tree-table = ''
// # reverse-tree-table = '' // # reverse-tree-table = ''
@@ -1612,7 +1783,7 @@ func TestReadmeComments(t *testing.T) {
}, },
} }
out, err := toml.Marshal(example) out, err := toml.Marshal(example)
require.NoError(t, err) assert.NoError(t, err)
expected := `# Host IP to connect to. expected := `# Host IP to connect to.
host = '127.0.0.1' host = '127.0.0.1'
@@ -1624,5 +1795,5 @@ port = 4242
# cipher = 'AEAD-AES128-GCM-SHA256' # cipher = 'AEAD-AES128-GCM-SHA256'
# version = 'TLS 1.3' # version = 'TLS 1.3'
` `
require.Equal(t, expected, string(out)) assert.Equal(t, expected, string(out))
} }
-3
View File
@@ -1,6 +1,3 @@
//go:build go1.18 || go1.19 || go1.20 || go1.21
// +build go1.18 go1.19 go1.20 go1.21
package ossfuzz package ossfuzz
import ( import (
+5 -4
View File
@@ -1,3 +1,4 @@
//go:generate go run github.com/toml-lang/toml-test/cmd/toml-test@master -copy ./tests
//go:generate go run ./cmd/tomltestgen/main.go -o toml_testgen_test.go //go:generate go run ./cmd/tomltestgen/main.go -o toml_testgen_test.go
// This is a support file for toml_testgen_test.go // This is a support file for toml_testgen_test.go
@@ -8,8 +9,8 @@ import (
"testing" "testing"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/pelletier/go-toml/v2/internal/assert"
"github.com/pelletier/go-toml/v2/internal/testsuite" "github.com/pelletier/go-toml/v2/internal/testsuite"
"github.com/stretchr/testify/require"
) )
func testgenInvalid(t *testing.T, input string) { func testgenInvalid(t *testing.T, input string) {
@@ -44,15 +45,15 @@ func testgenValid(t *testing.T, input string, jsonRef string) {
t.Fatalf("failed parsing toml: %s", err) t.Fatalf("failed parsing toml: %s", err)
} }
j, err := testsuite.ValueToTaggedJSON(doc) j, err := testsuite.ValueToTaggedJSON(doc)
require.NoError(t, err) assert.NoError(t, err)
var ref interface{} var ref interface{}
err = json.Unmarshal([]byte(jsonRef), &ref) err = json.Unmarshal([]byte(jsonRef), &ref)
require.NoError(t, err) assert.NoError(t, err)
var actual interface{} var actual interface{}
err = json.Unmarshal([]byte(j), &actual) err = json.Unmarshal([]byte(j), &actual)
require.NoError(t, err) assert.NoError(t, err)
testsuite.CmpJSON(t, "", ref, actual) testsuite.CmpJSON(t, "", ref, actual)
} }
+1365 -352
View File
File diff suppressed because it is too large Load Diff
+86 -16
View File
@@ -5,9 +5,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math" "math"
"reflect" "reflect"
"strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -21,10 +21,8 @@ import (
// //
// It is a shortcut for Decoder.Decode() with the default options. // It is a shortcut for Decoder.Decode() with the default options.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
p := unstable.Parser{} d := decoder{}
p.Reset(data) d.p.Reset(data)
d := decoder{p: &p}
return d.FromParser(v) return d.FromParser(v)
} }
@@ -35,6 +33,9 @@ type Decoder struct {
// global settings // global settings
strict bool strict bool
// toggles unmarshaler interface
unmarshalerInterface bool
} }
// NewDecoder creates a new Decoder that will read from r. // NewDecoder creates a new Decoder that will read from r.
@@ -54,6 +55,24 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
return d return d
} }
// EnableUnmarshalerInterface allows to enable unmarshaler interface.
//
// With this feature enabled, types implementing the unstable/Unmarshaler
// interface can be decoded from any structure of the document. It allows types
// that don't have a straightforward TOML representation to provide their own
// decoding logic.
//
// Currently, types can only decode from a single value. Tables and array tables
// are not supported.
//
// *Unstable:* This method does not follow the compatibility guarantees of
// semver. It can be changed or removed without a new major version being
// issued.
func (d *Decoder) EnableUnmarshalerInterface() *Decoder {
d.unmarshalerInterface = true
return d
}
// Decode the whole content of r into v. // Decode the whole content of r into v.
// //
// By default, values in the document that don't exist in the target Go value // By default, values in the document that don't exist in the target Go value
@@ -96,26 +115,25 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
// Inline Table -> same as Table // Inline Table -> same as Table
// Array of Tables -> same as Array and Table // Array of Tables -> same as Array and Table
func (d *Decoder) Decode(v interface{}) error { func (d *Decoder) Decode(v interface{}) error {
b, err := ioutil.ReadAll(d.r) b, err := io.ReadAll(d.r)
if err != nil { if err != nil {
return fmt.Errorf("toml: %w", err) return fmt.Errorf("toml: %w", err)
} }
p := unstable.Parser{}
p.Reset(b)
dec := decoder{ dec := decoder{
p: &p,
strict: strict{ strict: strict{
Enabled: d.strict, Enabled: d.strict,
}, },
unmarshalerInterface: d.unmarshalerInterface,
} }
dec.p.Reset(b)
return dec.FromParser(v) return dec.FromParser(v)
} }
type decoder struct { type decoder struct {
// Which parser instance in use for this decoding session. // Which parser instance in use for this decoding session.
p *unstable.Parser p unstable.Parser
// Flag indicating that the current expression is stashed. // Flag indicating that the current expression is stashed.
// If set to true, calling nextExpr will not actually pull a new expression // If set to true, calling nextExpr will not actually pull a new expression
@@ -127,6 +145,10 @@ type decoder struct {
// need to be skipped. // need to be skipped.
skipUntilTable bool skipUntilTable bool
// Flag indicating that the current array/slice table should be cleared because
// it is the first encounter of an array table.
clearArrayTable bool
// Tracks position in Go arrays. // Tracks position in Go arrays.
// This is used when decoding [[array tables]] into Go arrays. Given array // This is used when decoding [[array tables]] into Go arrays. Given array
// tables are separate TOML expression, we need to keep track of where we // tables are separate TOML expression, we need to keep track of where we
@@ -139,6 +161,9 @@ type decoder struct {
// Strict mode // Strict mode
strict strict strict strict
// Flag that enables/disables unmarshaler interface.
unmarshalerInterface bool
// Current context for the error. // Current context for the error.
errorContext *errorContext errorContext *errorContext
} }
@@ -246,9 +271,10 @@ Rules for the unmarshal code:
func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) error { func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) error {
var x reflect.Value var x reflect.Value
var err error var err error
var first bool // used for to clear array tables on first use
if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) { if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) {
err = d.seen.CheckExpression(expr) first, err = d.seen.CheckExpression(expr)
if err != nil { if err != nil {
return err return err
} }
@@ -267,6 +293,7 @@ func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) err
case unstable.ArrayTable: case unstable.ArrayTable:
d.skipUntilTable = false d.skipUntilTable = false
d.strict.EnterArrayTable(expr) d.strict.EnterArrayTable(expr)
d.clearArrayTable = first
x, err = d.handleArrayTable(expr.Key(), v) x, err = d.handleArrayTable(expr.Key(), v)
default: default:
panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind)) panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind))
@@ -307,6 +334,10 @@ func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflec
reflect.Copy(nelem, elem) reflect.Copy(nelem, elem)
elem = nelem elem = nelem
} }
if d.clearArrayTable && elem.Len() > 0 {
elem.SetLen(0)
d.clearArrayTable = false
}
} }
return d.handleArrayTableCollectionLast(key, elem) return d.handleArrayTableCollectionLast(key, elem)
case reflect.Ptr: case reflect.Ptr:
@@ -325,6 +356,10 @@ func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflec
return v, nil return v, nil
case reflect.Slice: case reflect.Slice:
if d.clearArrayTable && v.Len() > 0 {
v.SetLen(0)
d.clearArrayTable = false
}
elemType := v.Type().Elem() elemType := v.Type().Elem()
var elem reflect.Value var elem reflect.Value
if elemType.Kind() == reflect.Interface { if elemType.Kind() == reflect.Interface {
@@ -576,7 +611,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
break break
} }
err := d.seen.CheckExpression(expr) _, err := d.seen.CheckExpression(expr)
if err != nil { if err != nil {
return reflect.Value{}, err return reflect.Value{}, err
} }
@@ -634,6 +669,14 @@ func (d *decoder) handleValue(value *unstable.Node, v reflect.Value) error {
v = initAndDereferencePointer(v) v = initAndDereferencePointer(v)
} }
if d.unmarshalerInterface {
if v.CanAddr() && v.Addr().CanInterface() {
if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok {
return outi.UnmarshalTOML(value)
}
}
}
ok, err := d.tryTextUnmarshaler(value, v) ok, err := d.tryTextUnmarshaler(value, v)
if ok || err != nil { if ok || err != nil {
return err return err
@@ -1031,12 +1074,39 @@ func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value,
} }
return mk, nil return mk, nil
case reflect.PtrTo(keyType).Implements(textUnmarshalerType): case reflect.PointerTo(keyType).Implements(textUnmarshalerType):
mk := reflect.New(keyType) mk := reflect.New(keyType)
if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err) return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err)
} }
return mk.Elem(), nil return mk.Elem(), nil
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
key, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err)
}
return reflect.ValueOf(key).Convert(keyType), nil
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
key, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err)
}
return reflect.ValueOf(key).Convert(keyType), nil
case keyType.Kind() == reflect.Float32:
key, err := strconv.ParseFloat(string(data), 32)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
}
return reflect.ValueOf(float32(key)), nil
case keyType.Kind() == reflect.Float64:
key, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
}
return reflect.ValueOf(float64(key)), nil
} }
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType) return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType)
} }
@@ -1097,9 +1167,9 @@ func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node
f := fieldByIndex(v, path) f := fieldByIndex(v, path)
if !f.CanSet() { if !f.CanAddr() {
// If the field is not settable, need to take a slower path and make a copy of // If the field is not addressable, need to take a slower path and
// the struct itself to a new location. // make a copy of the struct itself to a new location.
nvp := reflect.New(v.Type()) nvp := reflect.New(v.Type())
nvp.Elem().Set(v) nvp.Elem().Set(v)
v = nvp.Elem() v = nvp.Elem()
+437 -119
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -6,7 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/pelletier/go-toml/v2/internal/assert"
) )
func TestParser_AST_Numbers(t *testing.T) { func TestParser_AST_Numbers(t *testing.T) {
@@ -141,9 +141,9 @@ func TestParser_AST_Numbers(t *testing.T) {
p.NextExpression() p.NextExpression()
err := p.Error() err := p.Error()
if e.err { if e.err {
require.Error(t, err) assert.Error(t, err)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
expected := astNode{ expected := astNode{
Kind: KeyValue, Kind: KeyValue,
@@ -168,8 +168,8 @@ type (
func compareNode(t *testing.T, e astNode, n *Node) { func compareNode(t *testing.T, e astNode, n *Node) {
t.Helper() t.Helper()
require.Equal(t, e.Kind, n.Kind) assert.Equal(t, e.Kind, n.Kind)
require.Equal(t, e.Data, n.Data) assert.Equal(t, e.Data, n.Data)
compareIterator(t, e.Children, n.Children()) compareIterator(t, e.Children, n.Children())
} }
@@ -341,9 +341,9 @@ func TestParser_AST(t *testing.T) {
p.NextExpression() p.NextExpression()
err := p.Error() err := p.Error()
if e.err { if e.err {
require.Error(t, err) assert.Error(t, err)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
compareNode(t, e.ast, p.Expression()) compareNode(t, e.ast, p.Expression())
} }
}) })
@@ -431,9 +431,9 @@ func TestParser_AST_DateTimes(t *testing.T) {
p.NextExpression() p.NextExpression()
err := p.Error() err := p.Error()
if e.err { if e.err {
require.Error(t, err) assert.Error(t, err)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
expected := astNode{ expected := astNode{
Kind: KeyValue, Kind: KeyValue,
+7
View File
@@ -0,0 +1,7 @@
package unstable
// The Unmarshaler interface may be implemented by types to customize their
// behavior when being unmarshaled from a TOML document.
type Unmarshaler interface {
UnmarshalTOML(value *Node) error
}