Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b730b2be5d | |||
| a437caafe5 | |||
| be6c57be30 | |||
| d55304782e | |||
| 0977c05dd5 | |||
| bccd6e48f4 | |||
| 9b890cf9c5 | |||
| a3d5a0bb53 | |||
| d00d2cca6e | |||
| 86608d7fca | |||
| 4a1877957a | |||
| 3021d6d033 | |||
| 32788f26f8 | |||
| 8ed6d131eb | |||
| 7dad87762a | |||
| 2b69615b5d | |||
| 06fb30bf2e | |||
| 2e087bdf5f | |||
| caeb9f9631 | |||
| e7223fb40e | |||
| 05bedf36d8 | |||
| f5486d590f | |||
| 2ca21fb7b4 | |||
| 34765b4a9e | |||
| 358c8d2c23 | |||
| fd8d0bf4d9 | |||
| a76e18e8c5 | |||
| dff0c128d0 | |||
| 3573ce3770 | |||
| ae933f2e2a |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.23"
|
||||||
- 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}"
|
||||||
|
|||||||
@@ -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.23"
|
||||||
- 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 }}
|
||||||
|
|||||||
@@ -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.22', '1.23' ]
|
||||||
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
@@ -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/
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
version: 2
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
|
|||||||
+14
-17
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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: |
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import (
|
|||||||
|
|
||||||
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 +27,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,6 +51,7 @@ 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)
|
require.Error(t, err)
|
||||||
|
|||||||
+43
-88
@@ -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,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 (
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
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
|
require github.com/stretchr/testify v1.9.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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":
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-9
@@ -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 {
|
||||||
|
|||||||
+150
-3
@@ -587,13 +587,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: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -948,6 +1004,29 @@ func TestEncoderSetIndentSymbol(t *testing.T) {
|
|||||||
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(""),
|
||||||
|
})
|
||||||
|
require.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"`
|
||||||
@@ -1304,6 +1383,38 @@ value = ''
|
|||||||
require.Equal(t, expected, string(result))
|
require.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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(result))
|
||||||
|
}
|
||||||
|
|
||||||
func TestLocalTime(t *testing.T) {
|
func TestLocalTime(t *testing.T) {
|
||||||
v := map[string]toml.LocalTime{
|
v := map[string]toml.LocalTime{
|
||||||
"a": {
|
"a": {
|
||||||
@@ -1444,6 +1555,43 @@ func TestMarshalCommented(t *testing.T) {
|
|||||||
require.Equal(t, expected, string(out))
|
require.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)
|
||||||
|
require.NoError(t, enc.Encode(c))
|
||||||
|
require.Equal(t, expected, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleMarshal() {
|
func ExampleMarshal() {
|
||||||
type MyConfig struct {
|
type MyConfig struct {
|
||||||
Version int
|
Version int
|
||||||
@@ -1473,7 +1621,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"`
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+1364
-351
File diff suppressed because it is too large
Load Diff
+86
-16
@@ -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 straightfoward 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()
|
||||||
|
|||||||
+337
-18
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/pelletier/go-toml/v2/unstable"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -204,7 +205,6 @@ func TestUnmarshal_Floats(t *testing.T) {
|
|||||||
testFn func(t *testing.T, v float64)
|
testFn func(t *testing.T, v float64)
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
|
|
||||||
{
|
{
|
||||||
desc: "float pi",
|
desc: "float pi",
|
||||||
input: `3.1415`,
|
input: `3.1415`,
|
||||||
@@ -839,8 +839,10 @@ huey = 'dewey'
|
|||||||
|
|
||||||
return test{
|
return test{
|
||||||
target: &doc{},
|
target: &doc{},
|
||||||
expected: &doc{A: []interface{}{"0", "1", "2", "3", "4", "5", "6",
|
expected: &doc{A: []interface{}{
|
||||||
"7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17"}},
|
"0", "1", "2", "3", "4", "5", "6",
|
||||||
|
"7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1695,16 +1697,6 @@ B = "data"`,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "empty map into map with invalid key type",
|
|
||||||
input: ``,
|
|
||||||
gen: func() test {
|
|
||||||
return test{
|
|
||||||
target: &map[int]string{},
|
|
||||||
expected: &map[int]string{},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "into map with convertible key type",
|
desc: "into map with convertible key type",
|
||||||
input: `A = "hello"`,
|
input: `A = "hello"`,
|
||||||
@@ -1941,6 +1933,150 @@ B = "data"`,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of int to string",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[int]string{},
|
||||||
|
expected: &map[int]string{1: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of int8 to string",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[int8]string{},
|
||||||
|
expected: &map[int8]string{1: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of int64 to string",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[int64]string{},
|
||||||
|
expected: &map[int64]string{1: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of uint to string",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[uint]string{},
|
||||||
|
expected: &map[uint]string{1: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of uint8 to string",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[uint8]string{},
|
||||||
|
expected: &map[uint8]string{1: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of uint64 to string",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[uint64]string{},
|
||||||
|
expected: &map[uint64]string{1: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of uint with invalid key",
|
||||||
|
input: `-1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[uint]string{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of float64 to string",
|
||||||
|
input: `'1.01' = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[float64]string{},
|
||||||
|
expected: &map[float64]string{1.01: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of float64 with invalid key",
|
||||||
|
input: `key = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[float64]string{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of float32 to string",
|
||||||
|
input: `'1.01' = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[float32]string{},
|
||||||
|
expected: &map[float32]string{1.01: "a"},
|
||||||
|
assert: func(t *testing.T, test test) {
|
||||||
|
assert.Equal(t, test.expected, test.target)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "into map of float32 with invalid key",
|
||||||
|
input: `key = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[float32]string{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid map key type",
|
||||||
|
input: `1 = "a"`,
|
||||||
|
gen: func() test {
|
||||||
|
return test{
|
||||||
|
target: &map[struct{ int }]string{},
|
||||||
|
err: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range examples {
|
for _, e := range examples {
|
||||||
@@ -2652,7 +2788,7 @@ func TestIssue772(t *testing.T) {
|
|||||||
FileHandling `toml:"filehandling"`
|
FileHandling `toml:"filehandling"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultConfigFile = []byte(`
|
defaultConfigFile := []byte(`
|
||||||
[filehandling]
|
[filehandling]
|
||||||
pattern = "reach-masterdev-"`)
|
pattern = "reach-masterdev-"`)
|
||||||
|
|
||||||
@@ -2749,7 +2885,7 @@ func TestIssue866(t *testing.T) {
|
|||||||
PipelineMapping map[string]*Pipeline `toml:"pipelines"`
|
PipelineMapping map[string]*Pipeline `toml:"pipelines"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var badToml = `
|
badToml := `
|
||||||
[pipelines.register]
|
[pipelines.register]
|
||||||
mapping.inst.req = [
|
mapping.inst.req = [
|
||||||
["param1", "value1"],
|
["param1", "value1"],
|
||||||
@@ -2767,7 +2903,7 @@ mapping.inst.res = [
|
|||||||
t.Fatal("unmarshal failed with mismatch value")
|
t.Fatal("unmarshal failed with mismatch value")
|
||||||
}
|
}
|
||||||
|
|
||||||
var goodTooToml = `
|
goodTooToml := `
|
||||||
[pipelines.register]
|
[pipelines.register]
|
||||||
mapping.inst.req = [
|
mapping.inst.req = [
|
||||||
["param1", "value1"],
|
["param1", "value1"],
|
||||||
@@ -2782,7 +2918,7 @@ mapping.inst.req = [
|
|||||||
t.Fatal("unmarshal failed with mismatch value")
|
t.Fatal("unmarshal failed with mismatch value")
|
||||||
}
|
}
|
||||||
|
|
||||||
var goodToml = `
|
goodToml := `
|
||||||
[pipelines.register.mapping.inst]
|
[pipelines.register.mapping.inst]
|
||||||
req = [
|
req = [
|
||||||
["param1", "value1"],
|
["param1", "value1"],
|
||||||
@@ -2801,6 +2937,98 @@ res = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue915(t *testing.T) {
|
||||||
|
type blah struct {
|
||||||
|
A string `toml:"a"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Fizz string `toml:"fizz"`
|
||||||
|
blah `toml:"blah"`
|
||||||
|
}
|
||||||
|
|
||||||
|
b := []byte(`
|
||||||
|
fizz = "abc"
|
||||||
|
blah.a = "def"`)
|
||||||
|
var cfg config
|
||||||
|
err := toml.Unmarshal(b, &cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "abc", cfg.Fizz)
|
||||||
|
require.Equal(t, "def", cfg.blah.A)
|
||||||
|
require.Equal(t, "def", cfg.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue931(t *testing.T) {
|
||||||
|
type item struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type items struct {
|
||||||
|
Slice []item
|
||||||
|
}
|
||||||
|
|
||||||
|
its := items{[]item{{"a"}, {"b"}}}
|
||||||
|
|
||||||
|
b := []byte(`
|
||||||
|
[[Slice]]
|
||||||
|
Name = 'c'
|
||||||
|
|
||||||
|
[[Slice]]
|
||||||
|
Name = 'd'
|
||||||
|
`)
|
||||||
|
|
||||||
|
toml.Unmarshal(b, &its)
|
||||||
|
require.Equal(t, items{[]item{{"c"}, {"d"}}}, its)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue931Interface(t *testing.T) {
|
||||||
|
type items struct {
|
||||||
|
Slice interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type item = map[string]interface{}
|
||||||
|
|
||||||
|
its := items{[]interface{}{item{"Name": "a"}, item{"Name": "b"}}}
|
||||||
|
|
||||||
|
b := []byte(`
|
||||||
|
[[Slice]]
|
||||||
|
Name = 'c'
|
||||||
|
|
||||||
|
[[Slice]]
|
||||||
|
Name = 'd'
|
||||||
|
`)
|
||||||
|
|
||||||
|
toml.Unmarshal(b, &its)
|
||||||
|
require.Equal(t, items{[]interface{}{item{"Name": "c"}, item{"Name": "d"}}}, its)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue931SliceInterface(t *testing.T) {
|
||||||
|
type items struct {
|
||||||
|
Slice []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type item = map[string]interface{}
|
||||||
|
|
||||||
|
its := items{
|
||||||
|
[]interface{}{
|
||||||
|
item{"Name": "a"},
|
||||||
|
item{"Name": "b"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := []byte(`
|
||||||
|
[[Slice]]
|
||||||
|
Name = 'c'
|
||||||
|
|
||||||
|
[[Slice]]
|
||||||
|
Name = 'd'
|
||||||
|
`)
|
||||||
|
|
||||||
|
toml.Unmarshal(b, &its)
|
||||||
|
require.Equal(t, items{[]interface{}{item{"Name": "c"}, item{"Name": "d"}}}, its)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalDecodeErrors(t *testing.T) {
|
func TestUnmarshalDecodeErrors(t *testing.T) {
|
||||||
examples := []struct {
|
examples := []struct {
|
||||||
desc string
|
desc string
|
||||||
@@ -3269,7 +3497,7 @@ func TestOmitEmpty(t *testing.T) {
|
|||||||
X []elem `toml:",inline"`
|
X []elem `toml:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
d := doc{X: []elem{elem{
|
d := doc{X: []elem{{
|
||||||
Foo: "test",
|
Foo: "test",
|
||||||
Inner: inner{
|
Inner: inner{
|
||||||
V: "alue",
|
V: "alue",
|
||||||
@@ -3680,3 +3908,94 @@ func TestUnmarshal_Nil(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomUnmarshalerKey struct {
|
||||||
|
A int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *CustomUnmarshalerKey) UnmarshalTOML(value *unstable.Node) error {
|
||||||
|
item, err := strconv.ParseInt(string(value.Data), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting to int64, %v", err)
|
||||||
|
}
|
||||||
|
k.A = item
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshal_CustomUnmarshaler(t *testing.T) {
|
||||||
|
type MyConfig struct {
|
||||||
|
Unmarshalers []CustomUnmarshalerKey `toml:"unmarshalers"`
|
||||||
|
Foo *string `toml:"foo,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
examples := []struct {
|
||||||
|
desc string
|
||||||
|
disableUnmarshalerInterface bool
|
||||||
|
input string
|
||||||
|
expected MyConfig
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
input: ``,
|
||||||
|
expected: MyConfig{Unmarshalers: []CustomUnmarshalerKey{}, Foo: nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "simple",
|
||||||
|
input: `unmarshalers = [1,2,3]`,
|
||||||
|
expected: MyConfig{
|
||||||
|
Unmarshalers: []CustomUnmarshalerKey{
|
||||||
|
{A: 1},
|
||||||
|
{A: 2},
|
||||||
|
{A: 3},
|
||||||
|
},
|
||||||
|
Foo: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "unmarshal string and custom unmarshaler",
|
||||||
|
input: `unmarshalers = [1,2,3]
|
||||||
|
foo = "bar"`,
|
||||||
|
expected: MyConfig{
|
||||||
|
Unmarshalers: []CustomUnmarshalerKey{
|
||||||
|
{A: 1},
|
||||||
|
{A: 2},
|
||||||
|
{A: 3},
|
||||||
|
},
|
||||||
|
Foo: func(v string) *string {
|
||||||
|
return &v
|
||||||
|
}("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "simple example, but unmarshaler interface disabled",
|
||||||
|
disableUnmarshalerInterface: true,
|
||||||
|
input: `unmarshalers = [1,2,3]`,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ex := range examples {
|
||||||
|
e := ex
|
||||||
|
t.Run(e.desc, func(t *testing.T) {
|
||||||
|
foo := MyConfig{}
|
||||||
|
|
||||||
|
decoder := toml.NewDecoder(bytes.NewReader([]byte(e.input)))
|
||||||
|
if !ex.disableUnmarshalerInterface {
|
||||||
|
decoder.EnableUnmarshalerInterface()
|
||||||
|
}
|
||||||
|
err := decoder.Decode(&foo)
|
||||||
|
|
||||||
|
if e.err {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, len(foo.Unmarshalers), len(e.expected.Unmarshalers))
|
||||||
|
for i := 0; i < len(foo.Unmarshalers); i++ {
|
||||||
|
require.Equal(t, foo.Unmarshalers[i], e.expected.Unmarshalers[i])
|
||||||
|
}
|
||||||
|
require.Equal(t, foo.Foo, e.expected.Foo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user