7 Commits
Author SHA1 Message Date
petrbalvin ad6c32d0c6 chore: prepare release v1.1.0
Test / test (push) Successful in 1m32s
Release / gates (push) Successful in 1m21s
Release / release (push) Successful in 52s
Assisted-by: GLM 5.3 Flash
2026-09-18 01:10:08 +02:00
petrbalvin 17574a0d15 docs: add the benchmarking document
Test / test (push) Canceled after 1m19s
Assisted-by: GLM 5.3 Flash
2026-09-18 00:20:42 +02:00
petrbalvin 8aa2b1b9c0 docs(api): name the newline a literal string may carry
Assisted-by: GLM 5.3 Flash
2026-09-18 00:20:42 +02:00
petrbalvin 6a043e2824 docs(architecture): scope the no-caching claim to the parser
Assisted-by: GLM 5.3 Flash
2026-09-18 00:20:31 +02:00
petrbalvin 81033bb27c docs(api): document the TOML 1.1 acceptance
Assisted-by: GLM 5.3 Flash
2026-09-18 00:20:31 +02:00
petrbalvin dfd5d240d2 docs(changelog): drop the stale toml-test run mode
Assisted-by: GLM 5.3 Flash
2026-09-18 00:20:31 +02:00
petrbalvin 78946578d1 docs(development): correct the release pipeline gates
Assisted-by: GLM 5.3 Flash
2026-09-18 00:20:31 +02:00
6 changed files with 80 additions and 18 deletions
+8 -2
View File
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
-
## [1.1.0] - 2026-09-18
### Added
- TOML 1.1 support, on by default: date-times and times without seconds - TOML 1.1 support, on by default: date-times and times without seconds
(`07:32`, `1979-05-27T07:32`, normalised to full seconds on output), the (`07:32`, `1979-05-27T07:32`, normalised to full seconds on output), the
`\e` and `\xHH` escape sequences, and multi-line inline tables with `\e` and `\xHH` escape sequences, and multi-line inline tables with
@@ -35,8 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- The compliance suite is [toml-test](https://github.com/toml-lang/toml-test) - The compliance suite is [toml-test](https://github.com/toml-lang/toml-test)
v2.2.0, run in TOML 1.0 mode. The new corpus holds 205 valid and 474 invalid v2.2.0, up from v1.6.0. Its TOML 1.0 corpus holds 205 valid and 474 invalid
cases (v1.6.0 had 185 and 371), and it caught the two documents the parser cases (185 and 371 before), and it caught the two documents the parser
still accepted, fixed below. still accepted, fixed below.
- The flattened struct layout the decoder consults is cached per struct type - The flattened struct layout the decoder consults is cached per struct type
and shared with the encoder, which now resolves duplicate field keys with and shared with the encoder, which now resolves duplicate field keys with
+6 -4
View File
@@ -53,9 +53,11 @@ just test
7. Open a pull request against `development`. 7. Open a pull request against `development`.
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The
release workflow runs the full gate set including the race detector and release workflow validates the tag, runs the static gates and the test suite
publishes the Gitea release with the matching `CHANGELOG.md` section as its with the coverage floor, and publishes the Gitea release with the matching
notes. `CHANGELOG.md` section as its notes. The race detector is not in that set: race
never runs on a push path, and the local `just gates` raced the tree before the
tag was cut.
## Code style ## Code style
@@ -114,7 +116,7 @@ Workflows live in `.gitea/workflows/` and run on the project's own runners:
|---|---|---| |---|---|---|
| Test | push or pull request to `development` | format check, vet, modernisation, build, the test suite with the coverage floor, the toml-test compliance suite | | Test | push or pull request to `development` | format check, vet, modernisation, build, the test suite with the coverage floor, the toml-test compliance suite |
| Race | `workflow_dispatch`, by hand | the suite under the race detector, the same race gate the local `just gates` runs | | Race | `workflow_dispatch`, by hand | the suite under the race detector, the same race gate the local `just gates` runs |
| Release | a `v*` tag | the same gates plus the race detector, then the Gitea release created from the `CHANGELOG.md` section | | Release | a `v*` tag | tag validation, format, vet, modernisation, build and the test suite with the coverage floor, then the Gitea release created from the `CHANGELOG.md` section; no race detector |
The local equivalent is `just gates`, which is the same set plus the race The local equivalent is `just gates`, which is the same set plus the race
detector. detector.
+11 -4
View File
@@ -7,6 +7,11 @@ package. The snippets assume:
import "sourcedock.dev/petrbalvin/interpres" import "sourcedock.dev/petrbalvin/interpres"
``` ```
The parser accepts TOML 1.0 documents plus the TOML 1.1 extensions: date-times
and times without seconds, the `\e` and `\xHH` escape sequences, and
multi-line inline tables with comments and trailing commas. The encoder emits
TOML 1.0, which is valid under both versions.
## Functions ## Functions
### `func Parse(data []byte) (map[string]any, error)` ### `func Parse(data []byte) (map[string]any, error)`
@@ -139,8 +144,10 @@ offending key or index, for example `p: interpres: integer 300 overflows uint8`.
Offset date-times decode into `time.Time` and keep their offset. The local Offset date-times decode into `time.Time` and keep their offset. The local
variants decode into `LocalDateTime`, `LocalDate` and `LocalTime`, whose variants decode into `LocalDateTime`, `LocalDate` and `LocalTime`, whose
embedded `time.Time` is normalised to UTC (midnight UTC for a local date, the embedded `time.Time` is normalised to UTC (midnight UTC for a local date, the
zero date for a local time). There is no implicit conversion between the offset zero date for a local time). Every kind may omit the seconds as of TOML 1.1
and local kinds; assigning one to the other is an error. (`07:32`, `1979-05-27T07:32`); such a value carries a zero second, and the
canonical rendering writes full seconds. There is no implicit conversion
between the offset and local kinds; assigning one to the other is an error.
### Arrays of tables ### Arrays of tables
@@ -362,8 +369,8 @@ out, err := interpres.NewEncoder().UseLiteralMultiline(80).Marshal(cfg)
Single-line strings keep the basic form regardless of the threshold, and a Single-line strings keep the basic form regardless of the threshold, and a
threshold of `0` or less disables the option. A string the literal form cannot threshold of `0` or less disables the option. A string the literal form cannot
carry verbatim (an embedded run of three single quotes, a control character carry verbatim (an embedded run of three single quotes, a control character
other than tab, or a carriage return outside a CRLF pair) also keeps the basic other than tab or newline, or a carriage return outside a CRLF pair) also keeps
form, so the output always re-parses to the same value. the basic form, so the output always re-parses to the same value.
### Cancellation ### Cancellation
+2 -2
View File
@@ -99,8 +99,8 @@ sequenceDiagram
`Decode`, `DecodeContext`, `Marshal` and `MarshalContext` call allocates its `Decode`, `DecodeContext`, `Marshal` and `MarshalContext` call allocates its
own unexported worker, so a configured type is safe for concurrent use; the own unexported worker, so a configured type is safe for concurrent use; the
setter methods are not, and must finish before the value is shared. setter methods are not, and must finish before the value is shared.
- The parser is allocated per `ParseContext` call; nothing is cached between - The parser is allocated per `ParseContext` call; the parser itself caches
documents. nothing between documents.
- The one piece of shared state is the struct-schema cache in `decode.go`: a - The one piece of shared state is the struct-schema cache in `decode.go`: a
`sync.Map` keyed by `reflect.Type`, holding the flattened field layout the `sync.Map` keyed by `reflect.Type`, holding the flattened field layout the
decoder and the encoder both consult. A schema is immutable once published, decoder and the encoder both consult. A schema is immutable once published,
+44
View File
@@ -0,0 +1,44 @@
# Benchmarking
How the performance numbers attached to this project are measured, so that a
number in a changelog entry or a release note can be reproduced and trusted.
## The suite
The benchmarks live in `bench_test.go`, next to the code they measure:
| Benchmark | What it measures |
|---|---|
| `BenchmarkParse` | `Parse` over a representative configuration document |
| `BenchmarkMarshal` | `Marshal` of the tree `Parse` produced from the same document |
| `BenchmarkStrictDecode` | `Decode` into a struct under `DisallowUnknownFields` |
| `BenchmarkParseLong` | `Parse` over a generated document with about 2000 array-of-tables entries |
## Running
```sh
just bench
```
The recipe runs the suite with `-benchmem -count=5`. Every benchmark uses
`b.Loop`, so setup runs outside the timed region, and `ReportAllocs` records
allocations per operation. The parse and marshal benchmarks set `SetBytes`, so
their results read as input bytes per second.
## Method
- An idle machine only: a loaded box times whatever else is running, and the
fastest sample can land on the wrong function.
- An A/B comparison runs both variants inside one process, in one binary;
separate processes of identical binaries differ by more than the effect
being measured.
- The five counts are compared through their medians, allocations and bytes
per operation alongside the times. Differences within 1 to 2 percent are
noise; only a difference beyond that is a result.
- When timing is hopeless, the allocation and byte counts are the result.
## Reports
The repository stores no benchmark reports. A performance claim in
`CHANGELOG.md` is measured with the method above on the change that makes it,
and the number travels with the claim.
+9 -6
View File
@@ -77,7 +77,9 @@ just bench
``` ```
Benchmark on an idle machine, and compare only runs made in one process against Benchmark on an idle machine, and compare only runs made in one process against
each other. The recipe sweeps `./...` five times with `-benchmem`. each other. The recipe sweeps `./...` five times with `-benchmem`. The binding
measurement method, and what counts as a result, is in
[docs/BENCHMARKING.md](BENCHMARKING.md).
## Debugging the build ## Debugging the build
@@ -97,12 +99,13 @@ pipeline.
|---|---|---| |---|---|---|
| `test.yml` | push or pull request to `development` | format check, vet, modernisation, build, the test suite with the 80 percent coverage floor, then the toml-test compliance suite | | `test.yml` | push or pull request to `development` | format check, vet, modernisation, build, the test suite with the 80 percent coverage floor, then the toml-test compliance suite |
| `race.yml` | `workflow_dispatch`, by hand | the suite under the race detector; the same race gate `just gates` runs locally | | `race.yml` | `workflow_dispatch`, by hand | the suite under the race detector; the same race gate `just gates` runs locally |
| `release.yml` | a `v*` tag | the same gates plus the race detector, then the Gitea release from the CHANGELOG section | | `release.yml` | a `v*` tag | tag validation, then format, vet, modernisation, build and the test suite with the coverage floor, then the Gitea release from the CHANGELOG section. No race detector: race never runs on a push path, and the local `just gates` raced the tree before the tag was cut |
## Releases ## Releases
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The
tag drives the release workflow: it validates the tag, runs the full gate set tag drives the release workflow: it validates the tag, runs the static gates
including the race detector, extracts the matching `## [X.Y.Z]` section from and the test suite with the coverage floor, extracts the matching `## [X.Y.Z]`
`CHANGELOG.md`, and publishes the release with that section as its body. A section from `CHANGELOG.md`, and publishes the release with that section as its
library ships no binaries, so the release carries the notes and nothing else. body. A library ships no binaries, so the release carries the notes and nothing
else.