Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f19f855f1 | |||
| 3cf1eb2312 | |||
| 2af3554f90 | |||
| 180c6ba2ba | |||
| dafc4173ef | |||
| f1a83be671 | |||
| 5aeb70b3f0 | |||
| 8384a5683c | |||
| 4369957cb4 | |||
| a0e8464967 | |||
| c57d0d559f | |||
| 644602b845 | |||
| 36df8eef6e | |||
| 18a2148713 | |||
| bc9958322f | |||
| 6d56ac8027 | |||
| 098464b61b | |||
| 85e2448ce5 | |||
| ee07c9203b | |||
| 014204cfb7 | |||
| 923b2ab478 | |||
| af236b689f | |||
| 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 | |||
| 3175efb395 | |||
| 9dd7f1af78 | |||
| 4a5c27c299 | |||
| 76cc96f6d8 | |||
| 4835627845 | |||
| cef80b96a4 | |||
| 4040373cfd | |||
| bb026cae89 | |||
| f7d9b9ba53 | |||
| fac33d6fa8 |
@@ -19,7 +19,7 @@ jobs:
|
||||
dry-run: false
|
||||
language: go
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
@@ -9,12 +9,12 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: report
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.24"
|
||||
- name: Run tests with coverage
|
||||
run: ./ci.sh coverage -d "${GITHUB_BASE_REF-HEAD}"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Go Versions Compatibility Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
go_versions:
|
||||
description: 'Go versions to test (space-separated, e.g., "1.21 1.22 1.23")'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Run Go versions compatibility test
|
||||
run: |
|
||||
VERSIONS="${{ github.event.inputs.go_versions }}"
|
||||
./test-go-versions.sh --output ./test-results $VERSIONS
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: go-versions-test-results
|
||||
path: |
|
||||
test-results/
|
||||
retention-days: 30
|
||||
@@ -16,24 +16,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.24"
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release ${{ inputs.args }} --rm-dist
|
||||
version: '~> v2'
|
||||
args: release ${{ inputs.args }} --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -11,22 +11,22 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest']
|
||||
go: [ '1.19', '1.20' ]
|
||||
os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest', 'macos-14' ]
|
||||
go: [ '1.23', '1.24' ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.go }}/${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Run unit tests
|
||||
run: go test -race ./...
|
||||
release-check:
|
||||
if: ${{ github.ref != 'refs/heads/v2' }}
|
||||
uses: pelletier/go-toml/.github/workflows/release.yml@v2
|
||||
uses: ./.github/workflows/release.yml
|
||||
with:
|
||||
args: --snapshot
|
||||
|
||||
@@ -4,3 +4,5 @@ cmd/tomll/tomll
|
||||
cmd/tomljson/tomljson
|
||||
cmd/tomltestgen/tomltestgen
|
||||
dist
|
||||
tests/
|
||||
test-results
|
||||
|
||||
+5
-1
@@ -1,3 +1,4 @@
|
||||
version: 2
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
@@ -18,6 +19,7 @@ builds:
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_arm
|
||||
- linux_riscv64
|
||||
- windows_amd64
|
||||
- windows_arm64
|
||||
- windows_arm
|
||||
@@ -37,6 +39,7 @@ builds:
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_arm
|
||||
- linux_riscv64
|
||||
- windows_amd64
|
||||
- windows_arm64
|
||||
- windows_arm
|
||||
@@ -55,6 +58,7 @@ builds:
|
||||
targets:
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_riscv64
|
||||
- linux_arm
|
||||
- windows_amd64
|
||||
- windows_arm64
|
||||
@@ -109,7 +113,7 @@ dockers:
|
||||
checksum:
|
||||
name_template: 'sha256sums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
version_template: "{{ incpatch .Version }}-next"
|
||||
release:
|
||||
github:
|
||||
owner: pelletier
|
||||
|
||||
+60
-21
@@ -33,7 +33,7 @@ The documentation is present in the [README][readme] and thorough the source
|
||||
code. On release, it gets updated on [pkg.go.dev][pkg.go.dev]. To make a change
|
||||
to the documentation, create a pull request with your proposed changes. For
|
||||
simple changes like that, the easiest way to go is probably the "Fork this
|
||||
project and edit the file" button on Github, displayed at the top right of the
|
||||
project and edit the file" button on GitHub, displayed at the top right of the
|
||||
file. Unless it's a trivial change (for example a typo), provide a little bit of
|
||||
context in your pull request description or commit message.
|
||||
|
||||
@@ -92,6 +92,48 @@ However, given GitHub's new policy to _not_ run Actions on pull requests until a
|
||||
maintainer clicks on button, it is highly recommended that you run them locally
|
||||
as you make changes.
|
||||
|
||||
### Test across Go versions
|
||||
|
||||
The repository includes tooling to test go-toml across multiple Go versions
|
||||
(1.11 through 1.25) both locally and in GitHub Actions.
|
||||
|
||||
#### Local testing with Docker
|
||||
|
||||
Prerequisites: Docker installed and running, Bash shell, `rsync` command.
|
||||
|
||||
```bash
|
||||
# Test all Go versions in parallel (default)
|
||||
./test-go-versions.sh
|
||||
|
||||
# Test specific versions
|
||||
./test-go-versions.sh 1.21 1.22 1.23
|
||||
|
||||
# Test sequentially (slower but uses less resources)
|
||||
./test-go-versions.sh --sequential
|
||||
|
||||
# Verbose output with custom results directory
|
||||
./test-go-versions.sh --verbose --output ./my-results 1.24 1.25
|
||||
|
||||
# Show all options
|
||||
./test-go-versions.sh --help
|
||||
```
|
||||
|
||||
The script creates Docker containers for each Go version and runs the full test
|
||||
suite. Results are saved to a `test-results/` directory with individual logs and
|
||||
a comprehensive summary report.
|
||||
|
||||
The script only exits with a non-zero status code if either of the two most
|
||||
recent Go versions fail.
|
||||
|
||||
#### GitHub Actions testing (maintainers)
|
||||
|
||||
1. Go to the **Actions** tab in the GitHub repository
|
||||
2. Select **"Go Versions Compatibility Test"** from the workflow list
|
||||
3. Click **"Run workflow"**
|
||||
4. Optionally customize:
|
||||
- **Go versions**: Space-separated list (e.g., `1.21 1.22 1.23`)
|
||||
- **Execution mode**: Parallel (faster) or sequential (more stable)
|
||||
|
||||
### Check coverage
|
||||
|
||||
We use `go tool cover` to compute test coverage. Most code editors have a way to
|
||||
@@ -111,7 +153,7 @@ code lowers the coverage.
|
||||
|
||||
Go-toml aims to stay efficient. We rely on a set of scenarios executed with Go's
|
||||
builtin benchmark systems. Because of their noisy nature, containers provided by
|
||||
Github Actions cannot be reliably used for benchmarking. As a result, you are
|
||||
GitHub Actions cannot be reliably used for benchmarking. As a result, you are
|
||||
responsible for checking that your changes do not incur a performance penalty.
|
||||
You can run their following to execute benchmarks:
|
||||
|
||||
@@ -165,25 +207,22 @@ Checklist:
|
||||
|
||||
### New release
|
||||
|
||||
1. Decide on the next version number. Use semver.
|
||||
2. Generate release notes using [`gh`][gh]. Example:
|
||||
```
|
||||
$ gh api -X POST \
|
||||
-F tag_name='v2.0.0-beta.5' \
|
||||
-F target_commitish='v2' \
|
||||
-F previous_tag_name='v2.0.0-beta.4' \
|
||||
--jq '.body' \
|
||||
repos/pelletier/go-toml/releases/generate-notes
|
||||
```
|
||||
3. Look for "Other changes". That would indicate a pull request not labeled
|
||||
properly. Tweak labels and pull request titles until changelog looks good for
|
||||
users.
|
||||
4. [Draft new release][new-release].
|
||||
5. Fill tag and target with the same value used to generate the changelog.
|
||||
6. Set title to the new tag value.
|
||||
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.
|
||||
1. Decide on the next version number. Use semver. Review commits since last
|
||||
version to assess.
|
||||
2. Tag release. For example:
|
||||
```
|
||||
git checkout v2
|
||||
git pull
|
||||
git tag v2.2.0
|
||||
git push --tags
|
||||
```
|
||||
3. CI automatically builds a draft GitHub release. Review it and edit as
|
||||
necessary. Look for "Other changes". That would indicate a pull request not
|
||||
labeled properly. Tweak labels and pull request titles until changelog looks
|
||||
good for users.
|
||||
4. Check "create discussion" box, in the "Releases" category.
|
||||
5. If new version is an alpha or beta only, check pre-release box.
|
||||
|
||||
|
||||
[issues-tracker]: https://github.com/pelletier/go-toml/issues
|
||||
[bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton
|
||||
go-toml v2
|
||||
Copyright (c) 2021 - 2023 Thomas Pelletier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -45,16 +45,15 @@ to check for typos. [See example in the documentation][strict].
|
||||
|
||||
### Contextualized errors
|
||||
|
||||
When most decoding errors occur, go-toml returns [`DecodeError`][decode-err]),
|
||||
When most decoding errors occur, go-toml returns [`DecodeError`][decode-err],
|
||||
which contains a human readable contextualized version of the error. For
|
||||
example:
|
||||
|
||||
```
|
||||
2| key1 = "value1"
|
||||
3| key2 = "missing2"
|
||||
| ~~~~ missing field
|
||||
4| key3 = "missing3"
|
||||
5| key4 = "value4"
|
||||
1| [server]
|
||||
2| path = 100
|
||||
| ~~~ cannot decode TOML integer into struct field toml_test.Server.Path of type string
|
||||
3| port = 50
|
||||
```
|
||||
|
||||
[decode-err]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError
|
||||
@@ -73,6 +72,26 @@ representation.
|
||||
[tlt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalTime
|
||||
[tldt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDateTime
|
||||
|
||||
### Commented config
|
||||
|
||||
Since TOML is often used for configuration files, go-toml can emit documents
|
||||
annotated with [comments and commented-out values][comments-example]. For
|
||||
example, it can generate the following file:
|
||||
|
||||
```toml
|
||||
# Host IP to connect to.
|
||||
host = '127.0.0.1'
|
||||
# Port of the remote server.
|
||||
port = 4242
|
||||
|
||||
# Encryption parameters (optional)
|
||||
# [TLS]
|
||||
# cipher = 'AEAD-AES128-GCM-SHA256'
|
||||
# version = 'TLS 1.3'
|
||||
```
|
||||
|
||||
[comments-example]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Marshal-Commented
|
||||
|
||||
## Getting started
|
||||
|
||||
Given the following struct, let's see how to read it and write it as TOML:
|
||||
@@ -88,7 +107,11 @@ type MyConfig struct {
|
||||
### Unmarshaling
|
||||
|
||||
[`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its
|
||||
content. For example:
|
||||
content.
|
||||
|
||||
Note that the struct variable names are _capitalized_, while the variables in the toml document are _lowercase_.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
doc := `
|
||||
@@ -114,6 +137,62 @@ fmt.Println("tags:", cfg.Tags)
|
||||
|
||||
[unmarshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Unmarshal
|
||||
|
||||
|
||||
Here is an example using tables with some simple nesting:
|
||||
|
||||
```go
|
||||
doc := `
|
||||
age = 45
|
||||
fruits = ["apple", "pear"]
|
||||
|
||||
# these are very important!
|
||||
[my-variables]
|
||||
first = 1
|
||||
second = 0.2
|
||||
third = "abc"
|
||||
|
||||
# this is not so important.
|
||||
[my-variables.b]
|
||||
bfirst = 123
|
||||
`
|
||||
|
||||
var Document struct {
|
||||
Age int
|
||||
Fruits []string
|
||||
|
||||
Myvariables struct {
|
||||
First int
|
||||
Second float64
|
||||
Third string
|
||||
|
||||
B struct {
|
||||
Bfirst int
|
||||
}
|
||||
} `toml:"my-variables"`
|
||||
}
|
||||
|
||||
err := toml.Unmarshal([]byte(doc), &Document)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("age:", Document.Age)
|
||||
fmt.Println("fruits:", Document.Fruits)
|
||||
fmt.Println("my-variables.first:", Document.Myvariables.First)
|
||||
fmt.Println("my-variables.second:", Document.Myvariables.Second)
|
||||
fmt.Println("my-variables.third:", Document.Myvariables.Third)
|
||||
fmt.Println("my-variables.B.Bfirst:", Document.Myvariables.B.Bfirst)
|
||||
|
||||
// Output:
|
||||
// age: 45
|
||||
// fruits: [apple pear]
|
||||
// my-variables.first: 1
|
||||
// my-variables.second: 0.2
|
||||
// my-variables.third: abc
|
||||
// my-variables.B.Bfirst: 123
|
||||
```
|
||||
|
||||
|
||||
### Marshaling
|
||||
|
||||
[`Marshal`][marshal] is the opposite of Unmarshal: it represents a Go structure
|
||||
@@ -160,12 +239,12 @@ Execution time speedup compared to other Go TOML libraries:
|
||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>1.9x</td></tr>
|
||||
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>1.8x</td></tr>
|
||||
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>2.5x</td></tr>
|
||||
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.9x</td></tr>
|
||||
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.9x</td></tr>
|
||||
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.4x</td><td>5.3x</td></tr>
|
||||
<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>2.1x</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.7x</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.6x</td><td>5.1x</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<details><summary>See more</summary>
|
||||
@@ -178,17 +257,17 @@ provided for completeness.</p>
|
||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.9x</td></tr>
|
||||
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>4.2x</td></tr>
|
||||
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>4.5x</td><td>3.1x</td></tr>
|
||||
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>6.2x</td><td>3.9x</td></tr>
|
||||
<tr><td>UnmarshalDataset/example-2</td><td>3.1x</td><td>3.5x</td></tr>
|
||||
<tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>3.1x</td></tr>
|
||||
<tr><td>UnmarshalDataset/twitter-2</td><td>2.5x</td><td>2.6x</td></tr>
|
||||
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.1x</td><td>2.2x</td></tr>
|
||||
<tr><td>UnmarshalDataset/canada-2</td><td>1.6x</td><td>1.3x</td></tr>
|
||||
<tr><td>UnmarshalDataset/config-2</td><td>4.3x</td><td>3.2x</td></tr>
|
||||
<tr><td>[Geo mean]</td><td>2.7x</td><td>2.8x</td></tr>
|
||||
<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>3.8x</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>5.6x</td><td>4.1x</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>2.9x</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.2x</td><td>2.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.1x</td><td>2.9x</td></tr>
|
||||
<tr><td>geomean</td><td>2.7x</td><td>2.8x</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
|
||||
@@ -242,7 +321,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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -497,27 +576,20 @@ is not necessary anymore.
|
||||
|
||||
V1 used to provide multiple struct tags: `comment`, `commented`, `multiline`,
|
||||
`toml`, and `omitempty`. To behave more like the standard library, v2 has merged
|
||||
`toml`, `multiline`, and `omitempty`. For example:
|
||||
`toml`, `multiline`, `commented`, and `omitempty`. For example:
|
||||
|
||||
```go
|
||||
type doc struct {
|
||||
// v1
|
||||
F string `toml:"field" multiline:"true" omitempty:"true"`
|
||||
F string `toml:"field" multiline:"true" omitempty:"true" commented:"true"`
|
||||
// v2
|
||||
F string `toml:"field,multiline,omitempty"`
|
||||
F string `toml:"field,multiline,omitempty,commented"`
|
||||
}
|
||||
```
|
||||
|
||||
Has a result, the `Encoder.SetTag*` methods have been removed, as there is just
|
||||
one tag now.
|
||||
|
||||
|
||||
#### `commented` tag has been removed
|
||||
|
||||
There is no replacement for the `commented` tag. This feature would be better
|
||||
suited in a proper document model for go-toml v2, which has been [cut from
|
||||
scope][nodoc] at the moment.
|
||||
|
||||
#### `Encoder.ArraysWithOneElementPerLine` has been renamed
|
||||
|
||||
The new name is `Encoder.SetArraysMultiline`. The behavior should be the same.
|
||||
@@ -553,10 +625,11 @@ complete solutions exist out there.
|
||||
|
||||
## Versioning
|
||||
|
||||
Go-toml follows [Semantic Versioning](https://semver.org). The supported version
|
||||
of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of
|
||||
this document. The last two major versions of Go are supported
|
||||
(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)).
|
||||
Expect for parts explicitly marked otherwise, go-toml follows [Semantic
|
||||
Versioning](https://semver.org). The supported version of
|
||||
[TOML](https://github.com/toml-lang/toml) is indicated at the beginning of this
|
||||
document. The last two major versions of Go are supported (see [Go Release
|
||||
Policy](https://golang.org/doc/devel/release.html#policy)).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ---------- | ------------------ |
|
||||
| Latest 2.x | :white_check_mark: |
|
||||
|
||||
@@ -3,13 +3,13 @@ package benchmark_test
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
var bench_inputs = []struct {
|
||||
@@ -35,11 +35,11 @@ func TestUnmarshalDatasetCode(t *testing.T) {
|
||||
buf := fixture(t, tc.name)
|
||||
|
||||
var v interface{}
|
||||
require.NoError(t, toml.Unmarshal(buf, &v))
|
||||
assert.NoError(t, toml.Unmarshal(buf, &v))
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(b), tc.jsonLen)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(b), tc.jsonLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func BenchmarkUnmarshalDataset(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
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) {
|
||||
tb.Skip("benchmark fixture not found:", file)
|
||||
}
|
||||
require.NoError(tb, err)
|
||||
assert.NoError(tb, err)
|
||||
defer f.Close()
|
||||
|
||||
gz, err := gzip.NewReader(f)
|
||||
require.NoError(tb, err)
|
||||
assert.NoError(tb, err)
|
||||
|
||||
buf, err := ioutil.ReadAll(gz)
|
||||
require.NoError(tb, err)
|
||||
buf, err := io.ReadAll(gz)
|
||||
assert.NoError(tb, err)
|
||||
return buf
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package benchmark_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestUnmarshalSimple(t *testing.T) {
|
||||
@@ -59,7 +59,7 @@ func BenchmarkUnmarshal(b *testing.B) {
|
||||
})
|
||||
|
||||
b.Run("ReferenceFile", func(b *testing.B) {
|
||||
bytes, err := ioutil.ReadFile("benchmark.toml")
|
||||
bytes, err := os.ReadFile("benchmark.toml")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func BenchmarkMarshal(b *testing.B) {
|
||||
})
|
||||
|
||||
b.Run("ReferenceFile", func(b *testing.B) {
|
||||
bytes, err := ioutil.ReadFile("benchmark.toml")
|
||||
bytes, err := os.ReadFile("benchmark.toml")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -344,11 +344,11 @@ type benchmarkDoc struct {
|
||||
}
|
||||
|
||||
func TestUnmarshalReferenceFile(t *testing.T) {
|
||||
bytes, err := ioutil.ReadFile("benchmark.toml")
|
||||
require.NoError(t, err)
|
||||
bytes, err := os.ReadFile("benchmark.toml")
|
||||
assert.NoError(t, err)
|
||||
d := benchmarkDoc{}
|
||||
err = toml.Unmarshal(bytes, &d)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := benchmarkDoc{
|
||||
Table: struct {
|
||||
@@ -627,7 +627,7 @@ trimmed in raw strings.
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, expected, d)
|
||||
assert.Equal(t, expected, d)
|
||||
}
|
||||
|
||||
var hugoFrontMatterbytes = []byte(`
|
||||
|
||||
@@ -77,7 +77,7 @@ cover() {
|
||||
|
||||
pushd "$dir"
|
||||
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
|
||||
echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2
|
||||
popd
|
||||
@@ -152,7 +152,7 @@ bench() {
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
if [ "${branch}" != "HEAD" ]; then
|
||||
@@ -161,10 +161,12 @@ bench() {
|
||||
}
|
||||
|
||||
fmktemp() {
|
||||
if mktemp --version|grep GNU >/dev/null; then
|
||||
mktemp --suffix=-$1;
|
||||
if mktemp --version &> /dev/null; then
|
||||
# GNU
|
||||
mktemp --suffix=-$1
|
||||
else
|
||||
mktemp -t $1;
|
||||
# BSD
|
||||
mktemp -t $1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -184,12 +186,14 @@ with open(sys.argv[1]) as f:
|
||||
lines.append(line.split(','))
|
||||
|
||||
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])
|
||||
results.append([
|
||||
line[0].replace("-32", ""),
|
||||
"%.1fx" % (float(line[3])/v2), # v1
|
||||
"%.1fx" % (float(line[5])/v2), # bs
|
||||
"%.1fx" % (float(line[7])/v2), # bs
|
||||
])
|
||||
# move geomean to the end
|
||||
results.append(results[0])
|
||||
@@ -260,10 +264,10 @@ benchmark() {
|
||||
|
||||
if [ "$1" = "-html" ]; then
|
||||
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
|
||||
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
|
||||
|
||||
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 (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
@@ -33,7 +34,11 @@ Reading from a file:
|
||||
jsontoml file.json > file.toml
|
||||
`
|
||||
|
||||
var useJsonNumber bool
|
||||
|
||||
func main() {
|
||||
flag.BoolVar(&useJsonNumber, "use-json-number", false, "unmarshal numbers into `json.Number` type instead of as `float64`")
|
||||
|
||||
p := cli.Program{
|
||||
Usage: usage,
|
||||
Fn: convert,
|
||||
@@ -45,11 +50,17 @@ func convert(r io.Reader, w io.Writer) error {
|
||||
var v interface{}
|
||||
|
||||
d := json.NewDecoder(r)
|
||||
e := toml.NewEncoder(w)
|
||||
|
||||
if useJsonNumber {
|
||||
d.UseNumber()
|
||||
e.SetMarshalJsonNumbers(true)
|
||||
}
|
||||
|
||||
err := d.Decode(&v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e := toml.NewEncoder(w)
|
||||
return e.Encode(v)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
@@ -15,6 +14,7 @@ func TestConvert(t *testing.T) {
|
||||
input string
|
||||
expected string
|
||||
errors bool
|
||||
useJsonNumber bool
|
||||
}{
|
||||
{
|
||||
name: "valid json",
|
||||
@@ -26,6 +26,19 @@ func TestConvert(t *testing.T) {
|
||||
}`,
|
||||
expected: `[mytoml]
|
||||
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 {
|
||||
b := new(bytes.Buffer)
|
||||
useJsonNumber = e.useJsonNumber
|
||||
err := convert(strings.NewReader(e.input), b)
|
||||
if e.errors {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, e.expected, b.String())
|
||||
|
||||
@@ -7,8 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
@@ -46,7 +45,7 @@ a = 42`),
|
||||
b := new(bytes.Buffer)
|
||||
err := convert(e.input, b)
|
||||
if e.errors {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, e.expected, b.String())
|
||||
|
||||
@@ -5,8 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
@@ -36,7 +35,7 @@ a = 42.0
|
||||
b := new(bytes.Buffer)
|
||||
err := convert(strings.NewReader(e.input), b)
|
||||
if e.errors {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, e.expected, b.String())
|
||||
|
||||
+39
-82
@@ -7,17 +7,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -64,30 +60,6 @@ const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on {
|
||||
"}\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 {
|
||||
camel := ""
|
||||
nextUpper := true
|
||||
@@ -107,19 +79,6 @@ func kebabToCamel(kebab string) string {
|
||||
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 {
|
||||
return strconv.Quote(input)
|
||||
}
|
||||
@@ -138,61 +97,59 @@ func main() {
|
||||
flag.Usage = usage
|
||||
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{
|
||||
Ref: *ref,
|
||||
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"))
|
||||
name = strings.ReplaceAll(name, ".", "_")
|
||||
|
||||
for _, f := range zipReader.File {
|
||||
zipFilesMap[f.Name] = f
|
||||
log.Printf("> [%s] %s\n", "invalid", name)
|
||||
|
||||
tomlContent, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to read test file: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
testFileRegexp := regexp.MustCompile(`([^/]+/tests/(valid|invalid)/(.+))\.(toml)`)
|
||||
for _, f := range zipReader.File {
|
||||
groups := testFileRegexp.FindStringSubmatch(f.Name)
|
||||
if len(groups) > 0 {
|
||||
name := kebabToCamel(groups[3])
|
||||
testType := groups[2]
|
||||
|
||||
log.Printf("> [%s] %s\n", testType, name)
|
||||
|
||||
tomlContent := readFileFromZip(f)
|
||||
|
||||
switch testType {
|
||||
case "invalid":
|
||||
collection.Invalid = append(collection.Invalid, invalid{
|
||||
Name: name,
|
||||
Input: tomlContent,
|
||||
Input: string(tomlContent),
|
||||
})
|
||||
collection.Count++
|
||||
case "valid":
|
||||
baseFilePath := groups[1]
|
||||
jsonFilePath := baseFilePath + ".json"
|
||||
jsonContent := readFileFromZip(zipFilesMap[jsonFilePath])
|
||||
}
|
||||
|
||||
dirContent, _ = filepath.Glob("tests/valid/**/*.toml")
|
||||
for _, f := range dirContent {
|
||||
filename := strings.TrimPrefix(f, "tests/valid/")
|
||||
name := kebabToCamel(strings.TrimSuffix(filename, ".toml"))
|
||||
name = strings.ReplaceAll(name, ".", "_")
|
||||
|
||||
log.Printf("> [%s] %s\n", "valid", name)
|
||||
|
||||
tomlContent, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
fmt.Printf("failed reading test file: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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: tomlContent,
|
||||
JsonRef: jsonContent,
|
||||
Input: string(tomlContent),
|
||||
JsonRef: string(jsonContent),
|
||||
})
|
||||
collection.Count++
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown test type: %s", testType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Collected %d tests from toml-test\n", collection.Count)
|
||||
@@ -202,7 +159,7 @@ func main() {
|
||||
}
|
||||
t := template.Must(template.New("src").Funcs(funcMap).Parse(srcTemplate))
|
||||
buf := new(bytes.Buffer)
|
||||
err = t.Execute(buf, collection)
|
||||
err := t.Execute(buf, collection)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -216,7 +173,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(*out, outputBytes, 0644)
|
||||
err = os.WriteFile(*out, outputBytes, 0o644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -144,13 +144,23 @@ func parseDateTime(b []byte) (time.Time, error) {
|
||||
return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone")
|
||||
}
|
||||
|
||||
// Normalize leap seconds (second=60) to second=59 to prevent overflow
|
||||
// when Go's time.Date normalizes the time. This is necessary because
|
||||
// time.Date(9999, 12, 31, 23, 59, 60, 0, UTC) normalizes to year 10000,
|
||||
// which is outside the valid TOML date range (0000-9999).
|
||||
// See: https://github.com/pelletier/go-toml/issues/1015
|
||||
second := dt.Second
|
||||
if second == 60 {
|
||||
second = 59
|
||||
}
|
||||
|
||||
t := time.Date(
|
||||
dt.Year,
|
||||
time.Month(dt.Month),
|
||||
dt.Day,
|
||||
dt.Hour,
|
||||
dt.Minute,
|
||||
dt.Second,
|
||||
second,
|
||||
dt.Nanosecond,
|
||||
zone)
|
||||
|
||||
@@ -318,7 +328,7 @@ func parseFloat(b []byte) (float64, error) {
|
||||
if cleaned[0] == '+' || cleaned[0] == '-' {
|
||||
start = 1
|
||||
}
|
||||
if cleaned[start] == '0' && isDigit(cleaned[start+1]) {
|
||||
if cleaned[start] == '0' && len(cleaned) > start+1 && isDigit(cleaned[start+1]) {
|
||||
return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes")
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,17 @@ func (s *StrictMissingError) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Unwrap returns wrapped decode errors
|
||||
//
|
||||
// Implements errors.Join() interface.
|
||||
func (s *StrictMissingError) Unwrap() []error {
|
||||
var errs []error
|
||||
for i := range s.Errors {
|
||||
errs = append(errs, &s.Errors[i])
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
type Key []string
|
||||
|
||||
// Error returns the error message contained in the DecodeError.
|
||||
@@ -78,7 +89,7 @@ func (e *DecodeError) Key() Key {
|
||||
return e.key
|
||||
}
|
||||
|
||||
// decodeErrorFromHighlight creates a DecodeError referencing a highlighted
|
||||
// wrapDecodeError creates a DecodeError referencing a highlighted
|
||||
// range of bytes from document.
|
||||
//
|
||||
// highlight needs to be a sub-slice of document, or this function panics.
|
||||
|
||||
+16
-1
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
"github.com/pelletier/go-toml/v2/unstable"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
@@ -205,6 +205,21 @@ func TestDecodeError_Accessors(t *testing.T) {
|
||||
assert.Equal(t, "bar", e.String())
|
||||
}
|
||||
|
||||
func TestStrictErrorUnwrap(t *testing.T) {
|
||||
fo := bytes.NewBufferString(`
|
||||
Missing = 1
|
||||
OtherMissing = 1
|
||||
`)
|
||||
var out struct{}
|
||||
err := NewDecoder(fo).DisallowUnknownFields().Decode(&out)
|
||||
assert.Error(t, err)
|
||||
|
||||
strictErr := &StrictMissingError{}
|
||||
assert.True(t, errors.As(err, &strictErr))
|
||||
|
||||
assert.Equal(t, 2, len(strictErr.Unwrap()))
|
||||
}
|
||||
|
||||
func ExampleDecodeError() {
|
||||
doc := `name = 123__456`
|
||||
|
||||
|
||||
+15
-15
@@ -4,28 +4,28 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestFastSimpleInt(t *testing.T) {
|
||||
m := map[string]int64{}
|
||||
err := toml.Unmarshal([]byte(`a = 42`), &m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]int64{"a": 42}, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]int64{"a": 42}, m)
|
||||
}
|
||||
|
||||
func TestFastSimpleFloat(t *testing.T) {
|
||||
m := map[string]float64{}
|
||||
err := toml.Unmarshal([]byte("a = 42\nb = 1.1\nc = 12341234123412341234123412341234"), &m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]float64{"a": 42, "b": 1.1, "c": 1.2341234123412342e+31}, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]float64{"a": 42, "b": 1.1, "c": 1.2341234123412342e+31}, m)
|
||||
}
|
||||
|
||||
func TestFastSimpleString(t *testing.T) {
|
||||
m := map[string]string{}
|
||||
err := toml.Unmarshal([]byte(`a = "hello"`), &m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{"a": "hello"}, m)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{"a": "hello"}, m)
|
||||
}
|
||||
|
||||
func TestFastSimpleInterface(t *testing.T) {
|
||||
@@ -33,8 +33,8 @@ func TestFastSimpleInterface(t *testing.T) {
|
||||
err := toml.Unmarshal([]byte(`
|
||||
a = "hello"
|
||||
b = 42`), &m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"a": "hello",
|
||||
"b": int64(42),
|
||||
}, m)
|
||||
@@ -46,8 +46,8 @@ func TestFastMultipartKeyInterface(t *testing.T) {
|
||||
a.interim = "test"
|
||||
a.b.c = "hello"
|
||||
b = 42`), &m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"interim": "test",
|
||||
"b": map[string]interface{}{
|
||||
@@ -66,8 +66,8 @@ func TestFastExistingMap(t *testing.T) {
|
||||
ints.one = 1
|
||||
ints.two = 2
|
||||
strings.yo = "hello"`), &m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]interface{}{
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"ints": map[string]interface{}{
|
||||
"one": int64(1),
|
||||
"two": int64(2),
|
||||
@@ -90,9 +90,9 @@ func TestFastArrayTable(t *testing.T) {
|
||||
m := map[string]interface{}{}
|
||||
|
||||
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{}{
|
||||
"nested": []interface{}{
|
||||
map[string]interface{}{
|
||||
|
||||
+4
-7
@@ -1,19 +1,16 @@
|
||||
//go:build go1.18 || go1.19 || go1.20
|
||||
// +build go1.18 go1.19 go1.20
|
||||
|
||||
package toml_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func FuzzUnmarshal(f *testing.F) {
|
||||
file, err := ioutil.ReadFile("benchmark/benchmark.toml")
|
||||
file, err := os.ReadFile("benchmark/benchmark.toml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -51,6 +48,6 @@ func FuzzUnmarshal(f *testing.F) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed round trip: %s", err)
|
||||
}
|
||||
require.Equal(t, v, v2)
|
||||
assert.Equal(t, v, v2)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
module github.com/pelletier/go-toml/v2
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/stretchr/testify v1.8.4
|
||||
go 1.21.0
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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%v", msg, 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%v", msg, 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
+3
-4
@@ -6,7 +6,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
@@ -23,7 +22,7 @@ type Program struct {
|
||||
}
|
||||
|
||||
func (p *Program) Execute() {
|
||||
flag.Usage = func() { fmt.Fprintf(os.Stderr, p.Usage) }
|
||||
flag.Usage = func() { fmt.Fprint(os.Stderr, p.Usage) }
|
||||
flag.Parse()
|
||||
os.Exit(p.main(flag.Args(), os.Stdin, os.Stdout, os.Stderr))
|
||||
}
|
||||
@@ -72,7 +71,7 @@ func (p *Program) runAllFilesInPlace(files []string) error {
|
||||
}
|
||||
|
||||
func (p *Program) runFileInPlace(path string) error {
|
||||
in, err := ioutil.ReadFile(path)
|
||||
in, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,5 +83,5 @@ func (p *Program) runFileInPlace(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(path, out.Bytes(), 0600)
|
||||
return os.WriteFile(path, out.Bytes(), 0600)
|
||||
}
|
||||
|
||||
+37
-39
@@ -4,15 +4,13 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func processMain(args []string, input io.Reader, stdout, stderr io.Writer, f ConvertFn) int {
|
||||
@@ -30,8 +28,8 @@ func TestProcessMainStdin(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Equal(t, 0, exit)
|
||||
assert.Empty(t, stdout.String())
|
||||
assert.Empty(t, stderr.String())
|
||||
assert.Zero(t, stdout.String())
|
||||
assert.Zero(t, stderr.String())
|
||||
}
|
||||
|
||||
func TestProcessMainStdinErr(t *testing.T) {
|
||||
@@ -44,8 +42,8 @@ func TestProcessMainStdinErr(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Equal(t, -1, exit)
|
||||
assert.Empty(t, stdout.String())
|
||||
assert.NotEmpty(t, stderr.String())
|
||||
assert.Zero(t, stdout.String())
|
||||
assert.NotZero(t, stderr.String())
|
||||
}
|
||||
|
||||
func TestProcessMainStdinDecodeErr(t *testing.T) {
|
||||
@@ -59,16 +57,16 @@ func TestProcessMainStdinDecodeErr(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Equal(t, -1, exit)
|
||||
assert.Empty(t, stdout.String())
|
||||
assert.Contains(t, stderr.String(), "error occurred at")
|
||||
assert.Zero(t, stdout.String())
|
||||
assert.True(t, strings.Contains(stderr.String(), "error occurred at"))
|
||||
}
|
||||
|
||||
func TestProcessMainFileExists(t *testing.T) {
|
||||
tmpfile, err := ioutil.TempFile("", "example")
|
||||
require.NoError(t, err)
|
||||
tmpfile, err := os.CreateTemp("", "example")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
_, err = tmpfile.Write([]byte(`some data`))
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
@@ -78,8 +76,8 @@ func TestProcessMainFileExists(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Equal(t, 0, exit)
|
||||
assert.Empty(t, stdout.String())
|
||||
assert.Empty(t, stderr.String())
|
||||
assert.Zero(t, stdout.String())
|
||||
assert.Zero(t, stderr.String())
|
||||
}
|
||||
|
||||
func TestProcessMainFileDoesNotExist(t *testing.T) {
|
||||
@@ -91,22 +89,22 @@ func TestProcessMainFileDoesNotExist(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Equal(t, -1, exit)
|
||||
assert.Empty(t, stdout.String())
|
||||
assert.NotEmpty(t, stderr.String())
|
||||
assert.Zero(t, stdout.String())
|
||||
assert.NotZero(t, stderr.String())
|
||||
}
|
||||
|
||||
func TestProcessMainFilesInPlace(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
path1 := path.Join(dir, "file1")
|
||||
path2 := path.Join(dir, "file2")
|
||||
|
||||
err = ioutil.WriteFile(path1, []byte("content 1"), 0600)
|
||||
require.NoError(t, err)
|
||||
err = ioutil.WriteFile(path2, []byte("content 2"), 0600)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(path1, []byte("content 1"), 0600)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(path2, []byte("content 2"), 0600)
|
||||
assert.NoError(t, err)
|
||||
|
||||
p := Program{
|
||||
Fn: dummyFileFn,
|
||||
@@ -115,15 +113,15 @@ func TestProcessMainFilesInPlace(t *testing.T) {
|
||||
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1", string(v1))
|
||||
v1, err := os.ReadFile(path1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1", string(v1))
|
||||
|
||||
v2, err := ioutil.ReadFile(path2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "2", string(v2))
|
||||
v2, err := os.ReadFile(path2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "2", string(v2))
|
||||
}
|
||||
|
||||
func TestProcessMainFilesInPlaceErrRead(t *testing.T) {
|
||||
@@ -134,18 +132,18 @@ func TestProcessMainFilesInPlaceErrRead(t *testing.T) {
|
||||
|
||||
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) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
path1 := path.Join(dir, "file1")
|
||||
|
||||
err = ioutil.WriteFile(path1, []byte("content 1"), 0600)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(path1, []byte("content 1"), 0600)
|
||||
assert.NoError(t, err)
|
||||
|
||||
p := Program{
|
||||
Fn: func(io.Reader, io.Writer) error { return fmt.Errorf("oh no") },
|
||||
@@ -154,15 +152,15 @@ func TestProcessMainFilesInPlaceFailFn(t *testing.T) {
|
||||
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "content 1", string(v1))
|
||||
v1, err := os.ReadFile(path1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "content 1", string(v1))
|
||||
}
|
||||
|
||||
func dummyFileFn(r io.Reader, w io.Writer) error {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
||||
)
|
||||
|
||||
@@ -72,7 +70,7 @@ func TestSubsliceOffsetInvalid(t *testing.T) {
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
d, s := e.test()
|
||||
require.Panics(t, func() {
|
||||
assert.Panics(t, func() {
|
||||
danger.SubsliceOffset(d, s)
|
||||
})
|
||||
})
|
||||
@@ -83,9 +81,9 @@ func TestStride(t *testing.T) {
|
||||
a := []byte{1, 2, 3, 4}
|
||||
x := &a[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))
|
||||
require.Equal(t, &a[0], n)
|
||||
assert.Equal(t, &a[0], n)
|
||||
}
|
||||
|
||||
func TestBytesRange(t *testing.T) {
|
||||
@@ -166,12 +164,12 @@ func TestBytesRange(t *testing.T) {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
start, end := e.test()
|
||||
if e.expected == nil {
|
||||
require.Panics(t, func() {
|
||||
assert.Panics(t, func() {
|
||||
danger.BytesRange(start, end)
|
||||
})
|
||||
} else {
|
||||
res := danger.BytesRange(start, end)
|
||||
require.Equal(t, e.expected, res)
|
||||
assert.Equal(t, e.expected, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestDocMarshal(t *testing.T) {
|
||||
@@ -107,13 +107,13 @@ name = 'List.Second'
|
||||
`
|
||||
|
||||
result, err := toml.Marshal(docData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, marshalTestToml, string(result))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, marshalTestToml, string(result))
|
||||
}
|
||||
|
||||
func TestBasicMarshalQuotedKey(t *testing.T) {
|
||||
result, err := toml.Marshal(quotedKeyMarshalTestData)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `'Z.string-àéù' = 'Hello'
|
||||
'Yfloat-𝟘' = 3.5
|
||||
@@ -128,7 +128,7 @@ String2 = 'Two'
|
||||
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{},
|
||||
}
|
||||
result, err := toml.Marshal(doc)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `title = 'Placeholder'
|
||||
bool = false
|
||||
@@ -164,7 +164,7 @@ stringlist = []
|
||||
[map]
|
||||
`
|
||||
|
||||
require.Equal(t, string(expected), string(result))
|
||||
assert.Equal(t, string(expected), string(result))
|
||||
}
|
||||
|
||||
type textMarshaler struct {
|
||||
@@ -187,13 +187,13 @@ func TestTextMarshaler(t *testing.T) {
|
||||
t.Run("at root", func(t *testing.T) {
|
||||
_, err := toml.Marshal(m)
|
||||
// in v2 we do not allow TextMarshaler at root
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("leaf", func(t *testing.T) {
|
||||
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"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
type basicMarshalTestStruct struct {
|
||||
@@ -123,7 +122,7 @@ func TestInterface(t *testing.T) {
|
||||
var config Conf
|
||||
config.Inter = &NestedStruct{}
|
||||
err := toml.Unmarshal(doc, &config)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
expected := Conf{
|
||||
Name: "rui",
|
||||
Age: 18,
|
||||
@@ -139,8 +138,8 @@ func TestInterface(t *testing.T) {
|
||||
func TestBasicUnmarshal(t *testing.T) {
|
||||
result := basicMarshalTestStruct{}
|
||||
err := toml.Unmarshal(basicTestToml, &result)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, basicTestData, result)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, basicTestData, result)
|
||||
}
|
||||
|
||||
type quotedKeyMarshalTestStruct struct {
|
||||
@@ -300,7 +299,7 @@ func TestDocUnmarshal(t *testing.T) {
|
||||
result := testDoc{}
|
||||
err := toml.Unmarshal(marshalTestToml, &result)
|
||||
expected := docData
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
@@ -340,7 +339,7 @@ shouldntBeHere = 2
|
||||
func TestUnexportedUnmarshal(t *testing.T) {
|
||||
result := unexportedMarshalTestStruct{}
|
||||
err := toml.Unmarshal(unexportedTestToml, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, unexportedTestData, result)
|
||||
}
|
||||
|
||||
@@ -456,7 +455,7 @@ func TestEmptytomlUnmarshal(t *testing.T) {
|
||||
|
||||
result := emptyMarshalTestStruct{}
|
||||
err := toml.Unmarshal(emptyTestToml, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, emptyTestData, result)
|
||||
}
|
||||
|
||||
@@ -504,7 +503,7 @@ Str = "Hello"
|
||||
func TestPointerUnmarshal(t *testing.T) {
|
||||
result := pointerMarshalTestStruct{}
|
||||
err := toml.Unmarshal(pointerTestToml, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, pointerTestData, result)
|
||||
}
|
||||
|
||||
@@ -540,7 +539,7 @@ StringPtr = [["Three", "Four"]]
|
||||
func TestNestedUnmarshal(t *testing.T) {
|
||||
result := nestedMarshalTestStruct{}
|
||||
err := toml.Unmarshal(nestedTestToml, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nestedTestData, result)
|
||||
}
|
||||
|
||||
@@ -834,7 +833,7 @@ func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
result := Test{}
|
||||
err := toml.Unmarshal(test.input, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
@@ -963,7 +962,7 @@ func TestUnmarshalTypeTableHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := map[header]map[string]int{
|
||||
"test": map[string]int{"a": 1},
|
||||
"test": {"a": 1},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
@@ -1090,7 +1089,7 @@ func TestUnmarshalCheckConversionFloatInt(t *testing.T) {
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
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 {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
err := toml.Unmarshal([]byte(test.input), &overflow{})
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1745,7 +1744,7 @@ Age = 23
|
||||
}
|
||||
actual := OuterStruct{}
|
||||
err := toml.Unmarshal(doc, &actual)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
@@ -1830,7 +1829,7 @@ InnerField = "After4"
|
||||
}
|
||||
|
||||
err := toml.Unmarshal(doc, &actual)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
@@ -1879,7 +1878,7 @@ type arrayTooSmallStruct struct {
|
||||
func TestUnmarshalSlice(t *testing.T) {
|
||||
var actual sliceStruct
|
||||
err := toml.Unmarshal(sliceTomlDemo, &actual)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
expected := sliceStruct{
|
||||
Slice: []string{"Howdy", "Hey There"},
|
||||
SlicePtr: &[]string{"Howdy", "Hey There"},
|
||||
@@ -1930,7 +1929,7 @@ func TestUnmarshalMixedTypeSlice(t *testing.T) {
|
||||
},
|
||||
}
|
||||
err := toml.Unmarshal(doc, &actual)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
@@ -1939,7 +1938,7 @@ func TestUnmarshalArray(t *testing.T) {
|
||||
|
||||
var actual arrayStruct
|
||||
err = toml.Unmarshal(sliceTomlDemo, &actual)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := arrayStruct{
|
||||
Slice: [4]string{"Howdy", "Hey There"},
|
||||
@@ -1998,8 +1997,13 @@ func TestDecoderStrict(t *testing.T) {
|
||||
}
|
||||
|
||||
err := strictDecoder(input).Decode(&doc)
|
||||
require.Error(t, err)
|
||||
require.IsType(t, &toml.StrictMissingError{}, err)
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
reflect.TypeOf(err), reflect.TypeOf(&toml.StrictMissingError{}),
|
||||
"Expected a *toml.StrictMissingError, got: %v", reflect.TypeOf(err),
|
||||
)
|
||||
|
||||
se := err.(*toml.StrictMissingError)
|
||||
|
||||
keys := []toml.Key{}
|
||||
@@ -2015,10 +2019,10 @@ func TestDecoderStrict(t *testing.T) {
|
||||
{"undecoded", "array"},
|
||||
}
|
||||
|
||||
require.Equal(t, expectedKeys, keys)
|
||||
assert.Equal(t, expectedKeys, keys)
|
||||
|
||||
err = decoder(input).Decode(&doc)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var m map[string]interface{}
|
||||
err = decoder(input).Decode(&m)
|
||||
@@ -2036,7 +2040,7 @@ func TestDecoderStrictValid(t *testing.T) {
|
||||
}
|
||||
|
||||
err := strictDecoder(input).Decode(&doc)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
type docUnmarshalTOML struct {
|
||||
@@ -2087,7 +2091,7 @@ func TestCustomUnmarshal(t *testing.T) {
|
||||
|
||||
var d parent
|
||||
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, "ok2", d.DocPointer.Decoded.Key)
|
||||
}
|
||||
@@ -2153,7 +2157,7 @@ Int = 21
|
||||
Float = 2.0
|
||||
`
|
||||
err := toml.Unmarshal([]byte(input), &doc)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 12, doc.UnixTime.Value)
|
||||
assert.Equal(t, 42, doc.Version.Value)
|
||||
assert.Equal(t, 1, doc.Bool.Value)
|
||||
@@ -2223,7 +2227,10 @@ func TestUnmarshalEmptyInterface(t *testing.T) {
|
||||
if err != nil {
|
||||
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{})
|
||||
assert.Equal(t, "pelletier", x["User"])
|
||||
@@ -2276,7 +2283,7 @@ func (c *Custom) UnmarshalTOML(v interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGithubIssue431(t *testing.T) {
|
||||
func TestGitHubIssue431(t *testing.T) {
|
||||
doc := `key = "value"`
|
||||
var c Config
|
||||
if err := toml.Unmarshal([]byte(doc), &c); err != nil {
|
||||
@@ -2314,7 +2321,7 @@ type config437 struct {
|
||||
} `toml:"HTTP"`
|
||||
}
|
||||
|
||||
func TestGithubIssue437(t *testing.T) {
|
||||
func TestGitHubIssue437(t *testing.T) {
|
||||
t.Skipf("unmarshalTOML not implemented")
|
||||
src := `
|
||||
[HTTP]
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// Array: remove tags from all itenm.
|
||||
// Array: remove tags from all items.
|
||||
case []interface{}:
|
||||
a := make([]interface{}, len(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 f, nil
|
||||
|
||||
//toml.LocalDate{Year:2020, Month:12, Day:12}
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
switch v {
|
||||
case "true":
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// Marshal is a helpfer function for calling toml.Marshal
|
||||
// Marshal is a helper function for calling toml.Marshal
|
||||
//
|
||||
// Only needed to avoid package import loops.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
@@ -48,3 +48,21 @@ func DecodeStdin() error {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var pool sync.Pool
|
||||
var pool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &SeenTracker{}
|
||||
},
|
||||
}
|
||||
|
||||
func (s *SeenTracker) reset() {
|
||||
// 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
|
||||
// keys that have been seen in previous calls, and validates that types are
|
||||
// consistent.
|
||||
func (s *SeenTracker) CheckExpression(node *unstable.Node) error {
|
||||
// consistent. It returns true if it is the first time this node's key is seen.
|
||||
// Useful to clear array tables on first use.
|
||||
func (s *SeenTracker) CheckExpression(node *unstable.Node) (bool, error) {
|
||||
if s.entries == nil {
|
||||
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 {
|
||||
s.setExplicitFlag(s.currentIdx)
|
||||
}
|
||||
@@ -192,7 +197,7 @@ func (s *SeenTracker) checkTable(node *unstable.Node) error {
|
||||
} else {
|
||||
entry := s.entries[idx]
|
||||
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
|
||||
@@ -201,25 +206,27 @@ func (s *SeenTracker) checkTable(node *unstable.Node) error {
|
||||
k := it.Node().Data
|
||||
idx := s.find(parentIdx, k)
|
||||
|
||||
first := false
|
||||
if idx >= 0 {
|
||||
kind := s.entries[idx].kind
|
||||
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 {
|
||||
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
|
||||
} else {
|
||||
idx = s.create(parentIdx, k, tableKind, true, false)
|
||||
first = true
|
||||
}
|
||||
|
||||
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 {
|
||||
s.setExplicitFlag(s.currentIdx)
|
||||
}
|
||||
@@ -242,7 +249,7 @@ func (s *SeenTracker) checkArrayTable(node *unstable.Node) error {
|
||||
} else {
|
||||
entry := s.entries[idx]
|
||||
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
|
||||
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
|
||||
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)
|
||||
} else {
|
||||
idx = s.create(parentIdx, k, arrayTableKind, true, false)
|
||||
}
|
||||
|
||||
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
|
||||
it := node.Key()
|
||||
|
||||
@@ -281,11 +289,11 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) error {
|
||||
} else {
|
||||
entry := s.entries[idx]
|
||||
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 {
|
||||
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 {
|
||||
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 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()
|
||||
for it.Next() {
|
||||
n := it.Node()
|
||||
switch n.Kind {
|
||||
case unstable.InlineTable:
|
||||
err := s.checkInlineTable(n)
|
||||
first, err = s.checkInlineTable(n)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
case unstable.Array:
|
||||
err := s.checkArray(n)
|
||||
first, err = s.checkArray(n)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return first, nil
|
||||
}
|
||||
|
||||
func (s *SeenTracker) checkInlineTable(node *unstable.Node) error {
|
||||
if pool.New == nil {
|
||||
pool.New = func() interface{} {
|
||||
return &SeenTracker{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SeenTracker) checkInlineTable(node *unstable.Node) (first bool, err error) {
|
||||
s = pool.Get().(*SeenTracker)
|
||||
s.reset()
|
||||
|
||||
it := node.Children()
|
||||
for it.Next() {
|
||||
n := it.Node()
|
||||
err := s.checkKeyValue(n)
|
||||
first, err = s.checkKeyValue(n)
|
||||
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
|
||||
// a value.
|
||||
pool.Put(s)
|
||||
return nil
|
||||
return first, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestEntrySize(t *testing.T) {
|
||||
@@ -12,5 +12,9 @@ func TestEntrySize(t *testing.T) {
|
||||
// performance of unmarshaling documents. Should only be increased with care
|
||||
// and a very good reason.
|
||||
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{})),
|
||||
)
|
||||
}
|
||||
|
||||
+7
-1
@@ -94,7 +94,13 @@ type LocalDateTime struct {
|
||||
|
||||
// AsTime converts d into a specific time instance in zone.
|
||||
func (d LocalDateTime) AsTime(zone *time.Location) time.Time {
|
||||
return time.Date(d.Year, time.Month(d.Month), d.Day, d.Hour, d.Minute, d.Second, d.Nanosecond, zone)
|
||||
// Normalize leap seconds (second=60) to second=59 to prevent overflow
|
||||
// when Go's time.Date normalizes the time.
|
||||
second := d.Second
|
||||
if second == 60 {
|
||||
second = 59
|
||||
}
|
||||
return time.Date(d.Year, time.Month(d.Month), d.Day, d.Hour, d.Minute, second, d.Nanosecond, zone)
|
||||
}
|
||||
|
||||
// String returns RFC 3339 representation of d.
|
||||
|
||||
+28
-28
@@ -5,73 +5,73 @@ import (
|
||||
"time"
|
||||
|
||||
"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) {
|
||||
d := toml.LocalDate{2021, 6, 8}
|
||||
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) {
|
||||
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) {
|
||||
d := toml.LocalDate{2021, 6, 8}
|
||||
b, err := d.MarshalText()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("2021-06-08"), b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("2021-06-08"), b)
|
||||
}
|
||||
|
||||
func TestLocalDate_UnmarshalMarshalText(t *testing.T) {
|
||||
d := toml.LocalDate{}
|
||||
err := d.UnmarshalText([]byte("2021-06-08"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, toml.LocalDate{2021, 6, 8}, d)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, toml.LocalDate{2021, 6, 8}, d)
|
||||
|
||||
err = d.UnmarshalText([]byte("what"))
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestLocalTime_String(t *testing.T) {
|
||||
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}
|
||||
require.Equal(t, "20:12:01", d.String())
|
||||
assert.Equal(t, "20:12:01", d.String())
|
||||
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}
|
||||
require.Equal(t, "20:12:01.0000001", d.String())
|
||||
assert.Equal(t, "20:12:01.0000001", d.String())
|
||||
}
|
||||
|
||||
func TestLocalTime_MarshalText(t *testing.T) {
|
||||
d := toml.LocalTime{20, 12, 1, 2, 9}
|
||||
b, err := d.MarshalText()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("20:12:01.000000002"), b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("20:12:01.000000002"), b)
|
||||
}
|
||||
|
||||
func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
|
||||
d := toml.LocalTime{}
|
||||
err := d.UnmarshalText([]byte("20:12:01.000000002"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, toml.LocalTime{20, 12, 1, 2, 9}, d)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, toml.LocalTime{20, 12, 1, 2, 9}, d)
|
||||
|
||||
err = d.UnmarshalText([]byte("what"))
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
|
||||
err = d.UnmarshalText([]byte("20:12:01.000000002 bad"))
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestLocalTime_RoundTrip(t *testing.T) {
|
||||
var d struct{ A toml.LocalTime }
|
||||
err := toml.Unmarshal([]byte("a=20:12:01.500"), &d)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "20:12:01.500", d.A.String())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "20:12:01.500", d.A.String())
|
||||
}
|
||||
|
||||
func TestLocalDateTime_AsTime(t *testing.T) {
|
||||
@@ -80,7 +80,7 @@ func TestLocalDateTime_AsTime(t *testing.T) {
|
||||
toml.LocalTime{20, 12, 1, 2, 9},
|
||||
}
|
||||
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) {
|
||||
@@ -88,7 +88,7 @@ func TestLocalDateTime_String(t *testing.T) {
|
||||
toml.LocalDate{2021, 6, 8},
|
||||
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) {
|
||||
@@ -97,22 +97,22 @@ func TestLocalDateTime_MarshalText(t *testing.T) {
|
||||
toml.LocalTime{20, 12, 1, 2, 9},
|
||||
}
|
||||
b, err := d.MarshalText()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("2021-06-08T20:12:01.000000002"), b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("2021-06-08T20:12:01.000000002"), b)
|
||||
}
|
||||
|
||||
func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) {
|
||||
d := toml.LocalDateTime{}
|
||||
err := d.UnmarshalText([]byte("2021-06-08 20:12:01.000000002"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, toml.LocalDateTime{
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, toml.LocalDateTime{
|
||||
toml.LocalDate{2021, 6, 8},
|
||||
toml.LocalTime{20, 12, 1, 2, 9},
|
||||
}, d)
|
||||
|
||||
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"))
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
+118
-9
@@ -3,11 +3,12 @@ package toml
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -41,6 +42,7 @@ type Encoder struct {
|
||||
arraysMultiline bool
|
||||
indentSymbol string
|
||||
indentTables bool
|
||||
marshalJsonNumbers bool
|
||||
}
|
||||
|
||||
// NewEncoder returns a new Encoder that writes to w.
|
||||
@@ -87,6 +89,17 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
||||
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.
|
||||
//
|
||||
// If v cannot be represented to TOML it returns an error.
|
||||
@@ -148,6 +161,11 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
||||
//
|
||||
// The "omitempty" option prevents empty values or groups from being emitted.
|
||||
//
|
||||
// The "omitzero" option prevents zero values or groups from being emitted.
|
||||
//
|
||||
// The "commented" option prefixes the value and all its children with a comment
|
||||
// symbol.
|
||||
//
|
||||
// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
|
||||
// a TOML comment before the value being annotated. Comments are ignored inside
|
||||
// inline tables. For array tables, the comment is only present before the first
|
||||
@@ -180,6 +198,8 @@ func (enc *Encoder) Encode(v interface{}) error {
|
||||
type valueOptions struct {
|
||||
multiline bool
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
commented bool
|
||||
comment string
|
||||
}
|
||||
|
||||
@@ -205,6 +225,9 @@ type encoderCtx struct {
|
||||
// Indentation level
|
||||
indent int
|
||||
|
||||
// Prefix the current value with a comment.
|
||||
commented bool
|
||||
|
||||
// Options coming from struct tags
|
||||
options valueOptions
|
||||
}
|
||||
@@ -245,10 +268,22 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
||||
return append(b, x.String()...), nil
|
||||
case LocalDateTime:
|
||||
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)
|
||||
if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
|
||||
if hasTextMarshaler || (v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
|
||||
if !hasTextMarshaler {
|
||||
v = v.Addr()
|
||||
}
|
||||
@@ -352,11 +387,16 @@ func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
|
||||
return options.omitempty && isEmptyValue(v)
|
||||
}
|
||||
|
||||
func shouldOmitZero(options valueOptions, v reflect.Value) bool {
|
||||
return options.omitzero && v.IsZero()
|
||||
}
|
||||
|
||||
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
if !ctx.inline {
|
||||
b = enc.encodeComment(ctx.indent, options.comment, b)
|
||||
b = enc.commented(ctx.commented, b)
|
||||
b = enc.indent(ctx.indent, b)
|
||||
}
|
||||
|
||||
@@ -378,6 +418,13 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) commented(commented bool, b []byte) []byte {
|
||||
if commented {
|
||||
return append(b, "# "...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
@@ -477,12 +524,26 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt
|
||||
del = 0x7f
|
||||
)
|
||||
|
||||
for _, r := range []byte(v) {
|
||||
bv := []byte(v)
|
||||
for i := 0; i < len(bv); i++ {
|
||||
r := bv[i]
|
||||
switch r {
|
||||
case '\\':
|
||||
b = append(b, `\\`...)
|
||||
case '"':
|
||||
if multiline {
|
||||
// Quotation marks do not need to be quoted in multiline strings unless
|
||||
// it contains 3 consecutive. If 3+ quotes appear, quote all of them
|
||||
// because it's visually better
|
||||
if i+2 > len(bv) || bv[i+1] != '"' || bv[i+2] != '"' {
|
||||
b = append(b, r)
|
||||
} else {
|
||||
b = append(b, `\"\"\"`...)
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
b = append(b, `\"`...)
|
||||
}
|
||||
case '\b':
|
||||
b = append(b, `\b`...)
|
||||
case '\f':
|
||||
@@ -526,6 +587,8 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
||||
|
||||
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
||||
|
||||
b = enc.commented(ctx.commented, b)
|
||||
|
||||
b = enc.indent(ctx.indent, b)
|
||||
|
||||
b = append(b, '[')
|
||||
@@ -589,6 +652,18 @@ func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
|
||||
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
|
||||
}
|
||||
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())
|
||||
}
|
||||
@@ -626,8 +701,8 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte
|
||||
}
|
||||
|
||||
func sortEntriesByKey(e []entry) {
|
||||
sort.Slice(e, func(i, j int) bool {
|
||||
return e[i].Key < e[j].Key
|
||||
slices.SortFunc(e, func(a, b entry) int {
|
||||
return strings.Compare(a.Key, b.Key)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -690,6 +765,8 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
||||
if fieldType.Anonymous {
|
||||
if fieldType.Type.Kind() == reflect.Struct {
|
||||
walkStruct(ctx, t, f)
|
||||
} else if fieldType.Type.Kind() == reflect.Ptr && !f.IsNil() && f.Elem().Kind() == reflect.Struct {
|
||||
walkStruct(ctx, t, f.Elem())
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
@@ -704,6 +781,8 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
||||
options := valueOptions{
|
||||
multiline: opts.multiline,
|
||||
omitempty: opts.omitempty,
|
||||
omitzero: opts.omitzero,
|
||||
commented: opts.commented,
|
||||
comment: fieldType.Tag.Get("comment"),
|
||||
}
|
||||
|
||||
@@ -763,6 +842,8 @@ type tagOptions struct {
|
||||
multiline bool
|
||||
inline bool
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
commented bool
|
||||
}
|
||||
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
@@ -774,7 +855,7 @@ func parseTag(tag string) (string, tagOptions) {
|
||||
}
|
||||
|
||||
raw := tag[idx+1:]
|
||||
tag = string(tag[:idx])
|
||||
tag = tag[:idx]
|
||||
for raw != "" {
|
||||
var o string
|
||||
i := strings.Index(raw, ",")
|
||||
@@ -790,6 +871,10 @@ func parseTag(tag string) (string, tagOptions) {
|
||||
opts.inline = true
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "omitzero":
|
||||
opts.omitzero = true
|
||||
case "commented":
|
||||
opts.commented = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,11 +907,16 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
||||
if shouldOmitEmpty(kv.Options, kv.Value) {
|
||||
continue
|
||||
}
|
||||
if shouldOmitZero(kv.Options, kv.Value) {
|
||||
continue
|
||||
}
|
||||
hasNonEmptyKV = true
|
||||
|
||||
ctx.setKey(kv.Key)
|
||||
ctx2 := ctx
|
||||
ctx2.commented = kv.Options.commented || ctx2.commented
|
||||
|
||||
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
|
||||
b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -839,6 +929,9 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
||||
if shouldOmitEmpty(table.Options, table.Value) {
|
||||
continue
|
||||
}
|
||||
if shouldOmitZero(table.Options, table.Value) {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
first = false
|
||||
if hasNonEmptyKV {
|
||||
@@ -851,8 +944,10 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
||||
ctx.setKey(table.Key)
|
||||
|
||||
ctx.options = table.Options
|
||||
ctx2 := ctx
|
||||
ctx2.commented = ctx2.commented || ctx.options.commented
|
||||
|
||||
b, err = enc.encode(b, ctx, table.Value)
|
||||
b, err = enc.encode(b, ctx2, table.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -871,6 +966,9 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
|
||||
if shouldOmitEmpty(kv.Options, kv.Value) {
|
||||
continue
|
||||
}
|
||||
if shouldOmitZero(kv.Options, kv.Value) {
|
||||
continue
|
||||
}
|
||||
|
||||
if first {
|
||||
first = false
|
||||
@@ -899,7 +997,7 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
|
||||
if !v.IsValid() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -970,6 +1068,13 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
||||
ctx.shiftKey()
|
||||
|
||||
scratch := make([]byte, 0, 64)
|
||||
|
||||
scratch = enc.commented(ctx.commented, scratch)
|
||||
|
||||
if enc.indentTables {
|
||||
scratch = enc.indent(ctx.indent, scratch)
|
||||
}
|
||||
|
||||
scratch = append(scratch, "[["...)
|
||||
|
||||
for i, k := range ctx.parentKey {
|
||||
@@ -985,6 +1090,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
|
||||
|
||||
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
||||
|
||||
if enc.indentTables {
|
||||
ctx.indent++
|
||||
}
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i != 0 {
|
||||
b = append(b, "\n"...)
|
||||
|
||||
+632
-61
@@ -6,13 +6,14 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
type marshalTextKey struct {
|
||||
@@ -30,6 +31,27 @@ func (k marshalBadTextKey) MarshalText() ([]byte, 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) {
|
||||
someInt := 42
|
||||
|
||||
@@ -366,6 +388,54 @@ name = 'Alice'
|
||||
expected: `A = """
|
||||
hello
|
||||
world"""
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "multi-line quotation",
|
||||
v: struct {
|
||||
A string `toml:",multiline"`
|
||||
}{
|
||||
A: "hello\n\"world\"",
|
||||
},
|
||||
expected: `A = """
|
||||
hello
|
||||
"world""""
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "multi-line triple quotation",
|
||||
v: struct {
|
||||
A string `toml:",multiline"`
|
||||
}{
|
||||
A: "hello\n\"\"\"world\"",
|
||||
},
|
||||
expected: `A = """
|
||||
hello
|
||||
\"\"\"world""""
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "multi-line triple quotation",
|
||||
v: struct {
|
||||
A string `toml:",multiline"`
|
||||
}{
|
||||
A: "hello\n\"world\"\"\"",
|
||||
},
|
||||
expected: `A = """
|
||||
hello
|
||||
"world\"\"\""""
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "multi-line sextuple quotation",
|
||||
v: struct {
|
||||
A string `toml:",multiline"`
|
||||
}{
|
||||
A: "hello\n\"\"\"\"\"\"world\"",
|
||||
},
|
||||
expected: `A = """
|
||||
hello
|
||||
\"\"\"\"\"\"world""""
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -587,13 +657,69 @@ foo = 42
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "invalid map key",
|
||||
desc: "int map key",
|
||||
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,
|
||||
},
|
||||
{
|
||||
desc: "invalid map key but empty",
|
||||
v: map[int]interface{}{},
|
||||
v: map[struct{ int }]interface{}{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
@@ -708,18 +834,18 @@ Three = [1, 2, 3]
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
b, err := toml.Marshal(e.v)
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, e.expected, string(b))
|
||||
|
||||
// make sure the output is always valid TOML
|
||||
defaultMap := map[string]interface{}{}
|
||||
err = toml.Unmarshal(b, &defaultMap)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testWithAllFlags(t, func(t *testing.T, flags int) {
|
||||
t.Helper()
|
||||
@@ -729,13 +855,13 @@ Three = [1, 2, 3]
|
||||
setFlags(enc, flags)
|
||||
|
||||
err := enc.Encode(e.v)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
inlineMap := map[string]interface{}{}
|
||||
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 +928,8 @@ nan = nan
|
||||
`
|
||||
|
||||
actual, err := toml.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(actual))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(actual))
|
||||
|
||||
v64 := map[string]float64{
|
||||
"nan": math.NaN(),
|
||||
@@ -812,8 +938,8 @@ nan = nan
|
||||
}
|
||||
|
||||
actual, err = toml.Marshal(v64)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(actual))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(actual))
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
@@ -873,7 +999,7 @@ func TestMarshalIndentTables(t *testing.T) {
|
||||
enc := toml.NewEncoder(&buf)
|
||||
enc.SetIndentTables(true)
|
||||
err := enc.Encode(e.v)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, e.expected, buf.String())
|
||||
})
|
||||
}
|
||||
@@ -893,13 +1019,13 @@ func (c *customTextMarshaler) MarshalText() ([]byte, error) {
|
||||
func TestMarshalTextMarshaler_NoRoot(t *testing.T) {
|
||||
c := customTextMarshaler{}
|
||||
_, err := toml.Marshal(&c)
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalTextMarshaler_Error(t *testing.T) {
|
||||
m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
|
||||
_, err := toml.Marshal(m)
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
|
||||
@@ -912,13 +1038,13 @@ func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := toml.Marshal(d)
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMarshalTextMarshaler(t *testing.T) {
|
||||
m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
|
||||
r, err := toml.Marshal(m)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "a = '::2'\n", string(r))
|
||||
}
|
||||
|
||||
@@ -932,7 +1058,7 @@ func TestEncodeToBrokenWriter(t *testing.T) {
|
||||
w := brokenWriter{}
|
||||
enc := toml.NewEncoder(&w)
|
||||
err := enc.Encode(map[string]string{"hello": "world"})
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEncoderSetIndentSymbol(t *testing.T) {
|
||||
@@ -941,13 +1067,36 @@ func TestEncoderSetIndentSymbol(t *testing.T) {
|
||||
enc.SetIndentTables(true)
|
||||
enc.SetIndentSymbol(">>>")
|
||||
err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}})
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
expected := `[parent]
|
||||
>>>hello = 'world'
|
||||
`
|
||||
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) {
|
||||
type doc struct {
|
||||
String string `toml:",omitempty,multiline"`
|
||||
@@ -969,14 +1118,78 @@ func TestEncoderOmitempty(t *testing.T) {
|
||||
Ptr *string `toml:",omitempty,multiline"`
|
||||
Iface interface{} `toml:",omitempty,multiline"`
|
||||
Struct struct{} `toml:",omitempty,multiline"`
|
||||
Inline struct {
|
||||
String string `toml:",omitempty,multiline"`
|
||||
} `toml:",inline"`
|
||||
}
|
||||
|
||||
d := doc{}
|
||||
|
||||
b, err := toml.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := ``
|
||||
expected := `Inline = {}
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, string(b))
|
||||
}
|
||||
|
||||
func TestEncoderOmitzero(t *testing.T) {
|
||||
type doc struct {
|
||||
String string `toml:",omitzero,multiline"`
|
||||
Bool bool `toml:",omitzero,multiline"`
|
||||
Int int `toml:",omitzero,multiline"`
|
||||
Int8 int8 `toml:",omitzero,multiline"`
|
||||
Int16 int16 `toml:",omitzero,multiline"`
|
||||
Int32 int32 `toml:",omitzero,multiline"`
|
||||
Int64 int64 `toml:",omitzero,multiline"`
|
||||
Uint uint `toml:",omitzero,multiline"`
|
||||
Uint8 uint8 `toml:",omitzero,multiline"`
|
||||
Uint16 uint16 `toml:",omitzero,multiline"`
|
||||
Uint32 uint32 `toml:",omitzero,multiline"`
|
||||
Uint64 uint64 `toml:",omitzero,multiline"`
|
||||
Float32 float32 `toml:",omitzero,multiline"`
|
||||
Float64 float64 `toml:",omitzero,multiline"`
|
||||
MapNil map[string]string `toml:",omitzero,multiline"`
|
||||
Slice []string `toml:",omitzero,multiline"`
|
||||
Ptr *string `toml:",omitzero,multiline"`
|
||||
Iface interface{} `toml:",omitzero,multiline"`
|
||||
Struct struct{} `toml:",omitzero,multiline"`
|
||||
Time time.Time `toml:",omitzero,multiline"`
|
||||
IP netip.Addr `toml:",omitzero,multiline"`
|
||||
Inline struct {
|
||||
String string `toml:",omitzero,multiline"`
|
||||
} `toml:",inline"`
|
||||
}
|
||||
|
||||
d := doc{}
|
||||
|
||||
b, err := toml.Marshal(d)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `Inline = {}
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, string(b))
|
||||
}
|
||||
|
||||
func TestEncoderOmitzeroOpaqueStruct(t *testing.T) {
|
||||
type doc struct {
|
||||
Time time.Time `toml:",omitzero"`
|
||||
IP netip.Addr `toml:",omitzero"`
|
||||
}
|
||||
|
||||
d := doc{
|
||||
Time: time.Date(2001, 2, 3, 4, 5, 6, 7, time.UTC),
|
||||
IP: netip.MustParseAddr("192.168.178.35"),
|
||||
}
|
||||
|
||||
b, err := toml.Marshal(d)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `Time = 2001-02-03T04:05:06.000000007Z
|
||||
IP = '192.168.178.35'
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, string(b))
|
||||
}
|
||||
@@ -991,7 +1204,7 @@ func TestEncoderTagFieldName(t *testing.T) {
|
||||
d := doc{String: "world"}
|
||||
|
||||
b, err := toml.Marshal(d)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `hello = 'world'
|
||||
'#' = ''
|
||||
@@ -1006,11 +1219,11 @@ func TestIssue436(t *testing.T) {
|
||||
|
||||
var v interface{}
|
||||
err := json.Unmarshal(data, &v)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = toml.NewEncoder(&buf).Encode(v)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `[[a]]
|
||||
[a.b]
|
||||
@@ -1032,27 +1245,30 @@ func TestIssue424(t *testing.T) {
|
||||
msg2 := Message2{"Hello\\World"}
|
||||
|
||||
toml1, err := toml.Marshal(msg1)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
toml2, err := toml.Marshal(msg2)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg1parsed := Message1{}
|
||||
err = toml.Unmarshal(toml1, &msg1parsed)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, msg1, msg1parsed)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, msg1, msg1parsed)
|
||||
|
||||
msg2parsed := Message2{}
|
||||
err = toml.Unmarshal(toml2, &msg2parsed)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, msg2, msg2parsed)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, msg2, msg2parsed)
|
||||
}
|
||||
|
||||
func TestIssue567(t *testing.T) {
|
||||
var m map[string]interface{}
|
||||
err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, m["A"], toml.LocalTime{})
|
||||
assert.NoError(t, err)
|
||||
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) {
|
||||
@@ -1061,7 +1277,7 @@ func TestIssue590(t *testing.T) {
|
||||
Option CustomType `toml:"option"`
|
||||
}
|
||||
err := toml.Unmarshal([]byte("option = 42"), &cfg)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIssue571(t *testing.T) {
|
||||
@@ -1077,14 +1293,14 @@ func TestIssue571(t *testing.T) {
|
||||
Float64: 43,
|
||||
}
|
||||
b, err := toml.Marshal(foo)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var foo2 Foo
|
||||
err = toml.Unmarshal(b, &foo2)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.InDelta(t, 42, foo2.Float32, closeEnough)
|
||||
assert.InDelta(t, 43, foo2.Float64, closeEnough)
|
||||
inDelta(t, 42, foo2.Float32, closeEnough)
|
||||
inDelta(t, 43, foo2.Float64, closeEnough)
|
||||
}
|
||||
|
||||
func TestIssue678(t *testing.T) {
|
||||
@@ -1097,13 +1313,13 @@ func TestIssue678(t *testing.T) {
|
||||
}
|
||||
|
||||
out, err := toml.Marshal(cfg)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "BigInt = '123'\n", string(out))
|
||||
|
||||
cfg2 := &Config{}
|
||||
err = toml.Unmarshal(out, cfg2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg, cfg2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, cfg, cfg2)
|
||||
}
|
||||
|
||||
func TestIssue752(t *testing.T) {
|
||||
@@ -1118,8 +1334,8 @@ func TestIssue752(t *testing.T) {
|
||||
c := Container{}
|
||||
|
||||
out, err := toml.Marshal(c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", string(out))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", string(out))
|
||||
}
|
||||
|
||||
func TestIssue768(t *testing.T) {
|
||||
@@ -1128,14 +1344,14 @@ func TestIssue768(t *testing.T) {
|
||||
}
|
||||
|
||||
out, err := toml.Marshal(&cfg{})
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `# This is a multiline comment.
|
||||
# This is line 2.
|
||||
Name = ''
|
||||
`
|
||||
|
||||
require.Equal(t, expected, string(out))
|
||||
assert.Equal(t, expected, string(out))
|
||||
}
|
||||
|
||||
func TestIssue786(t *testing.T) {
|
||||
@@ -1151,9 +1367,9 @@ func TestIssue786(t *testing.T) {
|
||||
|
||||
x := Test{}
|
||||
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 {
|
||||
From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"`
|
||||
@@ -1175,7 +1391,8 @@ func TestIssue786(t *testing.T) {
|
||||
config.Custom = []Custom{{Name: "omit", General: General{Randomize: false}}}
|
||||
config.Custom = append(config.Custom, Custom{Name: "present", General: General{From: "-2d", Randomize: true}})
|
||||
encoder := toml.NewEncoder(buf)
|
||||
encoder.Encode(config)
|
||||
err = encoder.Encode(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `# from in graphite-web format, the local TZ is used
|
||||
from = '-2d'
|
||||
@@ -1198,7 +1415,47 @@ from = '-2d'
|
||||
randomize = true
|
||||
`
|
||||
|
||||
require.Equal(t, expected, buf.String())
|
||||
assert.Equal(t, expected, buf.String())
|
||||
}
|
||||
|
||||
func TestMarshalIssue888(t *testing.T) {
|
||||
type Thing struct {
|
||||
FieldA string `comment:"my field A"`
|
||||
FieldB string `comment:"my field B"`
|
||||
}
|
||||
|
||||
type Cfg struct {
|
||||
Custom []Thing `comment:"custom config"`
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
config := Cfg{
|
||||
Custom: []Thing{
|
||||
{FieldA: "field a 1", FieldB: "field b 1"},
|
||||
{FieldA: "field a 2", FieldB: "field b 2"},
|
||||
},
|
||||
}
|
||||
|
||||
encoder := toml.NewEncoder(buf).SetIndentTables(true)
|
||||
err := encoder.Encode(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `# custom config
|
||||
[[Custom]]
|
||||
# my field A
|
||||
FieldA = 'field a 1'
|
||||
# my field B
|
||||
FieldB = 'field b 1'
|
||||
|
||||
[[Custom]]
|
||||
# my field A
|
||||
FieldA = 'field a 2'
|
||||
# my field B
|
||||
FieldB = 'field b 2'
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, buf.String())
|
||||
}
|
||||
|
||||
func TestMarshalNestedAnonymousStructs(t *testing.T) {
|
||||
@@ -1234,8 +1491,8 @@ value = ''
|
||||
`
|
||||
|
||||
result, err := toml.Marshal(doc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(result))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(result))
|
||||
}
|
||||
|
||||
func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
|
||||
@@ -1260,9 +1517,41 @@ value = ''
|
||||
`
|
||||
|
||||
result, err := toml.Marshal(doc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(result))
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
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) {
|
||||
@@ -1279,12 +1568,12 @@ func TestLocalTime(t *testing.T) {
|
||||
`
|
||||
|
||||
out, err := toml.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(out))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, string(out))
|
||||
}
|
||||
|
||||
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
|
||||
// supported by standard-compliant parsers, uint64 > max int64 cannot be
|
||||
// marshaled.
|
||||
@@ -1293,12 +1582,12 @@ func TestMarshalUint64Overflow(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := toml.Marshal(x)
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIndentWithInlineTable(t *testing.T) {
|
||||
x := map[string][]map[string]string{
|
||||
"one": []map[string]string{
|
||||
"one": {
|
||||
{"0": "0"},
|
||||
{"1": "1"},
|
||||
},
|
||||
@@ -1313,7 +1602,132 @@ func TestIndentWithInlineTable(t *testing.T) {
|
||||
enc.SetIndentTables(true)
|
||||
enc.SetTablesInline(true)
|
||||
enc.SetArraysMultiline(true)
|
||||
require.NoError(t, enc.Encode(x))
|
||||
assert.NoError(t, enc.Encode(x))
|
||||
assert.Equal(t, expected, buf.String())
|
||||
}
|
||||
|
||||
type C3 struct {
|
||||
Value int `toml:",commented"`
|
||||
Values []int `toml:",commented"`
|
||||
}
|
||||
|
||||
type C2 struct {
|
||||
Int int64
|
||||
String string
|
||||
ArrayInts []int
|
||||
Structs []C3
|
||||
}
|
||||
|
||||
type C1 struct {
|
||||
Int int64 `toml:",commented"`
|
||||
String string `toml:",commented"`
|
||||
ArrayInts []int `toml:",commented"`
|
||||
Structs []C3 `toml:",commented"`
|
||||
}
|
||||
|
||||
type Commented struct {
|
||||
Int int64 `toml:",commented"`
|
||||
String string `toml:",commented"`
|
||||
|
||||
C1 C1
|
||||
C2 C2 `toml:",commented"` // same as C1, but commented at top level
|
||||
}
|
||||
|
||||
func TestMarshalCommented(t *testing.T) {
|
||||
c := Commented{
|
||||
Int: 42,
|
||||
String: "root",
|
||||
|
||||
C1: C1{
|
||||
Int: 11,
|
||||
String: "C1",
|
||||
ArrayInts: []int{1, 2, 3},
|
||||
Structs: []C3{
|
||||
{Value: 100},
|
||||
{Values: []int{4, 5, 6}},
|
||||
},
|
||||
},
|
||||
C2: C2{
|
||||
Int: 22,
|
||||
String: "C2",
|
||||
ArrayInts: []int{1, 2, 3},
|
||||
Structs: []C3{
|
||||
{Value: 100},
|
||||
{Values: []int{4, 5, 6}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := toml.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `# Int = 42
|
||||
# String = 'root'
|
||||
|
||||
[C1]
|
||||
# Int = 11
|
||||
# String = 'C1'
|
||||
# ArrayInts = [1, 2, 3]
|
||||
|
||||
# [[C1.Structs]]
|
||||
# Value = 100
|
||||
# Values = []
|
||||
|
||||
# [[C1.Structs]]
|
||||
# Value = 0
|
||||
# Values = [4, 5, 6]
|
||||
|
||||
# [C2]
|
||||
# Int = 22
|
||||
# String = 'C2'
|
||||
# ArrayInts = [1, 2, 3]
|
||||
|
||||
# [[C2.Structs]]
|
||||
# Value = 100
|
||||
# Values = []
|
||||
|
||||
# [[C2.Structs]]
|
||||
# Value = 0
|
||||
# Values = [4, 5, 6]
|
||||
`
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -1341,3 +1755,160 @@ func ExampleMarshal() {
|
||||
// Name = 'go-toml'
|
||||
// Tags = ['go', 'toml']
|
||||
}
|
||||
|
||||
// Example that uses the 'commented' field tag option to generate an example
|
||||
// configuration file that has commented out sections (example from
|
||||
// go-graphite/graphite-clickhouse).
|
||||
func ExampleMarshal_commented() {
|
||||
type Common struct {
|
||||
Listen string `toml:"listen" comment:"general listener"`
|
||||
PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`
|
||||
MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"`
|
||||
MemoryReturnInterval time.Duration `toml:"memory-return-interval" comment:"daemon will return the freed memory to the OS when it>0"`
|
||||
}
|
||||
|
||||
type Costs struct {
|
||||
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 equivalence without wildcards) (additional tuning, usually not needed)"`
|
||||
}
|
||||
|
||||
type ClickHouse struct {
|
||||
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 queries"`
|
||||
RenderConcurrentQueries int `toml:"render-concurrent-queries" comment:"Concurrent queries to render queries"`
|
||||
TaggedCosts map[string]*Costs `toml:"tagged-costs,commented"`
|
||||
TreeTable string `toml:"tree-table,commented"`
|
||||
ReverseTreeTable string `toml:"reverse-tree-table,commented"`
|
||||
DateTreeTable string `toml:"date-tree-table,commented"`
|
||||
DateTreeTableVersion int `toml:"date-tree-table-version,commented"`
|
||||
TreeTimeout time.Duration `toml:"tree-timeout,commented"`
|
||||
TagTable string `toml:"tag-table,commented"`
|
||||
ExtraPrefix string `toml:"extra-prefix" comment:"add extra prefix (directory in graphite) for all metrics, w/o trailing dot"`
|
||||
ConnectTimeout time.Duration `toml:"connect-timeout" comment:"TCP connection timeout"`
|
||||
DataTableLegacy string `toml:"data-table,commented"`
|
||||
RollupConfLegacy string `toml:"rollup-conf,commented"`
|
||||
MaxDataPoints int `toml:"max-data-points" comment:"max points per metric when internal-aggregation=true"`
|
||||
InternalAggregation bool `toml:"internal-aggregation" comment:"ClickHouse-side aggregation, see doc/aggregation.md"`
|
||||
}
|
||||
|
||||
type Tags struct {
|
||||
Rules string `toml:"rules"`
|
||||
Date string `toml:"date"`
|
||||
ExtraWhere string `toml:"extra-where"`
|
||||
InputFile string `toml:"input-file"`
|
||||
OutputFile string `toml:"output-file"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Common Common `toml:"common"`
|
||||
ClickHouse ClickHouse `toml:"clickhouse"`
|
||||
Tags Tags `toml:"tags,commented"`
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Common: Common{
|
||||
Listen: ":9090",
|
||||
PprofListen: "",
|
||||
MaxMetricsPerTarget: 15000, // This is arbitrary value to protect CH from overload
|
||||
MemoryReturnInterval: 0,
|
||||
},
|
||||
ClickHouse: ClickHouse{
|
||||
URL: "http://localhost:8123?cancel_http_readonly_queries_on_client_close=1",
|
||||
ExtraPrefix: "",
|
||||
ConnectTimeout: time.Second,
|
||||
DataTableLegacy: "",
|
||||
RollupConfLegacy: "auto",
|
||||
MaxDataPoints: 1048576,
|
||||
InternalAggregation: true,
|
||||
},
|
||||
Tags: Tags{},
|
||||
}
|
||||
|
||||
out, err := toml.Marshal(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = toml.Unmarshal(out, &cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
|
||||
// Output:
|
||||
// [common]
|
||||
// # general listener
|
||||
// listen = ':9090'
|
||||
// # listener to serve /debug/pprof requests. '-pprof' argument overrides it
|
||||
// pprof-listen = ''
|
||||
// # limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited
|
||||
// max-metrics-per-target = 15000
|
||||
// # daemon will return the freed memory to the OS when it>0
|
||||
// memory-return-interval = 0
|
||||
//
|
||||
// [clickhouse]
|
||||
// # 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'
|
||||
// # Max queries to render queries
|
||||
// render-max-queries = 0
|
||||
// # Concurrent queries to render queries
|
||||
// render-concurrent-queries = 0
|
||||
// # tree-table = ''
|
||||
// # reverse-tree-table = ''
|
||||
// # date-tree-table = ''
|
||||
// # date-tree-table-version = 0
|
||||
// # tree-timeout = 0
|
||||
// # tag-table = ''
|
||||
// # add extra prefix (directory in graphite) for all metrics, w/o trailing dot
|
||||
// extra-prefix = ''
|
||||
// # TCP connection timeout
|
||||
// connect-timeout = 1000000000
|
||||
// # data-table = ''
|
||||
// # rollup-conf = 'auto'
|
||||
// # max points per metric when internal-aggregation=true
|
||||
// max-data-points = 1048576
|
||||
// # ClickHouse-side aggregation, see doc/aggregation.md
|
||||
// internal-aggregation = true
|
||||
//
|
||||
// # [tags]
|
||||
// # rules = ''
|
||||
// # date = ''
|
||||
// # extra-where = ''
|
||||
// # input-file = ''
|
||||
// # output-file = ''
|
||||
}
|
||||
|
||||
func TestReadmeComments(t *testing.T) {
|
||||
type TLS struct {
|
||||
Cipher string `toml:"cipher"`
|
||||
Version string `toml:"version"`
|
||||
}
|
||||
type Config struct {
|
||||
Host string `toml:"host" comment:"Host IP to connect to."`
|
||||
Port int `toml:"port" comment:"Port of the remote server."`
|
||||
Tls TLS `toml:"TLS,commented" comment:"Encryption parameters (optional)"`
|
||||
}
|
||||
example := Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 4242,
|
||||
Tls: TLS{
|
||||
Cipher: "AEAD-AES128-GCM-SHA256",
|
||||
Version: "TLS 1.3",
|
||||
},
|
||||
}
|
||||
out, err := toml.Marshal(example)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `# Host IP to connect to.
|
||||
host = '127.0.0.1'
|
||||
# Port of the remote server.
|
||||
port = 4242
|
||||
|
||||
# Encryption parameters (optional)
|
||||
# [TLS]
|
||||
# cipher = 'AEAD-AES128-GCM-SHA256'
|
||||
# version = 'TLS 1.3'
|
||||
`
|
||||
assert.Equal(t, expected, string(out))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build go1.18 || go1.19 || go1.20
|
||||
// +build go1.18 go1.19 go1.20
|
||||
|
||||
package ossfuzz
|
||||
|
||||
import (
|
||||
|
||||
Executable
+596
@@ -0,0 +1,596 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Go versions to test (1.11 through 1.25)
|
||||
GO_VERSIONS=(
|
||||
"1.11"
|
||||
"1.12"
|
||||
"1.13"
|
||||
"1.14"
|
||||
"1.15"
|
||||
"1.16"
|
||||
"1.17"
|
||||
"1.18"
|
||||
"1.19"
|
||||
"1.20"
|
||||
"1.21"
|
||||
"1.22"
|
||||
"1.23"
|
||||
"1.24"
|
||||
"1.25"
|
||||
)
|
||||
|
||||
# Default values
|
||||
PARALLEL=true
|
||||
VERBOSE=false
|
||||
OUTPUT_DIR="test-results"
|
||||
DOCKER_TIMEOUT="10m"
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $0 [OPTIONS] [GO_VERSIONS...]
|
||||
|
||||
Test go-toml across multiple Go versions using Docker containers.
|
||||
|
||||
The script reports the lowest continuous supported Go version (where all subsequent
|
||||
versions pass) and only exits with non-zero status if either of the two most recent
|
||||
Go versions fail, indicating immediate attention is needed.
|
||||
|
||||
Note: For Go versions < 1.21, the script automatically updates go.mod to match the
|
||||
target version, but older versions may still fail due to missing standard library
|
||||
features (e.g., the 'slices' package introduced in Go 1.21).
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-s, --sequential Run tests sequentially instead of in parallel
|
||||
-v, --verbose Enable verbose output
|
||||
-o, --output DIR Output directory for test results (default: test-results)
|
||||
-t, --timeout TIME Docker timeout for each test (default: 10m)
|
||||
--list List available Go versions and exit
|
||||
|
||||
ARGUMENTS:
|
||||
GO_VERSIONS Specific Go versions to test (default: all supported versions)
|
||||
Examples: 1.21 1.22 1.23
|
||||
|
||||
EXAMPLES:
|
||||
$0 # Test all Go versions in parallel
|
||||
$0 --sequential # Test all Go versions sequentially
|
||||
$0 1.21 1.22 1.23 # Test specific versions
|
||||
$0 --verbose --output ./results 1.24 1.25 # Verbose output to custom directory
|
||||
|
||||
EXIT CODES:
|
||||
0 Recent Go versions pass (good compatibility)
|
||||
1 Recent Go versions fail (needs attention) or script error
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%H:%M:%S')]${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%H:%M:%S')] ✓${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%H:%M:%S')] ✗${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[$(date +'%H:%M:%S')] ⚠${NC} $*" >&2
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-s|--sequential)
|
||||
PARALLEL=false
|
||||
shift
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
-o|--output)
|
||||
OUTPUT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--timeout)
|
||||
DOCKER_TIMEOUT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--list)
|
||||
echo "Available Go versions:"
|
||||
printf '%s\n' "${GO_VERSIONS[@]}"
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
# Remaining arguments are Go versions
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If specific versions provided, use those instead of defaults
|
||||
if [[ $# -gt 0 ]]; then
|
||||
GO_VERSIONS=("$@")
|
||||
fi
|
||||
|
||||
# Validate Go versions
|
||||
for version in "${GO_VERSIONS[@]}"; do
|
||||
if ! [[ "$version" =~ ^1\.(1[1-9]|2[0-5])$ ]]; then
|
||||
log_error "Invalid Go version: $version. Supported versions: 1.11-1.25"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if Docker is available
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker is required but not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Docker daemon is running
|
||||
if ! docker info &> /dev/null; then
|
||||
log_error "Docker daemon is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Function to test a single Go version
|
||||
test_go_version() {
|
||||
local go_version="$1"
|
||||
local container_name="go-toml-test-${go_version}"
|
||||
local result_file="${OUTPUT_DIR}/go-${go_version}.txt"
|
||||
local dockerfile_content
|
||||
|
||||
log "Testing Go $go_version..."
|
||||
|
||||
# Create a temporary Dockerfile for this version
|
||||
# For Go versions < 1.21, we need to update go.mod to match the Go version
|
||||
local needs_go_mod_update=false
|
||||
if [[ $(echo "$go_version 1.21" | tr ' ' '\n' | sort -V | head -n1) == "$go_version" && "$go_version" != "1.21" ]]; then
|
||||
needs_go_mod_update=true
|
||||
fi
|
||||
|
||||
dockerfile_content="FROM golang:${go_version}-alpine
|
||||
|
||||
# Install git (required for go mod)
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy source code
|
||||
COPY . ."
|
||||
|
||||
# Add go.mod update step for older Go versions
|
||||
if [[ "$needs_go_mod_update" == true ]]; then
|
||||
dockerfile_content="$dockerfile_content
|
||||
|
||||
# Update go.mod to match Go version (required for Go < 1.21)
|
||||
RUN if [ -f go.mod ]; then sed -i 's/^go [0-9]\\+\\.[0-9]\\+\\(\\.[0-9]\\+\\)\\?/go $go_version/' go.mod; fi
|
||||
|
||||
# Note: Go versions < 1.21 may fail due to missing standard library packages (e.g., slices)
|
||||
# This is expected for projects that use Go 1.21+ features"
|
||||
fi
|
||||
|
||||
dockerfile_content="$dockerfile_content
|
||||
|
||||
# Run tests
|
||||
CMD [\"sh\", \"-c\", \"go version && echo '--- Running go test ./... ---' && go test ./...\"]"
|
||||
|
||||
# Create temporary directory for this test
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
# Copy source to temp directory (excluding test results and git)
|
||||
rsync -a --exclude="$OUTPUT_DIR" --exclude=".git" --exclude="*.test" . "$temp_dir/"
|
||||
|
||||
# Create Dockerfile in temp directory
|
||||
echo "$dockerfile_content" > "$temp_dir/Dockerfile"
|
||||
|
||||
# Build and run container
|
||||
local exit_code=0
|
||||
local output
|
||||
|
||||
if $VERBOSE; then
|
||||
log "Building Docker image for Go $go_version..."
|
||||
fi
|
||||
|
||||
# Capture both stdout and stderr, and the exit code
|
||||
if output=$(cd "$temp_dir" && timeout "$DOCKER_TIMEOUT" docker build -t "$container_name" . 2>&1 && \
|
||||
timeout "$DOCKER_TIMEOUT" docker run --rm "$container_name" 2>&1); then
|
||||
log_success "Go $go_version: PASSED"
|
||||
echo "PASSED" > "${result_file}.status"
|
||||
else
|
||||
exit_code=$?
|
||||
log_error "Go $go_version: FAILED (exit code: $exit_code)"
|
||||
echo "FAILED" > "${result_file}.status"
|
||||
fi
|
||||
|
||||
# Save full output
|
||||
echo "$output" > "$result_file"
|
||||
|
||||
# Clean up
|
||||
docker rmi "$container_name" &> /dev/null || true
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
if $VERBOSE; then
|
||||
echo "--- Go $go_version output ---"
|
||||
echo "$output"
|
||||
echo "--- End Go $go_version output ---"
|
||||
fi
|
||||
|
||||
return $exit_code
|
||||
}
|
||||
|
||||
# Function to run tests in parallel
|
||||
run_parallel() {
|
||||
local pids=()
|
||||
local failed_versions=()
|
||||
|
||||
log "Starting parallel tests for ${#GO_VERSIONS[@]} Go versions..."
|
||||
|
||||
# Start all tests in background
|
||||
for version in "${GO_VERSIONS[@]}"; do
|
||||
test_go_version "$version" &
|
||||
pids+=($!)
|
||||
done
|
||||
|
||||
# Wait for all tests to complete
|
||||
for i in "${!pids[@]}"; do
|
||||
local pid=${pids[$i]}
|
||||
local version=${GO_VERSIONS[$i]}
|
||||
|
||||
if ! wait $pid; then
|
||||
failed_versions+=("$version")
|
||||
fi
|
||||
done
|
||||
|
||||
return ${#failed_versions[@]}
|
||||
}
|
||||
|
||||
# Function to run tests sequentially
|
||||
run_sequential() {
|
||||
local failed_versions=()
|
||||
|
||||
log "Starting sequential tests for ${#GO_VERSIONS[@]} Go versions..."
|
||||
|
||||
for version in "${GO_VERSIONS[@]}"; do
|
||||
if ! test_go_version "$version"; then
|
||||
failed_versions+=("$version")
|
||||
fi
|
||||
done
|
||||
|
||||
return ${#failed_versions[@]}
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
local start_time
|
||||
start_time=$(date +%s)
|
||||
|
||||
log "Starting Go version compatibility tests..."
|
||||
log "Testing versions: ${GO_VERSIONS[*]}"
|
||||
log "Output directory: $OUTPUT_DIR"
|
||||
log "Parallel execution: $PARALLEL"
|
||||
|
||||
local failed_count
|
||||
if $PARALLEL; then
|
||||
run_parallel
|
||||
failed_count=$?
|
||||
else
|
||||
run_sequential
|
||||
failed_count=$?
|
||||
fi
|
||||
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
|
||||
# Collect results for display
|
||||
local passed_versions=()
|
||||
local failed_versions=()
|
||||
local unknown_versions=()
|
||||
local passed_count=0
|
||||
|
||||
for version in "${GO_VERSIONS[@]}"; do
|
||||
local status_file="${OUTPUT_DIR}/go-${version}.txt.status"
|
||||
if [[ -f "$status_file" ]]; then
|
||||
local status
|
||||
status=$(cat "$status_file")
|
||||
if [[ "$status" == "PASSED" ]]; then
|
||||
passed_versions+=("$version")
|
||||
((passed_count++))
|
||||
else
|
||||
failed_versions+=("$version")
|
||||
fi
|
||||
else
|
||||
unknown_versions+=("$version")
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate summary report
|
||||
local summary_file="${OUTPUT_DIR}/summary.txt"
|
||||
{
|
||||
echo "Go Version Compatibility Test Summary"
|
||||
echo "====================================="
|
||||
echo "Date: $(date)"
|
||||
echo "Duration: ${duration}s"
|
||||
echo "Parallel: $PARALLEL"
|
||||
echo ""
|
||||
echo "Results:"
|
||||
|
||||
for version in "${GO_VERSIONS[@]}"; do
|
||||
local status_file="${OUTPUT_DIR}/go-${version}.txt.status"
|
||||
if [[ -f "$status_file" ]]; then
|
||||
local status
|
||||
status=$(cat "$status_file")
|
||||
if [[ "$status" == "PASSED" ]]; then
|
||||
echo " Go $version: ✓ PASSED"
|
||||
else
|
||||
echo " Go $version: ✗ FAILED"
|
||||
fi
|
||||
else
|
||||
echo " Go $version: ? UNKNOWN (no status file)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Summary: $passed_count/${#GO_VERSIONS[@]} versions passed"
|
||||
|
||||
if [[ $failed_count -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "Failed versions details:"
|
||||
for version in "${failed_versions[@]}"; do
|
||||
echo ""
|
||||
echo "--- Go $version (FAILED) ---"
|
||||
local result_file="${OUTPUT_DIR}/go-${version}.txt"
|
||||
if [[ -f "$result_file" ]]; then
|
||||
tail -n 30 "$result_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
} > "$summary_file"
|
||||
|
||||
# Find lowest continuous supported version and check recent versions
|
||||
local lowest_continuous_version=""
|
||||
local recent_versions_failed=false
|
||||
|
||||
# Sort versions to ensure proper order
|
||||
local sorted_versions=()
|
||||
for version in "${GO_VERSIONS[@]}"; do
|
||||
sorted_versions+=("$version")
|
||||
done
|
||||
# Sort versions numerically (1.11, 1.12, ..., 1.25)
|
||||
IFS=$'\n' sorted_versions=($(sort -V <<< "${sorted_versions[*]}"))
|
||||
|
||||
# Find lowest continuous supported version (all versions from this point onwards pass)
|
||||
for version in "${sorted_versions[@]}"; do
|
||||
local status_file="${OUTPUT_DIR}/go-${version}.txt.status"
|
||||
local all_subsequent_pass=true
|
||||
|
||||
# Check if this version and all subsequent versions pass
|
||||
local found_current=false
|
||||
for check_version in "${sorted_versions[@]}"; do
|
||||
if [[ "$check_version" == "$version" ]]; then
|
||||
found_current=true
|
||||
fi
|
||||
|
||||
if [[ "$found_current" == true ]]; then
|
||||
local check_status_file="${OUTPUT_DIR}/go-${check_version}.txt.status"
|
||||
if [[ -f "$check_status_file" ]]; then
|
||||
local status
|
||||
status=$(cat "$check_status_file")
|
||||
if [[ "$status" != "PASSED" ]]; then
|
||||
all_subsequent_pass=false
|
||||
break
|
||||
fi
|
||||
else
|
||||
all_subsequent_pass=false
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$all_subsequent_pass" == true ]]; then
|
||||
lowest_continuous_version="$version"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if the two most recent versions failed
|
||||
local num_versions=${#sorted_versions[@]}
|
||||
if [[ $num_versions -ge 2 ]]; then
|
||||
local second_recent="${sorted_versions[$((num_versions-2))]}"
|
||||
local most_recent="${sorted_versions[$((num_versions-1))]}"
|
||||
|
||||
local second_recent_status_file="${OUTPUT_DIR}/go-${second_recent}.txt.status"
|
||||
local most_recent_status_file="${OUTPUT_DIR}/go-${most_recent}.txt.status"
|
||||
|
||||
local second_recent_failed=false
|
||||
local most_recent_failed=false
|
||||
|
||||
if [[ -f "$second_recent_status_file" ]]; then
|
||||
local status
|
||||
status=$(cat "$second_recent_status_file")
|
||||
if [[ "$status" != "PASSED" ]]; then
|
||||
second_recent_failed=true
|
||||
fi
|
||||
else
|
||||
second_recent_failed=true
|
||||
fi
|
||||
|
||||
if [[ -f "$most_recent_status_file" ]]; then
|
||||
local status
|
||||
status=$(cat "$most_recent_status_file")
|
||||
if [[ "$status" != "PASSED" ]]; then
|
||||
most_recent_failed=true
|
||||
fi
|
||||
else
|
||||
most_recent_failed=true
|
||||
fi
|
||||
|
||||
if [[ "$second_recent_failed" == true || "$most_recent_failed" == true ]]; then
|
||||
recent_versions_failed=true
|
||||
fi
|
||||
elif [[ $num_versions -eq 1 ]]; then
|
||||
# Only one version tested, check if it's the most recent and failed
|
||||
local only_version="${sorted_versions[0]}"
|
||||
local only_status_file="${OUTPUT_DIR}/go-${only_version}.txt.status"
|
||||
|
||||
if [[ -f "$only_status_file" ]]; then
|
||||
local status
|
||||
status=$(cat "$only_status_file")
|
||||
if [[ "$status" != "PASSED" ]]; then
|
||||
recent_versions_failed=true
|
||||
fi
|
||||
else
|
||||
recent_versions_failed=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Display summary
|
||||
echo ""
|
||||
log "Test completed in ${duration}s"
|
||||
log "Summary report: $summary_file"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " FINAL RESULTS"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Display passed versions
|
||||
if [[ ${#passed_versions[@]} -gt 0 ]]; then
|
||||
log_success "PASSED (${#passed_versions[@]}/${#GO_VERSIONS[@]}):"
|
||||
# Sort passed versions for display
|
||||
local sorted_passed=()
|
||||
for version in "${sorted_versions[@]}"; do
|
||||
for passed_version in "${passed_versions[@]}"; do
|
||||
if [[ "$version" == "$passed_version" ]]; then
|
||||
sorted_passed+=("$version")
|
||||
break
|
||||
fi
|
||||
done
|
||||
done
|
||||
for version in "${sorted_passed[@]}"; do
|
||||
echo -e " ${GREEN}✓${NC} Go $version"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Display failed versions
|
||||
if [[ ${#failed_versions[@]} -gt 0 ]]; then
|
||||
log_error "FAILED (${#failed_versions[@]}/${#GO_VERSIONS[@]}):"
|
||||
# Sort failed versions for display
|
||||
local sorted_failed=()
|
||||
for version in "${sorted_versions[@]}"; do
|
||||
for failed_version in "${failed_versions[@]}"; do
|
||||
if [[ "$version" == "$failed_version" ]]; then
|
||||
sorted_failed+=("$version")
|
||||
break
|
||||
fi
|
||||
done
|
||||
done
|
||||
for version in "${sorted_failed[@]}"; do
|
||||
echo -e " ${RED}✗${NC} Go $version"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Show failure details
|
||||
echo "========================================"
|
||||
echo " FAILURE DETAILS"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
for version in "${sorted_failed[@]}"; do
|
||||
echo -e "${RED}--- Go $version FAILURE LOGS (last 30 lines) ---${NC}"
|
||||
local result_file="${OUTPUT_DIR}/go-${version}.txt"
|
||||
if [[ -f "$result_file" ]]; then
|
||||
tail -n 30 "$result_file" | sed 's/^/ /'
|
||||
else
|
||||
echo " No log file found: $result_file"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
|
||||
# Display unknown versions
|
||||
if [[ ${#unknown_versions[@]} -gt 0 ]]; then
|
||||
log_warning "UNKNOWN (${#unknown_versions[@]}/${#GO_VERSIONS[@]}):"
|
||||
for version in "${unknown_versions[@]}"; do
|
||||
echo -e " ${YELLOW}?${NC} Go $version (no status file)"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "========================================"
|
||||
echo " COMPATIBILITY SUMMARY"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
if [[ -n "$lowest_continuous_version" ]]; then
|
||||
log_success "Lowest continuous supported version: Go $lowest_continuous_version"
|
||||
echo " (All versions from Go $lowest_continuous_version onwards pass)"
|
||||
else
|
||||
log_error "No continuous version support found"
|
||||
echo " (No version has all subsequent versions passing)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Full detailed logs available in: $OUTPUT_DIR"
|
||||
echo "========================================"
|
||||
|
||||
# Determine exit code based on recent versions
|
||||
if [[ "$recent_versions_failed" == true ]]; then
|
||||
log_error "OVERALL RESULT: Recent Go versions failed - this needs attention!"
|
||||
if [[ -n "$lowest_continuous_version" ]]; then
|
||||
echo "Note: Continuous support starts from Go $lowest_continuous_version"
|
||||
fi
|
||||
exit 1
|
||||
else
|
||||
log_success "OVERALL RESULT: Recent Go versions pass - compatibility looks good!"
|
||||
if [[ -n "$lowest_continuous_version" ]]; then
|
||||
echo "Continuous support starts from Go $lowest_continuous_version"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Trap to clean up on exit
|
||||
cleanup() {
|
||||
# Kill any remaining background processes
|
||||
jobs -p | xargs -r kill 2>/dev/null || true
|
||||
|
||||
# Clean up any remaining Docker containers
|
||||
docker ps -q --filter "name=go-toml-test-" | xargs -r docker stop 2>/dev/null || true
|
||||
docker images -q --filter "reference=go-toml-test-*" | xargs -r docker rmi 2>/dev/null || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# Run main function
|
||||
main
|
||||
@@ -1,4 +1,5 @@
|
||||
//go:generate go run ./cmd/tomltestgen/main.go -o toml_testgen_test.go
|
||||
//go:generate go run github.com/toml-lang/toml-test/cmd/toml-test@v1.6.0 -copy ./tests
|
||||
//go:generate go run ./cmd/tomltestgen/main.go -r v1.6.0 -o toml_testgen_test.go
|
||||
|
||||
// This is a support file for toml_testgen_test.go
|
||||
package toml_test
|
||||
@@ -8,8 +9,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
"github.com/pelletier/go-toml/v2/internal/testsuite"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
j, err := testsuite.ValueToTaggedJSON(doc)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ref interface{}
|
||||
err = json.Unmarshal([]byte(jsonRef), &ref)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var actual interface{}
|
||||
err = json.Unmarshal([]byte(j), &actual)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(j, &actual)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testsuite.CmpJSON(t, "", ref, actual)
|
||||
}
|
||||
|
||||
+1613
-412
File diff suppressed because it is too large
Load Diff
+130
-21
@@ -5,9 +5,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -21,10 +21,8 @@ import (
|
||||
//
|
||||
// It is a shortcut for Decoder.Decode() with the default options.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
p := unstable.Parser{}
|
||||
p.Reset(data)
|
||||
d := decoder{p: &p}
|
||||
|
||||
d := decoder{}
|
||||
d.p.Reset(data)
|
||||
return d.FromParser(v)
|
||||
}
|
||||
|
||||
@@ -35,6 +33,9 @@ type Decoder struct {
|
||||
|
||||
// global settings
|
||||
strict bool
|
||||
|
||||
// toggles unmarshaler interface
|
||||
unmarshalerInterface bool
|
||||
}
|
||||
|
||||
// NewDecoder creates a new Decoder that will read from r.
|
||||
@@ -54,6 +55,24 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
// Array of Tables -> same as Array and Table
|
||||
func (d *Decoder) Decode(v interface{}) error {
|
||||
b, err := ioutil.ReadAll(d.r)
|
||||
b, err := io.ReadAll(d.r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("toml: %w", err)
|
||||
}
|
||||
|
||||
p := unstable.Parser{}
|
||||
p.Reset(b)
|
||||
dec := decoder{
|
||||
p: &p,
|
||||
strict: strict{
|
||||
Enabled: d.strict,
|
||||
},
|
||||
unmarshalerInterface: d.unmarshalerInterface,
|
||||
}
|
||||
dec.p.Reset(b)
|
||||
|
||||
return dec.FromParser(v)
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
// Which parser instance in use for this decoding session.
|
||||
p *unstable.Parser
|
||||
p unstable.Parser
|
||||
|
||||
// Flag indicating that the current expression is stashed.
|
||||
// If set to true, calling nextExpr will not actually pull a new expression
|
||||
@@ -127,6 +145,10 @@ type decoder struct {
|
||||
// need to be skipped.
|
||||
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.
|
||||
// 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
|
||||
@@ -139,6 +161,9 @@ type decoder struct {
|
||||
// Strict mode
|
||||
strict strict
|
||||
|
||||
// Flag that enables/disables unmarshaler interface.
|
||||
unmarshalerInterface bool
|
||||
|
||||
// Current context for the error.
|
||||
errorContext *errorContext
|
||||
}
|
||||
@@ -149,12 +174,16 @@ type errorContext struct {
|
||||
}
|
||||
|
||||
func (d *decoder) typeMismatchError(toml string, target reflect.Type) error {
|
||||
return fmt.Errorf("toml: %s", d.typeMismatchString(toml, target))
|
||||
}
|
||||
|
||||
func (d *decoder) typeMismatchString(toml string, target reflect.Type) string {
|
||||
if d.errorContext != nil && d.errorContext.Struct != nil {
|
||||
ctx := d.errorContext
|
||||
f := ctx.Struct.FieldByIndex(ctx.Field)
|
||||
return fmt.Errorf("toml: cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type)
|
||||
return fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type)
|
||||
}
|
||||
return fmt.Errorf("toml: cannot decode TOML %s into a Go value of type %s", toml, target)
|
||||
return fmt.Sprintf("cannot decode TOML %s into a Go value of type %s", toml, target)
|
||||
}
|
||||
|
||||
func (d *decoder) expr() *unstable.Node {
|
||||
@@ -242,9 +271,10 @@ Rules for the unmarshal code:
|
||||
func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) error {
|
||||
var x reflect.Value
|
||||
var err error
|
||||
var first bool // used for to clear array tables on first use
|
||||
|
||||
if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) {
|
||||
err = d.seen.CheckExpression(expr)
|
||||
first, err = d.seen.CheckExpression(expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -263,6 +293,7 @@ func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) err
|
||||
case unstable.ArrayTable:
|
||||
d.skipUntilTable = false
|
||||
d.strict.EnterArrayTable(expr)
|
||||
d.clearArrayTable = first
|
||||
x, err = d.handleArrayTable(expr.Key(), v)
|
||||
default:
|
||||
panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind))
|
||||
@@ -303,6 +334,10 @@ func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflec
|
||||
reflect.Copy(nelem, elem)
|
||||
elem = nelem
|
||||
}
|
||||
if d.clearArrayTable && elem.Len() > 0 {
|
||||
elem.SetLen(0)
|
||||
d.clearArrayTable = false
|
||||
}
|
||||
}
|
||||
return d.handleArrayTableCollectionLast(key, elem)
|
||||
case reflect.Ptr:
|
||||
@@ -321,6 +356,10 @@ func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflec
|
||||
|
||||
return v, nil
|
||||
case reflect.Slice:
|
||||
if d.clearArrayTable && v.Len() > 0 {
|
||||
v.SetLen(0)
|
||||
d.clearArrayTable = false
|
||||
}
|
||||
elemType := v.Type().Elem()
|
||||
var elem reflect.Value
|
||||
if elemType.Kind() == reflect.Interface {
|
||||
@@ -377,15 +416,39 @@ func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Va
|
||||
|
||||
return v, nil
|
||||
case reflect.Slice:
|
||||
elem := v.Index(v.Len() - 1)
|
||||
// Create a new element when the slice is empty; otherwise operate on
|
||||
// the last element.
|
||||
var (
|
||||
elem reflect.Value
|
||||
created bool
|
||||
)
|
||||
if v.Len() == 0 {
|
||||
created = true
|
||||
elemType := v.Type().Elem()
|
||||
if elemType.Kind() == reflect.Interface {
|
||||
elem = makeMapStringInterface()
|
||||
} else {
|
||||
elem = reflect.New(elemType).Elem()
|
||||
}
|
||||
} else {
|
||||
elem = v.Index(v.Len() - 1)
|
||||
}
|
||||
|
||||
x, err := d.handleArrayTable(key, elem)
|
||||
if err != nil || d.skipUntilTable {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
if x.IsValid() {
|
||||
if created {
|
||||
elem = x
|
||||
} else {
|
||||
elem.Set(x)
|
||||
}
|
||||
}
|
||||
|
||||
if created {
|
||||
return reflect.Append(v, elem), nil
|
||||
}
|
||||
return v, err
|
||||
case reflect.Array:
|
||||
idx := d.arrayIndex(false, v)
|
||||
@@ -572,7 +635,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
|
||||
break
|
||||
}
|
||||
|
||||
err := d.seen.CheckExpression(expr)
|
||||
_, err := d.seen.CheckExpression(expr)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
@@ -630,6 +693,14 @@ func (d *decoder) handleValue(value *unstable.Node, v reflect.Value) error {
|
||||
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)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
@@ -963,7 +1034,7 @@ func (d *decoder) unmarshalInteger(value *unstable.Node, v reflect.Value) error
|
||||
case reflect.Interface:
|
||||
r = reflect.ValueOf(i)
|
||||
default:
|
||||
return d.typeMismatchError("integer", v.Type())
|
||||
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("integer", v.Type()))
|
||||
}
|
||||
|
||||
if !r.Type().AssignableTo(v.Type()) {
|
||||
@@ -982,7 +1053,7 @@ func (d *decoder) unmarshalString(value *unstable.Node, v reflect.Value) error {
|
||||
case reflect.Interface:
|
||||
v.Set(reflect.ValueOf(string(value.Data)))
|
||||
default:
|
||||
return unstable.NewParserError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind())
|
||||
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("string", v.Type()))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1027,12 +1098,39 @@ func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value,
|
||||
}
|
||||
return mk, nil
|
||||
|
||||
case reflect.PtrTo(keyType).Implements(textUnmarshalerType):
|
||||
case reflect.PointerTo(keyType).Implements(textUnmarshalerType):
|
||||
mk := reflect.New(keyType)
|
||||
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 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)
|
||||
}
|
||||
@@ -1080,6 +1178,17 @@ func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node
|
||||
case reflect.Struct:
|
||||
path, found := structFieldPath(v, string(key.Node().Data))
|
||||
if !found {
|
||||
// If no matching struct field is found but the target implements the
|
||||
// unstable.Unmarshaler interface (and it is enabled), delegate the
|
||||
// decoding of this value to the custom unmarshaler.
|
||||
if d.unmarshalerInterface {
|
||||
if v.CanAddr() && v.Addr().CanInterface() {
|
||||
if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok {
|
||||
return reflect.Value{}, outi.UnmarshalTOML(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, keep previous behavior and skip until the next table.
|
||||
d.skipUntilTable = true
|
||||
break
|
||||
}
|
||||
@@ -1093,9 +1202,9 @@ func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node
|
||||
|
||||
f := fieldByIndex(v, path)
|
||||
|
||||
if !f.CanSet() {
|
||||
// If the field is not settable, need to take a slower path and make a copy of
|
||||
// the struct itself to a new location.
|
||||
if !f.CanAddr() {
|
||||
// If the field is not addressable, need to take a slower path and
|
||||
// make a copy of the struct itself to a new location.
|
||||
nvp := reflect.New(v.Type())
|
||||
nvp.Elem().Set(v)
|
||||
v = nvp.Elem()
|
||||
|
||||
+805
-113
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -89,7 +89,7 @@ func (n *Node) Next() *Node {
|
||||
}
|
||||
|
||||
// Child returns a pointer to the first child node of this node. Other children
|
||||
// can be accessed calling Next on the first child. Returns an nil if this Node
|
||||
// can be accessed calling Next on the first child. Returns nil if this Node
|
||||
// has no child.
|
||||
func (n *Node) Child() *Node {
|
||||
if n.child == 0 {
|
||||
|
||||
+7
-1
@@ -1013,6 +1013,7 @@ func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error)
|
||||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:3],
|
||||
Raw: p.Range(b[:3]),
|
||||
}), b[3:], nil
|
||||
case 'n':
|
||||
if !scanFollowsNan(b) {
|
||||
@@ -1022,6 +1023,7 @@ func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error)
|
||||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:3],
|
||||
Raw: p.Range(b[:3]),
|
||||
}), b[3:], nil
|
||||
case '+', '-':
|
||||
return p.scanIntOrFloat(b)
|
||||
@@ -1074,7 +1076,7 @@ byteLoop:
|
||||
}
|
||||
case c == 'T' || c == 't' || c == ':' || c == '.':
|
||||
hasTime = true
|
||||
case c == '+' || c == '-' || c == 'Z' || c == 'z':
|
||||
case c == '+' || c == 'Z' || c == 'z':
|
||||
hasTz = true
|
||||
case c == ' ':
|
||||
if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) {
|
||||
@@ -1146,6 +1148,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
||||
return p.builder.Push(Node{
|
||||
Kind: Integer,
|
||||
Data: b[:i],
|
||||
Raw: p.Range(b[:i]),
|
||||
}), b[i:], nil
|
||||
}
|
||||
|
||||
@@ -1169,6 +1172,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
||||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:i+3],
|
||||
Raw: p.Range(b[:i+3]),
|
||||
}), b[i+3:], nil
|
||||
}
|
||||
|
||||
@@ -1180,6 +1184,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
||||
return p.builder.Push(Node{
|
||||
Kind: Float,
|
||||
Data: b[:i+3],
|
||||
Raw: p.Range(b[:i+3]),
|
||||
}), b[i+3:], nil
|
||||
}
|
||||
|
||||
@@ -1202,6 +1207,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
||||
return p.builder.Push(Node{
|
||||
Kind: kind,
|
||||
Data: b[:i],
|
||||
Raw: p.Range(b[:i]),
|
||||
}), b[i:], nil
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pelletier/go-toml/v2/internal/assert"
|
||||
)
|
||||
|
||||
func TestParser_AST_Numbers(t *testing.T) {
|
||||
@@ -141,9 +141,9 @@ func TestParser_AST_Numbers(t *testing.T) {
|
||||
p.NextExpression()
|
||||
err := p.Error()
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := astNode{
|
||||
Kind: KeyValue,
|
||||
@@ -168,8 +168,8 @@ type (
|
||||
|
||||
func compareNode(t *testing.T, e astNode, n *Node) {
|
||||
t.Helper()
|
||||
require.Equal(t, e.Kind, n.Kind)
|
||||
require.Equal(t, e.Data, n.Data)
|
||||
assert.Equal(t, e.Kind, n.Kind)
|
||||
assert.Equal(t, e.Data, n.Data)
|
||||
|
||||
compareIterator(t, e.Children, n.Children())
|
||||
}
|
||||
@@ -341,9 +341,9 @@ func TestParser_AST(t *testing.T) {
|
||||
p.NextExpression()
|
||||
err := p.Error()
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
compareNode(t, e.ast, p.Expression())
|
||||
}
|
||||
})
|
||||
@@ -431,9 +431,9 @@ func TestParser_AST_DateTimes(t *testing.T) {
|
||||
p.NextExpression()
|
||||
err := p.Error()
|
||||
if e.err {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := astNode{
|
||||
Kind: KeyValue,
|
||||
@@ -569,9 +569,9 @@ key5 = [ # Next to start of inline array.
|
||||
// ---
|
||||
// 1:1->1:1 (0->0) | KeyValue []
|
||||
// 1:1->1:1 (0->0) | Array []
|
||||
// 1:1->1:1 (0->0) | Integer [1]
|
||||
// 1:1->1:1 (0->0) | Integer [2]
|
||||
// 1:1->1:1 (0->0) | Integer [3]
|
||||
// 19:11->19:12 (396->397) | Integer [1]
|
||||
// 19:14->19:15 (399->400) | Integer [2]
|
||||
// 19:17->19:18 (402->403) | Integer [3]
|
||||
// 19:1->19:6 (386->391) | Key [array]
|
||||
// 19:21->19:46 (406->431) | Comment [# Next to one-line array.]
|
||||
// ---
|
||||
@@ -583,12 +583,12 @@ key5 = [ # Next to start of inline array.
|
||||
// 1:1->1:1 (0->0) | Array []
|
||||
// 23:10->23:42 (483->515) | Comment [# Next to start of inline array.]
|
||||
// 24:3->24:38 (518->553) | Comment [# Second line before array content.]
|
||||
// 1:1->1:1 (0->0) | Integer [1]
|
||||
// 25:3->25:4 (556->557) | Integer [1]
|
||||
// 25:6->25:30 (559->583) | Comment [# Next to first element.]
|
||||
// 26:3->26:25 (586->608) | Comment [# After first element.]
|
||||
// 27:3->27:27 (611->635) | Comment [# Before second element.]
|
||||
// 1:1->1:1 (0->0) | Integer [2]
|
||||
// 1:1->1:1 (0->0) | Integer [3]
|
||||
// 28:3->28:4 (638->639) | Integer [2]
|
||||
// 29:3->29:4 (643->644) | Integer [3]
|
||||
// 29:6->29:28 (646->668) | Comment [# Next to last element]
|
||||
// 30:3->30:24 (671->692) | Comment [# After last element.]
|
||||
// 23:1->23:5 (474->478) | Key [key5]
|
||||
|
||||
@@ -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