diff --git a/go.mod b/go.mod index 8f1ef87..7c244a1 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/moby/patternmatcher v0.6.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 - github.com/pelletier/go-toml v1.9.5 + github.com/pelletier/go-toml/v2 v2.4.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 89ffe36..7ce47a4 100644 --- a/go.sum +++ b/go.sum @@ -137,10 +137,8 @@ github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22 github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.1 h1:j5OMOImsH+j2k7GJ5YO+RxfWwohNiH6t5zB/+h3bagc= +github.com/pelletier/go-toml/v2 v2.4.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/pkg/buildkit/config.go b/pkg/buildkit/config.go index d6cdf08..7958a0d 100644 --- a/pkg/buildkit/config.go +++ b/pkg/buildkit/config.go @@ -20,7 +20,7 @@ import ( "os" "path/filepath" - "github.com/pelletier/go-toml" + "github.com/pelletier/go-toml/v2" ) const ConfigPath = "/etc/buildkit/buildkitd.toml" diff --git a/vendor/github.com/pelletier/go-toml/CONTRIBUTING.md b/vendor/github.com/pelletier/go-toml/CONTRIBUTING.md deleted file mode 100644 index 98b9893..0000000 --- a/vendor/github.com/pelletier/go-toml/CONTRIBUTING.md +++ /dev/null @@ -1,132 +0,0 @@ -## Contributing - -Thank you for your interest in go-toml! We appreciate you considering -contributing to go-toml! - -The main goal is the project is to provide an easy-to-use TOML -implementation for Go that gets the job done and gets out of your way – -dealing with TOML is probably not the central piece of your project. - -As the single maintainer of go-toml, time is scarce. All help, big or -small, is more than welcomed! - -### Ask questions - -Any question you may have, somebody else might have it too. Always feel -free to ask them on the [issues tracker][issues-tracker]. We will try to -answer them as clearly and quickly as possible, time permitting. - -Asking questions also helps us identify areas where the documentation needs -improvement, or new features that weren't envisioned before. Sometimes, a -seemingly innocent question leads to the fix of a bug. Don't hesitate and -ask away! - -### Improve the documentation - -The best way to share your knowledge and experience with go-toml is to -improve the documentation. Fix a typo, clarify an interface, add an -example, anything goes! - -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 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. - -### Report a bug - -Found a bug! Sorry to hear that :(. Help us and other track them down and -fix by reporting it. [File a new bug report][bug-report] on the [issues -tracker][issues-tracker]. The template should provide enough guidance on -what to include. When in doubt: add more details! By reducing ambiguity and -providing more information, it decreases back and forth and saves everyone -time. - -### Code changes - -Want to contribute a patch? Very happy to hear that! - -First, some high-level rules: - -* A short proposal with some POC code is better than a lengthy piece of - text with no code. Code speaks louder than words. -* No backward-incompatible patch will be accepted unless discussed. - Sometimes it's hard, and Go's lack of versioning by default does not - help, but we try not to break people's programs unless we absolutely have - to. -* If you are writing a new feature or extending an existing one, make sure - to write some documentation. -* Bug fixes need to be accompanied with regression tests. -* New code needs to be tested. -* Your commit messages need to explain why the change is needed, even if - already included in the PR description. - -It does sound like a lot, but those best practices are here to save time -overall and continuously improve the quality of the project, which is -something everyone benefits from. - -#### Get started - -The fairly standard code contribution process looks like that: - -1. [Fork the project][fork]. -2. Make your changes, commit on any branch you like. -3. [Open up a pull request][pull-request] -4. Review, potential ask for changes. -5. Merge. You're in! - -Feel free to ask for help! You can create draft pull requests to gather -some early feedback! - -#### Run the tests - -You can run tests for go-toml using Go's test tool: `go test ./...`. -When creating a pull requests, all tests will be ran on Linux on a few Go -versions (Travis CI), and on Windows using the latest Go version -(AppVeyor). - -#### Style - -Try to look around and follow the same format and structure as the rest of -the code. We enforce using `go fmt` on the whole code base. - ---- - -### Maintainers-only - -#### Merge pull request - -Checklist: - -* Passing CI. -* Does not introduce backward-incompatible changes (unless discussed). -* Has relevant doc changes. -* Has relevant unit tests. - -1. Merge using "squash and merge". -2. Make sure to edit the commit message to keep all the useful information - nice and clean. -3. Make sure the commit title is clear and contains the PR number (#123). - -#### New release - -1. Go to [releases][releases]. Click on "X commits to master since this - release". -2. Make note of all the changes. Look for backward incompatible changes, - new features, and bug fixes. -3. Pick the new version using the above and semver. -4. Create a [new release][new-release]. -5. Follow the same format as [1.1.0][release-110]. - -[issues-tracker]: https://github.com/pelletier/go-toml/issues -[bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md -[pkg.go.dev]: https://pkg.go.dev/github.com/pelletier/go-toml -[readme]: ./README.md -[fork]: https://help.github.com/articles/fork-a-repo -[pull-request]: https://help.github.com/en/articles/creating-a-pull-request -[releases]: https://github.com/pelletier/go-toml/releases -[new-release]: https://github.com/pelletier/go-toml/releases/new -[release-110]: https://github.com/pelletier/go-toml/releases/tag/v1.1.0 diff --git a/vendor/github.com/pelletier/go-toml/Dockerfile b/vendor/github.com/pelletier/go-toml/Dockerfile deleted file mode 100644 index fffdb01..0000000 --- a/vendor/github.com/pelletier/go-toml/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.12-alpine3.9 as builder -WORKDIR /go/src/github.com/pelletier/go-toml -COPY . . -ENV CGO_ENABLED=0 -ENV GOOS=linux -RUN go install ./... - -FROM scratch -COPY --from=builder /go/bin/tomll /usr/bin/tomll -COPY --from=builder /go/bin/tomljson /usr/bin/tomljson -COPY --from=builder /go/bin/jsontoml /usr/bin/jsontoml diff --git a/vendor/github.com/pelletier/go-toml/LICENSE b/vendor/github.com/pelletier/go-toml/LICENSE deleted file mode 100644 index f414553..0000000 --- a/vendor/github.com/pelletier/go-toml/LICENSE +++ /dev/null @@ -1,247 +0,0 @@ -The bulk of github.com/pelletier/go-toml is distributed under the MIT license -(see below), with the exception of localtime.go and localtime.test.go. -Those two files have been copied over from Google's civil library at revision -ed46f5086358513cf8c25f8e3f022cb838a49d66, and are distributed under the Apache -2.0 license (see below). - - -github.com/pelletier/go-toml: - - -The MIT License (MIT) - -Copyright (c) 2013 - 2021 Thomas Pelletier, Eric Anderton - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -localtime.go, localtime_test.go: - -Originals: - https://raw.githubusercontent.com/googleapis/google-cloud-go/ed46f5086358513cf8c25f8e3f022cb838a49d66/civil/civil.go - https://raw.githubusercontent.com/googleapis/google-cloud-go/ed46f5086358513cf8c25f8e3f022cb838a49d66/civil/civil_test.go -Changes: - * Renamed files from civil* to localtime*. - * Package changed from civil to toml. - * 'Local' prefix added to all structs. -License: - https://raw.githubusercontent.com/googleapis/google-cloud-go/ed46f5086358513cf8c25f8e3f022cb838a49d66/LICENSE - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/pelletier/go-toml/Makefile b/vendor/github.com/pelletier/go-toml/Makefile deleted file mode 100644 index 9e4503a..0000000 --- a/vendor/github.com/pelletier/go-toml/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -export CGO_ENABLED=0 -go := go -go.goos ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f1) -go.goarch ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f2) - -out.tools := tomll tomljson jsontoml -out.dist := $(out.tools:=_$(go.goos)_$(go.goarch).tar.xz) -sources := $(wildcard **/*.go) - - -.PHONY: -tools: $(out.tools) - -$(out.tools): $(sources) - GOOS=$(go.goos) GOARCH=$(go.goarch) $(go) build ./cmd/$@ - -.PHONY: -dist: $(out.dist) - -$(out.dist):%_$(go.goos)_$(go.goarch).tar.xz: % - if [ "$(go.goos)" = "windows" ]; then \ - tar -cJf $@ $^.exe; \ - else \ - tar -cJf $@ $^; \ - fi - -.PHONY: -clean: - rm -rf $(out.tools) $(out.dist) diff --git a/vendor/github.com/pelletier/go-toml/PULL_REQUEST_TEMPLATE.md b/vendor/github.com/pelletier/go-toml/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 041cdc4..0000000 --- a/vendor/github.com/pelletier/go-toml/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,5 +0,0 @@ -**Issue:** add link to pelletier/go-toml issue here - -Explanation of what this pull request does. - -More detailed description of the decisions being made and the reasons why (if the patch is non-trivial). diff --git a/vendor/github.com/pelletier/go-toml/README.md b/vendor/github.com/pelletier/go-toml/README.md deleted file mode 100644 index 7399e04..0000000 --- a/vendor/github.com/pelletier/go-toml/README.md +++ /dev/null @@ -1,176 +0,0 @@ -# go-toml - -Go library for the [TOML](https://toml.io/) format. - -This library supports TOML version -[v1.0.0-rc.3](https://toml.io/en/v1.0.0-rc.3) - -[![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml.svg)](https://pkg.go.dev/github.com/pelletier/go-toml) -[![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE) -[![Build Status](https://dev.azure.com/pelletierthomas/go-toml-ci/_apis/build/status/pelletier.go-toml?branchName=master)](https://dev.azure.com/pelletierthomas/go-toml-ci/_build/latest?definitionId=1&branchName=master) -[![codecov](https://codecov.io/gh/pelletier/go-toml/branch/master/graph/badge.svg)](https://codecov.io/gh/pelletier/go-toml) -[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml) -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml?ref=badge_shield) - - -## Development status - -**â„šī¸ Consider go-toml v2!** - -The next version of go-toml is in [active development][v2-dev], and -[nearing completion][v2-map]. - -Though technically in beta, v2 is already more tested, [fixes bugs][v1-bugs], -and [much faster][v2-bench]. If you only need reading and writing TOML documents -(majority of cases), those features are implemented and the API unlikely to -change. - -The remaining features will be added shortly. While pull-requests are welcome on -v1, no active development is expected on it. When v2.0.0 is released, v1 will be -deprecated. - -👉 [go-toml v2][v2] - -[v2]: https://github.com/pelletier/go-toml/tree/v2 -[v2-map]: https://github.com/pelletier/go-toml/discussions/506 -[v2-dev]: https://github.com/pelletier/go-toml/tree/v2 -[v1-bugs]: https://github.com/pelletier/go-toml/issues?q=is%3Aissue+is%3Aopen+label%3Av2-fixed -[v2-bench]: https://github.com/pelletier/go-toml/tree/v2#benchmarks - -## Features - -Go-toml provides the following features for using data parsed from TOML documents: - -* Load TOML documents from files and string data -* Easily navigate TOML structure using Tree -* Marshaling and unmarshaling to and from data structures -* Line & column position data for all parsed elements -* [Query support similar to JSON-Path](query/) -* Syntax errors contain line and column numbers - -## Import - -```go -import "github.com/pelletier/go-toml" -``` - -## Usage example - -Read a TOML document: - -```go -config, _ := toml.Load(` -[postgres] -user = "pelletier" -password = "mypassword"`) -// retrieve data directly -user := config.Get("postgres.user").(string) - -// or using an intermediate object -postgresConfig := config.Get("postgres").(*toml.Tree) -password := postgresConfig.Get("password").(string) -``` - -Or use Unmarshal: - -```go -type Postgres struct { - User string - Password string -} -type Config struct { - Postgres Postgres -} - -doc := []byte(` -[Postgres] -User = "pelletier" -Password = "mypassword"`) - -config := Config{} -toml.Unmarshal(doc, &config) -fmt.Println("user=", config.Postgres.User) -``` - -Or use a query: - -```go -// use a query to gather elements without walking the tree -q, _ := query.Compile("$..[user,password]") -results := q.Execute(config) -for ii, item := range results.Values() { - fmt.Printf("Query result %d: %v\n", ii, item) -} -``` - -## Documentation - -The documentation and additional examples are available at -[pkg.go.dev](https://pkg.go.dev/github.com/pelletier/go-toml). - -## Tools - -Go-toml provides three handy command line tools: - -* `tomll`: Reads TOML files and lints them. - - ``` - go install github.com/pelletier/go-toml/cmd/tomll - tomll --help - ``` -* `tomljson`: Reads a TOML file and outputs its JSON representation. - - ``` - go install github.com/pelletier/go-toml/cmd/tomljson - tomljson --help - ``` - - * `jsontoml`: Reads a JSON file and outputs a TOML representation. - - ``` - go install github.com/pelletier/go-toml/cmd/jsontoml - jsontoml --help - ``` - -### Docker image - -Those tools are also available as a Docker image from -[dockerhub](https://hub.docker.com/r/pelletier/go-toml). For example, to -use `tomljson`: - -``` -docker run -v $PWD:/workdir pelletier/go-toml tomljson /workdir/example.toml -``` - -Only master (`latest`) and tagged versions are published to dockerhub. You -can build your own image as usual: - -``` -docker build -t go-toml . -``` - -## Contribute - -Feel free to report bugs and patches using GitHub's pull requests system on -[pelletier/go-toml](https://github.com/pelletier/go-toml). Any feedback would be -much appreciated! - -### Run tests - -`go test ./...` - -### Fuzzing - -The script `./fuzz.sh` is available to -run [go-fuzz](https://github.com/dvyukov/go-fuzz) on go-toml. - -## Versioning - -Go-toml follows [Semantic Versioning](http://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 - -The MIT License (MIT) + Apache 2.0. Read [LICENSE](LICENSE). diff --git a/vendor/github.com/pelletier/go-toml/azure-pipelines.yml b/vendor/github.com/pelletier/go-toml/azure-pipelines.yml deleted file mode 100644 index 4af198b..0000000 --- a/vendor/github.com/pelletier/go-toml/azure-pipelines.yml +++ /dev/null @@ -1,188 +0,0 @@ -trigger: -- master - -stages: -- stage: run_checks - displayName: "Check" - dependsOn: [] - jobs: - - job: fmt - displayName: "fmt" - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go 1.16" - inputs: - version: "1.16" - - task: Go@0 - displayName: "go fmt ./..." - inputs: - command: 'custom' - customCommand: 'fmt' - arguments: './...' - - job: coverage - displayName: "coverage" - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go 1.16" - inputs: - version: "1.16" - - task: Go@0 - displayName: "Generate coverage" - inputs: - command: 'test' - arguments: "-race -coverprofile=coverage.txt -covermode=atomic" - - task: Bash@3 - inputs: - targetType: 'inline' - script: 'bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}' - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) - - job: benchmark - displayName: "benchmark" - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go 1.16" - inputs: - version: "1.16" - - script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/" - - task: Bash@3 - inputs: - filePath: './benchmark.sh' - arguments: "master $(Build.Repository.Uri)" - - - job: go_unit_tests - displayName: "unit tests" - strategy: - matrix: - linux 1.16: - goVersion: '1.16' - imageName: 'ubuntu-latest' - mac 1.16: - goVersion: '1.16' - imageName: 'macOS-latest' - windows 1.16: - goVersion: '1.16' - imageName: 'windows-latest' - linux 1.15: - goVersion: '1.15' - imageName: 'ubuntu-latest' - mac 1.15: - goVersion: '1.15' - imageName: 'macOS-latest' - windows 1.15: - goVersion: '1.15' - imageName: 'windows-latest' - pool: - vmImage: $(imageName) - steps: - - task: GoTool@0 - displayName: "Install Go $(goVersion)" - inputs: - version: $(goVersion) - - task: Go@0 - displayName: "go test ./..." - inputs: - command: 'test' - arguments: './...' -- stage: build_binaries - displayName: "Build binaries" - dependsOn: run_checks - jobs: - - job: build_binary - displayName: "Build binary" - strategy: - matrix: - linux_amd64: - GOOS: linux - GOARCH: amd64 - darwin_amd64: - GOOS: darwin - GOARCH: amd64 - windows_amd64: - GOOS: windows - GOARCH: amd64 - pool: - vmImage: ubuntu-latest - steps: - - task: GoTool@0 - displayName: "Install Go" - inputs: - version: 1.16 - - task: Bash@3 - inputs: - targetType: inline - script: "make dist" - env: - go.goos: $(GOOS) - go.goarch: $(GOARCH) - - task: CopyFiles@2 - inputs: - sourceFolder: '$(Build.SourcesDirectory)' - contents: '*.tar.xz' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: binaries -- stage: build_binaries_manifest - displayName: "Build binaries manifest" - dependsOn: build_binaries - jobs: - - job: build_manifest - displayName: "Build binaries manifest" - steps: - - task: DownloadBuildArtifacts@0 - inputs: - buildType: 'current' - downloadType: 'single' - artifactName: 'binaries' - downloadPath: '$(Build.SourcesDirectory)' - - task: Bash@3 - inputs: - targetType: inline - script: "cd binaries && sha256sum --binary *.tar.xz | tee $(Build.ArtifactStagingDirectory)/sha256sums.txt" - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: manifest - -- stage: build_docker_image - displayName: "Build Docker image" - dependsOn: run_checks - jobs: - - job: build - displayName: "Build" - pool: - vmImage: ubuntu-latest - steps: - - task: Docker@2 - inputs: - command: 'build' - Dockerfile: 'Dockerfile' - buildContext: '.' - addPipelineData: false - -- stage: publish_docker_image - displayName: "Publish Docker image" - dependsOn: build_docker_image - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master')) - jobs: - - job: publish - displayName: "Publish" - pool: - vmImage: ubuntu-latest - steps: - - task: Docker@2 - inputs: - containerRegistry: 'DockerHub' - repository: 'pelletier/go-toml' - command: 'buildAndPush' - Dockerfile: 'Dockerfile' - buildContext: '.' - tags: 'latest' diff --git a/vendor/github.com/pelletier/go-toml/benchmark.sh b/vendor/github.com/pelletier/go-toml/benchmark.sh deleted file mode 100644 index a69d304..0000000 --- a/vendor/github.com/pelletier/go-toml/benchmark.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -set -ex - -reference_ref=${1:-master} -reference_git=${2:-.} - -if ! `hash benchstat 2>/dev/null`; then - echo "Installing benchstat" - go get golang.org/x/perf/cmd/benchstat -fi - -tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX` -ref_tempdir="${tempdir}/ref" -ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt" -local_benchmark="`pwd`/benchmark-local.txt" - -echo "=== ${reference_ref} (${ref_tempdir})" -git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null -pushd ${ref_tempdir} >/dev/null -git checkout ${reference_ref} >/dev/null 2>/dev/null -go test -bench=. -benchmem | tee ${ref_benchmark} -cd benchmark -go test -bench=. -benchmem | tee -a ${ref_benchmark} -popd >/dev/null - -echo "" -echo "=== local" -go test -bench=. -benchmem | tee ${local_benchmark} -cd benchmark -go test -bench=. -benchmem | tee -a ${local_benchmark} - -echo "" -echo "=== diff" -benchstat -delta-test=none ${ref_benchmark} ${local_benchmark} diff --git a/vendor/github.com/pelletier/go-toml/doc.go b/vendor/github.com/pelletier/go-toml/doc.go deleted file mode 100644 index a1406a3..0000000 --- a/vendor/github.com/pelletier/go-toml/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package toml is a TOML parser and manipulation library. -// -// This version supports the specification as described in -// https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md -// -// Marshaling -// -// Go-toml can marshal and unmarshal TOML documents from and to data -// structures. -// -// TOML document as a tree -// -// Go-toml can operate on a TOML document as a tree. Use one of the Load* -// functions to parse TOML data and obtain a Tree instance, then one of its -// methods to manipulate the tree. -// -// JSONPath-like queries -// -// The package github.com/pelletier/go-toml/query implements a system -// similar to JSONPath to quickly retrieve elements of a TOML document using a -// single expression. See the package documentation for more information. -// -package toml diff --git a/vendor/github.com/pelletier/go-toml/example-crlf.toml b/vendor/github.com/pelletier/go-toml/example-crlf.toml deleted file mode 100644 index 780d9c6..0000000 --- a/vendor/github.com/pelletier/go-toml/example-crlf.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it -score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported \ No newline at end of file diff --git a/vendor/github.com/pelletier/go-toml/example.toml b/vendor/github.com/pelletier/go-toml/example.toml deleted file mode 100644 index f45bf88..0000000 --- a/vendor/github.com/pelletier/go-toml/example.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it -score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported \ No newline at end of file diff --git a/vendor/github.com/pelletier/go-toml/fuzz.go b/vendor/github.com/pelletier/go-toml/fuzz.go deleted file mode 100644 index 14570c8..0000000 --- a/vendor/github.com/pelletier/go-toml/fuzz.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build gofuzz - -package toml - -func Fuzz(data []byte) int { - tree, err := LoadBytes(data) - if err != nil { - if tree != nil { - panic("tree must be nil if there is an error") - } - return 0 - } - - str, err := tree.ToTomlString() - if err != nil { - if str != "" { - panic(`str must be "" if there is an error`) - } - panic(err) - } - - tree, err = Load(str) - if err != nil { - if tree != nil { - panic("tree must be nil if there is an error") - } - return 0 - } - - return 1 -} diff --git a/vendor/github.com/pelletier/go-toml/fuzz.sh b/vendor/github.com/pelletier/go-toml/fuzz.sh deleted file mode 100644 index 3204b4c..0000000 --- a/vendor/github.com/pelletier/go-toml/fuzz.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/sh -set -eu - -go get github.com/dvyukov/go-fuzz/go-fuzz -go get github.com/dvyukov/go-fuzz/go-fuzz-build - -if [ ! -e toml-fuzz.zip ]; then - go-fuzz-build github.com/pelletier/go-toml -fi - -rm -fr fuzz -mkdir -p fuzz/corpus -cp *.toml fuzz/corpus - -go-fuzz -bin=toml-fuzz.zip -workdir=fuzz diff --git a/vendor/github.com/pelletier/go-toml/keysparsing.go b/vendor/github.com/pelletier/go-toml/keysparsing.go deleted file mode 100644 index e091500..0000000 --- a/vendor/github.com/pelletier/go-toml/keysparsing.go +++ /dev/null @@ -1,112 +0,0 @@ -// Parsing keys handling both bare and quoted keys. - -package toml - -import ( - "errors" - "fmt" -) - -// Convert the bare key group string to an array. -// The input supports double quotation and single quotation, -// but escape sequences are not supported. Lexers must unescape them beforehand. -func parseKey(key string) ([]string, error) { - runes := []rune(key) - var groups []string - - if len(key) == 0 { - return nil, errors.New("empty key") - } - - idx := 0 - for idx < len(runes) { - for ; idx < len(runes) && isSpace(runes[idx]); idx++ { - // skip leading whitespace - } - if idx >= len(runes) { - break - } - r := runes[idx] - if isValidBareChar(r) { - // parse bare key - startIdx := idx - endIdx := -1 - idx++ - for idx < len(runes) { - r = runes[idx] - if isValidBareChar(r) { - idx++ - } else if r == '.' { - endIdx = idx - break - } else if isSpace(r) { - endIdx = idx - for ; idx < len(runes) && isSpace(runes[idx]); idx++ { - // skip trailing whitespace - } - if idx < len(runes) && runes[idx] != '.' { - return nil, fmt.Errorf("invalid key character after whitespace: %c", runes[idx]) - } - break - } else { - return nil, fmt.Errorf("invalid bare key character: %c", r) - } - } - if endIdx == -1 { - endIdx = idx - } - groups = append(groups, string(runes[startIdx:endIdx])) - } else if r == '\'' { - // parse single quoted key - idx++ - startIdx := idx - for { - if idx >= len(runes) { - return nil, fmt.Errorf("unclosed single-quoted key") - } - r = runes[idx] - if r == '\'' { - groups = append(groups, string(runes[startIdx:idx])) - idx++ - break - } - idx++ - } - } else if r == '"' { - // parse double quoted key - idx++ - startIdx := idx - for { - if idx >= len(runes) { - return nil, fmt.Errorf("unclosed double-quoted key") - } - r = runes[idx] - if r == '"' { - groups = append(groups, string(runes[startIdx:idx])) - idx++ - break - } - idx++ - } - } else if r == '.' { - idx++ - if idx >= len(runes) { - return nil, fmt.Errorf("unexpected end of key") - } - r = runes[idx] - if !isValidBareChar(r) && r != '\'' && r != '"' && r != ' ' { - return nil, fmt.Errorf("expecting key part after dot") - } - } else { - return nil, fmt.Errorf("invalid key character: %c", r) - } - } - if len(groups) == 0 { - return nil, fmt.Errorf("empty key") - } - return groups, nil -} - -func isValidBareChar(r rune) bool { - return isAlphanumeric(r) || r == '-' || isDigit(r) -} diff --git a/vendor/github.com/pelletier/go-toml/lexer.go b/vendor/github.com/pelletier/go-toml/lexer.go deleted file mode 100644 index 313908e..0000000 --- a/vendor/github.com/pelletier/go-toml/lexer.go +++ /dev/null @@ -1,1031 +0,0 @@ -// TOML lexer. -// -// Written using the principles developed by Rob Pike in -// http://www.youtube.com/watch?v=HxaD_trXwRE - -package toml - -import ( - "bytes" - "errors" - "fmt" - "strconv" - "strings" -) - -// Define state functions -type tomlLexStateFn func() tomlLexStateFn - -// Define lexer -type tomlLexer struct { - inputIdx int - input []rune // Textual source - currentTokenStart int - currentTokenStop int - tokens []token - brackets []rune - line int - col int - endbufferLine int - endbufferCol int -} - -// Basic read operations on input - -func (l *tomlLexer) read() rune { - r := l.peek() - if r == '\n' { - l.endbufferLine++ - l.endbufferCol = 1 - } else { - l.endbufferCol++ - } - l.inputIdx++ - return r -} - -func (l *tomlLexer) next() rune { - r := l.read() - - if r != eof { - l.currentTokenStop++ - } - return r -} - -func (l *tomlLexer) ignore() { - l.currentTokenStart = l.currentTokenStop - l.line = l.endbufferLine - l.col = l.endbufferCol -} - -func (l *tomlLexer) skip() { - l.next() - l.ignore() -} - -func (l *tomlLexer) fastForward(n int) { - for i := 0; i < n; i++ { - l.next() - } -} - -func (l *tomlLexer) emitWithValue(t tokenType, value string) { - l.tokens = append(l.tokens, token{ - Position: Position{l.line, l.col}, - typ: t, - val: value, - }) - l.ignore() -} - -func (l *tomlLexer) emit(t tokenType) { - l.emitWithValue(t, string(l.input[l.currentTokenStart:l.currentTokenStop])) -} - -func (l *tomlLexer) peek() rune { - if l.inputIdx >= len(l.input) { - return eof - } - return l.input[l.inputIdx] -} - -func (l *tomlLexer) peekString(size int) string { - maxIdx := len(l.input) - upperIdx := l.inputIdx + size // FIXME: potential overflow - if upperIdx > maxIdx { - upperIdx = maxIdx - } - return string(l.input[l.inputIdx:upperIdx]) -} - -func (l *tomlLexer) follow(next string) bool { - return next == l.peekString(len(next)) -} - -// Error management - -func (l *tomlLexer) errorf(format string, args ...interface{}) tomlLexStateFn { - l.tokens = append(l.tokens, token{ - Position: Position{l.line, l.col}, - typ: tokenError, - val: fmt.Sprintf(format, args...), - }) - return nil -} - -// State functions - -func (l *tomlLexer) lexVoid() tomlLexStateFn { - for { - next := l.peek() - switch next { - case '}': // after '{' - return l.lexRightCurlyBrace - case '[': - return l.lexTableKey - case '#': - return l.lexComment(l.lexVoid) - case '=': - return l.lexEqual - case '\r': - fallthrough - case '\n': - l.skip() - continue - } - - if isSpace(next) { - l.skip() - } - - if isKeyStartChar(next) { - return l.lexKey - } - - if next == eof { - l.next() - break - } - } - - l.emit(tokenEOF) - return nil -} - -func (l *tomlLexer) lexRvalue() tomlLexStateFn { - for { - next := l.peek() - switch next { - case '.': - return l.errorf("cannot start float with a dot") - case '=': - return l.lexEqual - case '[': - return l.lexLeftBracket - case ']': - return l.lexRightBracket - case '{': - return l.lexLeftCurlyBrace - case '}': - return l.lexRightCurlyBrace - case '#': - return l.lexComment(l.lexRvalue) - case '"': - return l.lexString - case '\'': - return l.lexLiteralString - case ',': - return l.lexComma - case '\r': - fallthrough - case '\n': - l.skip() - if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '[' { - return l.lexRvalue - } - return l.lexVoid - } - - if l.follow("true") { - return l.lexTrue - } - - if l.follow("false") { - return l.lexFalse - } - - if l.follow("inf") { - return l.lexInf - } - - if l.follow("nan") { - return l.lexNan - } - - if isSpace(next) { - l.skip() - continue - } - - if next == eof { - l.next() - break - } - - if next == '+' || next == '-' { - return l.lexNumber - } - - if isDigit(next) { - return l.lexDateTimeOrNumber - } - - return l.errorf("no value can start with %c", next) - } - - l.emit(tokenEOF) - return nil -} - -func (l *tomlLexer) lexDateTimeOrNumber() tomlLexStateFn { - // Could be either a date/time, or a digit. - // The options for date/times are: - // YYYY-... => date or date-time - // HH:... => time - // Anything else should be a number. - - lookAhead := l.peekString(5) - if len(lookAhead) < 3 { - return l.lexNumber() - } - - for idx, r := range lookAhead { - if !isDigit(r) { - if idx == 2 && r == ':' { - return l.lexDateTimeOrTime() - } - if idx == 4 && r == '-' { - return l.lexDateTimeOrTime() - } - return l.lexNumber() - } - } - return l.lexNumber() -} - -func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn { - l.next() - l.emit(tokenLeftCurlyBrace) - l.brackets = append(l.brackets, '{') - return l.lexVoid -} - -func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn { - l.next() - l.emit(tokenRightCurlyBrace) - if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '{' { - return l.errorf("cannot have '}' here") - } - l.brackets = l.brackets[:len(l.brackets)-1] - return l.lexRvalue -} - -func (l *tomlLexer) lexDateTimeOrTime() tomlLexStateFn { - // Example matches: - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - // 07:32:00 - // 00:32:00.999999 - - // we already know those two are digits - l.next() - l.next() - - // Got 2 digits. At that point it could be either a time or a date(-time). - - r := l.next() - if r == ':' { - return l.lexTime() - } - - return l.lexDateTime() -} - -func (l *tomlLexer) lexDateTime() tomlLexStateFn { - // This state accepts an offset date-time, a local date-time, or a local date. - // - // v--- cursor - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - - // date - - // already checked by lexRvalue - l.next() // digit - l.next() // - - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid month digit in date: %c", r) - } - } - - r := l.next() - if r != '-' { - return l.errorf("expected - to separate month of a date, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid day digit in date: %c", r) - } - } - - l.emit(tokenLocalDate) - - r = l.peek() - - if r == eof { - - return l.lexRvalue - } - - if r != ' ' && r != 'T' { - return l.errorf("incorrect date/time separation character: %c", r) - } - - if r == ' ' { - lookAhead := l.peekString(3)[1:] - if len(lookAhead) < 2 { - return l.lexRvalue - } - for _, r := range lookAhead { - if !isDigit(r) { - return l.lexRvalue - } - } - } - - l.skip() // skip the T or ' ' - - // time - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid hour digit in time: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time hour/minute separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time minute/second separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid second digit in time: %c", r) - } - } - - r = l.peek() - if r == '.' { - l.next() - r := l.next() - if !isDigit(r) { - return l.errorf("expected at least one digit in time's fraction, not %c", r) - } - - for { - r := l.peek() - if !isDigit(r) { - break - } - l.next() - } - } - - l.emit(tokenLocalTime) - - return l.lexTimeOffset - -} - -func (l *tomlLexer) lexTimeOffset() tomlLexStateFn { - // potential offset - - // Z - // -07:00 - // +07:00 - // nothing - - r := l.peek() - - if r == 'Z' { - l.next() - l.emit(tokenTimeOffset) - } else if r == '+' || r == '-' { - l.next() - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid hour digit in time offset: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time offset hour/minute separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time offset: %c", r) - } - } - - l.emit(tokenTimeOffset) - } - - return l.lexRvalue -} - -func (l *tomlLexer) lexTime() tomlLexStateFn { - // v--- cursor - // 07:32:00 - // 00:32:00.999999 - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time: %c", r) - } - } - - r := l.next() - if r != ':' { - return l.errorf("time minute/second separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid second digit in time: %c", r) - } - } - - r = l.peek() - if r == '.' { - l.next() - r := l.next() - if !isDigit(r) { - return l.errorf("expected at least one digit in time's fraction, not %c", r) - } - - for { - r := l.peek() - if !isDigit(r) { - break - } - l.next() - } - } - - l.emit(tokenLocalTime) - return l.lexRvalue - -} - -func (l *tomlLexer) lexTrue() tomlLexStateFn { - l.fastForward(4) - l.emit(tokenTrue) - return l.lexRvalue -} - -func (l *tomlLexer) lexFalse() tomlLexStateFn { - l.fastForward(5) - l.emit(tokenFalse) - return l.lexRvalue -} - -func (l *tomlLexer) lexInf() tomlLexStateFn { - l.fastForward(3) - l.emit(tokenInf) - return l.lexRvalue -} - -func (l *tomlLexer) lexNan() tomlLexStateFn { - l.fastForward(3) - l.emit(tokenNan) - return l.lexRvalue -} - -func (l *tomlLexer) lexEqual() tomlLexStateFn { - l.next() - l.emit(tokenEqual) - return l.lexRvalue -} - -func (l *tomlLexer) lexComma() tomlLexStateFn { - l.next() - l.emit(tokenComma) - if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '{' { - return l.lexVoid - } - return l.lexRvalue -} - -// Parse the key and emits its value without escape sequences. -// bare keys, basic string keys and literal string keys are supported. -func (l *tomlLexer) lexKey() tomlLexStateFn { - var sb strings.Builder - - for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() { - if r == '"' { - l.next() - str, err := l.lexStringAsString(`"`, false, true) - if err != nil { - return l.errorf(err.Error()) - } - sb.WriteString("\"") - sb.WriteString(str) - sb.WriteString("\"") - l.next() - continue - } else if r == '\'' { - l.next() - str, err := l.lexLiteralStringAsString(`'`, false) - if err != nil { - return l.errorf(err.Error()) - } - sb.WriteString("'") - sb.WriteString(str) - sb.WriteString("'") - l.next() - continue - } else if r == '\n' { - return l.errorf("keys cannot contain new lines") - } else if isSpace(r) { - var str strings.Builder - str.WriteString(" ") - - // skip trailing whitespace - l.next() - for r = l.peek(); isSpace(r); r = l.peek() { - str.WriteRune(r) - l.next() - } - // break loop if not a dot - if r != '.' { - break - } - str.WriteString(".") - // skip trailing whitespace after dot - l.next() - for r = l.peek(); isSpace(r); r = l.peek() { - str.WriteRune(r) - l.next() - } - sb.WriteString(str.String()) - continue - } else if r == '.' { - // skip - } else if !isValidBareChar(r) { - return l.errorf("keys cannot contain %c character", r) - } - sb.WriteRune(r) - l.next() - } - l.emitWithValue(tokenKey, sb.String()) - return l.lexVoid -} - -func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn { - return func() tomlLexStateFn { - for next := l.peek(); next != '\n' && next != eof; next = l.peek() { - if next == '\r' && l.follow("\r\n") { - break - } - l.next() - } - l.ignore() - return previousState - } -} - -func (l *tomlLexer) lexLeftBracket() tomlLexStateFn { - l.next() - l.emit(tokenLeftBracket) - l.brackets = append(l.brackets, '[') - return l.lexRvalue -} - -func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) { - var sb strings.Builder - - if discardLeadingNewLine { - if l.follow("\r\n") { - l.skip() - l.skip() - } else if l.peek() == '\n' { - l.skip() - } - } - - // find end of string - for { - if l.follow(terminator) { - return sb.String(), nil - } - - next := l.peek() - if next == eof { - break - } - sb.WriteRune(l.next()) - } - - return "", errors.New("unclosed string") -} - -func (l *tomlLexer) lexLiteralString() tomlLexStateFn { - l.skip() - - // handle special case for triple-quote - terminator := "'" - discardLeadingNewLine := false - if l.follow("''") { - l.skip() - l.skip() - terminator = "'''" - discardLeadingNewLine = true - } - - str, err := l.lexLiteralStringAsString(terminator, discardLeadingNewLine) - if err != nil { - return l.errorf(err.Error()) - } - - l.emitWithValue(tokenString, str) - l.fastForward(len(terminator)) - l.ignore() - return l.lexRvalue -} - -// Lex a string and return the results as a string. -// Terminator is the substring indicating the end of the token. -// The resulting string does not include the terminator. -func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) { - var sb strings.Builder - - if discardLeadingNewLine { - if l.follow("\r\n") { - l.skip() - l.skip() - } else if l.peek() == '\n' { - l.skip() - } - } - - for { - if l.follow(terminator) { - return sb.String(), nil - } - - if l.follow("\\") { - l.next() - switch l.peek() { - case '\r': - fallthrough - case '\n': - fallthrough - case '\t': - fallthrough - case ' ': - // skip all whitespace chars following backslash - for strings.ContainsRune("\r\n\t ", l.peek()) { - l.next() - } - case '"': - sb.WriteString("\"") - l.next() - case 'n': - sb.WriteString("\n") - l.next() - case 'b': - sb.WriteString("\b") - l.next() - case 'f': - sb.WriteString("\f") - l.next() - case '/': - sb.WriteString("/") - l.next() - case 't': - sb.WriteString("\t") - l.next() - case 'r': - sb.WriteString("\r") - l.next() - case '\\': - sb.WriteString("\\") - l.next() - case 'u': - l.next() - var code strings.Builder - for i := 0; i < 4; i++ { - c := l.peek() - if !isHexDigit(c) { - return "", errors.New("unfinished unicode escape") - } - l.next() - code.WriteRune(c) - } - intcode, err := strconv.ParseInt(code.String(), 16, 32) - if err != nil { - return "", errors.New("invalid unicode escape: \\u" + code.String()) - } - sb.WriteRune(rune(intcode)) - case 'U': - l.next() - var code strings.Builder - for i := 0; i < 8; i++ { - c := l.peek() - if !isHexDigit(c) { - return "", errors.New("unfinished unicode escape") - } - l.next() - code.WriteRune(c) - } - intcode, err := strconv.ParseInt(code.String(), 16, 64) - if err != nil { - return "", errors.New("invalid unicode escape: \\U" + code.String()) - } - sb.WriteRune(rune(intcode)) - default: - return "", errors.New("invalid escape sequence: \\" + string(l.peek())) - } - } else { - r := l.peek() - - if 0x00 <= r && r <= 0x1F && r != '\t' && !(acceptNewLines && (r == '\n' || r == '\r')) { - return "", fmt.Errorf("unescaped control character %U", r) - } - l.next() - sb.WriteRune(r) - } - - if l.peek() == eof { - break - } - } - - return "", errors.New("unclosed string") -} - -func (l *tomlLexer) lexString() tomlLexStateFn { - l.skip() - - // handle special case for triple-quote - terminator := `"` - discardLeadingNewLine := false - acceptNewLines := false - if l.follow(`""`) { - l.skip() - l.skip() - terminator = `"""` - discardLeadingNewLine = true - acceptNewLines = true - } - - str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines) - if err != nil { - return l.errorf(err.Error()) - } - - l.emitWithValue(tokenString, str) - l.fastForward(len(terminator)) - l.ignore() - return l.lexRvalue -} - -func (l *tomlLexer) lexTableKey() tomlLexStateFn { - l.next() - - if l.peek() == '[' { - // token '[[' signifies an array of tables - l.next() - l.emit(tokenDoubleLeftBracket) - return l.lexInsideTableArrayKey - } - // vanilla table key - l.emit(tokenLeftBracket) - return l.lexInsideTableKey -} - -// Parse the key till "]]", but only bare keys are supported -func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn { - for r := l.peek(); r != eof; r = l.peek() { - switch r { - case ']': - if l.currentTokenStop > l.currentTokenStart { - l.emit(tokenKeyGroupArray) - } - l.next() - if l.peek() != ']' { - break - } - l.next() - l.emit(tokenDoubleRightBracket) - return l.lexVoid - case '[': - return l.errorf("table array key cannot contain ']'") - default: - l.next() - } - } - return l.errorf("unclosed table array key") -} - -// Parse the key till "]" but only bare keys are supported -func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn { - for r := l.peek(); r != eof; r = l.peek() { - switch r { - case ']': - if l.currentTokenStop > l.currentTokenStart { - l.emit(tokenKeyGroup) - } - l.next() - l.emit(tokenRightBracket) - return l.lexVoid - case '[': - return l.errorf("table key cannot contain ']'") - default: - l.next() - } - } - return l.errorf("unclosed table key") -} - -func (l *tomlLexer) lexRightBracket() tomlLexStateFn { - l.next() - l.emit(tokenRightBracket) - if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '[' { - return l.errorf("cannot have ']' here") - } - l.brackets = l.brackets[:len(l.brackets)-1] - return l.lexRvalue -} - -type validRuneFn func(r rune) bool - -func isValidHexRune(r rune) bool { - return r >= 'a' && r <= 'f' || - r >= 'A' && r <= 'F' || - r >= '0' && r <= '9' || - r == '_' -} - -func isValidOctalRune(r rune) bool { - return r >= '0' && r <= '7' || r == '_' -} - -func isValidBinaryRune(r rune) bool { - return r == '0' || r == '1' || r == '_' -} - -func (l *tomlLexer) lexNumber() tomlLexStateFn { - r := l.peek() - - if r == '0' { - follow := l.peekString(2) - if len(follow) == 2 { - var isValidRune validRuneFn - switch follow[1] { - case 'x': - isValidRune = isValidHexRune - case 'o': - isValidRune = isValidOctalRune - case 'b': - isValidRune = isValidBinaryRune - default: - if follow[1] >= 'a' && follow[1] <= 'z' || follow[1] >= 'A' && follow[1] <= 'Z' { - return l.errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(follow[1])) - } - } - - if isValidRune != nil { - l.next() - l.next() - digitSeen := false - for { - next := l.peek() - if !isValidRune(next) { - break - } - digitSeen = true - l.next() - } - - if !digitSeen { - return l.errorf("number needs at least one digit") - } - - l.emit(tokenInteger) - - return l.lexRvalue - } - } - } - - if r == '+' || r == '-' { - l.next() - if l.follow("inf") { - return l.lexInf - } - if l.follow("nan") { - return l.lexNan - } - } - - pointSeen := false - expSeen := false - digitSeen := false - for { - next := l.peek() - if next == '.' { - if pointSeen { - return l.errorf("cannot have two dots in one float") - } - l.next() - if !isDigit(l.peek()) { - return l.errorf("float cannot end with a dot") - } - pointSeen = true - } else if next == 'e' || next == 'E' { - expSeen = true - l.next() - r := l.peek() - if r == '+' || r == '-' { - l.next() - } - } else if isDigit(next) { - digitSeen = true - l.next() - } else if next == '_' { - l.next() - } else { - break - } - if pointSeen && !digitSeen { - return l.errorf("cannot start float with a dot") - } - } - - if !digitSeen { - return l.errorf("no digit in that number") - } - if pointSeen || expSeen { - l.emit(tokenFloat) - } else { - l.emit(tokenInteger) - } - return l.lexRvalue -} - -func (l *tomlLexer) run() { - for state := l.lexVoid; state != nil; { - state = state() - } -} - -// Entry point -func lexToml(inputBytes []byte) []token { - runes := bytes.Runes(inputBytes) - l := &tomlLexer{ - input: runes, - tokens: make([]token, 0, 256), - line: 1, - col: 1, - endbufferLine: 1, - endbufferCol: 1, - } - l.run() - return l.tokens -} diff --git a/vendor/github.com/pelletier/go-toml/localtime.go b/vendor/github.com/pelletier/go-toml/localtime.go deleted file mode 100644 index 9dfe4b9..0000000 --- a/vendor/github.com/pelletier/go-toml/localtime.go +++ /dev/null @@ -1,287 +0,0 @@ -// Implementation of TOML's local date/time. -// -// Copied over from Google's civil to avoid pulling all the Google dependencies. -// Originals: -// https://raw.githubusercontent.com/googleapis/google-cloud-go/ed46f5086358513cf8c25f8e3f022cb838a49d66/civil/civil.go -// Changes: -// * Renamed files from civil* to localtime*. -// * Package changed from civil to toml. -// * 'Local' prefix added to all structs. -// -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package civil implements types for civil time, a time-zone-independent -// representation of time that follows the rules of the proleptic -// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second -// minutes. -// -// Because they lack location information, these types do not represent unique -// moments or intervals of time. Use time.Time for that purpose. -package toml - -import ( - "fmt" - "time" -) - -// A LocalDate represents a date (year, month, day). -// -// This type does not include location information, and therefore does not -// describe a unique 24-hour timespan. -type LocalDate struct { - Year int // Year (e.g., 2014). - Month time.Month // Month of the year (January = 1, ...). - Day int // Day of the month, starting at 1. -} - -// LocalDateOf returns the LocalDate in which a time occurs in that time's location. -func LocalDateOf(t time.Time) LocalDate { - var d LocalDate - d.Year, d.Month, d.Day = t.Date() - return d -} - -// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents. -func ParseLocalDate(s string) (LocalDate, error) { - t, err := time.Parse("2006-01-02", s) - if err != nil { - return LocalDate{}, err - } - return LocalDateOf(t), nil -} - -// String returns the date in RFC3339 full-date format. -func (d LocalDate) String() string { - return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) -} - -// IsValid reports whether the date is valid. -func (d LocalDate) IsValid() bool { - return LocalDateOf(d.In(time.UTC)) == d -} - -// In returns the time corresponding to time 00:00:00 of the date in the location. -// -// In is always consistent with time.LocalDate, even when time.LocalDate returns a time -// on a different day. For example, if loc is America/Indiana/Vincennes, then both -// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc) -// and -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc) -// return 23:00:00 on April 30, 1955. -// -// In panics if loc is nil. -func (d LocalDate) In(loc *time.Location) time.Time { - return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc) -} - -// AddDays returns the date that is n days in the future. -// n can also be negative to go into the past. -func (d LocalDate) AddDays(n int) LocalDate { - return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n)) -} - -// DaysSince returns the signed number of days between the date and s, not including the end day. -// This is the inverse operation to AddDays. -func (d LocalDate) DaysSince(s LocalDate) (days int) { - // We convert to Unix time so we do not have to worry about leap seconds: - // Unix time increases by exactly 86400 seconds per day. - deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() - return int(deltaUnix / 86400) -} - -// Before reports whether d1 occurs before d2. -func (d1 LocalDate) Before(d2 LocalDate) bool { - if d1.Year != d2.Year { - return d1.Year < d2.Year - } - if d1.Month != d2.Month { - return d1.Month < d2.Month - } - return d1.Day < d2.Day -} - -// After reports whether d1 occurs after d2. -func (d1 LocalDate) After(d2 LocalDate) bool { - return d2.Before(d1) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of d.String(). -func (d LocalDate) MarshalText() ([]byte, error) { - return []byte(d.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The date is expected to be a string in a format accepted by ParseLocalDate. -func (d *LocalDate) UnmarshalText(data []byte) error { - var err error - *d, err = ParseLocalDate(string(data)) - return err -} - -// A LocalTime represents a time with nanosecond precision. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -// -// This type exists to represent the TIME type in storage-based APIs like BigQuery. -// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type. -type LocalTime struct { - Hour int // The hour of the day in 24-hour format; range [0-23] - Minute int // The minute of the hour; range [0-59] - Second int // The second of the minute; range [0-59] - Nanosecond int // The nanosecond of the second; range [0-999999999] -} - -// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs -// in that time's location. It ignores the date. -func LocalTimeOf(t time.Time) LocalTime { - var tm LocalTime - tm.Hour, tm.Minute, tm.Second = t.Clock() - tm.Nanosecond = t.Nanosecond() - return tm -} - -// ParseLocalTime parses a string and returns the time value it represents. -// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After -// the HH:MM:SS part of the string, an optional fractional part may appear, -// consisting of a decimal point followed by one to nine decimal digits. -// (RFC3339 admits only one digit after the decimal point). -func ParseLocalTime(s string) (LocalTime, error) { - t, err := time.Parse("15:04:05.999999999", s) - if err != nil { - return LocalTime{}, err - } - return LocalTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalTime. If Nanoseconds -// is zero, no fractional part will be generated. Otherwise, the result will -// end with a fractional part consisting of a decimal point and nine digits. -func (t LocalTime) String() string { - s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) - if t.Nanosecond == 0 { - return s - } - return s + fmt.Sprintf(".%09d", t.Nanosecond) -} - -// IsValid reports whether the time is valid. -func (t LocalTime) IsValid() bool { - // Construct a non-zero time. - tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) - return LocalTimeOf(tm) == t -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of t.String(). -func (t LocalTime) MarshalText() ([]byte, error) { - return []byte(t.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The time is expected to be a string in a format accepted by ParseLocalTime. -func (t *LocalTime) UnmarshalText(data []byte) error { - var err error - *t, err = ParseLocalTime(string(data)) - return err -} - -// A LocalDateTime represents a date and time. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -type LocalDateTime struct { - Date LocalDate - Time LocalTime -} - -// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub. - -// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location. -func LocalDateTimeOf(t time.Time) LocalDateTime { - return LocalDateTime{ - Date: LocalDateOf(t), - Time: LocalTimeOf(t), - } -} - -// ParseLocalDateTime parses a string and returns the LocalDateTime it represents. -// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits -// the time offset but includes an optional fractional time, as described in -// ParseLocalTime. Informally, the accepted format is -// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] -// where the 'T' may be a lower-case 't'. -func ParseLocalDateTime(s string) (LocalDateTime, error) { - t, err := time.Parse("2006-01-02T15:04:05.999999999", s) - if err != nil { - t, err = time.Parse("2006-01-02t15:04:05.999999999", s) - if err != nil { - return LocalDateTime{}, err - } - } - return LocalDateTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalDate. -func (dt LocalDateTime) String() string { - return dt.Date.String() + "T" + dt.Time.String() -} - -// IsValid reports whether the datetime is valid. -func (dt LocalDateTime) IsValid() bool { - return dt.Date.IsValid() && dt.Time.IsValid() -} - -// In returns the time corresponding to the LocalDateTime in the given location. -// -// If the time is missing or ambigous at the location, In returns the same -// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then -// both -// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc) -// and -// civil.LocalDateTime{ -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}}, -// civil.LocalTime{Minute: 30}}.In(loc) -// return 23:30:00 on April 30, 1955. -// -// In panics if loc is nil. -func (dt LocalDateTime) In(loc *time.Location) time.Time { - return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day, dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc) -} - -// Before reports whether dt1 occurs before dt2. -func (dt1 LocalDateTime) Before(dt2 LocalDateTime) bool { - return dt1.In(time.UTC).Before(dt2.In(time.UTC)) -} - -// After reports whether dt1 occurs after dt2. -func (dt1 LocalDateTime) After(dt2 LocalDateTime) bool { - return dt2.Before(dt1) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of dt.String(). -func (dt LocalDateTime) MarshalText() ([]byte, error) { - return []byte(dt.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The datetime is expected to be a string in a format accepted by ParseLocalDateTime -func (dt *LocalDateTime) UnmarshalText(data []byte) error { - var err error - *dt, err = ParseLocalDateTime(string(data)) - return err -} diff --git a/vendor/github.com/pelletier/go-toml/marshal.go b/vendor/github.com/pelletier/go-toml/marshal.go deleted file mode 100644 index 5712730..0000000 --- a/vendor/github.com/pelletier/go-toml/marshal.go +++ /dev/null @@ -1,1308 +0,0 @@ -package toml - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "io" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -const ( - tagFieldName = "toml" - tagFieldComment = "comment" - tagCommented = "commented" - tagMultiline = "multiline" - tagLiteral = "literal" - tagDefault = "default" -) - -type tomlOpts struct { - name string - nameFromTag bool - comment string - commented bool - multiline bool - literal bool - include bool - omitempty bool - defaultValue string -} - -type encOpts struct { - quoteMapKeys bool - arraysOneElementPerLine bool -} - -var encOptsDefaults = encOpts{ - quoteMapKeys: false, -} - -type annotation struct { - tag string - comment string - commented string - multiline string - literal string - defaultValue string -} - -var annotationDefault = annotation{ - tag: tagFieldName, - comment: tagFieldComment, - commented: tagCommented, - multiline: tagMultiline, - literal: tagLiteral, - defaultValue: tagDefault, -} - -type MarshalOrder int - -// Orders the Encoder can write the fields to the output stream. -const ( - // Sort fields alphabetically. - OrderAlphabetical MarshalOrder = iota + 1 - // Preserve the order the fields are encountered. For example, the order of fields in - // a struct. - OrderPreserve -) - -var timeType = reflect.TypeOf(time.Time{}) -var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() -var unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() -var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() -var localDateType = reflect.TypeOf(LocalDate{}) -var localTimeType = reflect.TypeOf(LocalTime{}) -var localDateTimeType = reflect.TypeOf(LocalDateTime{}) -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) - -// Check if the given marshal type maps to a Tree primitive -func isPrimitive(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isPrimitive(mtype.Elem()) - case reflect.Bool: - return true - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true - case reflect.Float32, reflect.Float64: - return true - case reflect.String: - return true - case reflect.Struct: - return isTimeType(mtype) - default: - return false - } -} - -func isTimeType(mtype reflect.Type) bool { - return mtype == timeType || mtype == localDateType || mtype == localDateTimeType || mtype == localTimeType -} - -// Check if the given marshal type maps to a Tree slice or array -func isTreeSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTreeSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isTree(mtype.Elem()) - default: - return false - } -} - -// Check if the given marshal type maps to a slice or array of a custom marshaler type -func isCustomMarshalerSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isCustomMarshalerSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isCustomMarshaler(mtype.Elem()) || isCustomMarshaler(reflect.New(mtype.Elem()).Type()) - default: - return false - } -} - -// Check if the given marshal type maps to a slice or array of a text marshaler type -func isTextMarshalerSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTextMarshalerSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isTextMarshaler(mtype.Elem()) || isTextMarshaler(reflect.New(mtype.Elem()).Type()) - default: - return false - } -} - -// Check if the given marshal type maps to a non-Tree slice or array -func isOtherSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isOtherSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return !isTreeSequence(mtype) - default: - return false - } -} - -// Check if the given marshal type maps to a Tree -func isTree(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTree(mtype.Elem()) - case reflect.Map: - return true - case reflect.Struct: - return !isPrimitive(mtype) - default: - return false - } -} - -func isCustomMarshaler(mtype reflect.Type) bool { - return mtype.Implements(marshalerType) -} - -func callCustomMarshaler(mval reflect.Value) ([]byte, error) { - return mval.Interface().(Marshaler).MarshalTOML() -} - -func isTextMarshaler(mtype reflect.Type) bool { - return mtype.Implements(textMarshalerType) && !isTimeType(mtype) -} - -func callTextMarshaler(mval reflect.Value) ([]byte, error) { - return mval.Interface().(encoding.TextMarshaler).MarshalText() -} - -func isCustomUnmarshaler(mtype reflect.Type) bool { - return mtype.Implements(unmarshalerType) -} - -func callCustomUnmarshaler(mval reflect.Value, tval interface{}) error { - return mval.Interface().(Unmarshaler).UnmarshalTOML(tval) -} - -func isTextUnmarshaler(mtype reflect.Type) bool { - return mtype.Implements(textUnmarshalerType) -} - -func callTextUnmarshaler(mval reflect.Value, text []byte) error { - return mval.Interface().(encoding.TextUnmarshaler).UnmarshalText(text) -} - -// Marshaler is the interface implemented by types that -// can marshal themselves into valid TOML. -type Marshaler interface { - MarshalTOML() ([]byte, error) -} - -// Unmarshaler is the interface implemented by types that -// can unmarshal a TOML description of themselves. -type Unmarshaler interface { - UnmarshalTOML(interface{}) error -} - -/* -Marshal returns the TOML encoding of v. Behavior is similar to the Go json -encoder, except that there is no concept of a Marshaler interface or MarshalTOML -function for sub-structs, and currently only definite types can be marshaled -(i.e. no `interface{}`). - -The following struct annotations are supported: - - toml:"Field" Overrides the field's name to output. - omitempty When set, empty values and groups are not emitted. - comment:"comment" Emits a # comment on the same line. This supports new lines. - commented:"true" Emits the value as commented. - -Note that pointers are automatically assigned the "omitempty" option, as TOML -explicitly does not handle null values (saying instead the label should be -dropped). - -Tree structural types and corresponding marshal types: - - *Tree (*)struct, (*)map[string]interface{} - []*Tree (*)[](*)struct, (*)[](*)map[string]interface{} - []interface{} (as interface{}) (*)[]primitive, (*)[]([]interface{}) - interface{} (*)primitive - -Tree primitive types and corresponding marshal types: - - uint64 uint, uint8-uint64, pointers to same - int64 int, int8-uint64, pointers to same - float64 float32, float64, pointers to same - string string, pointers to same - bool bool, pointers to same - time.LocalTime time.LocalTime{}, pointers to same - -For additional flexibility, use the Encoder API. -*/ -func Marshal(v interface{}) ([]byte, error) { - return NewEncoder(nil).marshal(v) -} - -// Encoder writes TOML values to an output stream. -type Encoder struct { - w io.Writer - encOpts - annotation - line int - col int - order MarshalOrder - promoteAnon bool - compactComments bool - indentation string -} - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - w: w, - encOpts: encOptsDefaults, - annotation: annotationDefault, - line: 0, - col: 1, - order: OrderAlphabetical, - indentation: " ", - } -} - -// Encode writes the TOML encoding of v to the stream. -// -// See the documentation for Marshal for details. -func (e *Encoder) Encode(v interface{}) error { - b, err := e.marshal(v) - if err != nil { - return err - } - if _, err := e.w.Write(b); err != nil { - return err - } - return nil -} - -// QuoteMapKeys sets up the encoder to encode -// maps with string type keys with quoted TOML keys. -// -// This relieves the character limitations on map keys. -func (e *Encoder) QuoteMapKeys(v bool) *Encoder { - e.quoteMapKeys = v - return e -} - -// ArraysWithOneElementPerLine sets up the encoder to encode arrays -// with more than one element on multiple lines instead of one. -// -// For example: -// -// A = [1,2,3] -// -// Becomes -// -// A = [ -// 1, -// 2, -// 3, -// ] -func (e *Encoder) ArraysWithOneElementPerLine(v bool) *Encoder { - e.arraysOneElementPerLine = v - return e -} - -// Order allows to change in which order fields will be written to the output stream. -func (e *Encoder) Order(ord MarshalOrder) *Encoder { - e.order = ord - return e -} - -// Indentation allows to change indentation when marshalling. -func (e *Encoder) Indentation(indent string) *Encoder { - e.indentation = indent - return e -} - -// SetTagName allows changing default tag "toml" -func (e *Encoder) SetTagName(v string) *Encoder { - e.tag = v - return e -} - -// SetTagComment allows changing default tag "comment" -func (e *Encoder) SetTagComment(v string) *Encoder { - e.comment = v - return e -} - -// SetTagCommented allows changing default tag "commented" -func (e *Encoder) SetTagCommented(v string) *Encoder { - e.commented = v - return e -} - -// SetTagMultiline allows changing default tag "multiline" -func (e *Encoder) SetTagMultiline(v string) *Encoder { - e.multiline = v - return e -} - -// PromoteAnonymous allows to change how anonymous struct fields are marshaled. -// Usually, they are marshaled as if the inner exported fields were fields in -// the outer struct. However, if an anonymous struct field is given a name in -// its TOML tag, it is treated like a regular struct field with that name. -// rather than being anonymous. -// -// In case anonymous promotion is enabled, all anonymous structs are promoted -// and treated like regular struct fields. -func (e *Encoder) PromoteAnonymous(promote bool) *Encoder { - e.promoteAnon = promote - return e -} - -// CompactComments removes the new line before each comment in the tree. -func (e *Encoder) CompactComments(cc bool) *Encoder { - e.compactComments = cc - return e -} - -func (e *Encoder) marshal(v interface{}) ([]byte, error) { - // Check if indentation is valid - for _, char := range e.indentation { - if !isSpace(char) { - return []byte{}, fmt.Errorf("invalid indentation: must only contains space or tab characters") - } - } - - mtype := reflect.TypeOf(v) - if mtype == nil { - return []byte{}, errors.New("nil cannot be marshaled to TOML") - } - - switch mtype.Kind() { - case reflect.Struct, reflect.Map: - case reflect.Ptr: - if mtype.Elem().Kind() != reflect.Struct { - return []byte{}, errors.New("Only pointer to struct can be marshaled to TOML") - } - if reflect.ValueOf(v).IsNil() { - return []byte{}, errors.New("nil pointer cannot be marshaled to TOML") - } - default: - return []byte{}, errors.New("Only a struct or map can be marshaled to TOML") - } - - sval := reflect.ValueOf(v) - if isCustomMarshaler(mtype) { - return callCustomMarshaler(sval) - } - if isTextMarshaler(mtype) { - return callTextMarshaler(sval) - } - t, err := e.valueToTree(mtype, sval) - if err != nil { - return []byte{}, err - } - - var buf bytes.Buffer - _, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order, e.indentation, e.compactComments, false) - - return buf.Bytes(), err -} - -// Create next tree with a position based on Encoder.line -func (e *Encoder) nextTree() *Tree { - return newTreeWithPosition(Position{Line: e.line, Col: 1}) -} - -// Convert given marshal struct or map value to toml tree -func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) { - if mtype.Kind() == reflect.Ptr { - return e.valueToTree(mtype.Elem(), mval.Elem()) - } - tval := e.nextTree() - switch mtype.Kind() { - case reflect.Struct: - switch mval.Interface().(type) { - case Tree: - reflect.ValueOf(tval).Elem().Set(mval) - default: - for i := 0; i < mtype.NumField(); i++ { - mtypef, mvalf := mtype.Field(i), mval.Field(i) - opts := tomlOptions(mtypef, e.annotation) - if opts.include && ((mtypef.Type.Kind() != reflect.Interface && !opts.omitempty) || !isZero(mvalf)) { - val, err := e.valueToToml(mtypef.Type, mvalf) - if err != nil { - return nil, err - } - if tree, ok := val.(*Tree); ok && mtypef.Anonymous && !opts.nameFromTag && !e.promoteAnon { - e.appendTree(tval, tree) - } else { - val = e.wrapTomlValue(val, tval) - tval.SetPathWithOptions([]string{opts.name}, SetOptions{ - Comment: opts.comment, - Commented: opts.commented, - Multiline: opts.multiline, - Literal: opts.literal, - }, val) - } - } - } - } - case reflect.Map: - keys := mval.MapKeys() - if e.order == OrderPreserve && len(keys) > 0 { - // Sorting []reflect.Value is not straight forward. - // - // OrderPreserve will support deterministic results when string is used - // as the key to maps. - typ := keys[0].Type() - kind := keys[0].Kind() - if kind == reflect.String { - ikeys := make([]string, len(keys)) - for i := range keys { - ikeys[i] = keys[i].Interface().(string) - } - sort.Strings(ikeys) - for i := range ikeys { - keys[i] = reflect.ValueOf(ikeys[i]).Convert(typ) - } - } - } - for _, key := range keys { - mvalf := mval.MapIndex(key) - if (mtype.Elem().Kind() == reflect.Ptr || mtype.Elem().Kind() == reflect.Interface) && mvalf.IsNil() { - continue - } - val, err := e.valueToToml(mtype.Elem(), mvalf) - if err != nil { - return nil, err - } - val = e.wrapTomlValue(val, tval) - if e.quoteMapKeys { - keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine) - if err != nil { - return nil, err - } - tval.SetPath([]string{keyStr}, val) - } else { - tval.SetPath([]string{key.String()}, val) - } - } - } - return tval, nil -} - -// Convert given marshal slice to slice of Toml trees -func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*Tree, error) { - tval := make([]*Tree, mval.Len(), mval.Len()) - for i := 0; i < mval.Len(); i++ { - val, err := e.valueToTree(mtype.Elem(), mval.Index(i)) - if err != nil { - return nil, err - } - tval[i] = val - } - return tval, nil -} - -// Convert given marshal slice to slice of toml values -func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - tval := make([]interface{}, mval.Len(), mval.Len()) - for i := 0; i < mval.Len(); i++ { - val, err := e.valueToToml(mtype.Elem(), mval.Index(i)) - if err != nil { - return nil, err - } - tval[i] = val - } - return tval, nil -} - -// Convert given marshal value to toml value -func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - if mtype.Kind() == reflect.Ptr { - switch { - case isCustomMarshaler(mtype): - return callCustomMarshaler(mval) - case isTextMarshaler(mtype): - b, err := callTextMarshaler(mval) - return string(b), err - default: - return e.valueToToml(mtype.Elem(), mval.Elem()) - } - } - if mtype.Kind() == reflect.Interface { - return e.valueToToml(mval.Elem().Type(), mval.Elem()) - } - switch { - case isCustomMarshaler(mtype): - return callCustomMarshaler(mval) - case isTextMarshaler(mtype): - b, err := callTextMarshaler(mval) - return string(b), err - case isTree(mtype): - return e.valueToTree(mtype, mval) - case isOtherSequence(mtype), isCustomMarshalerSequence(mtype), isTextMarshalerSequence(mtype): - return e.valueToOtherSlice(mtype, mval) - case isTreeSequence(mtype): - return e.valueToTreeSlice(mtype, mval) - default: - switch mtype.Kind() { - case reflect.Bool: - return mval.Bool(), nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if mtype.Kind() == reflect.Int64 && mtype == reflect.TypeOf(time.Duration(1)) { - return fmt.Sprint(mval), nil - } - return mval.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return mval.Uint(), nil - case reflect.Float32, reflect.Float64: - return mval.Float(), nil - case reflect.String: - return mval.String(), nil - case reflect.Struct: - return mval.Interface(), nil - default: - return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) - } - } -} - -func (e *Encoder) appendTree(t, o *Tree) error { - for key, value := range o.values { - if _, ok := t.values[key]; ok { - continue - } - if tomlValue, ok := value.(*tomlValue); ok { - tomlValue.position.Col = t.position.Col - } - t.values[key] = value - } - return nil -} - -// Create a toml value with the current line number as the position line -func (e *Encoder) wrapTomlValue(val interface{}, parent *Tree) interface{} { - _, isTree := val.(*Tree) - _, isTreeS := val.([]*Tree) - if isTree || isTreeS { - e.line++ - return val - } - - ret := &tomlValue{ - value: val, - position: Position{ - e.line, - parent.position.Col, - }, - } - e.line++ - return ret -} - -// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v. -// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for -// sub-structs, and only definite types can be unmarshaled. -func (t *Tree) Unmarshal(v interface{}) error { - d := Decoder{tval: t, tagName: tagFieldName} - return d.unmarshal(v) -} - -// Marshal returns the TOML encoding of Tree. -// See Marshal() documentation for types mapping table. -func (t *Tree) Marshal() ([]byte, error) { - var buf bytes.Buffer - _, err := t.WriteTo(&buf) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// Unmarshal parses the TOML-encoded data and stores the result in the value -// pointed to by v. Behavior is similar to the Go json encoder, except that there -// is no concept of an Unmarshaler interface or UnmarshalTOML function for -// sub-structs, and currently only definite types can be unmarshaled to (i.e. no -// `interface{}`). -// -// The following struct annotations are supported: -// -// toml:"Field" Overrides the field's name to map to. -// default:"foo" Provides a default value. -// -// For default values, only fields of the following types are supported: -// * string -// * bool -// * int -// * int64 -// * float64 -// -// See Marshal() documentation for types mapping table. -func Unmarshal(data []byte, v interface{}) error { - t, err := LoadReader(bytes.NewReader(data)) - if err != nil { - return err - } - return t.Unmarshal(v) -} - -// Decoder reads and decodes TOML values from an input stream. -type Decoder struct { - r io.Reader - tval *Tree - encOpts - tagName string - strict bool - visitor visitorState -} - -// NewDecoder returns a new decoder that reads from r. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{ - r: r, - encOpts: encOptsDefaults, - tagName: tagFieldName, - } -} - -// Decode reads a TOML-encoded value from it's input -// and unmarshals it in the value pointed at by v. -// -// See the documentation for Marshal for details. -func (d *Decoder) Decode(v interface{}) error { - var err error - d.tval, err = LoadReader(d.r) - if err != nil { - return err - } - return d.unmarshal(v) -} - -// SetTagName allows changing default tag "toml" -func (d *Decoder) SetTagName(v string) *Decoder { - d.tagName = v - return d -} - -// Strict allows changing to strict decoding. Any fields that are found in the -// input data and do not have a corresponding struct member cause an error. -func (d *Decoder) Strict(strict bool) *Decoder { - d.strict = strict - return d -} - -func (d *Decoder) unmarshal(v interface{}) error { - mtype := reflect.TypeOf(v) - if mtype == nil { - return errors.New("nil cannot be unmarshaled from TOML") - } - if mtype.Kind() != reflect.Ptr { - return errors.New("only a pointer to struct or map can be unmarshaled from TOML") - } - - elem := mtype.Elem() - - switch elem.Kind() { - case reflect.Struct, reflect.Map: - case reflect.Interface: - elem = mapStringInterfaceType - default: - return errors.New("only a pointer to struct or map can be unmarshaled from TOML") - } - - if reflect.ValueOf(v).IsNil() { - return errors.New("nil pointer cannot be unmarshaled from TOML") - } - - vv := reflect.ValueOf(v).Elem() - - if d.strict { - d.visitor = newVisitorState(d.tval) - } - - sval, err := d.valueFromTree(elem, d.tval, &vv) - if err != nil { - return err - } - if err := d.visitor.validate(); err != nil { - return err - } - reflect.ValueOf(v).Elem().Set(sval) - return nil -} - -// Convert toml tree to marshal struct or map, using marshal type. When mval1 -// is non-nil, merge fields into the given value instead of allocating a new one. -func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.Value) (reflect.Value, error) { - if mtype.Kind() == reflect.Ptr { - return d.unwrapPointer(mtype, tval, mval1) - } - - // Check if pointer to value implements the Unmarshaler interface. - if mvalPtr := reflect.New(mtype); isCustomUnmarshaler(mvalPtr.Type()) { - d.visitor.visitAll() - - if tval == nil { - return mvalPtr.Elem(), nil - } - - if err := callCustomUnmarshaler(mvalPtr, tval.ToMap()); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err) - } - return mvalPtr.Elem(), nil - } - - var mval reflect.Value - switch mtype.Kind() { - case reflect.Struct: - if mval1 != nil { - mval = *mval1 - } else { - mval = reflect.New(mtype).Elem() - } - - switch mval.Interface().(type) { - case Tree: - mval.Set(reflect.ValueOf(tval).Elem()) - default: - for i := 0; i < mtype.NumField(); i++ { - mtypef := mtype.Field(i) - an := annotation{tag: d.tagName} - opts := tomlOptions(mtypef, an) - if !opts.include { - continue - } - baseKey := opts.name - keysToTry := []string{ - baseKey, - strings.ToLower(baseKey), - strings.ToTitle(baseKey), - strings.ToLower(string(baseKey[0])) + baseKey[1:], - } - - found := false - if tval != nil { - for _, key := range keysToTry { - exists := tval.HasPath([]string{key}) - if !exists { - continue - } - - d.visitor.push(key) - val := tval.GetPath([]string{key}) - fval := mval.Field(i) - mvalf, err := d.valueFromToml(mtypef.Type, val, &fval) - if err != nil { - return mval, formatError(err, tval.GetPositionPath([]string{key})) - } - mval.Field(i).Set(mvalf) - found = true - d.visitor.pop() - break - } - } - - if !found && opts.defaultValue != "" { - mvalf := mval.Field(i) - var val interface{} - var err error - switch mvalf.Kind() { - case reflect.String: - val = opts.defaultValue - case reflect.Bool: - val, err = strconv.ParseBool(opts.defaultValue) - case reflect.Uint: - val, err = strconv.ParseUint(opts.defaultValue, 10, 0) - case reflect.Uint8: - val, err = strconv.ParseUint(opts.defaultValue, 10, 8) - case reflect.Uint16: - val, err = strconv.ParseUint(opts.defaultValue, 10, 16) - case reflect.Uint32: - val, err = strconv.ParseUint(opts.defaultValue, 10, 32) - case reflect.Uint64: - val, err = strconv.ParseUint(opts.defaultValue, 10, 64) - case reflect.Int: - val, err = strconv.ParseInt(opts.defaultValue, 10, 0) - case reflect.Int8: - val, err = strconv.ParseInt(opts.defaultValue, 10, 8) - case reflect.Int16: - val, err = strconv.ParseInt(opts.defaultValue, 10, 16) - case reflect.Int32: - val, err = strconv.ParseInt(opts.defaultValue, 10, 32) - case reflect.Int64: - // Check if the provided number has a non-numeric extension. - var hasExtension bool - if len(opts.defaultValue) > 0 { - lastChar := opts.defaultValue[len(opts.defaultValue)-1] - if lastChar < '0' || lastChar > '9' { - hasExtension = true - } - } - // If the value is a time.Duration with extension, parse as duration. - // If the value is an int64 or a time.Duration without extension, parse as number. - if hasExtension && mvalf.Type().String() == "time.Duration" { - val, err = time.ParseDuration(opts.defaultValue) - } else { - val, err = strconv.ParseInt(opts.defaultValue, 10, 64) - } - case reflect.Float32: - val, err = strconv.ParseFloat(opts.defaultValue, 32) - case reflect.Float64: - val, err = strconv.ParseFloat(opts.defaultValue, 64) - default: - return mvalf, fmt.Errorf("unsupported field type for default option") - } - - if err != nil { - return mvalf, err - } - mvalf.Set(reflect.ValueOf(val).Convert(mvalf.Type())) - } - - // save the old behavior above and try to check structs - if !found && opts.defaultValue == "" && mtypef.Type.Kind() == reflect.Struct { - tmpTval := tval - if !mtypef.Anonymous { - tmpTval = nil - } - fval := mval.Field(i) - v, err := d.valueFromTree(mtypef.Type, tmpTval, &fval) - if err != nil { - return v, err - } - mval.Field(i).Set(v) - } - } - } - case reflect.Map: - mval = reflect.MakeMap(mtype) - for _, key := range tval.Keys() { - d.visitor.push(key) - // TODO: path splits key - val := tval.GetPath([]string{key}) - mvalf, err := d.valueFromToml(mtype.Elem(), val, nil) - if err != nil { - return mval, formatError(err, tval.GetPositionPath([]string{key})) - } - mval.SetMapIndex(reflect.ValueOf(key).Convert(mtype.Key()), mvalf) - d.visitor.pop() - } - } - return mval, nil -} - -// Convert toml value to marshal struct/map slice, using marshal type -func (d *Decoder) valueFromTreeSlice(mtype reflect.Type, tval []*Tree) (reflect.Value, error) { - mval, err := makeSliceOrArray(mtype, len(tval)) - if err != nil { - return mval, err - } - - for i := 0; i < len(tval); i++ { - d.visitor.push(strconv.Itoa(i)) - val, err := d.valueFromTree(mtype.Elem(), tval[i], nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - d.visitor.pop() - } - return mval, nil -} - -// Convert toml value to marshal primitive slice, using marshal type -func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { - mval, err := makeSliceOrArray(mtype, len(tval)) - if err != nil { - return mval, err - } - - for i := 0; i < len(tval); i++ { - val, err := d.valueFromToml(mtype.Elem(), tval[i], nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - } - return mval, nil -} - -// Convert toml value to marshal primitive slice, using marshal type -func (d *Decoder) valueFromOtherSliceI(mtype reflect.Type, tval interface{}) (reflect.Value, error) { - val := reflect.ValueOf(tval) - length := val.Len() - - mval, err := makeSliceOrArray(mtype, length) - if err != nil { - return mval, err - } - - for i := 0; i < length; i++ { - val, err := d.valueFromToml(mtype.Elem(), val.Index(i).Interface(), nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - } - return mval, nil -} - -// Create a new slice or a new array with specified length -func makeSliceOrArray(mtype reflect.Type, tLength int) (reflect.Value, error) { - var mval reflect.Value - switch mtype.Kind() { - case reflect.Slice: - mval = reflect.MakeSlice(mtype, tLength, tLength) - case reflect.Array: - mval = reflect.New(reflect.ArrayOf(mtype.Len(), mtype.Elem())).Elem() - if tLength > mtype.Len() { - return mval, fmt.Errorf("unmarshal: TOML array length (%v) exceeds destination array length (%v)", tLength, mtype.Len()) - } - } - return mval, nil -} - -// Convert toml value to marshal value, using marshal type. When mval1 is non-nil -// and the given type is a struct value, merge fields into it. -func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) { - if mtype.Kind() == reflect.Ptr { - return d.unwrapPointer(mtype, tval, mval1) - } - - switch t := tval.(type) { - case *Tree: - var mval11 *reflect.Value - if mtype.Kind() == reflect.Struct { - mval11 = mval1 - } - - if isTree(mtype) { - return d.valueFromTree(mtype, t, mval11) - } - - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromTree(reflect.TypeOf(map[string]interface{}{}), t, nil) - } else { - return d.valueFromToml(mval1.Elem().Type(), t, nil) - } - } - - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval) - case []*Tree: - if isTreeSequence(mtype) { - return d.valueFromTreeSlice(mtype, t) - } - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromTreeSlice(reflect.TypeOf([]map[string]interface{}{}), t) - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval) - case []interface{}: - d.visitor.visit() - if isOtherSequence(mtype) { - return d.valueFromOtherSlice(mtype, t) - } - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromOtherSlice(reflect.TypeOf([]interface{}{}), t) - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) - default: - d.visitor.visit() - mvalPtr := reflect.New(mtype) - - // Check if pointer to value implements the Unmarshaler interface. - if isCustomUnmarshaler(mvalPtr.Type()) { - if err := callCustomUnmarshaler(mvalPtr, tval); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err) - } - return mvalPtr.Elem(), nil - } - - // Check if pointer to value implements the encoding.TextUnmarshaler. - if isTextUnmarshaler(mvalPtr.Type()) && !isTimeType(mtype) { - if err := d.unmarshalText(tval, mvalPtr); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal text: %v", err) - } - return mvalPtr.Elem(), nil - } - - switch mtype.Kind() { - case reflect.Bool, reflect.Struct: - val := reflect.ValueOf(tval) - - switch val.Type() { - case localDateType: - localDate := val.Interface().(LocalDate) - switch mtype { - case timeType: - return reflect.ValueOf(time.Date(localDate.Year, localDate.Month, localDate.Day, 0, 0, 0, 0, time.Local)), nil - } - case localDateTimeType: - localDateTime := val.Interface().(LocalDateTime) - switch mtype { - case timeType: - return reflect.ValueOf(time.Date( - localDateTime.Date.Year, - localDateTime.Date.Month, - localDateTime.Date.Day, - localDateTime.Time.Hour, - localDateTime.Time.Minute, - localDateTime.Time.Second, - localDateTime.Time.Nanosecond, - time.Local)), nil - } - } - - // if this passes for when mtype is reflect.Struct, tval is a time.LocalTime - if !val.Type().ConvertibleTo(mtype) { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.String: - val := reflect.ValueOf(tval) - // stupidly, int64 is convertible to string. So special case this. - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val := reflect.ValueOf(tval) - if mtype.Kind() == reflect.Int64 && mtype == reflect.TypeOf(time.Duration(1)) && val.Kind() == reflect.String { - d, err := time.ParseDuration(val.String()) - if err != nil { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v. %s", tval, tval, mtype.String(), err) - } - return reflect.ValueOf(d), nil - } - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Convert(reflect.TypeOf(int64(0))).Int()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - val := reflect.ValueOf(tval) - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - if val.Type().Kind() != reflect.Uint64 && val.Convert(reflect.TypeOf(int(1))).Int() < 0 { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) is negative so does not fit in %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowUint(val.Convert(reflect.TypeOf(uint64(0))).Uint()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Float32, reflect.Float64: - val := reflect.ValueOf(tval) - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Convert(reflect.TypeOf(float64(0))).Float()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Interface: - if mval1 == nil || mval1.IsNil() { - return reflect.ValueOf(tval), nil - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - case reflect.Slice, reflect.Array: - if isOtherSequence(mtype) && isOtherSequence(reflect.TypeOf(t)) { - return d.valueFromOtherSliceI(mtype, t) - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) - default: - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) - } - } -} - -func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) { - var melem *reflect.Value - - if mval1 != nil && !mval1.IsNil() && (mtype.Elem().Kind() == reflect.Struct || mtype.Elem().Kind() == reflect.Interface) { - elem := mval1.Elem() - melem = &elem - } - - val, err := d.valueFromToml(mtype.Elem(), tval, melem) - if err != nil { - return reflect.ValueOf(nil), err - } - mval := reflect.New(mtype.Elem()) - mval.Elem().Set(val) - return mval, nil -} - -func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error { - var buf bytes.Buffer - fmt.Fprint(&buf, tval) - return callTextUnmarshaler(mval, buf.Bytes()) -} - -func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { - tag := vf.Tag.Get(an.tag) - parse := strings.Split(tag, ",") - var comment string - if c := vf.Tag.Get(an.comment); c != "" { - comment = c - } - commented, _ := strconv.ParseBool(vf.Tag.Get(an.commented)) - multiline, _ := strconv.ParseBool(vf.Tag.Get(an.multiline)) - literal, _ := strconv.ParseBool(vf.Tag.Get(an.literal)) - defaultValue := vf.Tag.Get(tagDefault) - result := tomlOpts{ - name: vf.Name, - nameFromTag: false, - comment: comment, - commented: commented, - multiline: multiline, - literal: literal, - include: true, - omitempty: false, - defaultValue: defaultValue, - } - if parse[0] != "" { - if parse[0] == "-" && len(parse) == 1 { - result.include = false - } else { - result.name = strings.Trim(parse[0], " ") - result.nameFromTag = true - } - } - if vf.PkgPath != "" { - result.include = false - } - if len(parse) > 1 && strings.Trim(parse[1], " ") == "omitempty" { - result.omitempty = true - } - if vf.Type.Kind() == reflect.Ptr { - result.omitempty = true - } - return result -} - -func isZero(val reflect.Value) bool { - switch val.Type().Kind() { - case reflect.Slice, reflect.Array, reflect.Map: - return val.Len() == 0 - default: - return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) - } -} - -func formatError(err error, pos Position) error { - if err.Error()[0] == '(' { // Error already contains position information - return err - } - return fmt.Errorf("%s: %s", pos, err) -} - -// visitorState keeps track of which keys were unmarshaled. -type visitorState struct { - tree *Tree - path []string - keys map[string]struct{} - active bool -} - -func newVisitorState(tree *Tree) visitorState { - path, result := []string{}, map[string]struct{}{} - insertKeys(path, result, tree) - return visitorState{ - tree: tree, - path: path[:0], - keys: result, - active: true, - } -} - -func (s *visitorState) push(key string) { - if s.active { - s.path = append(s.path, key) - } -} - -func (s *visitorState) pop() { - if s.active { - s.path = s.path[:len(s.path)-1] - } -} - -func (s *visitorState) visit() { - if s.active { - delete(s.keys, strings.Join(s.path, ".")) - } -} - -func (s *visitorState) visitAll() { - if s.active { - for k := range s.keys { - if strings.HasPrefix(k, strings.Join(s.path, ".")) { - delete(s.keys, k) - } - } - } -} - -func (s *visitorState) validate() error { - if !s.active { - return nil - } - undecoded := make([]string, 0, len(s.keys)) - for key := range s.keys { - undecoded = append(undecoded, key) - } - sort.Strings(undecoded) - if len(undecoded) > 0 { - return fmt.Errorf("undecoded keys: %q", undecoded) - } - return nil -} - -func insertKeys(path []string, m map[string]struct{}, tree *Tree) { - for k, v := range tree.values { - switch node := v.(type) { - case []*Tree: - for i, item := range node { - insertKeys(append(path, k, strconv.Itoa(i)), m, item) - } - case *Tree: - insertKeys(append(path, k), m, node) - case *tomlValue: - m[strings.Join(append(path, k), ".")] = struct{}{} - } - } -} diff --git a/vendor/github.com/pelletier/go-toml/marshal_OrderPreserve_test.toml b/vendor/github.com/pelletier/go-toml/marshal_OrderPreserve_test.toml deleted file mode 100644 index 792b72e..0000000 --- a/vendor/github.com/pelletier/go-toml/marshal_OrderPreserve_test.toml +++ /dev/null @@ -1,39 +0,0 @@ -title = "TOML Marshal Testing" - -[basic_lists] - floats = [12.3,45.6,78.9] - bools = [true,false,true] - dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] - ints = [8001,8001,8002] - uints = [5002,5003] - strings = ["One","Two","Three"] - -[[subdocptrs]] - name = "Second" - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.second] - name = "Second" - - [subdoc.first] - name = "First" - -[basic] - uint = 5001 - bool = true - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - date = 1979-05-27T07:32:00Z - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" diff --git a/vendor/github.com/pelletier/go-toml/marshal_test.toml b/vendor/github.com/pelletier/go-toml/marshal_test.toml deleted file mode 100644 index ba5e110..0000000 --- a/vendor/github.com/pelletier/go-toml/marshal_test.toml +++ /dev/null @@ -1,39 +0,0 @@ -title = "TOML Marshal Testing" - -[basic] - bool = true - date = 1979-05-27T07:32:00Z - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - uint = 5001 - -[basic_lists] - bools = [true,false,true] - dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] - floats = [12.3,45.6,78.9] - ints = [8001,8001,8002] - strings = ["One","Two","Three"] - uints = [5002,5003] - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.first] - name = "First" - - [subdoc.second] - name = "Second" - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" - -[[subdocptrs]] - name = "Second" diff --git a/vendor/github.com/pelletier/go-toml/parser.go b/vendor/github.com/pelletier/go-toml/parser.go deleted file mode 100644 index b3726d0..0000000 --- a/vendor/github.com/pelletier/go-toml/parser.go +++ /dev/null @@ -1,507 +0,0 @@ -// TOML Parser. - -package toml - -import ( - "errors" - "fmt" - "math" - "reflect" - "strconv" - "strings" - "time" -) - -type tomlParser struct { - flowIdx int - flow []token - tree *Tree - currentTable []string - seenTableKeys []string -} - -type tomlParserStateFn func() tomlParserStateFn - -// Formats and panics an error message based on a token -func (p *tomlParser) raiseError(tok *token, msg string, args ...interface{}) { - panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) -} - -func (p *tomlParser) run() { - for state := p.parseStart; state != nil; { - state = state() - } -} - -func (p *tomlParser) peek() *token { - if p.flowIdx >= len(p.flow) { - return nil - } - return &p.flow[p.flowIdx] -} - -func (p *tomlParser) assume(typ tokenType) { - tok := p.getToken() - if tok == nil { - p.raiseError(tok, "was expecting token %s, but token stream is empty", tok) - } - if tok.typ != typ { - p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok) - } -} - -func (p *tomlParser) getToken() *token { - tok := p.peek() - if tok == nil { - return nil - } - p.flowIdx++ - return tok -} - -func (p *tomlParser) parseStart() tomlParserStateFn { - tok := p.peek() - - // end of stream, parsing is finished - if tok == nil { - return nil - } - - switch tok.typ { - case tokenDoubleLeftBracket: - return p.parseGroupArray - case tokenLeftBracket: - return p.parseGroup - case tokenKey: - return p.parseAssign - case tokenEOF: - return nil - case tokenError: - p.raiseError(tok, "parsing error: %s", tok.String()) - default: - p.raiseError(tok, "unexpected token %s", tok.typ) - } - return nil -} - -func (p *tomlParser) parseGroupArray() tomlParserStateFn { - startToken := p.getToken() // discard the [[ - key := p.getToken() - if key.typ != tokenKeyGroupArray { - p.raiseError(key, "unexpected token %s, was expecting a table array key", key) - } - - // get or create table array element at the indicated part in the path - keys, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid table array key: %s", err) - } - p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries - destTree := p.tree.GetPath(keys) - var array []*Tree - if destTree == nil { - array = make([]*Tree, 0) - } else if target, ok := destTree.([]*Tree); ok && target != nil { - array = destTree.([]*Tree) - } else { - p.raiseError(key, "key %s is already assigned and not of type table array", key) - } - p.currentTable = keys - - // add a new tree to the end of the table array - newTree := newTree() - newTree.position = startToken.Position - array = append(array, newTree) - p.tree.SetPath(p.currentTable, array) - - // remove all keys that were children of this table array - prefix := key.val + "." - found := false - for ii := 0; ii < len(p.seenTableKeys); { - tableKey := p.seenTableKeys[ii] - if strings.HasPrefix(tableKey, prefix) { - p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...) - } else { - found = (tableKey == key.val) - ii++ - } - } - - // keep this key name from use by other kinds of assignments - if !found { - p.seenTableKeys = append(p.seenTableKeys, key.val) - } - - // move to next parser state - p.assume(tokenDoubleRightBracket) - return p.parseStart -} - -func (p *tomlParser) parseGroup() tomlParserStateFn { - startToken := p.getToken() // discard the [ - key := p.getToken() - if key.typ != tokenKeyGroup { - p.raiseError(key, "unexpected token %s, was expecting a table key", key) - } - for _, item := range p.seenTableKeys { - if item == key.val { - p.raiseError(key, "duplicated tables") - } - } - - p.seenTableKeys = append(p.seenTableKeys, key.val) - keys, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid table array key: %s", err) - } - if err := p.tree.createSubTree(keys, startToken.Position); err != nil { - p.raiseError(key, "%s", err) - } - destTree := p.tree.GetPath(keys) - if target, ok := destTree.(*Tree); ok && target != nil && target.inline { - p.raiseError(key, "could not re-define exist inline table or its sub-table : %s", - strings.Join(keys, ".")) - } - p.assume(tokenRightBracket) - p.currentTable = keys - return p.parseStart -} - -func (p *tomlParser) parseAssign() tomlParserStateFn { - key := p.getToken() - p.assume(tokenEqual) - - parsedKey, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid key: %s", err.Error()) - } - - value := p.parseRvalue() - var tableKey []string - if len(p.currentTable) > 0 { - tableKey = p.currentTable - } else { - tableKey = []string{} - } - - prefixKey := parsedKey[0 : len(parsedKey)-1] - tableKey = append(tableKey, prefixKey...) - - // find the table to assign, looking out for arrays of tables - var targetNode *Tree - switch node := p.tree.GetPath(tableKey).(type) { - case []*Tree: - targetNode = node[len(node)-1] - case *Tree: - targetNode = node - case nil: - // create intermediate - if err := p.tree.createSubTree(tableKey, key.Position); err != nil { - p.raiseError(key, "could not create intermediate group: %s", err) - } - targetNode = p.tree.GetPath(tableKey).(*Tree) - default: - p.raiseError(key, "Unknown table type for path: %s", - strings.Join(tableKey, ".")) - } - - if targetNode.inline { - p.raiseError(key, "could not add key or sub-table to exist inline table or its sub-table : %s", - strings.Join(tableKey, ".")) - } - - // assign value to the found table - keyVal := parsedKey[len(parsedKey)-1] - localKey := []string{keyVal} - finalKey := append(tableKey, keyVal) - if targetNode.GetPath(localKey) != nil { - p.raiseError(key, "The following key was defined twice: %s", - strings.Join(finalKey, ".")) - } - var toInsert interface{} - - switch value.(type) { - case *Tree, []*Tree: - toInsert = value - default: - toInsert = &tomlValue{value: value, position: key.Position} - } - targetNode.values[keyVal] = toInsert - return p.parseStart -} - -var errInvalidUnderscore = errors.New("invalid use of _ in number") - -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore - } - } - hasBefore = isDigit(r) - } - return nil -} - -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") - -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex - } - } - hasBefore = isHexDigit(r) - } - return nil -} - -func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) - return cleanedVal -} - -func (p *tomlParser) parseRvalue() interface{} { - tok := p.getToken() - if tok == nil || tok.typ == tokenEOF { - p.raiseError(tok, "expecting a value") - } - - switch tok.typ { - case tokenString: - return tok.val - case tokenTrue: - return true - case tokenFalse: - return false - case tokenInf: - if tok.val[0] == '-' { - return math.Inf(-1) - } - return math.Inf(1) - case tokenNan: - return math.NaN() - case tokenInteger: - cleanedVal := cleanupNumberToken(tok.val) - base := 10 - s := cleanedVal - checkInvalidUnderscore := numberContainsInvalidUnderscore - if len(cleanedVal) >= 3 && cleanedVal[0] == '0' { - switch cleanedVal[1] { - case 'x': - checkInvalidUnderscore = hexNumberContainsInvalidUnderscore - base = 16 - case 'o': - base = 8 - case 'b': - base = 2 - default: - panic("invalid base") // the lexer should catch this first - } - s = cleanedVal[2:] - } - - err := checkInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - - var val interface{} - val, err = strconv.ParseInt(s, base, 64) - if err == nil { - return val - } - - if s[0] != '-' { - if val, err = strconv.ParseUint(s, base, 64); err == nil { - return val - } - } - p.raiseError(tok, "%s", err) - case tokenFloat: - err := numberContainsInvalidUnderscore(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - cleanedVal := cleanupNumberToken(tok.val) - val, err := strconv.ParseFloat(cleanedVal, 64) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLocalTime: - val, err := ParseLocalTime(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLocalDate: - // a local date may be followed by: - // * nothing: this is a local date - // * a local time: this is a local date-time - - next := p.peek() - if next == nil || next.typ != tokenLocalTime { - val, err := ParseLocalDate(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - } - - localDate := tok - localTime := p.getToken() - - next = p.peek() - if next == nil || next.typ != tokenTimeOffset { - v := localDate.val + "T" + localTime.val - val, err := ParseLocalDateTime(v) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - } - - offset := p.getToken() - - layout := time.RFC3339Nano - v := localDate.val + "T" + localTime.val + offset.val - val, err := time.ParseInLocation(layout, v, time.UTC) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLeftBracket: - return p.parseArray() - case tokenLeftCurlyBrace: - return p.parseInlineTable() - case tokenEqual: - p.raiseError(tok, "cannot have multiple equals for the same key") - case tokenError: - p.raiseError(tok, "%s", tok) - default: - panic(fmt.Errorf("unhandled token: %v", tok)) - } - - return nil -} - -func tokenIsComma(t *token) bool { - return t != nil && t.typ == tokenComma -} - -func (p *tomlParser) parseInlineTable() *Tree { - tree := newTree() - var previous *token -Loop: - for { - follow := p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated inline table") - } - switch follow.typ { - case tokenRightCurlyBrace: - p.getToken() - break Loop - case tokenKey, tokenInteger, tokenString: - if !tokenIsComma(previous) && previous != nil { - p.raiseError(follow, "comma expected between fields in inline table") - } - key := p.getToken() - p.assume(tokenEqual) - - parsedKey, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid key: %s", err) - } - - value := p.parseRvalue() - tree.SetPath(parsedKey, value) - case tokenComma: - if tokenIsComma(previous) { - p.raiseError(follow, "need field between two commas in inline table") - } - p.getToken() - default: - p.raiseError(follow, "unexpected token type in inline table: %s", follow.String()) - } - previous = follow - } - if tokenIsComma(previous) { - p.raiseError(previous, "trailing comma at the end of inline table") - } - tree.inline = true - return tree -} - -func (p *tomlParser) parseArray() interface{} { - var array []interface{} - arrayType := reflect.TypeOf(newTree()) - for { - follow := p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated array") - } - if follow.typ == tokenRightBracket { - p.getToken() - break - } - val := p.parseRvalue() - if reflect.TypeOf(val) != arrayType { - arrayType = nil - } - array = append(array, val) - follow = p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated array") - } - if follow.typ != tokenRightBracket && follow.typ != tokenComma { - p.raiseError(follow, "missing comma") - } - if follow.typ == tokenComma { - p.getToken() - } - } - - // if the array is a mixed-type array or its length is 0, - // don't convert it to a table array - if len(array) <= 0 { - arrayType = nil - } - // An array of Trees is actually an array of inline - // tables, which is a shorthand for a table array. If the - // array was not converted from []interface{} to []*Tree, - // the two notations would not be equivalent. - if arrayType == reflect.TypeOf(newTree()) { - tomlArray := make([]*Tree, len(array)) - for i, v := range array { - tomlArray[i] = v.(*Tree) - } - return tomlArray - } - return array -} - -func parseToml(flow []token) *Tree { - result := newTree() - result.position = Position{1, 1} - parser := &tomlParser{ - flowIdx: 0, - flow: flow, - tree: result, - currentTable: make([]string, 0), - seenTableKeys: make([]string, 0), - } - parser.run() - return result -} diff --git a/vendor/github.com/pelletier/go-toml/position.go b/vendor/github.com/pelletier/go-toml/position.go deleted file mode 100644 index c17bff8..0000000 --- a/vendor/github.com/pelletier/go-toml/position.go +++ /dev/null @@ -1,29 +0,0 @@ -// Position support for go-toml - -package toml - -import ( - "fmt" -) - -// Position of a document element within a TOML document. -// -// Line and Col are both 1-indexed positions for the element's line number and -// column number, respectively. Values of zero or less will cause Invalid(), -// to return true. -type Position struct { - Line int // line within the document - Col int // column within the line -} - -// String representation of the position. -// Displays 1-indexed line and column numbers. -func (p Position) String() string { - return fmt.Sprintf("(%d, %d)", p.Line, p.Col) -} - -// Invalid returns whether or not the position is valid (i.e. with negative or -// null values) -func (p Position) Invalid() bool { - return p.Line <= 0 || p.Col <= 0 -} diff --git a/vendor/github.com/pelletier/go-toml/token.go b/vendor/github.com/pelletier/go-toml/token.go deleted file mode 100644 index b437fdd..0000000 --- a/vendor/github.com/pelletier/go-toml/token.go +++ /dev/null @@ -1,136 +0,0 @@ -package toml - -import "fmt" - -// Define tokens -type tokenType int - -const ( - eof = -(iota + 1) -) - -const ( - tokenError tokenType = iota - tokenEOF - tokenComment - tokenKey - tokenString - tokenInteger - tokenTrue - tokenFalse - tokenFloat - tokenInf - tokenNan - tokenEqual - tokenLeftBracket - tokenRightBracket - tokenLeftCurlyBrace - tokenRightCurlyBrace - tokenLeftParen - tokenRightParen - tokenDoubleLeftBracket - tokenDoubleRightBracket - tokenLocalDate - tokenLocalTime - tokenTimeOffset - tokenKeyGroup - tokenKeyGroupArray - tokenComma - tokenColon - tokenDollar - tokenStar - tokenQuestion - tokenDot - tokenDotDot - tokenEOL -) - -var tokenTypeNames = []string{ - "Error", - "EOF", - "Comment", - "Key", - "String", - "Integer", - "True", - "False", - "Float", - "Inf", - "NaN", - "=", - "[", - "]", - "{", - "}", - "(", - ")", - "]]", - "[[", - "LocalDate", - "LocalTime", - "TimeOffset", - "KeyGroup", - "KeyGroupArray", - ",", - ":", - "$", - "*", - "?", - ".", - "..", - "EOL", -} - -type token struct { - Position - typ tokenType - val string -} - -func (tt tokenType) String() string { - idx := int(tt) - if idx < len(tokenTypeNames) { - return tokenTypeNames[idx] - } - return "Unknown" -} - -func (t token) String() string { - switch t.typ { - case tokenEOF: - return "EOF" - case tokenError: - return t.val - } - - return fmt.Sprintf("%q", t.val) -} - -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -func isAlphanumeric(r rune) bool { - return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' -} - -func isKeyChar(r rune) bool { - // Keys start with the first character that isn't whitespace or [ and end - // with the last non-whitespace character before the equals sign. Keys - // cannot contain a # character." - return !(r == '\r' || r == '\n' || r == eof || r == '=') -} - -func isKeyStartChar(r rune) bool { - return !(isSpace(r) || r == '\r' || r == '\n' || r == eof || r == '[') -} - -func isDigit(r rune) bool { - return '0' <= r && r <= '9' -} - -func isHexDigit(r rune) bool { - return isDigit(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} diff --git a/vendor/github.com/pelletier/go-toml/toml.go b/vendor/github.com/pelletier/go-toml/toml.go deleted file mode 100644 index 5541b94..0000000 --- a/vendor/github.com/pelletier/go-toml/toml.go +++ /dev/null @@ -1,533 +0,0 @@ -package toml - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "runtime" - "strings" -) - -type tomlValue struct { - value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list - comment string - commented bool - multiline bool - literal bool - position Position -} - -// Tree is the result of the parsing of a TOML file. -type Tree struct { - values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree - comment string - commented bool - inline bool - position Position -} - -func newTree() *Tree { - return newTreeWithPosition(Position{}) -} - -func newTreeWithPosition(pos Position) *Tree { - return &Tree{ - values: make(map[string]interface{}), - position: pos, - } -} - -// TreeFromMap initializes a new Tree object using the given map. -func TreeFromMap(m map[string]interface{}) (*Tree, error) { - result, err := toTree(m) - if err != nil { - return nil, err - } - return result.(*Tree), nil -} - -// Position returns the position of the tree. -func (t *Tree) Position() Position { - return t.position -} - -// Has returns a boolean indicating if the given key exists. -func (t *Tree) Has(key string) bool { - if key == "" { - return false - } - return t.HasPath(strings.Split(key, ".")) -} - -// HasPath returns true if the given path of keys exists, false otherwise. -func (t *Tree) HasPath(keys []string) bool { - return t.GetPath(keys) != nil -} - -// Keys returns the keys of the toplevel tree (does not recurse). -func (t *Tree) Keys() []string { - keys := make([]string, len(t.values)) - i := 0 - for k := range t.values { - keys[i] = k - i++ - } - return keys -} - -// Get the value at key in the Tree. -// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. -// If you need to retrieve non-bare keys, use GetPath. -// Returns nil if the path does not exist in the tree. -// If keys is of length zero, the current tree is returned. -func (t *Tree) Get(key string) interface{} { - if key == "" { - return t - } - return t.GetPath(strings.Split(key, ".")) -} - -// GetPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetPath(keys []string) interface{} { - if len(keys) == 0 { - return t - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return nil - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return nil - } - subtree = node[len(node)-1] - default: - return nil // cannot navigate through other node types - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - return node.value - default: - return node - } -} - -// GetArray returns the value at key in the Tree. -// It returns []string, []int64, etc type if key has homogeneous lists -// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. -// Returns nil if the path does not exist in the tree. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetArray(key string) interface{} { - if key == "" { - return t - } - return t.GetArrayPath(strings.Split(key, ".")) -} - -// GetArrayPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetArrayPath(keys []string) interface{} { - if len(keys) == 0 { - return t - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return nil - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return nil - } - subtree = node[len(node)-1] - default: - return nil // cannot navigate through other node types - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - switch n := node.value.(type) { - case []interface{}: - return getArray(n) - default: - return node.value - } - default: - return node - } -} - -// if homogeneous array, then return slice type object over []interface{} -func getArray(n []interface{}) interface{} { - var s []string - var i64 []int64 - var f64 []float64 - var bl []bool - for _, value := range n { - switch v := value.(type) { - case string: - s = append(s, v) - case int64: - i64 = append(i64, v) - case float64: - f64 = append(f64, v) - case bool: - bl = append(bl, v) - default: - return n - } - } - if len(s) == len(n) { - return s - } else if len(i64) == len(n) { - return i64 - } else if len(f64) == len(n) { - return f64 - } else if len(bl) == len(n) { - return bl - } - return n -} - -// GetPosition returns the position of the given key. -func (t *Tree) GetPosition(key string) Position { - if key == "" { - return t.position - } - return t.GetPositionPath(strings.Split(key, ".")) -} - -// SetPositionPath sets the position of element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree position is set. -func (t *Tree) SetPositionPath(keys []string, pos Position) { - if len(keys) == 0 { - t.position = pos - return - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return - } - subtree = node[len(node)-1] - default: - return - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - node.position = pos - return - case *Tree: - node.position = pos - return - case []*Tree: - // go to most recent element - if len(node) == 0 { - return - } - node[len(node)-1].position = pos - return - } -} - -// GetPositionPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetPositionPath(keys []string) Position { - if len(keys) == 0 { - return t.position - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return Position{0, 0} - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return Position{0, 0} - } - subtree = node[len(node)-1] - default: - return Position{0, 0} - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - return node.position - case *Tree: - return node.position - case []*Tree: - // go to most recent element - if len(node) == 0 { - return Position{0, 0} - } - return node[len(node)-1].position - default: - return Position{0, 0} - } -} - -// GetDefault works like Get but with a default value -func (t *Tree) GetDefault(key string, def interface{}) interface{} { - val := t.Get(key) - if val == nil { - return def - } - return val -} - -// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. -// The default values within the struct are valid default options. -type SetOptions struct { - Comment string - Commented bool - Multiline bool - Literal bool -} - -// SetWithOptions is the same as Set, but allows you to provide formatting -// instructions to the key, that will be used by Marshal(). -func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { - t.SetPathWithOptions(strings.Split(key, "."), opts, value) -} - -// SetPathWithOptions is the same as SetPath, but allows you to provide -// formatting instructions to the key, that will be reused by Marshal(). -func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { - subtree := t - for i, intermediateKey := range keys[:len(keys)-1] { - nextTree, exists := subtree.values[intermediateKey] - if !exists { - nextTree = newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) - subtree.values[intermediateKey] = nextTree // add new element here - } - switch node := nextTree.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - // create element if it does not exist - node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})) - subtree.values[intermediateKey] = node - } - subtree = node[len(node)-1] - } - } - - var toInsert interface{} - - switch v := value.(type) { - case *Tree: - v.comment = opts.Comment - v.commented = opts.Commented - toInsert = value - case []*Tree: - for i := range v { - v[i].commented = opts.Commented - } - toInsert = value - case *tomlValue: - v.comment = opts.Comment - v.commented = opts.Commented - v.multiline = opts.Multiline - v.literal = opts.Literal - toInsert = v - default: - toInsert = &tomlValue{value: value, - comment: opts.Comment, - commented: opts.Commented, - multiline: opts.Multiline, - literal: opts.Literal, - position: Position{Line: subtree.position.Line + len(subtree.values) + 1, Col: subtree.position.Col}} - } - - subtree.values[keys[len(keys)-1]] = toInsert -} - -// Set an element in the tree. -// Key is a dot-separated path (e.g. a.b.c). -// Creates all necessary intermediate trees, if needed. -func (t *Tree) Set(key string, value interface{}) { - t.SetWithComment(key, "", false, value) -} - -// SetWithComment is the same as Set, but allows you to provide comment -// information to the key, that will be reused by Marshal(). -func (t *Tree) SetWithComment(key string, comment string, commented bool, value interface{}) { - t.SetPathWithComment(strings.Split(key, "."), comment, commented, value) -} - -// SetPath sets an element in the tree. -// Keys is an array of path elements (e.g. {"a","b","c"}). -// Creates all necessary intermediate trees, if needed. -func (t *Tree) SetPath(keys []string, value interface{}) { - t.SetPathWithComment(keys, "", false, value) -} - -// SetPathWithComment is the same as SetPath, but allows you to provide comment -// information to the key, that will be reused by Marshal(). -func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) { - t.SetPathWithOptions(keys, SetOptions{Comment: comment, Commented: commented}, value) -} - -// Delete removes a key from the tree. -// Key is a dot-separated path (e.g. a.b.c). -func (t *Tree) Delete(key string) error { - keys, err := parseKey(key) - if err != nil { - return err - } - return t.DeletePath(keys) -} - -// DeletePath removes a key from the tree. -// Keys is an array of path elements (e.g. {"a","b","c"}). -func (t *Tree) DeletePath(keys []string) error { - keyLen := len(keys) - if keyLen == 1 { - delete(t.values, keys[0]) - return nil - } - tree := t.GetPath(keys[:keyLen-1]) - item := keys[keyLen-1] - switch node := tree.(type) { - case *Tree: - delete(node.values, item) - return nil - } - return errors.New("no such key to delete") -} - -// createSubTree takes a tree and a key and create the necessary intermediate -// subtrees to create a subtree at that point. In-place. -// -// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b] -// and tree[a][b][c] -// -// Returns nil on success, error object on failure -func (t *Tree) createSubTree(keys []string, pos Position) error { - subtree := t - for i, intermediateKey := range keys { - nextTree, exists := subtree.values[intermediateKey] - if !exists { - tree := newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) - tree.position = pos - tree.inline = subtree.inline - subtree.values[intermediateKey] = tree - nextTree = tree - } - - switch node := nextTree.(type) { - case []*Tree: - subtree = node[len(node)-1] - case *Tree: - subtree = node - default: - return fmt.Errorf("unknown type for path %s (%s): %T (%#v)", - strings.Join(keys, "."), intermediateKey, nextTree, nextTree) - } - } - return nil -} - -// LoadBytes creates a Tree from a []byte. -func LoadBytes(b []byte) (tree *Tree, err error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - err = fmt.Errorf("%s", r) - } - }() - - if len(b) >= 4 && (hasUTF32BigEndianBOM4(b) || hasUTF32LittleEndianBOM4(b)) { - b = b[4:] - } else if len(b) >= 3 && hasUTF8BOM3(b) { - b = b[3:] - } else if len(b) >= 2 && (hasUTF16BigEndianBOM2(b) || hasUTF16LittleEndianBOM2(b)) { - b = b[2:] - } - - tree = parseToml(lexToml(b)) - return -} - -func hasUTF16BigEndianBOM2(b []byte) bool { - return b[0] == 0xFE && b[1] == 0xFF -} - -func hasUTF16LittleEndianBOM2(b []byte) bool { - return b[0] == 0xFF && b[1] == 0xFE -} - -func hasUTF8BOM3(b []byte) bool { - return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF -} - -func hasUTF32BigEndianBOM4(b []byte) bool { - return b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF -} - -func hasUTF32LittleEndianBOM4(b []byte) bool { - return b[0] == 0xFF && b[1] == 0xFE && b[2] == 0x00 && b[3] == 0x00 -} - -// LoadReader creates a Tree from any io.Reader. -func LoadReader(reader io.Reader) (tree *Tree, err error) { - inputBytes, err := ioutil.ReadAll(reader) - if err != nil { - return - } - tree, err = LoadBytes(inputBytes) - return -} - -// Load creates a Tree from a string. -func Load(content string) (tree *Tree, err error) { - return LoadBytes([]byte(content)) -} - -// LoadFile creates a Tree from a file. -func LoadFile(path string) (tree *Tree, err error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - return LoadReader(file) -} diff --git a/vendor/github.com/pelletier/go-toml/tomlpub.go b/vendor/github.com/pelletier/go-toml/tomlpub.go deleted file mode 100644 index 4136b46..0000000 --- a/vendor/github.com/pelletier/go-toml/tomlpub.go +++ /dev/null @@ -1,71 +0,0 @@ -package toml - -// PubTOMLValue wrapping tomlValue in order to access all properties from outside. -type PubTOMLValue = tomlValue - -func (ptv *PubTOMLValue) Value() interface{} { - return ptv.value -} -func (ptv *PubTOMLValue) Comment() string { - return ptv.comment -} -func (ptv *PubTOMLValue) Commented() bool { - return ptv.commented -} -func (ptv *PubTOMLValue) Multiline() bool { - return ptv.multiline -} -func (ptv *PubTOMLValue) Position() Position { - return ptv.position -} - -func (ptv *PubTOMLValue) SetValue(v interface{}) { - ptv.value = v -} -func (ptv *PubTOMLValue) SetComment(s string) { - ptv.comment = s -} -func (ptv *PubTOMLValue) SetCommented(c bool) { - ptv.commented = c -} -func (ptv *PubTOMLValue) SetMultiline(m bool) { - ptv.multiline = m -} -func (ptv *PubTOMLValue) SetPosition(p Position) { - ptv.position = p -} - -// PubTree wrapping Tree in order to access all properties from outside. -type PubTree = Tree - -func (pt *PubTree) Values() map[string]interface{} { - return pt.values -} - -func (pt *PubTree) Comment() string { - return pt.comment -} - -func (pt *PubTree) Commented() bool { - return pt.commented -} - -func (pt *PubTree) Inline() bool { - return pt.inline -} - -func (pt *PubTree) SetValues(v map[string]interface{}) { - pt.values = v -} - -func (pt *PubTree) SetComment(c string) { - pt.comment = c -} - -func (pt *PubTree) SetCommented(c bool) { - pt.commented = c -} - -func (pt *PubTree) SetInline(i bool) { - pt.inline = i -} diff --git a/vendor/github.com/pelletier/go-toml/tomltree_create.go b/vendor/github.com/pelletier/go-toml/tomltree_create.go deleted file mode 100644 index 8035350..0000000 --- a/vendor/github.com/pelletier/go-toml/tomltree_create.go +++ /dev/null @@ -1,155 +0,0 @@ -package toml - -import ( - "fmt" - "reflect" - "time" -) - -var kindToType = [reflect.String + 1]reflect.Type{ - reflect.Bool: reflect.TypeOf(true), - reflect.String: reflect.TypeOf(""), - reflect.Float32: reflect.TypeOf(float64(1)), - reflect.Float64: reflect.TypeOf(float64(1)), - reflect.Int: reflect.TypeOf(int64(1)), - reflect.Int8: reflect.TypeOf(int64(1)), - reflect.Int16: reflect.TypeOf(int64(1)), - reflect.Int32: reflect.TypeOf(int64(1)), - reflect.Int64: reflect.TypeOf(int64(1)), - reflect.Uint: reflect.TypeOf(uint64(1)), - reflect.Uint8: reflect.TypeOf(uint64(1)), - reflect.Uint16: reflect.TypeOf(uint64(1)), - reflect.Uint32: reflect.TypeOf(uint64(1)), - reflect.Uint64: reflect.TypeOf(uint64(1)), -} - -// typeFor returns a reflect.Type for a reflect.Kind, or nil if none is found. -// supported values: -// string, bool, int64, uint64, float64, time.Time, int, int8, int16, int32, uint, uint8, uint16, uint32, float32 -func typeFor(k reflect.Kind) reflect.Type { - if k > 0 && int(k) < len(kindToType) { - return kindToType[k] - } - return nil -} - -func simpleValueCoercion(object interface{}) (interface{}, error) { - switch original := object.(type) { - case string, bool, int64, uint64, float64, time.Time: - return original, nil - case int: - return int64(original), nil - case int8: - return int64(original), nil - case int16: - return int64(original), nil - case int32: - return int64(original), nil - case uint: - return uint64(original), nil - case uint8: - return uint64(original), nil - case uint16: - return uint64(original), nil - case uint32: - return uint64(original), nil - case float32: - return float64(original), nil - case fmt.Stringer: - return original.String(), nil - case []interface{}: - value := reflect.ValueOf(original) - length := value.Len() - arrayValue := reflect.MakeSlice(value.Type(), 0, length) - for i := 0; i < length; i++ { - val := value.Index(i).Interface() - simpleValue, err := simpleValueCoercion(val) - if err != nil { - return nil, err - } - arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) - } - return arrayValue.Interface(), nil - default: - return nil, fmt.Errorf("cannot convert type %T to Tree", object) - } -} - -func sliceToTree(object interface{}) (interface{}, error) { - // arrays are a bit tricky, since they can represent either a - // collection of simple values, which is represented by one - // *tomlValue, or an array of tables, which is represented by an - // array of *Tree. - - // holding the assumption that this function is called from toTree only when value.Kind() is Array or Slice - value := reflect.ValueOf(object) - insideType := value.Type().Elem() - length := value.Len() - if length > 0 { - insideType = reflect.ValueOf(value.Index(0).Interface()).Type() - } - if insideType.Kind() == reflect.Map { - // this is considered as an array of tables - tablesArray := make([]*Tree, 0, length) - for i := 0; i < length; i++ { - table := value.Index(i) - tree, err := toTree(table.Interface()) - if err != nil { - return nil, err - } - tablesArray = append(tablesArray, tree.(*Tree)) - } - return tablesArray, nil - } - - sliceType := typeFor(insideType.Kind()) - if sliceType == nil { - sliceType = insideType - } - - arrayValue := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, length) - - for i := 0; i < length; i++ { - val := value.Index(i).Interface() - simpleValue, err := simpleValueCoercion(val) - if err != nil { - return nil, err - } - arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) - } - return &tomlValue{value: arrayValue.Interface(), position: Position{}}, nil -} - -func toTree(object interface{}) (interface{}, error) { - value := reflect.ValueOf(object) - - if value.Kind() == reflect.Map { - values := map[string]interface{}{} - keys := value.MapKeys() - for _, key := range keys { - if key.Kind() != reflect.String { - if _, ok := key.Interface().(string); !ok { - return nil, fmt.Errorf("map key needs to be a string, not %T (%v)", key.Interface(), key.Kind()) - } - } - - v := value.MapIndex(key) - newValue, err := toTree(v.Interface()) - if err != nil { - return nil, err - } - values[key.String()] = newValue - } - return &Tree{values: values, position: Position{}}, nil - } - - if value.Kind() == reflect.Array || value.Kind() == reflect.Slice { - return sliceToTree(object) - } - - simpleValue, err := simpleValueCoercion(object) - if err != nil { - return nil, err - } - return &tomlValue{value: simpleValue, position: Position{}}, nil -} diff --git a/vendor/github.com/pelletier/go-toml/tomltree_write.go b/vendor/github.com/pelletier/go-toml/tomltree_write.go deleted file mode 100644 index c9afbda..0000000 --- a/vendor/github.com/pelletier/go-toml/tomltree_write.go +++ /dev/null @@ -1,552 +0,0 @@ -package toml - -import ( - "bytes" - "fmt" - "io" - "math" - "math/big" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -type valueComplexity int - -const ( - valueSimple valueComplexity = iota + 1 - valueComplex -) - -type sortNode struct { - key string - complexity valueComplexity -} - -// Encodes a string to a TOML-compliant multi-line string value -// This function is a clone of the existing encodeTomlString function, except that whitespace characters -// are preserved. Quotation marks and backslashes are also not escaped. -func encodeMultilineTomlString(value string, commented string) string { - var b bytes.Buffer - adjacentQuoteCount := 0 - - b.WriteString(commented) - for i, rr := range value { - if rr != '"' { - adjacentQuoteCount = 0 - } else { - adjacentQuoteCount++ - } - switch rr { - case '\b': - b.WriteString(`\b`) - case '\t': - b.WriteString("\t") - case '\n': - b.WriteString("\n" + commented) - case '\f': - b.WriteString(`\f`) - case '\r': - b.WriteString("\r") - case '"': - if adjacentQuoteCount >= 3 || i == len(value)-1 { - adjacentQuoteCount = 0 - b.WriteString(`\"`) - } else { - b.WriteString(`"`) - } - case '\\': - b.WriteString(`\`) - default: - intRr := uint16(rr) - if intRr < 0x001F { - b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) - } else { - b.WriteRune(rr) - } - } - } - return b.String() -} - -// Encodes a string to a TOML-compliant string value -func encodeTomlString(value string) string { - var b bytes.Buffer - - for _, rr := range value { - switch rr { - case '\b': - b.WriteString(`\b`) - case '\t': - b.WriteString(`\t`) - case '\n': - b.WriteString(`\n`) - case '\f': - b.WriteString(`\f`) - case '\r': - b.WriteString(`\r`) - case '"': - b.WriteString(`\"`) - case '\\': - b.WriteString(`\\`) - default: - intRr := uint16(rr) - if intRr < 0x001F { - b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) - } else { - b.WriteRune(rr) - } - } - } - return b.String() -} - -func tomlTreeStringRepresentation(t *Tree, ord MarshalOrder) (string, error) { - var orderedVals []sortNode - switch ord { - case OrderPreserve: - orderedVals = sortByLines(t) - default: - orderedVals = sortAlphabetical(t) - } - - var values []string - for _, node := range orderedVals { - k := node.key - v := t.values[k] - - repr, err := tomlValueStringRepresentation(v, "", "", ord, false) - if err != nil { - return "", err - } - values = append(values, quoteKeyIfNeeded(k)+" = "+repr) - } - return "{ " + strings.Join(values, ", ") + " }", nil -} - -func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord MarshalOrder, arraysOneElementPerLine bool) (string, error) { - // this interface check is added to dereference the change made in the writeTo function. - // That change was made to allow this function to see formatting options. - tv, ok := v.(*tomlValue) - if ok { - v = tv.value - } else { - tv = &tomlValue{} - } - - switch value := v.(type) { - case uint64: - return strconv.FormatUint(value, 10), nil - case int64: - return strconv.FormatInt(value, 10), nil - case float64: - // Default bit length is full 64 - bits := 64 - // Float panics if nan is used - if !math.IsNaN(value) { - // if 32 bit accuracy is enough to exactly show, use 32 - _, acc := big.NewFloat(value).Float32() - if acc == big.Exact { - bits = 32 - } - } - if math.Trunc(value) == value { - return strings.ToLower(strconv.FormatFloat(value, 'f', 1, bits)), nil - } - return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil - case string: - if tv.multiline { - if tv.literal { - b := strings.Builder{} - b.WriteString("'''\n") - b.Write([]byte(value)) - b.WriteString("\n'''") - return b.String(), nil - } else { - return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil - } - } - return "\"" + encodeTomlString(value) + "\"", nil - case []byte: - b, _ := v.([]byte) - return string(b), nil - case bool: - if value { - return "true", nil - } - return "false", nil - case time.Time: - return value.Format(time.RFC3339), nil - case LocalDate: - return value.String(), nil - case LocalDateTime: - return value.String(), nil - case LocalTime: - return value.String(), nil - case *Tree: - return tomlTreeStringRepresentation(value, ord) - case nil: - return "", nil - } - - rv := reflect.ValueOf(v) - - if rv.Kind() == reflect.Slice { - var values []string - for i := 0; i < rv.Len(); i++ { - item := rv.Index(i).Interface() - itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine) - if err != nil { - return "", err - } - values = append(values, itemRepr) - } - if arraysOneElementPerLine && len(values) > 1 { - stringBuffer := bytes.Buffer{} - valueIndent := indent + ` ` // TODO: move that to a shared encoder state - - stringBuffer.WriteString("[\n") - - for _, value := range values { - stringBuffer.WriteString(valueIndent) - stringBuffer.WriteString(commented + value) - stringBuffer.WriteString(`,`) - stringBuffer.WriteString("\n") - } - - stringBuffer.WriteString(indent + commented + "]") - - return stringBuffer.String(), nil - } - return "[" + strings.Join(values, ", ") + "]", nil - } - return "", fmt.Errorf("unsupported value type %T: %v", v, v) -} - -func getTreeArrayLine(trees []*Tree) (line int) { - // Prevent returning 0 for empty trees - line = int(^uint(0) >> 1) - // get lowest line number >= 0 - for _, tv := range trees { - if tv.position.Line < line || line == 0 { - line = tv.position.Line - } - } - return -} - -func sortByLines(t *Tree) (vals []sortNode) { - var ( - line int - lines []int - tv *Tree - tom *tomlValue - node sortNode - ) - vals = make([]sortNode, 0) - m := make(map[int]sortNode) - - for k := range t.values { - v := t.values[k] - switch v.(type) { - case *Tree: - tv = v.(*Tree) - line = tv.position.Line - node = sortNode{key: k, complexity: valueComplex} - case []*Tree: - line = getTreeArrayLine(v.([]*Tree)) - node = sortNode{key: k, complexity: valueComplex} - default: - tom = v.(*tomlValue) - line = tom.position.Line - node = sortNode{key: k, complexity: valueSimple} - } - lines = append(lines, line) - vals = append(vals, node) - m[line] = node - } - sort.Ints(lines) - - for i, line := range lines { - vals[i] = m[line] - } - - return vals -} - -func sortAlphabetical(t *Tree) (vals []sortNode) { - var ( - node sortNode - simpVals []string - compVals []string - ) - vals = make([]sortNode, 0) - m := make(map[string]sortNode) - - for k := range t.values { - v := t.values[k] - switch v.(type) { - case *Tree, []*Tree: - node = sortNode{key: k, complexity: valueComplex} - compVals = append(compVals, node.key) - default: - node = sortNode{key: k, complexity: valueSimple} - simpVals = append(simpVals, node.key) - } - vals = append(vals, node) - m[node.key] = node - } - - // Simples first to match previous implementation - sort.Strings(simpVals) - i := 0 - for _, key := range simpVals { - vals[i] = m[key] - i++ - } - - sort.Strings(compVals) - for _, key := range compVals { - vals[i] = m[key] - i++ - } - - return vals -} - -func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) { - return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, " ", false, false) -} - -func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord MarshalOrder, indentString string, compactComments, parentCommented bool) (int64, error) { - var orderedVals []sortNode - - switch ord { - case OrderPreserve: - orderedVals = sortByLines(t) - default: - orderedVals = sortAlphabetical(t) - } - - for _, node := range orderedVals { - switch node.complexity { - case valueComplex: - k := node.key - v := t.values[k] - - combinedKey := quoteKeyIfNeeded(k) - if keyspace != "" { - combinedKey = keyspace + "." + combinedKey - } - - switch node := v.(type) { - // node has to be of those two types given how keys are sorted above - case *Tree: - tv, ok := t.values[k].(*Tree) - if !ok { - return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) - } - if tv.comment != "" { - comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1) - start := "# " - if strings.HasPrefix(comment, "#") { - start = "" - } - writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment) - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - - var commented string - if parentCommented || t.commented || tv.commented { - commented = "# " - } - writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, compactComments, parentCommented || t.commented || tv.commented) - if err != nil { - return bytesCount, err - } - case []*Tree: - for _, subTree := range node { - var commented string - if parentCommented || t.commented || subTree.commented { - commented = "# " - } - writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - - bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, compactComments, parentCommented || t.commented || subTree.commented) - if err != nil { - return bytesCount, err - } - } - } - default: // Simple - k := node.key - v, ok := t.values[k].(*tomlValue) - if !ok { - return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) - } - - var commented string - if parentCommented || t.commented || v.commented { - commented = "# " - } - repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine) - if err != nil { - return bytesCount, err - } - - if v.comment != "" { - comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1) - start := "# " - if strings.HasPrefix(comment, "#") { - start = "" - } - if !compactComments { - writtenBytesCountComment, errc := writeStrings(w, "\n") - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - writtenBytesCountComment, errc := writeStrings(w, indent, start, comment, "\n") - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - - quotedKey := quoteKeyIfNeeded(k) - writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - } - } - - return bytesCount, nil -} - -// quote a key if it does not fit the bare key format (A-Za-z0-9_-) -// quoted keys use the same rules as strings -func quoteKeyIfNeeded(k string) string { - // when encoding a map with the 'quoteMapKeys' option enabled, the tree will contain - // keys that have already been quoted. - // not an ideal situation, but good enough of a stop gap. - if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' { - return k - } - isBare := true - for _, r := range k { - if !isValidBareChar(r) { - isBare = false - break - } - } - if isBare { - return k - } - return quoteKey(k) -} - -func quoteKey(k string) string { - return "\"" + encodeTomlString(k) + "\"" -} - -func writeStrings(w io.Writer, s ...string) (int, error) { - var n int - for i := range s { - b, err := io.WriteString(w, s[i]) - n += b - if err != nil { - return n, err - } - } - return n, nil -} - -// WriteTo encode the Tree as Toml and writes it to the writer w. -// Returns the number of bytes written in case of success, or an error if anything happened. -func (t *Tree) WriteTo(w io.Writer) (int64, error) { - return t.writeTo(w, "", "", 0, false) -} - -// ToTomlString generates a human-readable representation of the current tree. -// Output spans multiple lines, and is suitable for ingest by a TOML parser. -// If the conversion cannot be performed, ToString returns a non-nil error. -func (t *Tree) ToTomlString() (string, error) { - b, err := t.Marshal() - if err != nil { - return "", err - } - return string(b), nil -} - -// String generates a human-readable representation of the current tree. -// Alias of ToString. Present to implement the fmt.Stringer interface. -func (t *Tree) String() string { - result, _ := t.ToTomlString() - return result -} - -// ToMap recursively generates a representation of the tree using Go built-in structures. -// The following types are used: -// -// * bool -// * float64 -// * int64 -// * string -// * uint64 -// * time.Time -// * map[string]interface{} (where interface{} is any of this list) -// * []interface{} (where interface{} is any of this list) -func (t *Tree) ToMap() map[string]interface{} { - result := map[string]interface{}{} - - for k, v := range t.values { - switch node := v.(type) { - case []*Tree: - var array []interface{} - for _, item := range node { - array = append(array, item.ToMap()) - } - result[k] = array - case *Tree: - result[k] = node.ToMap() - case *tomlValue: - result[k] = tomlValueToGo(node.value) - } - } - return result -} - -func tomlValueToGo(v interface{}) interface{} { - if tree, ok := v.(*Tree); ok { - return tree.ToMap() - } - - rv := reflect.ValueOf(v) - - if rv.Kind() != reflect.Slice { - return v - } - values := make([]interface{}, rv.Len()) - for i := 0; i < rv.Len(); i++ { - item := rv.Index(i).Interface() - values[i] = tomlValueToGo(item) - } - return values -} diff --git a/vendor/github.com/pelletier/go-toml/tomltree_writepub.go b/vendor/github.com/pelletier/go-toml/tomltree_writepub.go deleted file mode 100644 index fa32630..0000000 --- a/vendor/github.com/pelletier/go-toml/tomltree_writepub.go +++ /dev/null @@ -1,6 +0,0 @@ -package toml - -// ValueStringRepresentation transforms an interface{} value into its toml string representation. -func ValueStringRepresentation(v interface{}, commented string, indent string, ord MarshalOrder, arraysOneElementPerLine bool) (string, error) { - return tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine) -} diff --git a/vendor/github.com/pelletier/go-toml/.dockerignore b/vendor/github.com/pelletier/go-toml/v2/.dockerignore similarity index 100% rename from vendor/github.com/pelletier/go-toml/.dockerignore rename to vendor/github.com/pelletier/go-toml/v2/.dockerignore diff --git a/vendor/github.com/pelletier/go-toml/v2/.gitattributes b/vendor/github.com/pelletier/go-toml/v2/.gitattributes new file mode 100644 index 0000000..34a0a21 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +benchmark/benchmark.toml text eol=lf +testdata/** text eol=lf diff --git a/vendor/github.com/pelletier/go-toml/.gitignore b/vendor/github.com/pelletier/go-toml/v2/.gitignore similarity index 80% rename from vendor/github.com/pelletier/go-toml/.gitignore rename to vendor/github.com/pelletier/go-toml/v2/.gitignore index e6ba63a..eaf580d 100644 --- a/vendor/github.com/pelletier/go-toml/.gitignore +++ b/vendor/github.com/pelletier/go-toml/v2/.gitignore @@ -3,3 +3,6 @@ fuzz/ cmd/tomll/tomll cmd/tomljson/tomljson cmd/tomltestgen/tomltestgen +dist +tests/ +test-results diff --git a/vendor/github.com/pelletier/go-toml/v2/.golangci.toml b/vendor/github.com/pelletier/go-toml/v2/.golangci.toml new file mode 100644 index 0000000..7d2e5b0 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.golangci.toml @@ -0,0 +1,76 @@ +version = "2" + +[linters] +default = "none" +enable = [ + "asciicheck", + "bodyclose", + "dogsled", + "dupl", + "durationcheck", + "errcheck", + "errorlint", + "exhaustive", + "forbidigo", + "gochecknoinits", + "goconst", + "gocritic", + "godoclint", + "goheader", + "gomodguard", + "goprintffuncname", + "gosec", + "govet", + "importas", + "ineffassign", + "lll", + "makezero", + "mirror", + "misspell", + "nakedret", + "nilerr", + "noctx", + "nolintlint", + "perfsprint", + "prealloc", + "predeclared", + "revive", + "rowserrcheck", + "sqlclosecheck", + "staticcheck", + "thelper", + "tparallel", + "unconvert", + "unparam", + "unused", + "usetesting", + "wastedassign", + "whitespace", +] + +[linters.settings.exhaustive] +default-signifies-exhaustive = true + +[linters.settings.lll] +line-length = 150 + +[[linters.exclusions.rules]] +path = ".test.go" +linters = ["goconst", "gosec"] + +[[linters.exclusions.rules]] +path = "main.go" +linters = ["forbidigo"] + +[[linters.exclusions.rules]] +path = "internal" +linters = ["revive"] +text = "(exported|indent-error-flow): " + +[formatters] +enable = [ + "gci", + "gofmt", + "gofumpt", + "goimports", +] diff --git a/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml b/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml new file mode 100644 index 0000000..3208f72 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml @@ -0,0 +1,128 @@ +version: 2 +before: + hooks: + - go mod tidy + - go fmt ./... + - go test ./... +builds: + - id: tomll + main: ./cmd/tomll + binary: tomll + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} + mod_timestamp: '{{ .CommitTimestamp }}' + targets: + - linux_amd64 + - linux_arm64 + - linux_arm + - linux_riscv64 + - windows_amd64 + - windows_arm64 + - darwin_amd64 + - darwin_arm64 + - id: tomljson + main: ./cmd/tomljson + binary: tomljson + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} + mod_timestamp: '{{ .CommitTimestamp }}' + targets: + - linux_amd64 + - linux_arm64 + - linux_arm + - linux_riscv64 + - windows_amd64 + - windows_arm64 + - darwin_amd64 + - darwin_arm64 + - id: jsontoml + main: ./cmd/jsontoml + binary: jsontoml + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} + mod_timestamp: '{{ .CommitTimestamp }}' + targets: + - linux_amd64 + - linux_arm64 + - linux_riscv64 + - linux_arm + - windows_amd64 + - windows_arm64 + - darwin_amd64 + - darwin_arm64 +universal_binaries: + - id: tomll + replace: true + name_template: tomll + - id: tomljson + replace: true + name_template: tomljson + - id: jsontoml + replace: true + name_template: jsontoml +archives: +- id: jsontoml + formats: + - tar.xz + ids: + - jsontoml + files: + - none* + name_template: "{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}" +- id: tomljson + formats: + - tar.xz + ids: + - tomljson + files: + - none* + name_template: "{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}" +- id: tomll + formats: + - tar.xz + ids: + - tomll + files: + - none* + name_template: "{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}" +dockers_v2: + - id: tools + ids: + - jsontoml + - tomljson + - tomll + images: + - "ghcr.io/pelletier/go-toml" + tags: + - "latest" + - "{{ .Tag }}" + - "v{{ .Major }}" + platforms: + - linux/amd64 +checksum: + name_template: 'sha256sums.txt' +snapshot: + version_template: "{{ incpatch .Version }}-next" +release: + github: + owner: pelletier + name: go-toml + draft: true + prerelease: auto + mode: replace +changelog: + use: github-native +announce: + skip: true diff --git a/vendor/github.com/pelletier/go-toml/v2/AGENTS.md b/vendor/github.com/pelletier/go-toml/v2/AGENTS.md new file mode 100644 index 0000000..f495afd --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/AGENTS.md @@ -0,0 +1,73 @@ +# Agent Guidelines for go-toml + +This file provides guidelines for AI agents contributing to go-toml. All agents must follow these rules derived from [CONTRIBUTING.md](./CONTRIBUTING.md). + +## Project Overview + +go-toml is a TOML library for Go. The goal is to provide an easy-to-use and efficient TOML implementation that gets the job done without getting in the way. + +## Code Change Rules + +### Backward Compatibility + +- **No backward-incompatible changes** unless explicitly discussed and approved +- Avoid breaking people's programs unless absolutely necessary + +### Testing Requirements + +- **All bug fixes must include regression tests** +- **All new code must be tested** +- Run tests before submitting: `go test -race ./...` +- Test coverage must not decrease. Check with: + ```bash + go test -covermode=atomic -coverprofile=coverage.out + go tool cover -func=coverage.out + ``` +- All lines of code touched by changes should be covered by tests + +### Performance Requirements + +- go-toml aims to stay efficient; avoid performance regressions +- Run benchmarks to verify: `go test ./... -bench=. -count=10` +- Compare results using [benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat) + +### Documentation + +- New features or feature extensions must include documentation +- Documentation lives in [README.md](./README.md) and throughout source code + +### Code Style + +- Follow existing code format and structure +- Code must pass `go fmt` +- Code must pass linting with the same golangci-lint version as CI (see version in `.github/workflows/lint.yml`): + ```bash + # Install specific version (check lint.yml for current version) + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin + # Run linter + golangci-lint run ./... + ``` + +### Commit Messages + +- Commit messages must explain **why** the change is needed +- Keep messages clear and informative even if details are in the PR description + +### Capabilities + +go-toml tracks system-level capabilities using [capslock](https://github.com/google/capslock). The baseline is in `capability_baseline.txt` and CI enforces that it does not grow. + +- **Do not introduce new capabilities.** PRs that increase the capability set (e.g., adding network access, subprocess execution, syscalls) are unlikely to be accepted. +- If a change causes the capabilities check to fail, do not update the baseline to make it pass. Instead, rethink the approach to avoid requiring new capabilities. +- To check locally: `./caps.sh check` (requires `capslock` installed via `go install github.com/google/capslock/cmd/capslock@latest`) + +## Pull Request Checklist + +Before submitting: + +1. Tests pass (`go test -race ./...`) +2. No backward-incompatible changes (unless discussed) +3. Relevant documentation added/updated +4. No performance regression (verify with benchmarks) +5. Capabilities are not increasing (`./caps.sh check`) +6. Title is clear and understandable for changelog diff --git a/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md b/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md new file mode 100644 index 0000000..65a3ff4 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md @@ -0,0 +1,254 @@ +# Contributing + +Thank you for your interest in go-toml! We appreciate you considering +contributing to go-toml! + +The main goal is the project is to provide an easy-to-use and efficient TOML +implementation for Go that gets the job done and gets out of your way – dealing +with TOML is probably not the central piece of your project. + +As the single maintainer of go-toml, time is scarce. All help, big or small, is +more than welcomed! + +## Ask questions + +Any question you may have, somebody else might have it too. Always feel free to +ask them on the [discussion board][discussions]. We will try to answer them as +clearly and quickly as possible, time permitting. + +Asking questions also helps us identify areas where the documentation needs +improvement, or new features that weren't envisioned before. Sometimes, a +seemingly innocent question leads to the fix of a bug. Don't hesitate and ask +away! + +[discussions]: https://github.com/pelletier/go-toml/discussions + +## Improve the documentation + +The best way to share your knowledge and experience with go-toml is to improve +the documentation. Fix a typo, clarify an interface, add an example, anything +goes! + +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 +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. + +## Report a bug + +Found a bug! Sorry to hear that :(. Help us and other track them down and fix by +reporting it. [File a new bug report][bug-report] on the [issues +tracker][issues-tracker]. The template should provide enough guidance on what to +include. When in doubt: add more details! By reducing ambiguity and providing +more information, it decreases back and forth and saves everyone time. + +## Code changes + +Want to contribute a patch? Very happy to hear that! + +First, some high-level rules: + +- A short proposal with some POC code is better than a lengthy piece of text + with no code. Code speaks louder than words. That being said, bigger changes + should probably start with a [discussion][discussions]. +- No backward-incompatible patch will be accepted unless discussed. Sometimes + it's hard, but we try not to break people's programs unless we absolutely have + to. +- If you are writing a new feature or extending an existing one, make sure to + write some documentation. +- Bug fixes need to be accompanied with regression tests. +- New code needs to be tested. +- Your commit messages need to explain why the change is needed, even if already + included in the PR description. + +It does sound like a lot, but those best practices are here to save time overall +and continuously improve the quality of the project, which is something everyone +benefits from. + +### Get started + +The fairly standard code contribution process looks like that: + +1. [Fork the project][fork]. +2. Make your changes, commit on any branch you like. +3. [Open up a pull request][pull-request] +4. Review, potential ask for changes. +5. Merge. + +Feel free to ask for help! You can create draft pull requests to gather +some early feedback! + +### Run the tests + +You can run tests for go-toml using Go's test tool: `go test -race ./...`. + +During the pull request process, all tests will be ran on Linux, Windows, and +MacOS on the last two versions of Go. + +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 +run and display code coverage, but at the end of the day, we do this: + +``` +go test -covermode=atomic -coverprofile=coverage.out +go tool cover -func=coverage.out +``` + +and verify that the overall percentage of tested code does not go down. This is +a requirement. As a rule of thumb, all lines of code touched by your changes +should be covered. On Unix you can use `./ci.sh coverage -d v2` to check if your +code lowers the coverage. + +### Verify performance + +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 +responsible for checking that your changes do not incur a performance penalty. +You can run their following to execute benchmarks: + +``` +go test ./... -bench=. -count=10 +``` + +Benchmark results should be compared against each other with +[benchstat][benchstat]. Typical flow looks like this: + +1. On the `v2` branch, run `go test ./... -bench=. -count 10` and save output to + a file (for example `old.txt`). +2. Make some code changes. +3. Run `go test ....` again, and save the output to an other file (for example + `new.txt`). +4. Run `benchstat old.txt new.txt` to check that time/op does not go up in any + test. + +On Unix you can use `./ci.sh benchmark -d v2` to verify how your code impacts +performance. + +It is highly encouraged to add the benchstat results to your pull request +description. Pull requests that lower performance will receive more scrutiny. + +[benchstat]: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat + +### Capabilities + +We use [capslock](https://github.com/google/capslock) to track what +system-level capabilities (file access, network, syscalls, etc.) each package +requires. The current baseline is in `capability_baseline.txt`. CI will fail if +a change introduces a new capability. + +**Pull requests that increase the set of capabilities are unlikely to be +accepted.** go-toml is a parsing library and should not need network access, +subprocess execution, or other capabilities beyond what it already uses. + +If you believe a new capability is genuinely needed, discuss it in an issue +first. To update the baseline after approval: + +```bash +go install github.com/google/capslock/cmd/capslock@latest +./caps.sh generate +``` + +### Style + +Try to look around and follow the same format and structure as the rest of the +code. We enforce using `go fmt` on the whole code base. + +--- + +## Maintainers-only + +### Merge pull request + +Checklist: + +- Passing CI. +- Does not introduce backward-incompatible changes (unless discussed). +- Has relevant doc changes. +- Benchstat does not show performance regression. +- Pull request is [labeled appropriately][pr-labels]. +- Title will be understandable in the changelog. + +1. Merge using "squash and merge". +2. Make sure to edit the commit message to keep all the useful information + nice and clean. +3. Make sure the commit title is clear and contains the PR number (#123). + +### New release + +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 +[pkg.go.dev]: https://pkg.go.dev/github.com/pelletier/go-toml +[readme]: ./README.md +[fork]: https://help.github.com/articles/fork-a-repo +[pull-request]: https://help.github.com/en/articles/creating-a-pull-request +[new-release]: https://github.com/pelletier/go-toml/releases/new +[gh]: https://github.com/cli/cli +[pr-labels]: https://github.com/pelletier/go-toml/blob/v2/.github/release.yml diff --git a/vendor/github.com/pelletier/go-toml/v2/Dockerfile b/vendor/github.com/pelletier/go-toml/v2/Dockerfile new file mode 100644 index 0000000..ebd9bab --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/Dockerfile @@ -0,0 +1,6 @@ +FROM scratch +ENV PATH "$PATH:/bin" +ARG TARGETPLATFORM +COPY $TARGETPLATFORM/tomll /bin/tomll +COPY $TARGETPLATFORM/tomljson /bin/tomljson +COPY $TARGETPLATFORM/jsontoml /bin/jsontoml diff --git a/vendor/github.com/pelletier/go-toml/v2/LICENSE b/vendor/github.com/pelletier/go-toml/v2/LICENSE new file mode 100644 index 0000000..991e2ae --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pelletier/go-toml/v2/README.md b/vendor/github.com/pelletier/go-toml/v2/README.md new file mode 100644 index 0000000..067ba3f --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/README.md @@ -0,0 +1,327 @@ +# go-toml v2 + +Go library for the [TOML](https://toml.io/en/) format. + +This library supports [TOML v1.1.0](https://toml.io/en/v1.1.0). + +[🐞 Bug Reports](https://github.com/pelletier/go-toml/issues) + +[đŸ’Ŧ Anything else](https://github.com/pelletier/go-toml/discussions) + +## Documentation + +Full API, examples, and implementation notes are available in the Go +documentation. + +[![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml/v2.svg)](https://pkg.go.dev/github.com/pelletier/go-toml/v2) + +## Import + +```go +import "github.com/pelletier/go-toml/v2" +``` + +## Features + +### Stdlib behavior + +As much as possible, this library is designed to behave similarly as the +standard library's `encoding/json`. + +When encoding structs, fields tagged with `omitempty` are omitted if they are +empty. For `time.Time`, the zero value is considered empty, so timestamps such +as `created_at` or `updated_at` are not written unless you remove `omitempty` +from the struct tag or use a pointer type (`*time.Time`). + +### Performance + +While go-toml favors usability, it is written with performance in mind. Most +operations should not be shockingly slow. See [benchmarks](#benchmarks). + +### Strict mode + +`Decoder` can be set to "strict mode", which makes it error when some parts of +the TOML document was not present in the target structure. This is a great way +to check for typos. [See example in the documentation][strict]. + +[strict]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Decoder.DisallowUnknownFields + +### Contextualized errors + +When most decoding errors occur, go-toml returns [`DecodeError`][decode-err], +which contains a human readable contextualized version of the error. For +example: + +``` +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 + +### Local date and time support + +TOML supports native [local date/times][ldt]. It allows to represent a given +date, time, or date-time without relation to a timezone or offset. To support +this use-case, go-toml provides [`LocalDate`][tld], [`LocalTime`][tlt], and +[`LocalDateTime`][tldt]. Those types can be transformed to and from `time.Time`, +making them convenient yet unambiguous structures for their respective TOML +representation. + +[ldt]: https://toml.io/en/v1.1.0#local-date-time +[tld]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDate +[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: + +```go +type MyConfig struct { + Version int + Name string + Tags []string +} +``` + +### Unmarshaling + +[`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its +content. + +Note that the struct variable names are _capitalized_, while the variables in the toml document are _lowercase_. + +For example: + +```go +doc := ` +version = 2 +name = "go-toml" +tags = ["go", "toml"] +` + +var cfg MyConfig +err := toml.Unmarshal([]byte(doc), &cfg) +if err != nil { + panic(err) +} +fmt.Println("version:", cfg.Version) +fmt.Println("name:", cfg.Name) +fmt.Println("tags:", cfg.Tags) + +// Output: +// version: 2 +// name: go-toml +// tags: [go toml] +``` + +[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 +as a TOML document: + +```go +cfg := MyConfig{ + Version: 2, + Name: "go-toml", + Tags: []string{"go", "toml"}, +} + +b, err := toml.Marshal(cfg) +if err != nil { + panic(err) +} +fmt.Println(string(b)) + +// Output: +// Version = 2 +// Name = 'go-toml' +// Tags = ['go', 'toml'] +``` + +[marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal + +## Unstable API + +This API does not yet follow the backward compatibility guarantees of this +library. They provide early access to features that may have rough edges or an +API subject to change. + +### Parser + +Parser is the unstable API that allows iterative parsing of a TOML document at +the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable. + +## Benchmarks + +Execution time speedup compared to other Go TOML libraries: + + + + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
Marshal/HugoFrontMatter-22.3x2.4x
Marshal/ReferenceFile/map-22.2x2.6x
Marshal/ReferenceFile/struct-24.9x5.0x
Unmarshal/HugoFrontMatter-27.8x5.9x
Unmarshal/ReferenceFile/map-26.8x6.4x
Unmarshal/ReferenceFile/struct-26.8x6.3x
+
See more +

The table above has the results of the most common use-cases. The table below +contains the results of all benchmarks, including unrealistic ones. It is +provided for completeness.

+ + + + + + + + + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
Marshal/SimpleDocument/map-22.1x3.1x
Marshal/SimpleDocument/struct-23.4x4.8x
Unmarshal/SimpleDocument/map-210.1x7.0x
Unmarshal/SimpleDocument/struct-212.4x8.0x
UnmarshalDataset/example-28.2x6.9x
UnmarshalDataset/code-27.5x8.3x
UnmarshalDataset/twitter-29.0x7.6x
UnmarshalDataset/citm_catalog-25.0x4.5x
UnmarshalDataset/canada-26.4x4.7x
UnmarshalDataset/config-210.2x6.1x
geomean5.8x5.3x
+

This table can be generated with ./ci.sh benchmark -a -html.

+
+ +## Tools + +Go-toml provides three handy command line tools: + + * `tomljson`: Reads a TOML file and outputs its JSON representation. + + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest + $ tomljson --help + ``` + + * `jsontoml`: Reads a JSON file and outputs a TOML representation. + + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest + $ jsontoml --help + ``` + + * `tomll`: Lints and reformats a TOML file. + + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest + $ tomll --help + ``` + +### Docker image + +Those tools are also available as a [Docker image][docker]. For example, to use +`tomljson`: + +``` +docker run -i ghcr.io/pelletier/go-toml:v2 tomljson < example.toml +``` + +Multiple versions are available on [ghcr.io][docker]. + +[docker]: https://github.com/pelletier/go-toml/pkgs/container/go-toml + +## Versioning + +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 + +The MIT License (MIT). Read [LICENSE](LICENSE). diff --git a/vendor/github.com/pelletier/go-toml/SECURITY.md b/vendor/github.com/pelletier/go-toml/v2/SECURITY.md similarity index 81% rename from vendor/github.com/pelletier/go-toml/SECURITY.md rename to vendor/github.com/pelletier/go-toml/v2/SECURITY.md index b2f21cf..d4d554f 100644 --- a/vendor/github.com/pelletier/go-toml/SECURITY.md +++ b/vendor/github.com/pelletier/go-toml/v2/SECURITY.md @@ -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: | diff --git a/vendor/github.com/pelletier/go-toml/v2/capability_baseline.txt b/vendor/github.com/pelletier/go-toml/v2/capability_baseline.txt new file mode 100644 index 0000000..c556456 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/capability_baseline.txt @@ -0,0 +1 @@ +github.com/pelletier/go-toml/v2: CAPABILITY_REFLECT, CAPABILITY_UNANALYZED, CAPABILITY_UNSAFE_POINTER diff --git a/vendor/github.com/pelletier/go-toml/v2/caps.sh b/vendor/github.com/pelletier/go-toml/v2/caps.sh new file mode 100644 index 0000000..31fec2b --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/caps.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# +# Generates or checks the capability baseline for go-toml. +# +# Usage: +# ./caps.sh generate # regenerate capability_baseline.txt +# ./caps.sh check # check that capabilities haven't grown +# +# Requires: go, capslock (go install github.com/google/capslock/cmd/capslock@latest) + +set -euo pipefail + +BASELINE="capability_baseline.txt" +CAPSLOCK="${CAPSLOCK:-capslock}" + +# Capabilities that must never appear in any package. +FORBIDDEN_CAPS=( + CAPABILITY_NETWORK + CAPABILITY_CGO + CAPABILITY_EXEC +) + +capslock_to_baseline() { + "$CAPSLOCK" -packages=. -output=package -granularity=package \ + | jq -r 'to_entries | sort_by(.key) | .[] | .key + ": " + (.value | sort | join(", "))' +} + +generate() { + capslock_to_baseline > "$BASELINE" + echo "Wrote $BASELINE" +} + +check() { + if [ ! -f "$BASELINE" ]; then + echo "ERROR: $BASELINE not found. Run '$0 generate' first." + exit 1 + fi + + current=$(mktemp) + trap 'rm -f "$current"' EXIT + + capslock_to_baseline > "$current" + + failed=0 + + # Check for forbidden capabilities in current output. + for cap in "${FORBIDDEN_CAPS[@]}"; do + if grep -q "$cap" "$current"; then + echo "FORBIDDEN capability found: $cap" + grep "$cap" "$current" + failed=1 + fi + done + + # Extract all unique capability names from baseline and current. + baseline_caps=$(grep -oE 'CAPABILITY_[A-Z_]+' "$BASELINE" | sort -u) + current_caps=$(grep -oE 'CAPABILITY_[A-Z_]+' "$current" | sort -u) + + # Check for new capability names not in the baseline. + new_caps=$(comm -13 <(echo "$baseline_caps") <(echo "$current_caps")) + if [ -n "$new_caps" ]; then + echo "NEW capabilities detected (not in baseline):" + echo "$new_caps" + failed=1 + fi + + # Check for new per-package capabilities (a package gained a capability it didn't have before). + while IFS=': ' read -r pkg caps; do + baseline_pkg_caps=$(grep "^${pkg}:" "$BASELINE" 2>/dev/null | sed 's/^[^:]*: //' || true) + if [ -z "$baseline_pkg_caps" ]; then + echo "NEW package with capabilities: $pkg: $caps" + failed=1 + continue + fi + # Check each capability in current for this package + for cap in $(echo "$caps" | tr ', ' '\n' | grep -v '^$'); do + if ! echo "$baseline_pkg_caps" | grep -q "$cap"; then + echo "NEW capability for $pkg: $cap" + failed=1 + fi + done + done < "$current" + + if [ "$failed" -eq 1 ]; then + echo "" + echo "FAILED: capabilities have grown." + echo "If this is intentional, run '$0 generate' and commit the updated $BASELINE." + exit 1 + fi + + echo "OK: no new capabilities detected." +} + +case "${1:-}" in + generate) generate ;; + check) check ;; + *) + echo "Usage: $0 {generate|check}" + exit 1 + ;; +esac diff --git a/vendor/github.com/pelletier/go-toml/v2/ci.sh b/vendor/github.com/pelletier/go-toml/v2/ci.sh new file mode 100644 index 0000000..6b9723b --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/ci.sh @@ -0,0 +1,293 @@ +#!/usr/bin/env bash + + +stderr() { + echo "$@" 1>&2 +} + +usage() { + b=$(basename "$0") + echo $b: ERROR: "$@" 1>&2 + + cat 1>&2 < coverage.out + go tool cover -func=coverage.out + echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2 + popd + + if [ "${branch}" != "HEAD" ]; then + git worktree remove --force "$dir" + fi +} + +coverage() { + case "$1" in + -d) + shift + target="${1?Need to provide a target branch argument}" + + output_dir="$(mktemp -d)" + target_out="${output_dir}/target.txt" + head_out="${output_dir}/head.txt" + + cover "${target}" > "${target_out}" + cover "HEAD" > "${head_out}" + + cat "${target_out}" + cat "${head_out}" + + echo "" + + target_pct="$(tail -n2 ${target_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%.*/\1/')" + head_pct="$(tail -n2 ${head_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%/\1/')" + echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%" + + delta_pct=$(echo "$head_pct - $target_pct" | bc -l) + echo "Delta: ${delta_pct}" + + if [[ $delta_pct = \-* ]]; then + echo "Regression!"; + + target_diff="${output_dir}/target.diff.txt" + head_diff="${output_dir}/head.diff.txt" + cat "${target_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${target_diff}" + cat "${head_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${head_diff}" + + diff --side-by-side --suppress-common-lines "${target_diff}" "${head_diff}" + return 1 + fi + return 0 + ;; + esac + + cover "${1-HEAD}" +} + +bench() { + branch="${1}" + out="${2}" + replace="${3}" + dir="$(mktemp -d)" + + stderr "Executing benchmark for ${branch} at ${dir}" + + if [ "${branch}" = "HEAD" ]; then + cp -r . "${dir}/" + else + git worktree add "$dir" "$branch" + fi + + pushd "$dir" + + tags="" + if [ "${replace}" != "" ]; then + find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2\"|${replace}\"|g" {} \; + go get "${replace}" + # The realworld benchmarks use v2-only API and cannot compile against + # the other libraries; exclude them from cross-library comparisons. + tags="-tags cross_library_benchmark" + fi + + export GOMAXPROCS=2 + go test ${tags} '-bench=^Benchmark(Un)?[mM]arshal' -count=10 -run=Nothing ./... | tee "${out}" + popd + + if [ "${branch}" != "HEAD" ]; then + git worktree remove --force "$dir" + fi +} + +fmktemp() { + if mktemp --version &> /dev/null; then + # GNU + mktemp --suffix=-$1 + else + # BSD + mktemp -t $1 + fi +} + +benchstathtml() { +python3 - $1 <<'EOF' +import sys + +lines = [] +stop = False + +with open(sys.argv[1]) as f: + for line in f.readlines(): + line = line.strip() + if line == "": + stop = True + if not stop: + lines.append(line.split(',')) + +results = [] +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[7])/v2), # bs + ]) + +if not results: + print("No benchmark results to display.", file=sys.stderr) + sys.exit(1) + +# move geomean to the end +results.append(results[0]) +del results[0] + + +def printtable(data): + print(""" + + + + + """) + + for r in data: + print(" ".format(*r)) + + print(""" +
Benchmarkgo-toml v1BurntSushi/toml
{}{}{}
""") + + +def match(x): + return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0] + +above = [x for x in results if match(x)] +below = [x for x in results if not match(x)] + +printtable(above) +print("
See more") +print("""

The table above has the results of the most common use-cases. The table below +contains the results of all benchmarks, including unrealistic ones. It is +provided for completeness.

""") +printtable(below) +print('

This table can be generated with ./ci.sh benchmark -a -html.

') +print("
") + +EOF +} + +benchmark() { + case "$1" in + -d) + shift + target="${1?Need to provide a target branch argument}" + + old=`fmktemp ${target}` + bench "${target}" "${old}" + + new=`fmktemp HEAD` + bench HEAD "${new}" + + benchstat "${old}" "${new}" + return 0 + ;; + -a) + shift + + v2stats=`fmktemp go-toml-v2` + bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2" + v1stats=`fmktemp go-toml-v1` + bench HEAD "${v1stats}" "github.com/pelletier/go-toml" + bsstats=`fmktemp bs-toml` + bench HEAD "${bsstats}" "github.com/BurntSushi/toml" + + cp "${v2stats}" go-toml-v2.txt + cp "${v1stats}" go-toml-v1.txt + cp "${bsstats}" bs-toml.txt + + if [ "$1" = "-html" ]; then + tmpcsv=`fmktemp csv` + benchstat -format csv go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv + benchstathtml $tmpcsv + else + 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 + return $? + esac + + bench "${1-HEAD}" `mktemp` +} + +case "$1" in + coverage) shift; coverage $@;; + benchmark) shift; benchmark $@;; + *) usage "bad argument $1";; +esac diff --git a/vendor/github.com/pelletier/go-toml/v2/decode.go b/vendor/github.com/pelletier/go-toml/v2/decode.go new file mode 100644 index 0000000..46de2fb --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/decode.go @@ -0,0 +1,538 @@ +package toml + +import ( + "bytes" + "fmt" + "math" + "strconv" + "time" + + "github.com/pelletier/go-toml/v2/unstable" +) + +func parseInteger(b []byte) (int64, error) { + if len(b) > 2 && b[0] == '0' { + switch b[1] { + case 'x': + return parseIntHex(b) + case 'b': + return parseIntBin(b) + case 'o': + return parseIntOct(b) + default: + panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1])) + } + } + return parseIntDec(b) +} + +func parseIntHex(b []byte) (int64, error) { + var v uint64 + for _, c := range b[2:] { + if c == '_' { + continue + } + var d byte + switch { + case c >= '0' && c <= '9': + d = c - '0' + case c >= 'a' && c <= 'f': + d = c - 'a' + 10 + case c >= 'A' && c <= 'F': + d = c - 'A' + 10 + } + if v > math.MaxInt64>>4 { + return 0, unstable.NewParserError(b, "hexadecimal number is too large to fit in a 64-bit signed integer") + } + v = v<<4 | uint64(d) + } + return int64(v), nil +} + +func parseIntOct(b []byte) (int64, error) { + var v uint64 + for _, c := range b[2:] { + if c == '_' { + continue + } + if v > math.MaxInt64>>3 { + return 0, unstable.NewParserError(b, "octal number is too large to fit in a 64-bit signed integer") + } + v = v<<3 | uint64(c-'0') + } + return int64(v), nil +} + +func parseIntBin(b []byte) (int64, error) { + var v uint64 + for _, c := range b[2:] { + if c == '_' { + continue + } + if v > math.MaxInt64>>1 { + return 0, unstable.NewParserError(b, "binary number is too large to fit in a 64-bit signed integer") + } + v = v<<1 | uint64(c-'0') + } + return int64(v), nil +} + +func parseIntDec(b []byte) (int64, error) { + i := 0 + neg := false + switch b[0] { + case '-': + neg = true + i++ + case '+': + i++ + } + + var limit uint64 = math.MaxInt64 + if neg { + limit = math.MaxInt64 + 1 + } + + var v uint64 + for ; i < len(b); i++ { + c := b[i] + if c == '_' { + continue + } + if v > limit/10 { + return 0, unstable.NewParserError(b, "decimal number is too large to fit in a 64-bit signed integer") + } + v = v*10 + uint64(c-'0') + if v > limit { + return 0, unstable.NewParserError(b, "decimal number is too large to fit in a 64-bit signed integer") + } + } + if neg { + return -int64(v), nil //nolint:gosec // v <= MaxInt64+1, the conversion wraps to the intended negative value + } + return int64(v), nil //nolint:gosec // v <= MaxInt64 +} + +func parseFloat(b []byte) (float64, error) { + i := 0 + if len(b) > 0 && (b[0] == '+' || b[0] == '-') { + i = 1 + } + if len(b) == i+3 { + switch b[i] { + case 'i': + // inf + if b[0] == '-' { + return math.Inf(-1), nil + } + return math.Inf(1), nil + case 'n': + // nan + return math.NaN(), nil + } + } + + // Fast path: a plain decimal whose significand fits in 53 bits and whose + // base-10 exponent is within [-22, 22] is parsed exactly with a single + // rounding (Clinger's method) straight from the bytes, with no string + // allocation and no full strconv parse. This is the common shape for + // numeric data (e.g. coordinate lists). Anything outside those bounds, or + // with underscores, falls through to strconv, which is the reference. + if f, ok := fastParseFloat(b); ok { + return f, nil + } + + // strconv.ParseFloat is the reference implementation for parsing + // floating point numbers. The position of underscores has already been + // validated by the parser; strip them so that they do not interfere with + // Go's own underscore rules. + cleaned := b + if bytes.IndexByte(b, '_') >= 0 { + cleaned = make([]byte, 0, len(b)) + for _, c := range b { + if c != '_' { + cleaned = append(cleaned, c) + } + } + } + + f, err := strconv.ParseFloat(string(cleaned), 64) + if err != nil { + return 0, unstable.NewParserError(b, "unable to parse float: %s", err) + } + return f, nil +} + +// float64pow10 holds the powers of ten that are exactly representable as a +// float64 (10^0 .. 10^22). +var float64pow10 = [...]float64{ + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, +} + +// fastParseFloat parses b as a float64 using Clinger's exact method and reports +// whether it applied. It accepts only plain decimal numbers (optional sign, +// digits, one optional '.', optional 'e'/'E' exponent) whose significand fits +// in 53 bits and whose effective base-10 exponent is within [-22, 22]; under +// those conditions float64(significand) * 10^exp (or / 10^-exp) is the exact, +// correctly-rounded result, identical to strconv.ParseFloat. It returns +// ok=false (deferring to strconv) for underscores, hexadecimal floats, large +// significands or exponents, and any other shape. +func fastParseFloat(b []byte) (float64, bool) { + i := 0 + neg := false + if i < len(b) && (b[i] == '+' || b[i] == '-') { + neg = b[i] == '-' + i++ + } + + var mantissa uint64 + digits := 0 + fracDigits := 0 + sawDot := false + sawDigit := false + for ; i < len(b); i++ { + c := b[i] + switch { + case c >= '0' && c <= '9': + if digits >= 19 { + // Too many significant digits to accumulate without risking a + // uint64 overflow (and well past the 53-bit exact range). + return 0, false + } + mantissa = mantissa*10 + uint64(c-'0') + digits++ + if sawDot { + fracDigits++ + } + sawDigit = true + case c == '.': + if sawDot { + return 0, false + } + sawDot = true + default: + goto exponent + } + } +exponent: + if !sawDigit { + return 0, false + } + exp := -fracDigits + if i < len(b) && (b[i] == 'e' || b[i] == 'E') { + i++ + esign := 1 + if i < len(b) && (b[i] == '+' || b[i] == '-') { + if b[i] == '-' { + esign = -1 + } + i++ + } + if i >= len(b) { + return 0, false + } + eval := 0 + for ; i < len(b); i++ { + c := b[i] + if c < '0' || c > '9' { + return 0, false + } + eval = eval*10 + int(c-'0') + if eval > 1000 { + return 0, false + } + } + exp += esign * eval + } + if i != len(b) { + // Trailing bytes (an underscore, a hexadecimal marker, ...). + return 0, false + } + if mantissa > 1<<53 { + return 0, false + } + + f := float64(mantissa) + switch { + case exp == 0: + case exp > 0 && exp <= 22: + f *= float64pow10[exp] + case exp < 0 && exp >= -22: + f /= float64pow10[-exp] + default: + return 0, false + } + if neg { + f = -f + } + return f, true +} + +func isDecimalDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +// parseLocalDate parses a date of the exact form YYYY-MM-DD and validates +// its components. +func parseLocalDate(b []byte) (LocalDate, error) { + var date LocalDate + + if len(b) != 10 || b[4] != '-' || b[7] != '-' { + return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD") + } + + var err error + date.Year, err = parseDecimalDigits(b[0:4]) + if err != nil { + return date, err + } + date.Month, err = parseDecimalDigits(b[5:7]) + if err != nil { + return date, err + } + date.Day, err = parseDecimalDigits(b[8:10]) + if err != nil { + return date, err + } + + if date.Month < 1 || date.Month > 12 { + return date, unstable.NewParserError(b[5:7], "impossible date") + } + maxDay := daysIn(date.Month, date.Year) + if date.Day < 1 || date.Day > maxDay { + return date, unstable.NewParserError(b[8:10], "impossible date") + } + + return date, nil +} + +func daysIn(month int, year int) int { + switch month { + case 2: + if isLeapYear(year) { + return 29 + } + return 28 + case 4, 6, 9, 11: + return 30 + default: + return 31 + } +} + +func isLeapYear(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +// parseDecimalDigits parses a sequence of digits as a decimal number. +func parseDecimalDigits(b []byte) (int, error) { + v := 0 + for i, c := range b { + if !isDecimalDigit(c) { + return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)") + } + v = v*10 + int(c-'0') + } + return v, nil +} + +// parseLocalTime parses a time of the form HH:MM with optional seconds and an +// optional fractional part (TOML v1.1.0). It returns the remaining bytes after +// the time. +func parseLocalTime(b []byte) (LocalTime, []byte, error) { + var ( + nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} + t LocalTime + ) + + // check if b matches to have expected format HH:MM[:SS[.NNNNNN]] + const localTimeByteMinLen = 5 + if len(b) < localTimeByteMinLen { + return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM[:SS[.NNNNNN]]") + } + + var err error + t.Hour, err = parseDecimalDigits(b[0:2]) + if err != nil { + return t, nil, err + } + if t.Hour > 23 { + return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23") + } + if b[2] != ':' { + return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes") + } + + t.Minute, err = parseDecimalDigits(b[3:5]) + if err != nil { + return t, nil, err + } + if t.Minute > 59 { + return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59") + } + + b = b[5:] + + // Seconds are optional (TOML v1.1.0). Fractional seconds may only appear + // when seconds are present: + // partial-time = time-hour ":" time-minute [ ":" time-second [ time-secfrac ] ] + secondsPresent := false + + if len(b) >= 1 && b[0] == ':' { + if len(b) < 3 { + return t, nil, unstable.NewParserError(b, "incomplete seconds") + } + + t.Second, err = parseDecimalDigits(b[1:3]) + if err != nil { + return t, nil, err + } + + if t.Second > 59 { + return t, nil, unstable.NewParserError(b[1:3], "seconds cannot be greater than 59") + } + + b = b[3:] + secondsPresent = true + } + + if secondsPresent && len(b) >= 1 && b[0] == '.' { + frac := 0 + precision := 0 + digits := 0 + + for i, c := range b[1:] { + if !isDecimalDigit(c) { + if i == 0 { + return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point") + } + break + } + digits++ + if i < 9 { + frac = frac*10 + int(c-'0') + precision++ + } + } + + if digits == 0 { + return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point") + } + + t.Nanosecond = frac * nspow[precision] + t.Precision = precision + + return t, b[1+digits:], nil + } + return t, b, nil +} + +// parseLocalDateTime parses a local date time of the form +// YYYY-MM-DD(T| )HH:MM:SS[.NNNNNN]. It returns the remaining bytes after the +// date-time. +func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { + var dt LocalDateTime + + const localDateTimeByteMinLen = 11 + if len(b) < localDateTimeByteMinLen { + return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM[:SS[.NNNNNNNNN]]") + } + + date, err := parseLocalDate(b[:10]) + if err != nil { + return dt, nil, err + } + dt.LocalDate = date + + sep := b[10] + if sep != 'T' && sep != ' ' && sep != 't' { + return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space") + } + + t, rest, err := parseLocalTime(b[11:]) + if err != nil { + return dt, nil, err + } + dt.LocalTime = t + + return dt, rest, nil +} + +// parseDateTime parses a date-time with a timezone offset (Z or +/-HH:MM). +func parseDateTime(b []byte) (time.Time, error) { + dt, b, err := parseLocalDateTime(b) + if err != nil { + return time.Time{}, err + } + + var zone *time.Location + + if len(b) == 0 { + // parser should have checked that there is a timezone + return time.Time{}, unstable.NewParserError(b, "date-time is missing timezone") + } + + if b[0] == 'Z' || b[0] == 'z' { + b = b[1:] + zone = time.UTC + } else { + const dateTimeByteLen = 6 + if len(b) != dateTimeByteLen { + return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone") + } + var direction int + switch b[0] { + case '-': + direction = -1 + case '+': + direction = +1 + default: + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character") + } + + if b[3] != ':' { + return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator") + } + + hours, err := parseDecimalDigits(b[1:3]) + if err != nil { + return time.Time{}, err + } + if hours > 23 { + return time.Time{}, unstable.NewParserError(b[1:3], "invalid timezone offset hours") + } + + minutes, err := parseDecimalDigits(b[4:6]) + if err != nil { + return time.Time{}, err + } + if minutes > 59 { + return time.Time{}, unstable.NewParserError(b[4:6], "invalid timezone offset minutes") + } + + seconds := direction * (hours*3600 + minutes*60) + if seconds == 0 { + zone = time.UTC + } else { + zone = time.FixedZone("", seconds) + } + b = b[dateTimeByteLen:] + } + + if len(b) > 0 { + return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone") + } + + t := time.Date( + dt.Year, + time.Month(dt.Month), + dt.Day, + dt.Hour, + dt.Minute, + dt.Second, + dt.Nanosecond, + zone) + + return t, nil +} diff --git a/vendor/github.com/pelletier/go-toml/v2/decode_fused.go b/vendor/github.com/pelletier/go-toml/v2/decode_fused.go new file mode 100644 index 0000000..9de2d65 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/decode_fused.go @@ -0,0 +1,362 @@ +package toml + +import ( + "errors" + "reflect" + "strings" + + "github.com/pelletier/go-toml/v2/internal/parserbridge" + "github.com/pelletier/go-toml/v2/unstable" +) + +// unmarshalFused decodes a whole document into a native map[string]interface{} +// tree with no reflection on the document structure, and without building an +// AST for table headers and scalar key-values. Only container values (arrays +// and inline tables) are parsed into the parser arena, so that the seen-tracker +// can validate them and decodeAny can presize the resulting slices and maps — +// the AST is what makes that cheap O(1) presizing possible. +// +// It is used when the target is a fully generic value (interface{} or +// map[string]interface{}) and the unmarshaler interface is disabled. The +// seen-tracker validates the document (duplicate keys, type consistency), so +// the builder creates and merges containers without revalidating. Strict mode +// never applies to a generic target (a map has no "unknown fields"), and +// captures never apply (a generic value implements no Unmarshaler). +func (d *decoder) unmarshalFused(root reflect.Value, data []byte) error { + var m map[string]interface{} + if !root.IsNil() { + // Decode into (merge with) an existing generic map when present. + if em, ok := root.Interface().(map[string]interface{}); ok { + m = em + } + } + if m == nil { + m = map[string]interface{}{} + } + + if err := d.fusedDocument(m, data); err != nil { + return d.wrapFusedError(data, err) + } + + if root.CanSet() { + root.Set(reflect.ValueOf(m)) + } + return nil +} + +// fusedDocument runs the top-level expression loop, mirroring +// Parser.NextExpression but storing values directly into native maps. +func (d *decoder) fusedDocument(m map[string]interface{}, b []byte) error { + cur := m + for { + b = fusedSkipWS(b) + if len(b) == 0 { + return nil + } + switch b[0] { + case '\n': + b = b[1:] + case '\r': + if len(b) > 1 && b[1] == '\n' { + b = b[2:] + continue + } + return unstable.NewParserError(b[:1], "expected newline but got %#U", b[0]) + case '#': + _, rest, err := parserbridge.ScanComment(b) + if err != nil { + return err + } + rest, err = fusedConsumeEOL(rest) + if err != nil { + return err + } + b = rest + case '[': + rest, err := d.fusedTable(b, m, &cur) + if err != nil { + return err + } + b = rest + default: + rest, err := d.fusedKeyVal(b, cur) + if err != nil { + return err + } + b = rest + } + } +} + +// fusedTable handles a [table] or [[array table]] header. b starts at '['. It +// updates *cur to the table the following key-values belong to. +func (d *decoder) fusedTable(b []byte, root map[string]interface{}, cur *map[string]interface{}) ([]byte, error) { + arrayTable := len(b) > 1 && b[1] == '[' + + var start []byte + if arrayTable { + start = fusedSkipWS(b[2:]) + } else { + start = fusedSkipWS(b[1:]) + } + + var err error + var rawKey []byte + d.keyParts, rawKey, b, err = parserbridge.ScanKey(&d.p, start, d.keyParts[:0]) + if err != nil { + return nil, err + } + + if arrayTable { + if len(b) < 2 || b[0] != ']' || b[1] != ']' { + return nil, unstable.NewParserError(fusedHL1(b), "expected ']]' to close array table name") + } + b = b[2:] + } else { + if len(b) == 0 || b[0] != ']' { + return nil, unstable.NewParserError(fusedHL1(b), "expected ']' to close table name") + } + b = b[1:] + } + + // The whole expression (including its line termination) is parsed before + // it is validated, to keep error precedence identical to the AST path. + b, err = d.fusedFinishLine(b) + if err != nil { + return nil, err + } + + if arrayTable { + first, err := d.seen.CheckArrayTable(d.keyParts) + if err != nil { + return nil, d.fusedSeenError(rawKey, d.keyParts, err) + } + *cur = d.anyArrayTableParts(root, d.keyParts, first) + } else { + if _, err := d.seen.CheckTable(d.keyParts); err != nil { + return nil, d.fusedSeenError(rawKey, d.keyParts, err) + } + *cur = d.anyTableParts(root, d.keyParts) + } + return b, nil +} + +// fusedKeyVal handles a `key = value` expression relative to the current table +// cur. b starts at the first character of the key. +func (d *decoder) fusedKeyVal(b []byte, cur map[string]interface{}) ([]byte, error) { + var err error + var rawKey []byte + d.keyParts, rawKey, b, err = parserbridge.ScanKey(&d.p, b, d.keyParts[:0]) + if err != nil { + return nil, err + } + if len(b) == 0 || b[0] != '=' { + return nil, unstable.NewParserError(fusedHL1(b), "expected '=' after key") + } + b = fusedSkipWS(b[1:]) + if len(b) == 0 { + return nil, unstable.NewParserError(b, "expected value, not end of input") + } + + if c := b[0]; c == '[' || c == '{' { + // Container value: build its AST so the seen-tracker can validate it + // and decodeAny can presize the resulting slices and maps. + nodeAny, rest, err := parserbridge.ParseValue(&d.p, b) + if err != nil { + return nil, err + } + node := nodeAny.(*unstable.Node) + rest, err = d.fusedFinishLine(rest) + if err != nil { + return nil, err + } + leafID, err := d.seen.CheckKeyValue(d.keyParts) + if err != nil { + return nil, d.fusedSeenError(rawKey, d.keyParts, err) + } + if err := d.seen.CheckValueUnder(leafID, node); err != nil { + return nil, d.fusedSeenError(rawKey, d.keyParts, err) + } + av, err := d.decodeAny(node) + if err != nil { + return nil, err + } + d.setFusedLeaf(cur, d.keyParts, av) + return rest, nil + } + + // Scalar value: scan it without building a node, then validate and convert + // it natively. + k, _, value, rest, err := parserbridge.ScanScalar(&d.p, b) + if err != nil { + return nil, err + } + kind := unstable.Kind(k) + rest, err = d.fusedFinishLine(rest) + if err != nil { + return nil, err + } + if _, err := d.seen.CheckKeyValue(d.keyParts); err != nil { + return nil, d.fusedSeenError(rawKey, d.keyParts, err) + } + av, err := d.fusedScalar(kind, value) + if err != nil { + return nil, err + } + d.setFusedLeaf(cur, d.keyParts, av) + return rest, nil +} + +// fusedSeenError turns a bare error returned by a SeenTracker parts-method +// into a ParserError carrying the position (the raw key span) and key path of +// the offending expression, so that it is reported as a DecodeError with +// context. It mirrors decoder.wrapSeenError for the fused (AST-less) path. +func (d *decoder) fusedSeenError(rawKey []byte, parts [][]byte, err error) error { + key := make(Key, len(parts)) + for i, p := range parts { + key[i] = string(p) + } + return &unstable.ParserError{ + Highlight: rawKey, + Message: strings.TrimPrefix(err.Error(), "toml: "), + Key: key, + } +} + +// fusedScalar converts a scanned scalar value into the native Go value used +// for generic targets. It mirrors the scalar cases of decodeAny. +func (d *decoder) fusedScalar(kind unstable.Kind, value []byte) (interface{}, error) { + switch kind { + case unstable.String: + return string(value), nil + case unstable.Integer: + i, err := parseInteger(value) + return i, err + case unstable.Float: + f, err := parseFloat(value) + return f, err + case unstable.Bool: + return value[0] == 't', nil + case unstable.DateTime: + t, err := parseDateTime(value) + return t, err + case unstable.LocalDateTime: + dt, rest, err := parseLocalDateTime(value) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, unstable.NewParserError(rest, "extra characters at the end of a local date time") + } + return dt, nil + case unstable.LocalDate: + date, err := parseLocalDate(value) + return date, err + case unstable.LocalTime: + t, rest, err := parseLocalTime(value) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, unstable.NewParserError(rest, "extra characters at the end of a local time") + } + return t, nil + default: + return nil, unstable.NewParserError(value, "unsupported value kind %s", kind) + } +} + +// anyTableParts navigates a [table] header (given its key parts) to the map it +// designates, creating intermediate tables as needed. +func (d *decoder) anyTableParts(m map[string]interface{}, parts [][]byte) map[string]interface{} { + cur := m + for _, p := range parts { + cur = d.anyChildTable(cur, d.intern(p)) + } + return cur +} + +// anyArrayTableParts navigates a [[array table]] header (given its key parts), +// appends a fresh element to the designated array, and returns it. first is +// true the first time this header is seen, in which case any pre-existing array +// (from a reused target) is reset. +func (d *decoder) anyArrayTableParts(m map[string]interface{}, parts [][]byte, first bool) map[string]interface{} { + cur := m + name := d.intern(parts[0]) + for i := 1; i < len(parts); i++ { + cur = d.anyChildTable(cur, name) + name = d.intern(parts[i]) + } + s, _ := cur[name].([]interface{}) + if first { + s = s[:0] + } + elem := map[string]interface{}{} + cur[name] = append(s, elem) + return elem +} + +// setFusedLeaf assigns av at the (possibly dotted) key parts within cur, +// creating intermediate maps as needed. +func (d *decoder) setFusedLeaf(cur map[string]interface{}, parts [][]byte, av interface{}) { + for i := 0; i < len(parts)-1; i++ { + cur = d.anyChildTable(cur, d.intern(parts[i])) + } + cur[d.intern(parts[len(parts)-1])] = av +} + +// wrapFusedError gives document context to errors produced by the fused +// decoder. +func (d *decoder) wrapFusedError(data []byte, err error) error { + var perr *unstable.ParserError + if errors.As(err, &perr) && len(perr.Highlight) == 0 { + // Mirror NextExpression: give end-of-input errors a usable position by + // extending the empty highlight to the last byte of the document. + if offset := cap(data) - cap(perr.Highlight); offset > 0 && offset == len(data) { + perr.Highlight = data[offset-1 : offset] + } + } + return d.wrapError(data, err) +} + +func fusedSkipWS(b []byte) []byte { + for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') { + b = b[1:] + } + return b +} + +func fusedConsumeEOL(b []byte) ([]byte, error) { + if len(b) == 0 { + return b, nil + } + switch b[0] { + case '\n': + return b[1:], nil + case '\r': + if len(b) > 1 && b[1] == '\n' { + return b[2:], nil + } + } + return nil, unstable.NewParserError(b[:1], "expected newline but got %#U", b[0]) +} + +// fusedFinishLine consumes `ws [comment] (newline|eof)` after an expression. +func (d *decoder) fusedFinishLine(b []byte) ([]byte, error) { + b = fusedSkipWS(b) + if len(b) > 0 && b[0] == '#' { + _, rest, err := parserbridge.ScanComment(b) + if err != nil { + return nil, err + } + b = rest + } + return fusedConsumeEOL(b) +} + +func fusedHL1(b []byte) []byte { + if len(b) > 0 { + return b[:1] + } + return b +} diff --git a/vendor/github.com/pelletier/go-toml/v2/doc.go b/vendor/github.com/pelletier/go-toml/v2/doc.go new file mode 100644 index 0000000..b7bc599 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/doc.go @@ -0,0 +1,2 @@ +// Package toml is a library to read and write TOML documents. +package toml diff --git a/vendor/github.com/pelletier/go-toml/v2/errors.go b/vendor/github.com/pelletier/go-toml/v2/errors.go new file mode 100644 index 0000000..8e10b12 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/errors.go @@ -0,0 +1,233 @@ +package toml + +import ( + "errors" + "strconv" + "strings" + + "github.com/pelletier/go-toml/v2/unstable" +) + +// DecodeError represents an error encountered during the parsing or decoding +// of a TOML document. +// +// In addition to the error message, it contains the position in the document +// where it happened, as well as a human-readable representation that shows +// where the error occurred in the document. +type DecodeError struct { + message string + line int + column int + key Key + + human string +} + +// StrictMissingError occurs in a TOML document that does not have a +// corresponding field in the target value. It contains all the missing fields +// in Errors. +// +// Emitted by Decoder when DisallowUnknownFields() was called. +type StrictMissingError struct { + // One error per field that could not be found. + Errors []DecodeError +} + +// Error returns the canonical string for this error. +func (s *StrictMissingError) Error() string { + return "strict mode: fields in the document are missing in the target struct" +} + +// String returns a human readable description of all errors. +func (s *StrictMissingError) String() string { + var buf strings.Builder + + for i, e := range s.Errors { + if i > 0 { + buf.WriteString("\n---\n") + } + buf.WriteString(e.String()) + } + + return buf.String() +} + +// Unwrap returns wrapped decode errors +// +// Implements errors.Join() interface. +func (s *StrictMissingError) Unwrap() []error { + errs := make([]error, len(s.Errors)) + for i := range s.Errors { + errs[i] = &s.Errors[i] + } + return errs +} + +// Key represents a TOML key as a sequence of key parts. +type Key []string + +// Error returns the error message contained in the DecodeError. +func (e *DecodeError) Error() string { + return "toml: " + e.message +} + +// String returns the human-readable contextualized error. This string is +// multi-line. +func (e *DecodeError) String() string { + return e.human +} + +// Position returns the (line, column) pair indicating where the error +// occurred in the document. Positions are 1-indexed. +func (e *DecodeError) Position() (row int, column int) { + return e.line, e.column +} + +// Key that was being processed when the error occurred. +func (e *DecodeError) Key() Key { + return e.key +} + +// wrapDecodeError creates a DecodeError from a ParserError. The highlight of +// the ParserError needs to be a subslice of the document. +func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError { + if de == nil { + return nil + } + return newDecodeError(document, de.Highlight, de.Key, de.Message) +} + +// newDecodeError creates a DecodeError pointing at the given highlight, which +// needs to be a subslice of the document. +func newDecodeError(document []byte, highlight []byte, key Key, message string) *DecodeError { + offset := subsliceOffset(document, highlight) + + errLineIdx, errColumn := positionAt(document, offset) + + human := buildHumanContext(document, errLineIdx, errColumn, len(highlight), message) + + return &DecodeError{ + message: message, + line: errLineIdx + 1, + column: errColumn, + key: key, + human: human, + } +} + +// subsliceOffset returns the offset of the subslice b within the document. +func subsliceOffset(document, b []byte) int { + // Highlights are subslices of the document, which means they share the + // same backing array, and their capacity counts the bytes between their + // start and the end of the backing array. + offset := cap(document) - cap(b) + if offset < 0 || offset+len(b) > len(document) { + panic(errors.New("highlight is not a subslice of the document")) + } + return offset +} + +// positionAt returns the 0-indexed line and the 1-indexed column of the given +// offset in the document. +func positionAt(document []byte, offset int) (lineIdx int, column int) { + lineStart := 0 + for i := 0; i < offset; i++ { + if document[i] == '\n' { + lineIdx++ + lineStart = i + 1 + } + } + return lineIdx, offset - lineStart + 1 +} + +// docLines splits the document into lines, removing the trailing newline +// characters. +func docLines(document []byte) []string { + s := string(document) + lines := strings.Split(s, "\n") + for i, l := range lines { + lines[i] = strings.TrimSuffix(l, "\r") + } + return lines +} + +// buildHumanContext renders the human-readable multi-line context of an +// error: a window of up to 3 lines before and after the error line, with +// the error position underlined. +func buildHumanContext(document []byte, errLineIdx, errColumn, highlightLen int, message string) string { + lines := docLines(document) + + const window = 3 + firstIdx := errLineIdx - window + if firstIdx < 0 { + firstIdx = 0 + } + lastIdx := errLineIdx + window + if lastIdx > len(lines)-1 { + lastIdx = len(lines) - 1 + } + // Empty lines at the edges of the window are dropped, unless the error + // is about that very position. + for firstIdx < errLineIdx && lines[firstIdx] == "" { + firstIdx++ + } + for lastIdx > errLineIdx && lines[lastIdx] == "" { + lastIdx-- + } + + // Width of the column of line numbers. + width := len(strconv.Itoa(lastIdx + 1)) + + var buf strings.Builder + + writeLine := func(idx int) { + number := strconv.Itoa(idx + 1) + for i := len(number); i < width; i++ { + buf.WriteByte(' ') + } + buf.WriteString(number) + buf.WriteByte('|') + if len(lines[idx]) > 0 { + buf.WriteByte(' ') + buf.WriteString(lines[idx]) + } + buf.WriteByte('\n') + } + + for idx := firstIdx; idx <= errLineIdx; idx++ { + writeLine(idx) + } + + // Underline the error. + for i := 0; i < width; i++ { + buf.WriteByte(' ') + } + buf.WriteString("| ") + for i := 1; i < errColumn; i++ { + buf.WriteByte(' ') + } + // The highlight cannot extend past the end of its line. + tildes := highlightLen + if errLineIdx < len(lines) { + if avail := len(lines[errLineIdx]) - errColumn + 1; tildes > avail { + tildes = avail + } + } + if tildes < 1 { + tildes = 1 + } + for i := 0; i < tildes; i++ { + buf.WriteByte('~') + } + if message != "" { + buf.WriteByte(' ') + buf.WriteString(message) + } + buf.WriteByte('\n') + + for idx := errLineIdx + 1; idx <= lastIdx; idx++ { + writeLine(idx) + } + + return strings.TrimSuffix(buf.String(), "\n") +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/parserbridge/parserbridge.go b/vendor/github.com/pelletier/go-toml/v2/internal/parserbridge/parserbridge.go new file mode 100644 index 0000000..703acee --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/parserbridge/parserbridge.go @@ -0,0 +1,37 @@ +// Package parserbridge exposes the unstable parser's non-AST scanners to the +// root toml package without making them part of the unstable public API. +// +// The fused generic-decode fast path needs to scan keys, scalars and comments +// (and parse container values into the arena) without going through the +// AST-pushing NextExpression/Expression methods. Those scanners depend on +// Parser internals (the string-unescape scratch buffer and the node arena), so +// they have to live in the unstable package; but they are an implementation +// detail of the decoder, not something we want to commit to in the public API. +// +// The unstable package populates these variables in its init; the toml package +// reads them. The parser is passed as an any (it is always an *unstable.Parser) +// and the scalar kind is an int (it is always an unstable.Kind) so that this +// package imports neither unstable nor toml, avoiding an import cycle. Passing +// a pointer through an interface does not allocate, so the fused path keeps its +// allocation profile. +package parserbridge + +var ( + // ScanScalar scans a single scalar value (string, integer, float, bool or + // date/time) without building an AST node. kind is an unstable.Kind. + ScanScalar func(p any, b []byte) (kind int, raw, value, rest []byte, err error) + + // ScanKey scans a (possibly dotted) key without building AST nodes, + // appending each decoded part to dst. + ScanKey func(p any, b []byte, dst [][]byte) (parts [][]byte, raw, rest []byte, err error) + + // ScanComment scans a comment starting at '#', returning the comment bytes + // (including '#', excluding the line ending) and the rest of the input. It + // needs no parser state. + ScanComment func(b []byte) (comment, rest []byte, err error) + + // ParseValue parses a single value (including arrays and inline tables) into + // the parser arena, returning the root *unstable.Node and the rest of the + // input. + ParseValue func(p any, b []byte) (node any, rest []byte, err error) +) diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go new file mode 100644 index 0000000..661b11c --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go @@ -0,0 +1,49 @@ +package tracker + +import "github.com/pelletier/go-toml/v2/unstable" + +// KeyTracker is a tracker that keeps track of the current Key as the AST is +// walked. +type KeyTracker struct { + k []string +} + +// UpdateTable sets the state of the tracker with the AST table node. +func (t *KeyTracker) UpdateTable(node *unstable.Node) { + t.reset() + t.Push(node) +} + +// UpdateArrayTable sets the state of the tracker with the AST array table +// node. +func (t *KeyTracker) UpdateArrayTable(node *unstable.Node) { + t.reset() + t.Push(node) +} + +// Push the given key on the stack. +func (t *KeyTracker) Push(node *unstable.Node) { + it := node.Key() + for it.Next() { + t.k = append(t.k, string(it.Node().Data)) + } +} + +// Pop key from stack. +func (t *KeyTracker) Pop(node *unstable.Node) { + it := node.Key() + for it.Next() { + t.k = t.k[:len(t.k)-1] + } +} + +// Key returns the current key. +func (t *KeyTracker) Key() []string { + k := make([]string, len(t.k)) + copy(k, t.k) + return k +} + +func (t *KeyTracker) reset() { + t.k = t.k[:0] +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go new file mode 100644 index 0000000..e88cd94 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go @@ -0,0 +1,450 @@ +package tracker + +import ( + "bytes" + "fmt" + + "github.com/pelletier/go-toml/v2/unstable" +) + +type keyKind uint8 + +const ( + invalidKind keyKind = iota + // valueKind is a regular value (scalar, array, or inline table). It + // cannot be extended. + valueKind + // kvTableKind is a table created implicitly by a dotted key. It can only + // be extended by other dotted keys. + kvTableKind + // tableKind is a table created by a [header]. The explicit flag tells + // whether the table was created by its own header (true) or as an + // intermediate step of a longer key (false). + tableKind + // arrayTableKind is an array of tables created by [[header]]. + arrayTableKind + // anonymousKind is an entry that cannot be looked up by name. It serves + // as the parent of the content of inline tables stored inside arrays. + anonymousKind +) + +func (k keyKind) String() string { + switch k { + case invalidKind: + return "invalid" + case valueKind: + return "value" + case kvTableKind: + return "kv-table" + case tableKind: + return "table" + case arrayTableKind: + return "array-table" + case anonymousKind: + return "anonymous" + } + panic("missing keyKind string mapping") +} + +// entry represents a node that has been seen in the document. Its size has a +// direct impact on the performance of unmarshaling documents: keep it as +// small as possible. +type entry struct { + parent int32 + kind keyKind + explicit bool + name []byte +} + +// SeenTracker tracks which keys have been seen with which TOML type to flag +// duplicates and mismatches according to the spec. +// +// Each node in the visited tree is represented by an entry. Each entry has +// an identifier, which is provided by a counter. Entries are stored in the +// array entries. As new nodes are discovered (referenced for the first time +// in the TOML document), entries are created and appended to the array. An +// entry points to its parent using its id. +// +// To find whether a given key (sequence of []byte) has already been visited, +// the entries are linearly searched, looking for one with the right name and +// parent id. +// +// Given that all keys appear in the document after their parent, it is +// guaranteed that all descendants of a node are stored after the node, this +// speeds up the search process. +// +// When encountering [[array tables]], the descendants of that node are removed +// to allow that branch of the tree to be "rediscovered". To maintain the +// invariant above, the deletion process needs to keep the order of entries. +// This results in more copies in that case. +type SeenTracker struct { + entries []entry + currentTable int32 + + // scratch buffers for clear() + removedBuf []bool + remapBuf []int32 +} + +// Reset brings the tracker to its initial state, with just a root table, so +// that it can be reused across documents. +func (s *SeenTracker) Reset() { + s.reset() +} + +// reset brings the tracker to its initial state, with just a root table. +func (s *SeenTracker) reset() { + s.entries = append(s.entries[:0], entry{ + parent: -1, + kind: tableKind, + }) + s.currentTable = 0 +} + +// find returns the id of the entry with the given parent and name, or -1. +// Anonymous entries are never returned. +func (s *SeenTracker) find(parent int32, name []byte) int32 { + // Children always appear after their parent. + for i := int(parent) + 1; i < len(s.entries); i++ { + e := &s.entries[i] + if e.parent == parent && e.kind != anonymousKind && bytes.Equal(e.name, name) { + return int32(i) //nolint:gosec // entry counts are bounded by document size + } + } + return -1 +} + +// create appends a new entry and returns its id. +func (s *SeenTracker) create(parent int32, name []byte, kind keyKind, explicit bool) int32 { + id := int32(len(s.entries)) //nolint:gosec // entry counts are bounded by document size + s.entries = append(s.entries, entry{ + parent: parent, + kind: kind, + explicit: explicit, + name: name, + }) + return id +} + +// clear removes all the descendants of the entry with the given id, keeping +// the order of the remaining entries. +func (s *SeenTracker) clear(id int32) { + // Compute which entries are removed. Given that children always appear + // after their parent, a single forward pass is enough. + if cap(s.removedBuf) < len(s.entries) { + s.removedBuf = make([]bool, len(s.entries)) + s.remapBuf = make([]int32, len(s.entries)) + } + removed := s.removedBuf[:len(s.entries)] + remap := s.remapBuf[:len(s.entries)] + for i := range removed { + removed[i] = false + } + + n := int32(0) + for i := 0; i < len(s.entries); i++ { + parent := s.entries[i].parent + if parent >= 0 && (parent == id && s.entries[i].kind != invalidKind || removed[parent]) { + removed[i] = true + continue + } + remap[i] = n + if int32(i) != n { //nolint:gosec // entry counts are bounded by document size + e := s.entries[i] + e.parent = remap[e.parent] + s.entries[n] = e + } + n++ + } + s.entries = s.entries[:n] +} + +// 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. 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 len(s.entries) == 0 { + s.reset() + } + switch node.Kind { + case unstable.KeyValue: + return false, s.checkKeyValue(s.currentTable, node) + case unstable.Table: + return s.checkTable(node) + case unstable.ArrayTable: + return s.checkArrayTable(node) + default: + return false, fmt.Errorf("toml: unexpected expression kind %s", node.Kind) + } +} + +// CheckTable validates a [table] header given the decoded parts of its key. +// It mirrors checkTable but is driven directly from the key parts instead of +// an AST, for callers that decode without building one. It returns whether the +// table is seen for the first time. +func (s *SeenTracker) CheckTable(parts [][]byte) (bool, error) { + parent := int32(0) + for k := 0; k < len(parts); k++ { + name := parts[k] + if k == len(parts)-1 { + // Final part of the key. + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, tableKind, true) + s.currentTable = i + return true, nil + } + e := &s.entries[i] + switch e.kind { + case tableKind: + if e.explicit { + return false, fmt.Errorf("toml: table %s already exists", name) + } + e.explicit = true + s.currentTable = i + return false, nil + case kvTableKind: + return false, fmt.Errorf("toml: table %s already exists as defined by a dotted key", name) + case arrayTableKind: + return false, fmt.Errorf("toml: table %s already exists as an array of tables", name) + default: + return false, fmt.Errorf("toml: key %s should be a table, not a %s", name, e.kind) + } + } + + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, tableKind, false) + } else { + switch s.entries[i].kind { + case tableKind, arrayTableKind, kvTableKind: + // Tables created by dotted keys can receive new sub-tables, + // but cannot be redefined (handled by the last-part case). + default: + return false, fmt.Errorf("toml: key %s already exists as a value", name) + } + } + parent = i + } + panic("unreachable: table expression without key") +} + +// CheckArrayTable validates a [[array table]] header given the decoded parts +// of its key. It mirrors checkArrayTable but is driven directly from the key +// parts. It returns whether the array table is seen for the first time. +func (s *SeenTracker) CheckArrayTable(parts [][]byte) (bool, error) { + parent := int32(0) + for k := 0; k < len(parts); k++ { + name := parts[k] + if k == len(parts)-1 { + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, arrayTableKind, true) + s.currentTable = i + return true, nil + } + if s.entries[i].kind != arrayTableKind { + return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", name, s.entries[i].kind) + } + // Make the descendants of this array table re-discoverable for + // the new element. + s.clear(i) + s.currentTable = i + return false, nil + } + + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, tableKind, false) + } else { + switch s.entries[i].kind { + case tableKind, arrayTableKind, kvTableKind: + // Tables created by dotted keys can receive new sub-tables, + // but cannot be redefined (handled by the last-part case). + default: + return false, fmt.Errorf("toml: key %s already exists as a value", name) + } + } + parent = i + } + panic("unreachable: array table expression without key") +} + +// CheckKeyValue validates the (possibly dotted) key of a key-value under the +// current table, WITHOUT validating its value. It returns the id of the leaf +// entry, so the caller can validate a container value with CheckValueUnder. +func (s *SeenTracker) CheckKeyValue(parts [][]byte) (int32, error) { + parent := s.currentTable + for k := 0; k < len(parts); k++ { + name := parts[k] + if k == len(parts)-1 { + if i := s.find(parent, name); i >= 0 { + return -1, fmt.Errorf("toml: key %s is already defined", name) + } + return s.create(parent, name, valueKind, false), nil + } + + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, kvTableKind, false) + } else if s.entries[i].kind != kvTableKind { + return -1, fmt.Errorf("toml: key %s is already defined", name) + } + parent = i + } + panic("unreachable: key-value expression without key") +} + +// CheckValueUnder validates the content of a value stored under the given +// entry (typically the leaf returned by CheckKeyValue): inline tables cannot +// contain duplicate keys, including in the inline tables and arrays they +// contain. +func (s *SeenTracker) CheckValueUnder(parent int32, value *unstable.Node) error { + return s.checkValue(parent, value) +} + +func (s *SeenTracker) checkTable(node *unstable.Node) (bool, error) { + parent := int32(0) + + it := node.Key() + // Handle the intermediate parts of the key. + for it.Next() { + part := it.Node() + name := part.Data + if it.IsLast() { + // Final part of the key. + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, tableKind, true) + s.currentTable = i + return true, nil + } + e := &s.entries[i] + switch e.kind { + case tableKind: + if e.explicit { + return false, fmt.Errorf("toml: table %s already exists", name) + } + e.explicit = true + s.currentTable = i + return false, nil + case kvTableKind: + return false, fmt.Errorf("toml: table %s already exists as defined by a dotted key", name) + case arrayTableKind: + return false, fmt.Errorf("toml: table %s already exists as an array of tables", name) + default: + return false, fmt.Errorf("toml: key %s should be a table, not a %s", name, e.kind) + } + } + + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, tableKind, false) + } else { + switch s.entries[i].kind { + case tableKind, arrayTableKind, kvTableKind: + // Tables created by dotted keys can receive new sub-tables, + // but cannot be redefined (handled by the last-part case). + default: + return false, fmt.Errorf("toml: key %s already exists as a value", name) + } + } + parent = i + } + panic("unreachable: table expression without key") +} + +func (s *SeenTracker) checkArrayTable(node *unstable.Node) (bool, error) { + parent := int32(0) + + it := node.Key() + for it.Next() { + part := it.Node() + name := part.Data + if it.IsLast() { + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, arrayTableKind, true) + s.currentTable = i + return true, nil + } + if s.entries[i].kind != arrayTableKind { + return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", name, s.entries[i].kind) + } + // Make the descendants of this array table re-discoverable for + // the new element. + s.clear(i) + // Note: clear cannot move i because i comes before all its + // descendants. + s.currentTable = i + return false, nil + } + + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, tableKind, false) + } else { + switch s.entries[i].kind { + case tableKind, arrayTableKind, kvTableKind: + // Tables created by dotted keys can receive new sub-tables, + // but cannot be redefined (handled by the last-part case). + default: + return false, fmt.Errorf("toml: key %s already exists as a value", name) + } + } + parent = i + } + panic("unreachable: array table expression without key") +} + +func (s *SeenTracker) checkKeyValue(parent int32, node *unstable.Node) error { + it := node.Key() + for it.Next() { + part := it.Node() + name := part.Data + if it.IsLast() { + if i := s.find(parent, name); i >= 0 { + return fmt.Errorf("toml: key %s is already defined", name) + } + id := s.create(parent, name, valueKind, false) + return s.checkValue(id, node.Value()) + } + + i := s.find(parent, name) + if i < 0 { + i = s.create(parent, name, kvTableKind, false) + } else if s.entries[i].kind != kvTableKind { + return fmt.Errorf("toml: key %s is already defined", name) + } + parent = i + } + panic("unreachable: key-value expression without key") +} + +// checkValue verifies the content of a value: inline tables cannot contain +// duplicate keys, including in the inline tables and arrays they contain. +func (s *SeenTracker) checkValue(id int32, value *unstable.Node) error { + switch value.Kind { + case unstable.InlineTable: + it := value.Children() + for it.Next() { + if err := s.checkKeyValue(id, it.Node()); err != nil { + return err + } + } + case unstable.Array: + it := value.Children() + for it.Next() { + elem := it.Node() + if elem.Kind == unstable.InlineTable || elem.Kind == unstable.Array { + elemID := s.create(id, nil, anonymousKind, false) + if err := s.checkValue(elemID, elem); err != nil { + return err + } + } + } + default: + } + return nil +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go new file mode 100644 index 0000000..ed51038 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go @@ -0,0 +1,2 @@ +// Package tracker provides functions for keeping track of AST nodes. +package tracker diff --git a/vendor/github.com/pelletier/go-toml/v2/localtime.go b/vendor/github.com/pelletier/go-toml/v2/localtime.go new file mode 100644 index 0000000..eb8c20e --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/localtime.go @@ -0,0 +1,121 @@ +package toml + +import ( + "fmt" + "strings" + "time" + + "github.com/pelletier/go-toml/v2/unstable" +) + +// LocalDate represents a calendar day in no specific timezone. +type LocalDate struct { + Year int + Month int + Day int +} + +// AsTime converts d into a specific time instance at midnight in zone. +func (d LocalDate) AsTime(zone *time.Location) time.Time { + return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, zone) +} + +// String returns RFC 3339 representation of d. +func (d LocalDate) String() string { + return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) +} + +// MarshalText returns RFC 3339 representation of d. +func (d LocalDate) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalDate) UnmarshalText(b []byte) error { + res, err := parseLocalDate(b) + if err != nil { + return err + } + *d = res + return nil +} + +// LocalTime represents a time of day of no specific day in no specific +// timezone. +type LocalTime struct { + Hour int // Hour of the day: [0; 24[ + Minute int // Minute of the hour: [0; 60[ + Second int // Second of the minute: [0; 59] + Nanosecond int // Nanoseconds within the second: [0, 1000000000[ + Precision int // Number of digits to display for Nanosecond. +} + +// String returns RFC 3339 representation of d. +// If d.Nanosecond and d.Precision are zero, the time won't have a nanosecond +// component. If d.Nanosecond > 0 but d.Precision = 0, then the minimum number +// of digits for nanoseconds is provided. +func (d LocalTime) String() string { + s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second) + + if d.Precision > 0 { + s += fmt.Sprintf(".%09d", d.Nanosecond)[:d.Precision+1] + } else if d.Nanosecond > 0 { + // Nanoseconds are specified, but precision is not provided. Use the + // minimum. + s += strings.TrimRight(fmt.Sprintf(".%09d", d.Nanosecond), "0") + } + + return s +} + +// MarshalText returns RFC 3339 representation of d. +func (d LocalTime) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalTime) UnmarshalText(b []byte) error { + res, left, err := parseLocalTime(b) + if err == nil && len(left) != 0 { + err = unstable.NewParserError(left, "extra characters at the end of a local time") + } + if err != nil { + return err + } + *d = res + return nil +} + +// LocalDateTime represents a time of a specific day in no specific timezone. +type LocalDateTime struct { + LocalDate + LocalTime +} + +// 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) +} + +// String returns RFC 3339 representation of d. +func (d LocalDateTime) String() string { + return d.LocalDate.String() + "T" + d.LocalTime.String() +} + +// MarshalText returns RFC 3339 representation of d. +func (d LocalDateTime) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalDateTime) UnmarshalText(data []byte) error { + res, left, err := parseLocalDateTime(data) + if err == nil && len(left) != 0 { + err = unstable.NewParserError(left, "extra characters at the end of a local date time") + } + if err != nil { + return err + } + *d = res + return nil +} diff --git a/vendor/github.com/pelletier/go-toml/v2/marshaler.go b/vendor/github.com/pelletier/go-toml/v2/marshaler.go new file mode 100644 index 0000000..c7a08e3 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/marshaler.go @@ -0,0 +1,1268 @@ +package toml + +import ( + "bytes" + "encoding" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "reflect" + "slices" + "strconv" + "strings" + "sync" + "time" + "unicode/utf8" +) + +// Marshal serializes a Go value as a TOML document. +// +// It is a shortcut for Encoder.Encode() with the default options. +func Marshal(v interface{}) ([]byte, error) { + enc := Encoder{indentSymbol: " "} + + e := encoderStatePool.Get().(*encoderState) + e.Encoder = &enc + e.buf = e.buf[:0] + e.keyStack = e.keyStack[:0] + e.lastWasHeader = false + + err := e.encodeRoot(v) + if err != nil { + encoderStatePool.Put(e) + return nil, err + } + + out := make([]byte, len(e.buf)) + copy(out, e.buf) + encoderStatePool.Put(e) + return out, nil +} + +// Encoder writes a TOML document to an output stream. +type Encoder struct { + // output + w io.Writer + + // global settings + tablesInline bool + arraysMultiline bool + indentSymbol string + indentTables bool + marshalJSONNumbers bool +} + +// NewEncoder returns a new Encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: w, + indentSymbol: " ", + } +} + +// SetTablesInline forces the encoder to emit all tables inline. +// +// This behavior can be controlled on an individual struct field basis with +// the inline tag: +// +// MyField `toml:",inline"` +func (enc *Encoder) SetTablesInline(inline bool) *Encoder { + enc.tablesInline = inline + return enc +} + +// SetArraysMultiline forces the encoder to emit all arrays with one element +// per line. +// +// This behavior can be controlled on an individual struct field basis with +// the multiline tag: +// +// MyField `multiline:"true"` +func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder { + enc.arraysMultiline = multiline + return enc +} + +// SetIndentSymbol defines the string that should be used for indentation. The +// provided string is repeated for each indentation level. Defaults to two +// spaces. +func (enc *Encoder) SetIndentSymbol(s string) *Encoder { + enc.indentSymbol = s + return enc +} + +// SetIndentTables forces the encoder to intent tables and array tables. +func (enc *Encoder) SetIndentTables(indent bool) *Encoder { + enc.indentTables = indent + 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. +// +// # Encoding rules +// +// A top level slice containing only maps or structs is encoded as [[table +// array]]. +// +// All slices not matching rule 1 are encoded as [array]. As a result, any map +// or struct they contain is encoded as an {inline table}. +// +// Nil interfaces and nil pointers are not supported. +// +// Keys in key-values always have one part. +// +// Intermediate tables are always printed. +// +// By default, strings are encoded as literal string, unless they contain +// either a newline character or a single quote. In that case they are emitted +// as quoted strings. +// +// Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so +// results in an error. This rule exists because the TOML specification only +// requires parsers to support at least the 64 bits integer range. Allowing +// larger numbers would create non-standard TOML documents, which may not be +// readable (at best) by other implementations. To encode such numbers, a +// solution is a custom type that implements encoding.TextMarshaler. +// +// When encoding structs, fields are encoded in order of definition, with +// their exact name. +// +// Tables and array tables are separated by empty lines. However, consecutive +// subtables definitions are not. For example: +// +// [top1] +// +// [top2] +// [top2.child1] +// +// [[array]] +// +// [[array]] +// [array.child2] +// +// # Struct tags +// +// The encoding of each public struct field can be customized by the format +// string in the "toml" key of the struct field's tag. This follows +// encoding/json's convention. The format string starts with the name of the +// field, optionally followed by a comma-separated list of options. The name +// may be empty in order to provide options without overriding the default +// name. +// +// The "multiline" option emits strings as quoted multi-line TOML strings, and +// arrays with one element per line. For strings, it only takes effect when the +// value contains a newline; single-line values are emitted as regular strings. +// It has no effect on fields that would not be encoded as strings or arrays. +// +// The "inline" option turns fields that would be emitted as tables into +// inline tables instead. It has no effect on other fields. +// +// 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 element of the array. +func (enc *Encoder) Encode(v interface{}) error { + e := encoderStatePool.Get().(*encoderState) + e.Encoder = enc + e.buf = e.buf[:0] + e.keyStack = e.keyStack[:0] + e.lastWasHeader = false + + err := e.encodeRoot(v) + if err != nil { + encoderStatePool.Put(e) + return err + } + + _, err = enc.w.Write(e.buf) + encoderStatePool.Put(e) + if err != nil { + return fmt.Errorf("toml: cannot write: %w", err) + } + return nil +} + +var encoderStatePool = sync.Pool{ + New: func() interface{} { return &encoderState{} }, +} + +type encoderState struct { + *Encoder + + buf []byte + + // keyStack is the dotted key of the table being encoded, shared by the + // whole encode as a stack. + keyStack []string + + // entriesPool recycles entry slices across tables of the same encode. + entriesPool [][]entry + + // lastWasHeader is true when the last line written was a table header, + // used to avoid empty lines between consecutive table definitions. + lastWasHeader bool + + // stringKeyBuf is a reusable buffer to read string map keys without + // allocating one per map. + stringKeyBuf reflect.Value +} + +// valueOptions are the encoding options attached to one entry of a table. +type valueOptions struct { + multiline bool + inline bool + omitempty bool + omitzero bool + commented bool + comment string +} + +// entry is a deferred key-value of a table being encoded. +type entry struct { + key string + value reflect.Value + options valueOptions +} + +func (e *encoderState) encodeRoot(v interface{}) error { + if v == nil { + return errors.New("toml: cannot encode a nil interface") + } + + rv := reflect.ValueOf(v) + rv, ok := resolve(rv) + if !ok { + return errors.New("toml: cannot encode a nil pointer") + } + + switch rv.Kind() { + case reflect.Map, reflect.Struct: + if isValueKind(rv) { + return fmt.Errorf("toml: cannot encode a %s as a document root", rv.Type()) + } + return e.encodeTable(rv, false, 0) + default: + return fmt.Errorf("toml: cannot encode a %s as a document root", rv.Type()) + } +} + +// resolve unwraps pointers and interfaces until a concrete value is found. +// Returns false if it resolves to nil. +func resolve(v reflect.Value) (reflect.Value, bool) { + for { + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return v, false + } + v = v.Elem() + case reflect.Interface: + if v.IsNil() { + return v, false + } + v = v.Elem() + default: + return v, true + } + } +} + +// typeEncProps caches the per-type facts used on every value encode. +type typeEncProps struct { + // 0: not a TextMarshaler, 1: the type implements it, 2: its pointer does + text uint8 + // encoded as a TOML value (as opposed to a table) + isValue bool +} + +var typeEncPropsCache sync.Map // reflect.Type -> typeEncProps + +func encPropsForType(t reflect.Type) typeEncProps { + if p, ok := typeEncPropsCache.Load(t); ok { + return p.(typeEncProps) + } + var p typeEncProps + switch { + case t.Implements(textMarshalerType): + p.text = 1 + case reflect.PtrTo(t).Implements(textMarshalerType): + p.text = 2 + } + switch t { + case timeType, localDateType, localTimeType, localDateTimeType: + p.isValue = true + default: + if p.text != 0 { + p.isValue = true + } else { + switch t.Kind() { + case reflect.Map, reflect.Struct: + p.isValue = false + default: + p.isValue = true + } + } + } + typeEncPropsCache.Store(t, p) + return p +} + +// isValueKind returns true when the resolved value is encoded as a TOML +// value (as opposed to a table). +func isValueKind(v reflect.Value) bool { + return encPropsForType(v.Type()).isValue +} + +// isTableLike returns true when the value should be encoded as a table (or +// an array of tables for slices). +func (e *encoderState) isTableLike(v reflect.Value) bool { + v, ok := resolve(v) + if !ok { + // Unresolvable values (interface-held nil pointers) are encoded as + // the zero value of their element type by the value path. + return false + } + return !isValueKind(v) +} + +// isArrayOfTables returns true when the value is a non-empty slice or array +// containing only table-like values. +func (e *encoderState) isArrayOfTables(v reflect.Value) bool { + v, ok := resolve(v) + if !ok { + return false + } + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return false + } + if v.Len() == 0 { + return false + } + for i := 0; i < v.Len(); i++ { + elem, ok := resolve(v.Index(i)) + if !ok || isValueKind(elem) { + return false + } + } + return true +} + +// encodeTable writes the content of a table at the given key path. +func (e *encoderState) encodeTable(v reflect.Value, commented bool, indent int) error { + entries, err := e.collectEntries(v) + if err != nil { + return err + } + + // First pass: emit all key-values; tables are handled by the second + // pass. + for i := range entries { + ent := &entries[i] + if e.entryIsTable(ent) { + continue + } + err := e.encodeKeyValue(*ent, commented, indent) + if err != nil { + return err + } + } + + // Second pass: emit the sub-tables, extending the shared key stack. + for i := range entries { + ent := entries[i] + if !e.entryIsTable(&ent) { + continue + } + entCommented := commented || ent.options.commented + e.keyStack = append(e.keyStack, ent.key) + + if e.isArrayOfTables(ent.value) { + err := e.encodeArrayTable(ent, entCommented, indent) + if err != nil { + return err + } + e.keyStack = e.keyStack[:len(e.keyStack)-1] + continue + } + + // The value is resolvable: entryIsTable already resolved it. + tv, _ := resolve(ent.value) + + e.writeTableHeader(ent.options.comment, entCommented, false, indent) + + err := e.encodeTable(tv, entCommented, indent+1) + if err != nil { + return err + } + e.keyStack = e.keyStack[:len(e.keyStack)-1] + } + + e.putEntries(entries) + return nil +} + +// entryIsTable reports whether the entry is emitted as a (sub-)table rather +// than a key-value. +func (e *encoderState) entryIsTable(ent *entry) bool { + return !e.tablesInline && !ent.options.inline && (e.isTableLike(ent.value) || e.isArrayOfTables(ent.value)) +} + +// getEntries returns a reusable entry slice. +func (e *encoderState) getEntries() []entry { + if n := len(e.entriesPool); n > 0 { + s := e.entriesPool[n-1] + e.entriesPool = e.entriesPool[:n-1] + return s[:0] + } + return nil +} + +// putEntries returns an entry slice to the pool. +func (e *encoderState) putEntries(s []entry) { + if cap(s) > 0 { + e.entriesPool = append(e.entriesPool, s) + } +} + +// encodeArrayTable writes all the elements of an array of tables. +func (e *encoderState) encodeArrayTable(ent entry, commented bool, indent int) error { + v, _ := resolve(ent.value) + comment := ent.options.comment + for i := 0; i < v.Len(); i++ { + // Elements are resolvable: isArrayOfTables already resolved them. + elem, _ := resolve(v.Index(i)) + + e.writeTableHeader(comment, commented, true, indent) + // The comment is only present before the first element. + comment = "" + + err := e.encodeTable(elem, commented, indent+1) + if err != nil { + return err + } + } + return nil +} + +// writeTableHeader emits a [table] or [[array table]] header line, preceded +// by an empty line and comments as needed. +func (e *encoderState) writeTableHeader(comment string, commented bool, array bool, indent int) { + key := e.keyStack + if len(e.buf) > 0 && !e.lastWasHeader { + e.buf = append(e.buf, '\n') + } + + headerIndent := indent + + e.writeComment(comment, headerIndent) + + e.writeIndent(headerIndent) + if commented { + e.buf = append(e.buf, "# "...) + } + e.buf = append(e.buf, '[') + if array { + e.buf = append(e.buf, '[') + } + for i, part := range key { + if i > 0 { + e.buf = append(e.buf, '.') + } + e.buf = e.appendKey(e.buf, part) + } + e.buf = append(e.buf, ']') + if array { + e.buf = append(e.buf, ']') + } + e.buf = append(e.buf, '\n') + e.lastWasHeader = true +} + +func (e *encoderState) writeIndent(indent int) { + if !e.indentTables { + return + } + for i := 0; i < indent; i++ { + e.buf = append(e.buf, e.indentSymbol...) + } +} + +// writeComment emits the comment lines attached to an entry. +func (e *encoderState) writeComment(comment string, indent int) { + if comment == "" { + return + } + for _, line := range strings.Split(comment, "\n") { + e.writeIndent(indent) + e.buf = append(e.buf, "# "...) + e.buf = append(e.buf, line...) + e.buf = append(e.buf, '\n') + } +} + +// encodeKeyValue writes one `key = value` line of a table. +func (e *encoderState) encodeKeyValue(ent entry, commented bool, indent int) error { + commented = commented || ent.options.commented + + e.writeComment(ent.options.comment, indent) + + e.writeIndent(indent) + if commented { + e.buf = append(e.buf, "# "...) + } + e.buf = e.appendKey(e.buf, ent.key) + e.buf = append(e.buf, " = "...) + + // When tables are not indented, the key is emitted at column zero + // regardless of its nesting depth. Value continuation lines (most + // notably the elements of a multiline array) must line up with that key, + // so the value indentation starts from zero as well rather than from the + // table nesting depth. + valueIndent := indent + if !e.indentTables { + valueIndent = 0 + } + + var err error + e.buf, err = e.appendValue(e.buf, ent.value, ent.options, valueIndent) + if err != nil { + return err + } + e.buf = append(e.buf, '\n') + e.lastWasHeader = false + return nil +} + +// collectEntries builds the ordered list of the entries of a table, +// applying tags and omission rules. +func (e *encoderState) collectEntries(v reflect.Value) ([]entry, error) { + switch v.Kind() { + case reflect.Map: + return e.collectMapEntries(v) + case reflect.Struct: + entries := e.getEntries() + e.collectStructEntries(&entries, v) + return entries, nil + default: + return nil, fmt.Errorf("toml: cannot encode a %s as a table", v.Type()) + } +} + +func (e *encoderState) collectMapEntries(v reflect.Value) ([]entry, error) { + entries := e.getEntries() + + // Keys are converted to strings right away: read them into a reusable + // buffer to avoid one allocation per key. + var kbuf reflect.Value + if v.Type().Key() == stringType { + if !e.stringKeyBuf.IsValid() { + e.stringKeyBuf = reflect.New(stringType).Elem() + } + kbuf = e.stringKeyBuf + } else { + kbuf = reflect.New(v.Type().Key()).Elem() + } + + iter := v.MapRange() + for iter.Next() { + kbuf.SetIterKey(iter) + key, err := mapKeyString(kbuf) + if err != nil { + return nil, err + } + value := iter.Value() + if value.Kind() == reflect.Interface && value.IsNil() { + // nil interface values are skipped + continue + } + if value.Kind() == reflect.Ptr && value.IsNil() { + // nil pointers in maps are encoded as their zero value + value = reflect.New(value.Type().Elem()).Elem() + } + entries = append(entries, entry{key: key, value: value}) + } + + if len(entries) > 1 { + // slices.SortFunc avoids boxing the slice into a sort.Interface (an + // allocation that sort.Sort incurs for every table). + slices.SortFunc(entries, func(a, b entry) int { + return strings.Compare(a.key, b.key) + }) + } + + return entries, nil +} + +// mapKeyString converts a map key to its string representation. +func mapKeyString(k reflect.Value) (string, error) { + kr, ok := resolve(k) + if !ok { + return "", errors.New("toml: cannot encode a nil map key") + } + if kr.Type().Implements(textMarshalerType) { + b, err := kr.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return "", fmt.Errorf("toml: cannot marshal map key: %w", err) + } + return string(b), nil + } + if kr.CanAddr() && reflect.PtrTo(kr.Type()).Implements(textMarshalerType) { + b, err := kr.Addr().Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return "", fmt.Errorf("toml: cannot marshal map key: %w", err) + } + return string(b), nil + } + switch kr.Kind() { + case reflect.String: + return kr.String(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(kr.Int(), 10), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(kr.Uint(), 10), nil + case reflect.Float32: + return strconv.FormatFloat(kr.Float(), 'f', -1, 32), nil + case reflect.Float64: + return strconv.FormatFloat(kr.Float(), 'f', -1, 64), nil + default: + return "", fmt.Errorf("toml: cannot encode a map with key type %s", k.Type()) + } +} + +// encPlanField is the static encoding information of one field of a struct. +type encPlanField struct { + name string + index []int + depth int + options valueOptions +} + +// encPlan caches the per-type information needed to encode a struct: +// flattened fields with parsed tags, in order of definition, with shadowed +// duplicates already removed. +type encPlan struct { + fields []encPlanField +} + +var encPlans sync.Map // reflect.Type -> *encPlan + +func encPlanForType(t reflect.Type) *encPlan { + if plan, ok := encPlans.Load(t); ok { + return plan.(*encPlan) + } + plan := &encPlan{} + visited := map[reflect.Type]bool{} + buildEncPlan(plan, t, nil, 0, visited) + dedupEncPlan(plan) + encPlans.Store(t, plan) + return plan +} + +func buildEncPlan(plan *encPlan, t reflect.Type, prefix []int, depth int, visited map[reflect.Type]bool) { + if visited[t] { + return + } + visited[t] = true + defer delete(visited, t) + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + + tag, tagged := f.Tag.Lookup("toml") + if tag == "-" { + continue + } + + name := f.Name + var opts valueOptions + if tagged { + parts := strings.Split(tag, ",") + if parts[0] != "" { + name = parts[0] + } + for _, opt := range parts[1:] { + switch opt { + case "multiline": + opts.multiline = true + case "inline": + opts.inline = true + case "omitempty": + opts.omitempty = true + case "omitzero": + opts.omitzero = true + case "commented": + opts.commented = true + } + } + } + // Standalone boolean tags, e.g. multiline:"true". + const tagTrue = "true" + if f.Tag.Get("multiline") == tagTrue { + opts.multiline = true + } + if f.Tag.Get("inline") == tagTrue { + opts.inline = true + } + if f.Tag.Get("commented") == tagTrue { + opts.commented = true + } + opts.comment = f.Tag.Get("comment") + + index := make([]int, 0, len(prefix)+1) + index = append(index, prefix...) + index = append(index, i) + + if f.Anonymous { + ft := f.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if ft.Kind() == reflect.Struct && (!tagged || tagName(tag) == "") { + buildEncPlan(plan, ft, index, depth+1, visited) + continue + } + if f.PkgPath != "" && ft.Kind() != reflect.Interface { + continue + } + } else if f.PkgPath != "" { + // unexported + continue + } + + plan.fields = append(plan.fields, encPlanField{ + name: name, + index: index, + depth: depth, + options: opts, + }) + } +} + +// dedupEncPlan removes the fields shadowed by another one with the same +// name (the shallowest wins), keeping the order of first appearance. +func dedupEncPlan(plan *encPlan) { + byName := make(map[string]int, len(plan.fields)) + drop := false + for i := range plan.fields { + f := &plan.fields[i] + j, seen := byName[f.name] + if !seen { + byName[f.name] = i + continue + } + drop = true + // Shallowest wins; on equal depth, the first in order wins. + if f.depth < plan.fields[j].depth { + plan.fields[j].name = "" + byName[f.name] = i + } else { + f.name = "" + } + } + if !drop { + return + } + out := plan.fields[:0] + for _, f := range plan.fields { + if f.name != "" { + out = append(out, f) + } + } + plan.fields = out +} + +// collectStructEntries appends the entries of a struct, flattening embedded +// structs in place. +func (e *encoderState) collectStructEntries(entries *[]entry, v reflect.Value) { + plan := encPlanForType(v.Type()) + + for i := range plan.fields { + f := &plan.fields[i] + fv, ok := fieldByIndexSkipNil(v, f.index) + if !ok { + // nil embedded pointer on the way: skipped + continue + } + + // Anonymous interface fields that are nil are skipped. + if fv.Kind() == reflect.Interface && fv.IsNil() { + continue + } + // nil values in struct fields are skipped + if (fv.Kind() == reflect.Ptr || fv.Kind() == reflect.Map) && fv.IsNil() { + continue + } + + if f.options.omitempty && isEmptyValue(fv) { + continue + } + if f.options.omitzero && isZeroValue(fv) { + continue + } + + *entries = append(*entries, entry{key: f.name, value: fv, options: f.options}) + } +} + +// fieldByIndexSkipNil returns the field at the given index path, reporting +// false if a nil embedded pointer is found on the way. +func fieldByIndexSkipNil(v reflect.Value, index []int) (reflect.Value, bool) { + for i, x := range index { + if i > 0 { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return v, false + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v, true +} + +func tagName(tag string) string { + if idx := strings.IndexByte(tag, ','); idx >= 0 { + return tag[:idx] + } + return tag +} + +// isEmptyValue implements the omitempty rules. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Map, reflect.Slice, reflect.Array: + return v.Len() == 0 + case reflect.Ptr, reflect.Interface: + return v.IsNil() + case reflect.Struct: + // Structs that encode as a scalar value (time.Time, the local + // date/time types, or any TextMarshaler) are empty only when they + // equal their zero value; their fields are typically unexported, so + // recursing into them would be meaningless. + if encPropsForType(v.Type()).isValue { + return v.IsZero() + } + // Plain structs encode as tables and are empty when every field that + // would be encoded is itself empty. This matches the recursive rule + // used before the encoder rewrite and, in particular, treats a + // non-nil but empty map or slice as empty (reflect.Value.IsZero does + // not, which would otherwise emit an empty table header). + return isEmptyStruct(v) + default: + return false + } +} + +// isEmptyStruct reports whether all of a table-valued struct's encodable +// fields are empty per isEmptyValue. It mirrors the field selection done by +// collectStructEntries (embedded flattening, shadowing, and "-" skips) so the +// emptiness decision matches what would actually be encoded. +func isEmptyStruct(v reflect.Value) bool { + plan := encPlanForType(v.Type()) + for i := range plan.fields { + fv, ok := fieldByIndexSkipNil(v, plan.fields[i].index) + if !ok { + // A nil embedded pointer along the path contributes nothing. + continue + } + if !isEmptyValue(fv) { + return false + } + } + return true +} + +// isZeroValue implements the omitzero rules: the type's own IsZero() when +// implemented, the reflect zero value otherwise. +func isZeroValue(v reflect.Value) bool { + if v.Type().Implements(isZeroerType) { + return v.Interface().(isZeroer).IsZero() + } + if v.CanAddr() && reflect.PtrTo(v.Type()).Implements(isZeroerType) { + return v.Addr().Interface().(isZeroer).IsZero() + } + if !v.CanAddr() && reflect.PtrTo(v.Type()).Implements(isZeroerType) { + tmp := reflect.New(v.Type()) + tmp.Elem().Set(v) + return tmp.Interface().(isZeroer).IsZero() + } + return v.IsZero() +} + +// appendKey emits a key, quoted only if necessary. +func (e *encoderState) appendKey(b []byte, key string) []byte { + if isBareKey(key) { + return append(b, key...) + } + return e.appendString(b, key) +} + +func isBareKey(key string) bool { + if len(key) == 0 { + return false + } + for _, c := range []byte(key) { + if !isUnquotedKeyByte(c) { + return false + } + } + return true +} + +func isUnquotedKeyByte(c byte) bool { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' +} + +// appendValue emits a TOML value. +func (e *encoderState) appendValue(b []byte, v reflect.Value, opts valueOptions, indent int) ([]byte, error) { + t := v.Type() + + // Special types take precedence over their kind. + switch t { + case timeType: + return v.Interface().(time.Time).AppendFormat(b, "2006-01-02T15:04:05.999999999Z07:00"), nil + case localDateType: + return append(b, v.Interface().(LocalDate).String()...), nil + case localTimeType: + return append(b, v.Interface().(LocalTime).String()...), nil + case localDateTimeType: + return append(b, v.Interface().(LocalDateTime).String()...), nil + case jsonNumberType: + if e.marshalJSONNumbers { + return appendJSONNumber(b, v.Interface().(json.Number)) + } + } + + switch encPropsForType(t).text { + case 1: + if t.Kind() != reflect.String { + return e.appendTextMarshaler(b, v.Interface().(encoding.TextMarshaler)) + } + case 2: + if v.CanAddr() { + return e.appendTextMarshaler(b, v.Addr().Interface().(encoding.TextMarshaler)) + } + tmp := reflect.New(t) + tmp.Elem().Set(v) + return e.appendTextMarshaler(b, tmp.Interface().(encoding.TextMarshaler)) + } + + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + // nil pointers are encoded as the zero value of their element + // type. + return e.appendValue(b, reflect.Zero(t.Elem()), opts, indent) + } + return e.appendValue(b, v.Elem(), opts, indent) + case reflect.Interface: + if v.IsNil() { + return nil, errors.New("toml: cannot encode a nil interface") + } + return e.appendValue(b, v.Elem(), opts, indent) + case reflect.String: + s := v.String() + // The "multiline" option only takes effect when the string actually + // contains a newline. Wrapping a single-line value such as "2" in a + // """...""" block adds noise without improving readability, so it is + // emitted as a regular single-line string instead. + if opts.multiline && strings.IndexByte(s, '\n') >= 0 { + return e.appendMultilineString(b, s), nil + } + return e.appendString(b, s), nil + case reflect.Bool: + if v.Bool() { + return append(b, "true"...), nil + } + return append(b, "false"...), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.AppendInt(b, v.Int(), 10), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + u := v.Uint() + if u > math.MaxInt64 { + return nil, fmt.Errorf("toml: cannot encode an unsigned integer above math.MaxInt64: %d", u) + } + return strconv.AppendUint(b, u, 10), nil + case reflect.Float32: + return appendFloat(b, v.Float(), 32), nil + case reflect.Float64: + return appendFloat(b, v.Float(), 64), nil + case reflect.Slice, reflect.Array: + return e.appendArray(b, v, opts, indent) + case reflect.Map: + return e.appendInlineTable(b, v, indent) + case reflect.Struct: + return e.appendInlineTable(b, v, indent) + default: + return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Type()) + } +} + +var jsonNumberType = reflect.TypeOf(json.Number("")) + +func appendJSONNumber(b []byte, n json.Number) ([]byte, error) { + if n == "" { + return append(b, '0'), nil + } + if i, err := n.Int64(); err == nil { + return strconv.AppendInt(b, i, 10), nil + } + f, err := n.Float64() + if err != nil { + return nil, fmt.Errorf("toml: cannot encode json.Number %q: %w", string(n), err) + } + return appendFloat(b, f, 64), nil +} + +func appendFloat(b []byte, f float64, bitSize int) []byte { + switch { + case math.IsNaN(f): + return append(b, "nan"...) + case math.IsInf(f, 1): + return append(b, "inf"...) + case math.IsInf(f, -1): + return append(b, "-inf"...) + } + start := len(b) + b = strconv.AppendFloat(b, f, 'f', -1, bitSize) + // TOML floats must have a fractional part or an exponent. + if !bytes.ContainsAny(b[start:], ".eE") { + b = append(b, ".0"...) + } + return b +} + +func (e *encoderState) appendTextMarshaler(b []byte, m encoding.TextMarshaler) ([]byte, error) { + text, err := m.MarshalText() + if err != nil { + return nil, fmt.Errorf("toml: error calling MarshalText: %w", err) + } + return e.appendString(b, string(text)), nil +} + +// appendArray encodes a slice or array value. +func (e *encoderState) appendArray(b []byte, v reflect.Value, opts valueOptions, indent int) ([]byte, error) { + multiline := opts.multiline || e.arraysMultiline + + b = append(b, '[') + if multiline && v.Len() > 0 { + for i := 0; i < v.Len(); i++ { + if i > 0 { + b = append(b, ',') + } + b = append(b, '\n') + for j := 0; j <= indent; j++ { + b = append(b, e.indentSymbol...) + } + var err error + b, err = e.appendValue(b, v.Index(i), valueOptions{}, indent+1) + if err != nil { + return nil, err + } + } + b = append(b, '\n') + for j := 0; j < indent; j++ { + b = append(b, e.indentSymbol...) + } + } else { + for i := 0; i < v.Len(); i++ { + if i > 0 { + b = append(b, ", "...) + } + var err error + b, err = e.appendValue(b, v.Index(i), valueOptions{}, indent) + if err != nil { + return nil, err + } + } + } + return append(b, ']'), nil +} + +// appendInlineTable encodes a map or a struct as an inline table. +func (e *encoderState) appendInlineTable(b []byte, v reflect.Value, indent int) ([]byte, error) { + entries, err := e.collectEntries(v) + if err != nil { + return nil, err + } + + b = append(b, '{') + for i, ent := range entries { + if i > 0 { + b = append(b, ", "...) + } + b = e.appendKey(b, ent.key) + b = append(b, " = "...) + // multiline strings are not allowed inside inline tables: they + // would break the single-line requirement. + opts := ent.options + opts.multiline = false + b, err = e.appendValue(b, ent.value, opts, indent) + if err != nil { + return nil, err + } + } + e.putEntries(entries) + return append(b, '}'), nil +} + +// appendString encodes a string, using a literal string when possible and a +// basic string otherwise. +func (e *encoderState) appendString(b []byte, s string) []byte { + if canBeLiteral(s) { + b = append(b, '\'') + b = append(b, s...) + return append(b, '\'') + } + return appendBasicString(b, s) +} + +// canBeLiteral returns true when the string can be represented as a TOML +// literal string: no control characters, no single quote, no newline. +func canBeLiteral(s string) bool { + for i := 0; i < len(s); i++ { + c := s[i] + if c == '\'' || c == 0x7f || c < 0x20 { + return false + } + } + return utf8.ValidString(s) +} + +// appendBasicString encodes a string as a TOML basic (double-quoted) string. +func appendBasicString(b []byte, s string) []byte { + b = append(b, '"') + for i := 0; i < len(s); { + c := s[i] + switch { + case c == '"': + b = append(b, '\\', '"') + i++ + case c == '\\': + b = append(b, '\\', '\\') + i++ + case c == '\b': + b = append(b, '\\', 'b') + i++ + case c == '\f': + b = append(b, '\\', 'f') + i++ + case c == '\n': + b = append(b, '\\', 'n') + i++ + case c == '\r': + b = append(b, '\\', 'r') + i++ + case c == '\t': + b = append(b, '\\', 't') + i++ + case c < 0x20 || c == 0x7f: + b = append(b, fmt.Sprintf("\\u%04X", c)...) + i++ + default: + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + // Replace invalid bytes by the replacement character. + b = append(b, fmt.Sprintf("\\u%04X", c)...) + i++ + continue + } + b = append(b, s[i:i+size]...) + i += size + } + } + return append(b, '"') +} + +// appendMultilineString encodes a string as a TOML multi-line basic string. +func appendMultilineString(b []byte, s string) []byte { + b = append(b, `"""`...) + b = append(b, '\n') + for i := 0; i < len(s); { + c := s[i] + switch { + case c == '"': + // Runs of three or more quotes must be escaped. + j := i + for j < len(s) && s[j] == '"' { + j++ + } + if j-i >= 3 { + for ; i < j; i++ { + b = append(b, '\\', '"') + } + } else { + b = append(b, s[i:j]...) + i = j + } + case c == '\\': + b = append(b, '\\', '\\') + i++ + case c == '\n': + b = append(b, '\n') + i++ + case c == '\b': + b = append(b, '\\', 'b') + i++ + case c == '\f': + b = append(b, '\\', 'f') + i++ + case c == '\r': + b = append(b, '\\', 'r') + i++ + case c == '\t': + b = append(b, '\t') + i++ + case c < 0x20 || c == 0x7f: + b = append(b, fmt.Sprintf("\\u%04X", c)...) + i++ + default: + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + b = append(b, fmt.Sprintf("\\u%04X", c)...) + i++ + continue + } + b = append(b, s[i:i+size]...) + i += size + } + } + return append(b, `"""`...) +} + +func (e *encoderState) appendMultilineString(b []byte, s string) []byte { + return appendMultilineString(b, s) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/strict.go b/vendor/github.com/pelletier/go-toml/v2/strict.go new file mode 100644 index 0000000..b58571d --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/strict.go @@ -0,0 +1,108 @@ +package toml + +import ( + "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/unstable" +) + +type strict struct { + Enabled bool + + // Tracks the current key being processed. + key tracker.KeyTracker + + missing []decodeError +} + +// decodeError is the information needed to materialize a DecodeError once the +// whole document is available. +type decodeError struct { + highlight unstable.Range + key Key + message string +} + +// Reset clears the state of the tracker so it can be reused for another +// document. +func (s *strict) Reset() { + s.key = tracker.KeyTracker{} + s.missing = s.missing[:0] +} + +// EnterTable is called when a new table or array table expression starts +// being processed. +func (s *strict) EnterTable(node *unstable.Node) { + if !s.Enabled { + return + } + s.key.UpdateTable(node) +} + +// MissingTable is called when a table is present in the document but has no +// corresponding field in the target. +func (s *strict) MissingTable(node *unstable.Node) { + if !s.Enabled { + return + } + s.missing = append(s.missing, decodeError{ + highlight: keyLocation(node), + key: s.key.Key(), + message: "missing table", + }) +} + +// MissingField is called when a key-value is present in the document but has +// no corresponding field in the target. +func (s *strict) MissingField(node *unstable.Node) { + if !s.Enabled { + return + } + s.key.Push(node) + s.missing = append(s.missing, decodeError{ + highlight: keyLocation(node), + key: s.key.Key(), + message: "unknown field", + }) + s.key.Pop(node) +} + +// Error returns the cumulated StrictMissingError for the document, or nil. +func (s *strict) Error(document []byte) error { + if !s.Enabled || len(s.missing) == 0 { + return nil + } + + err := &StrictMissingError{ + Errors: make([]DecodeError, 0, len(s.missing)), + } + + for _, derr := range s.missing { + highlight := document[derr.highlight.Offset : derr.highlight.Offset+derr.highlight.Length] + err.Errors = append(err.Errors, *newDecodeError(document, highlight, derr.key, derr.message)) + } + + return err +} + +// keyLocation returns the range of the document covering all the parts of +// the key of the given node. +func keyLocation(node *unstable.Node) unstable.Range { + k := node.Key() + + hasOne := k.Next() + if !hasOne { + panic("should not be called with empty key") + } + + start := k.Node().Raw + end := start + + for k.Next() { + end = k.Node().Raw + } + + return unstable.Range{ + Offset: start.Offset, + Length: end.Offset + end.Length - start.Offset, + } +} diff --git a/vendor/github.com/pelletier/go-toml/v2/test-go-versions.sh b/vendor/github.com/pelletier/go-toml/v2/test-go-versions.sh new file mode 100644 index 0000000..5fe5c77 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/test-go-versions.sh @@ -0,0 +1,597 @@ +#!/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.26) +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" + "1.26" +) + +# 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.25 1.26 # 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-6])$ ]]; then + log_error "Invalid Go version: $version. Supported versions: 1.11-1.26" + 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 diff --git a/vendor/github.com/pelletier/go-toml/v2/toml.abnf b/vendor/github.com/pelletier/go-toml/v2/toml.abnf new file mode 100644 index 0000000..473f374 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/toml.abnf @@ -0,0 +1,243 @@ +;; This document describes TOML's syntax, using the ABNF format (defined in +;; RFC 5234 -- https://www.ietf.org/rfc/rfc5234.txt). +;; +;; All valid TOML documents will match this description, however certain +;; invalid documents would need to be rejected as per the semantics described +;; in the supporting text description. + +;; It is possible to try this grammar interactively, using instaparse. +;; http://instaparse.mojombo.com/ +;; +;; To do so, in the lower right, click on Options and change `:input-format` to +;; ':abnf'. Then paste this entire ABNF document into the grammar entry box +;; (above the options). Then you can type or paste a sample TOML document into +;; the beige box on the left. Tada! + +;; Overall Structure + +toml = expression *( newline expression ) + +expression = ws [ comment ] +expression =/ ws keyval ws [ comment ] +expression =/ ws table ws [ comment ] + +;; Whitespace + +ws = *wschar +wschar = %x20 ; Space +wschar =/ %x09 ; Horizontal tab + +;; Newline + +newline = %x0A ; LF +newline =/ %x0D.0A ; CRLF + +;; Comment + +comment-start-symbol = %x23 ; # +non-ascii = %x80-D7FF / %xE000-10FFFF +non-eol = %x09 / %x20-7F / non-ascii + +comment = comment-start-symbol *non-eol + +;; Key-Value pairs + +keyval = key keyval-sep val + +key = simple-key / dotted-key +simple-key = quoted-key / unquoted-key + +unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ +quoted-key = basic-string / literal-string +dotted-key = simple-key 1*( dot-sep simple-key ) + +dot-sep = ws %x2E ws ; . Period +keyval-sep = ws %x3D ws ; = + +val = string / boolean / array / inline-table / date-time / float / integer + +;; String + +string = ml-basic-string / basic-string / ml-literal-string / literal-string + +;; Basic String + +basic-string = quotation-mark *basic-char quotation-mark + +quotation-mark = %x22 ; " + +basic-char = basic-unescaped / escaped +basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii +escaped = escape escape-seq-char + +escape = %x5C ; \ +escape-seq-char = %x22 ; " quotation mark U+0022 +escape-seq-char =/ %x5C ; \ reverse solidus U+005C +escape-seq-char =/ %x62 ; b backspace U+0008 +escape-seq-char =/ %x66 ; f form feed U+000C +escape-seq-char =/ %x6E ; n line feed U+000A +escape-seq-char =/ %x72 ; r carriage return U+000D +escape-seq-char =/ %x74 ; t tab U+0009 +escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX +escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + +;; Multiline Basic String + +ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + ml-basic-string-delim +ml-basic-string-delim = 3quotation-mark +ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + +mlb-content = mlb-char / newline / mlb-escaped-nl +mlb-char = mlb-unescaped / escaped +mlb-quotes = 1*2quotation-mark +mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii +mlb-escaped-nl = escape ws newline *( wschar / newline ) + +;; Literal String + +literal-string = apostrophe *literal-char apostrophe + +apostrophe = %x27 ; ' apostrophe + +literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + +;; Multiline Literal String + +ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + ml-literal-string-delim +ml-literal-string-delim = 3apostrophe +ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + +mll-content = mll-char / newline +mll-char = %x09 / %x20-26 / %x28-7E / non-ascii +mll-quotes = 1*2apostrophe + +;; Integer + +integer = dec-int / hex-int / oct-int / bin-int + +minus = %x2D ; - +plus = %x2B ; + +underscore = %x5F ; _ +digit1-9 = %x31-39 ; 1-9 +digit0-7 = %x30-37 ; 0-7 +digit0-1 = %x30-31 ; 0-1 + +hex-prefix = %x30.78 ; 0x +oct-prefix = %x30.6F ; 0o +bin-prefix = %x30.62 ; 0b + +dec-int = [ minus / plus ] unsigned-dec-int +unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT ) + +hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG ) +oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 ) +bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 ) + +;; Float + +float = float-int-part ( exp / frac [ exp ] ) +float =/ special-float + +float-int-part = dec-int +frac = decimal-point zero-prefixable-int +decimal-point = %x2E ; . +zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT ) + +exp = "e" float-exp-part +float-exp-part = [ minus / plus ] zero-prefixable-int + +special-float = [ minus / plus ] ( inf / nan ) +inf = %x69.6e.66 ; inf +nan = %x6e.61.6e ; nan + +;; Boolean + +boolean = true / false + +true = %x74.72.75.65 ; true +false = %x66.61.6C.73.65 ; false + +;; Date and Time (as defined in RFC 3339) + +date-time = offset-date-time / local-date-time / local-date / local-time + +date-fullyear = 4DIGIT +date-month = 2DIGIT ; 01-12 +date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +time-delim = "T" / %x20 ; T, t, or space +time-hour = 2DIGIT ; 00-23 +time-minute = 2DIGIT ; 00-59 +time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules +time-secfrac = "." 1*DIGIT +time-numoffset = ( "+" / "-" ) time-hour ":" time-minute +time-offset = "Z" / time-numoffset + +partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ] +full-date = date-fullyear "-" date-month "-" date-mday +full-time = partial-time time-offset + +;; Offset Date-Time + +offset-date-time = full-date time-delim full-time + +;; Local Date-Time + +local-date-time = full-date time-delim partial-time + +;; Local Date + +local-date = full-date + +;; Local Time + +local-time = partial-time + +;; Array + +array = array-open [ array-values ] ws-comment-newline array-close + +array-open = %x5B ; [ +array-close = %x5D ; ] + +array-values = ws-comment-newline val ws-comment-newline array-sep array-values +array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + +array-sep = %x2C ; , Comma + +ws-comment-newline = *( wschar / [ comment ] newline ) + +;; Table + +table = std-table / array-table + +;; Standard Table + +std-table = std-table-open key std-table-close + +std-table-open = %x5B ws ; [ Left square bracket +std-table-close = ws %x5D ; ] Right square bracket + +;; Inline Table + +inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + +inline-table-open = %x7B ws ; { +inline-table-close = ws %x7D ; } +inline-table-sep = ws %x2C ws ; , Comma + +inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + +;; Array Table + +array-table = array-table-open key array-table-close + +array-table-open = %x5B.5B ws ; [[ Double left square bracket +array-table-close = ws %x5D.5D ; ]] Double right square bracket + +;; Built-in ABNF terms, reproduced here for clarity + +ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +DIGIT = %x30-39 ; 0-9 +HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" diff --git a/vendor/github.com/pelletier/go-toml/v2/types.go b/vendor/github.com/pelletier/go-toml/v2/types.go new file mode 100644 index 0000000..420f198 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/types.go @@ -0,0 +1,24 @@ +package toml + +import ( + "encoding" + "reflect" + "time" +) + +// isZeroer is used to check whether a value is the zero value for its type, +// as defined by the type itself. +type isZeroer interface { + IsZero() bool +} + +var isZeroerType = reflect.TypeOf(new(isZeroer)).Elem() + +var ( + timeType = reflect.TypeOf(time.Time{}) + textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() + textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() + mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil)) + sliceInterfaceType = reflect.TypeOf([]interface{}(nil)) + stringType = reflect.TypeOf("") +) diff --git a/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go b/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go new file mode 100644 index 0000000..d497669 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go @@ -0,0 +1,2334 @@ +package toml + +import ( + "encoding" + "errors" + "fmt" + "io" + "math" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/unstable" +) + +// decoderPool recycles decoders (and their internal buffers: parser arena, +// seen-tracker entries, scratch buffers) across calls to Unmarshal and +// Decode. +var decoderPool = sync.Pool{ + New: func() interface{} { return &decoder{} }, +} + +func getDecoder(strictMode, unmarshalerInterface bool) *decoder { + d := decoderPool.Get().(*decoder) + d.reset() + d.strict.Enabled = strictMode + d.unmarshalerInterface = unmarshalerInterface + return d +} + +func putDecoder(d *decoder) { + decoderPool.Put(d) +} + +// reset clears the per-document state of the decoder, keeping the allocated +// buffers for reuse. +func (d *decoder) reset() { + d.seen.Reset() + d.tableKey = d.tableKey[:0] + d.skipUntilTable = false + d.path = d.path[:0] + d.captures = d.captures[:0] + d.captureIdx = -1 + d.segIdx = d.segIdx[:0] + // Reuse the array-table counter slots across documents instead of + // deleting them: a zeroed slot is indistinguishable from an absent one, + // and keeping it alive means setArrayCount does not have to allocate a new + // *int every time the same path reappears. A safety valve bounds the table + // for adversarial inputs that introduce unboundedly many distinct paths. + if len(d.arrayCounts) > 1<<14 { + d.arrayCounts = nil + } else { + for _, p := range d.arrayCounts { + *p = 0 + } + } + d.tableTarget = reflect.Value{} + d.tableTargetValid = false + d.tableFlush = d.tableFlush[:0] + d.tableParentSlot = slotWriter{} + d.keyParts = d.keyParts[:0] + d.strict.Reset() +} + +// Unmarshal deserializes a TOML document into a Go value. +// +// It is a shortcut for Decoder.Decode() with the default options. +func Unmarshal(data []byte, v interface{}) error { + d := getDecoder(false, false) + err := d.unmarshal(data, v) + putDecoder(d) + return err +} + +// Decoder reads and decode a TOML document from an input stream. +type Decoder struct { + // input + r io.Reader + + // global settings + strict bool + + // toggles unmarshaler interface + unmarshalerInterface bool +} + +// NewDecoder creates a new Decoder that will read from r. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +// DisallowUnknownFields causes the Decoder to return an error when the +// destination is a struct and the input contains a key that does not match a +// non-ignored field. +// +// In that case, the Decoder returns a StrictMissingError that can be used to +// retrieve the individual errors as well as generate a human readable +// description of the missing fields. +func (d *Decoder) DisallowUnknownFields() *Decoder { + d.strict = true + 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. +// +// The UnmarshalTOML method receives raw TOML bytes: +// - For single values: the raw value bytes (e.g., `"hello"` for a string) +// - For tables: all key-value lines belonging to that table +// - For inline tables/arrays: the raw bytes of the inline structure +// +// The unstable.RawMessage type can be used to capture raw TOML bytes for +// later processing, similar to json.RawMessage. +// +// *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 +// are ignored. See Decoder.DisallowUnknownFields() to change this behavior. +// +// When a TOML local date, time, or date-time is decoded into a time.Time, its +// value is represented in time.Local timezone. Otherwise the appropriate Local* +// structure is used. For time values, precision up to the nanosecond is +// supported by truncating extra digits. +// +// Empty tables decoded in an interface{} create an empty initialized +// map[string]interface{}. +// +// Types implementing the encoding.TextUnmarshaler interface are decoded from a +// TOML string. +// +// When decoding a number, go-toml will return an error if the number is out of +// bounds for the target type (which includes negative numbers when decoding +// into an unsigned int). +// +// If an error occurs while decoding the content of the document, this function +// returns a toml.DecodeError, providing context about the issue. When using +// strict mode and a field is missing, a `toml.StrictMissingError` is +// returned. In any other case, this function returns a standard Go error. +// +// # Type mapping +// +// List of supported TOML types and their associated accepted Go types: +// +// String -> string +// Integer -> uint*, int*, depending on size +// Float -> float*, depending on size +// Boolean -> bool +// Offset Date-Time -> time.Time +// Local Date-time -> LocalDateTime, time.Time +// Local Date -> LocalDate, time.Time +// Local Time -> LocalTime, time.Time +// Array -> slice and array, depending on elements types +// Table -> map and struct +// Inline Table -> same as Table +// Array of Tables -> same as Array and Table +func (d *Decoder) Decode(v interface{}) error { + b, err := io.ReadAll(d.r) + if err != nil { + return fmt.Errorf("toml: %w", err) + } + + dec := getDecoder(d.strict, d.unmarshalerInterface) + err = dec.unmarshal(b, v) + putDecoder(dec) + return err +} + +// pathPart is one part of the key path leading to a value. Parts that come +// from the current table header only carry a name; parts that come from the +// key of the current key-value expression also carry the AST node, and their +// name is materialized lazily to avoid allocations. +type pathPart struct { + name string + node *unstable.Node +} + +// bytes returns the raw bytes of the key part. +func (p *pathPart) bytes() []byte { + if p.node != nil { + return p.node.Data + } + return []byte(p.name) +} + +// str returns the key part as a string, possibly allocating. +func (p *pathPart) str() string { + if p.node != nil { + return string(p.node.Data) + } + return p.name +} + +// rawCapture accumulates the raw bytes fed to a type implementing +// unstable.Unmarshaler for a table target. The target is identified by the +// parts of its key and the array-table indexes in effect when the capture +// was created, so that it can be located again once the whole document has +// been processed (the address of the target may change as slices grow). +type rawCapture struct { + names []string + // indexes[i] is the index to use when reaching a slice or array right + // before consuming names[i]. indexes[len(names)] is the index of the + // element when the target is an element of an array table. -1 when not + // relevant. + indexes []int + buf []byte +} + +type decoder struct { + p unstable.Parser + + // strict mode + strict strict + + // toggles unmarshaler interface + unmarshalerInterface bool + + // tracks the duplicate and type consistency of the keys + seen tracker.SeenTracker + + // path of the current table header, as copied strings + tableKey []string + + // true when the expressions under the current table header cannot be + // stored anywhere and should be skipped + skipUntilTable bool + + // scratch buffer for the key path of the current expression + path []pathPart + + // raw captures for the unmarshaler interface, in order of first + // appearance. captureIdx is the index of the capture the current table + // belongs to, or -1. + captures []rawCapture + captureIdx int + + // segIdx[i] records the array element index used when traversing a + // slice or array right before consuming the i-th part of the current + // table key. Reset for each table expression. + segIdx []int + + // arrayCounts tracks the number of elements appended to fixed-size + // arrays used as array tables, keyed by the NUL-joined key parts. + // Values are pointer slots so that updating an existing path does not + // allocate a new key string. + arrayCounts map[string]*int + + // Cached target of the current table, so that key-values do not need to + // walk the document structure from the root for every expression. + // tableFlush holds the write-backs to perform when leaving the table + // (for targets reached through map values, which are copies). + // tableParentSlot stores a replacement of the target itself (e.g. a nil + // map that was allocated) into its parent. + tableTarget reflect.Value + tableTargetValid bool + tableFlush []flushOp + tableParentSlot slotWriter + + // strKey is a reusable string value used as map key, so that map + // operations with string keys do not need to allocate a boxed key for + // every access. It must be refreshed with stringMapKey immediately + // before each use: any recursive call may overwrite it. + strKey reflect.Value + + // interned de-duplicates key strings: documents repeat the same keys + // over and over, and the table survives pooling, so repeated decodes + // of similar documents stop allocating key strings altogether. + interned map[string]string + + // pathScratch is the buffer used by joinPath. + pathScratch []byte + + // keyParts is the reusable buffer holding the decoded parts of the key of + // the current expression in the fused generic decode path. + keyParts [][]byte +} + +// slotWriter remembers how to store a value at some location of the target +// structure. Implemented as a struct instead of a closure to avoid +// allocations. +type slotWriter struct { + kind uint8 // 0: none, 1: slot.Set, 2: m.SetMapIndex(k, ...), 3: m.SetMapIndex(string key ks, ...) + slot reflect.Value + m reflect.Value + k reflect.Value + ks string +} + +func (d *decoder) storeSlot(s *slotWriter, nv reflect.Value) { + switch s.kind { + case 1: + if s.slot.CanSet() { + s.slot.Set(nv) + } + case 2: + s.m.SetMapIndex(s.k, nv) + case 3: + s.m.SetMapIndex(d.stringMapKey(s.ks), nv) + } +} + +// flushOp stores val using w when the table is flushed. +type flushOp struct { + w slotWriter + val reflect.Value +} + +// flushTable performs the pending write-backs of the cached table target, in +// reverse order so that inner copies land before their parents are stored. +func (d *decoder) flushTable() { + for i := len(d.tableFlush) - 1; i >= 0; i-- { + d.storeSlot(&d.tableFlush[i].w, d.tableFlush[i].val) + } + d.tableFlush = d.tableFlush[:0] + d.tableTargetValid = false + d.tableParentSlot = slotWriter{} + d.tableTarget = reflect.Value{} +} + +// intern returns the string corresponding to the given bytes, reusing a +// previous allocation when the same key has been seen before. +func (d *decoder) intern(b []byte) string { + if s, ok := d.interned[string(b)]; ok { // does not allocate + return s + } + if d.interned == nil { + d.interned = make(map[string]string, 64) + } else if len(d.interned) >= 1<<14 { + // Safety valve for adversarial inputs: do not let the table grow + // without bounds. + for k := range d.interned { + delete(d.interned, k) + } + } + s := string(b) + d.interned[s] = s + return s +} + +// partString returns the name of a path part, interning it when it comes +// from the document. +func (d *decoder) partString(p *pathPart) string { + if p.node != nil { + return d.intern(p.node.Data) + } + return p.name +} + +// stringMapKey returns a reflect.Value holding the given string, reusing the +// same allocation every time. The result must be used (the map operation +// performed) before any recursive call, which may overwrite the buffer. +func (d *decoder) stringMapKey(s string) reflect.Value { + if !d.strKey.IsValid() { + d.strKey = reflect.New(stringType).Elem() + } + d.strKey.SetString(s) + return d.strKey +} + +// joinPath builds the NUL-joined representation of a key path in the +// decoder's scratch buffer. The result is only valid until the next call. +func (d *decoder) joinPath(parts []string) []byte { + d.pathScratch = d.pathScratch[:0] + for i, p := range parts { + if i > 0 { + d.pathScratch = append(d.pathScratch, 0) + } + d.pathScratch = append(d.pathScratch, p...) + } + return d.pathScratch +} + +// arrayCount returns the number of elements appended so far to the array +// table at the given path. +func (d *decoder) arrayCount(key []byte) int { + if d.arrayCounts == nil { + return 0 + } + if p := d.arrayCounts[string(key)]; p != nil { // does not allocate + return *p + } + return 0 +} + +func (d *decoder) setArrayCount(key []byte, n int) { + if d.arrayCounts == nil { + d.arrayCounts = map[string]*int{} + } + if p := d.arrayCounts[string(key)]; p != nil { // does not allocate + *p = n + return + } + v := n + d.arrayCounts[string(key)] = &v +} + +// resetChildArrayCounts forgets the counts of all the array tables under +// the given path, so that a new element starts fresh. +func (d *decoder) resetChildArrayCounts(key []byte) { + if len(d.arrayCounts) == 0 { + return + } + for k, p := range d.arrayCounts { + // Prefix match without building the prefix string: same bytes as + // key, followed by the NUL separator. + if len(k) > len(key) && k[len(key)] == 0 && k[:len(key)] == string(key) { + // Zero instead of delete: the next element of the parent table + // will reuse the slot without allocating a new key. + *p = 0 + } + } +} + +func (d *decoder) typeMismatchError(toml string, target reflect.Type, highlight []byte) error { + return &typeMismatchError{ + toml: toml, + target: target, + highlight: highlight, + } +} + +type typeMismatchError struct { + toml string + target reflect.Type + highlight []byte + // key is the TOML key being processed when the mismatch occurred. It is + // populated lazily as the error propagates back up to the key-value + // handler (see contextualizeError). + key Key +} + +func (e *typeMismatchError) Error() string { + return fmt.Sprintf("cannot decode TOML %s into %s", e.toml, e.target) +} + +// contextualizeError attaches the TOML key currently being processed to errors +// raised while decoding a key-value expression, so that DecodeError.Key() +// reports the offending key (e.g. on type mismatch errors). The current key is +// reconstructed from d.path; when the table target is cached, d.path holds only +// the key-value parts, so the table key prefix is prepended. This only runs on +// the error path and adds no cost to successful decodes. +func (d *decoder) contextualizeError(err error, withTableKey bool) error { + var mm *typeMismatchError + if errors.As(err, &mm) { + if mm.key == nil { + mm.key = d.currentKey(withTableKey) + } + return err + } + var perr *unstable.ParserError + if errors.As(err, &perr) { + if perr.Key == nil { + perr.Key = d.currentKey(withTableKey) + } + } + return err +} + +// currentKey reconstructs the full TOML key being processed from the decoder's +// path. When withTableKey is true, d.path contains only the key-value parts +// (the table target is cached) and the table key is prepended. +func (d *decoder) currentKey(withTableKey bool) Key { + n := len(d.path) + if withTableKey { + n += len(d.tableKey) + } + key := make(Key, 0, n) + if withTableKey { + key = append(key, d.tableKey...) + } + for i := range d.path { + key = append(key, d.path[i].str()) + } + return key +} + +func (d *decoder) unmarshal(data []byte, v interface{}) error { + r := reflect.ValueOf(v) + if r.Kind() != reflect.Ptr { + return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind()) + } + if r.IsNil() { + return errors.New("toml: decoding pointer target cannot be nil") + } + + root := r.Elem() + + d.captureIdx = -1 + d.p.Reset(data) + + // Fully generic targets (interface{} or map[string]interface{}) are + // decoded straight into native Go maps and slices, with no reflection on + // the document structure at all. This covers the common "decode arbitrary + // TOML into a map" case, including every standard benchmark dataset. + if !d.unmarshalerInterface { + if k := root.Kind(); k == reflect.Interface || (k == reflect.Map && root.Type() == mapStringInterfaceType) { + return d.unmarshalFused(root, data) + } + } + + for d.p.NextExpression() { + err := d.handleRootExpression(d.p.Expression(), root) + if err != nil { + return d.wrapError(data, err) + } + } + if err := d.p.Error(); err != nil { + var perr *unstable.ParserError + if errors.As(err, &perr) { + return wrapDecodeError(data, perr) + } + return err + } + + d.flushTable() + + // Deliver the accumulated raw documents to the unmarshaler-interface + // targets. + for i := range d.captures { + nv, err := d.resolveCapture(root, &d.captures[i], 0, false) + if err != nil { + return err + } + if nv.IsValid() { + root.Set(nv) + } + } + + // An empty document into a generic target still initializes it. + switch root.Kind() { + case reflect.Map: + if root.IsNil() { + root.Set(reflect.MakeMap(root.Type())) + } + case reflect.Interface: + if root.IsNil() { + root.Set(reflect.ValueOf(map[string]interface{}{})) + } + default: + } + + return d.strict.Error(data) +} + +// setAnyKey assigns the value of a key-value into the native map m, following +// the (possibly dotted) key and creating intermediate maps as needed. +func (d *decoder) setAnyKey(m map[string]interface{}, key unstable.Iterator, value *unstable.Node) error { + cur := m + for key.Next() { + name := d.intern(key.Node().Data) + if key.IsLast() { + av, err := d.decodeAny(value) + if err != nil { + return err + } + cur[name] = av + return nil + } + cur = d.anyChildTable(cur, name) + } + return nil +} + +// anyChildTable returns the child table at name within cur, creating it if +// absent and descending into the current (last) element when an array table +// occupies the slot. A non-container in the slot cannot occur for a document +// the seen-tracker has accepted. +func (d *decoder) anyChildTable(cur map[string]interface{}, name string) map[string]interface{} { + switch v := cur[name].(type) { + case map[string]interface{}: + return v + case []interface{}: + if len(v) > 0 { + if last, ok := v[len(v)-1].(map[string]interface{}); ok { + return last + } + } + } + nm := map[string]interface{}{} + cur[name] = nm + return nm +} + +// wrapError gives document context to errors generated while processing an +// expression. +func (d *decoder) wrapError(data []byte, err error) error { + var perr *unstable.ParserError + if errors.As(err, &perr) { + return wrapDecodeError(data, perr) + } + var mm *typeMismatchError + if errors.As(err, &mm) { + return wrapDecodeError(data, &unstable.ParserError{ + Highlight: mm.highlight, + Message: mm.Error(), + Key: mm.key, + }) + } + return err +} + +// wrapSeenError turns an error returned by SeenTracker.CheckExpression into a +// ParserError carrying the position and key of the offending expression, so +// that redefinition and duplicate-key errors are reported as a DecodeError +// with context (see issue #668). +// +// The highlight spans the expression's key. Unlike Node.Raw, key nodes always +// carry a Raw range, so this works for tables and array tables too (whose own +// Raw range is not set by the parser). For a duplicate detected inside an +// inline table, node is the enclosing key-value expression, so the error +// points at that expression's key. +func (d *decoder) wrapSeenError(node *unstable.Node, err error) error { + if err == nil { + return nil + } + + var key Key + var start, end unstable.Range + it := node.Key() + for it.Next() { + n := it.Node() + key = append(key, string(n.Data)) + if len(key) == 1 { + start = n.Raw + } + end = n.Raw + } + + var highlight []byte + if len(key) > 0 { + highlight = d.p.Raw(unstable.Range{ + Offset: start.Offset, + Length: end.Offset + end.Length - start.Offset, + }) + } + + return &unstable.ParserError{ + Highlight: highlight, + Message: strings.TrimPrefix(err.Error(), "toml: "), + Key: key, + } +} + +func (d *decoder) handleRootExpression(expr *unstable.Node, root reflect.Value) error { + first, err := d.seen.CheckExpression(expr) + if err != nil { + return d.wrapSeenError(expr, err) + } + + switch expr.Kind { + case unstable.KeyValue: + if d.skipUntilTable { + return nil + } + if d.captureIdx >= 0 { + d.captureKeyValue(expr) + return nil + } + return d.handleKeyValueExpression(expr, root) + case unstable.Table: + d.flushTable() + d.skipUntilTable = false + d.captureIdx = -1 + d.strict.EnterTable(expr) + return d.handleTableExpression(expr, root, false, first) + case unstable.ArrayTable: + d.flushTable() + d.skipUntilTable = false + d.captureIdx = -1 + d.strict.EnterTable(expr) + return d.handleTableExpression(expr, root, true, first) + default: + return unstable.NewParserError(expr.Data, "unsupported expression kind %s", expr.Kind) + } +} + +// updateTableKey copies the parts of the key of a table expression into +// tableKey. +func (d *decoder) updateTableKey(expr *unstable.Node) { + d.tableKey = d.tableKey[:0] + it := expr.Key() + for it.Next() { + d.tableKey = append(d.tableKey, d.intern(it.Node().Data)) + } +} + +func (d *decoder) handleTableExpression(expr *unstable.Node, root reflect.Value, isArrayTable bool, first bool) error { + d.updateTableKey(expr) + + // Check whether this table belongs to an exisiting raw capture (split + // tables, or children of a table assigned to an Unmarshaler). + if d.unmarshalerInterface { + if d.resumeCapture(expr) { + return nil + } + } + + // Reset the per-segment array indexes. + d.segIdx = d.segIdx[:0] + for i := 0; i <= len(d.tableKey); i++ { + d.segIdx = append(d.segIdx, -1) + } + + return d.walkTable(root, expr, isArrayTable, first) +} + +// newContainerElem returns a fresh element for a slice of the given element +// type. Plain interface elements start out as an empty table. +func newContainerElem(et reflect.Type) reflect.Value { + if et == interfaceType { + return reflect.ValueOf(map[string]interface{}{}) + } + return reflect.New(et).Elem() +} + +// walkTable processes a [table] or [[array table]] header: it creates the +// intermediate containers, appends array-table elements, applies the strict +// policy, registers unmarshaler-interface captures, and caches the target +// container so that the key-values that follow are stored directly. +// +// Map values are not addressable: when one needs in-place mutations (struct +// or array values), a copy is made and registered to be stored back when the +// table changes (see flushTable). Maps and slices are references and are +// traversed without copies. +func (d *decoder) walkTable(root reflect.Value, expr *unstable.Node, isArrayTable bool, first bool) error { + v := root + pf := slotWriter{kind: 1, slot: root} + idx := 0 + +walk: + for { + // Dereference pointers in place. + for v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + elem := v.Elem() + pf = slotWriter{kind: 1, slot: elem} + v = elem + } + + // Tables assigned to a type implementing the unmarshaler interface + // are captured as raw bytes, delivered once the document is read. + if d.unmarshalerInterface && hasUnmarshaler(v) { + d.startCapture(idx, expr) + return nil + } + + if idx >= len(d.tableKey) { + break walk + } + + name := d.tableKey[idx] + + switch v.Kind() { + case reflect.Interface: + if !v.IsNil() { + c := v.Elem() + if k := c.Kind(); k == reflect.Map || k == reflect.Slice { + // Reference types: mutations are visible through the + // existing interface value. + v = c + continue + } + } + // Anything else is replaced by a fresh generic map. + if !mapStringInterfaceType.AssignableTo(v.Type()) { + return unstable.NewParserError(d.p.Raw(expr.Raw), "cannot store a table in a %s", v.Type()) + } + fresh := reflect.ValueOf(map[string]interface{}{}) + d.storeSlot(&pf, fresh) + v = fresh + case reflect.Slice: + if v.Len() == 0 { + // Implicit creation of the first element: the array table + // that would create it has not been seen yet (issue 995). + if v.IsNil() { + v = reflect.MakeSlice(v.Type(), 0, 4) + } + v = reflect.Append(v, newContainerElem(v.Type().Elem())) + d.storeSlot(&pf, v) + } + n := v.Len() - 1 + d.segIdx[idx] = n + elem := v.Index(n) + pf = slotWriter{kind: 1, slot: elem} + v = elem + case reflect.Array: + key := d.joinPath(d.tableKey[:idx]) + cnt := d.arrayCount(key) + if cnt == 0 { + cnt = 1 + d.setArrayCount(key, 1) + } + if cnt > v.Len() { + return unstable.NewParserError(d.p.Raw(expr.Raw), "cannot reach element %d of array of size %d", cnt-1, v.Len()) + } + d.segIdx[idx] = cnt - 1 + elem := v.Index(cnt - 1) + pf = slotWriter{kind: 1, slot: elem} + v = elem + case reflect.Map: + if v.IsNil() { + nm := reflect.MakeMap(v.Type()) + d.storeSlot(&pf, nm) + v = nm + } + var key reflect.Value + var w slotWriter + if v.Type().Key() == stringType { + key = d.stringMapKey(name) + w = slotWriter{kind: 3, m: v, ks: name} + } else { + k, err := makeMapKey(v.Type().Key(), name) + if err != nil { + return err + } + key = k + w = slotWriter{kind: 2, m: v, k: k} + } + + elem := v.MapIndex(key) + + // The last part of an array table is finalized as a slice + // container: do not materialize a table for it. + if isArrayTable && idx == len(d.tableKey)-1 { + et := v.Type().Elem() + switch et.Kind() { + case reflect.Interface, reflect.Slice, reflect.Array: + if elem.IsValid() { + v = elem + } else { + v = reflect.Zero(et) + } + pf = w + idx++ + continue + default: + } + } + + if elem.IsValid() { + ce := elem + ceIface := false + if ce.Kind() == reflect.Interface { + ceIface = true + if !ce.IsNil() { + ce = ce.Elem() + } + } + switch ce.Kind() { + case reflect.Map, reflect.Slice: + pf = w + v = ce + case reflect.Ptr: + if ce.IsNil() { + np := reflect.New(ce.Type().Elem()) + d.storeSlot(&w, np) + ce = np + } + pf = w + v = ce + case reflect.Struct, reflect.Array: + if ceIface { + // Interface-held non-generic content is replaced. + fresh := reflect.ValueOf(map[string]interface{}{}) + d.storeSlot(&w, fresh) + pf = w + v = fresh + } else { + tmp := reflect.New(elem.Type()).Elem() + tmp.Set(elem) + d.tableFlush = append(d.tableFlush, flushOp{w: w, val: tmp}) + pf = slotWriter{kind: 1, slot: tmp} + v = tmp + } + default: + if !ceIface { + return unstable.NewParserError(d.p.Raw(expr.Raw), "cannot store a table in a %s", ce.Type()) + } + fresh := reflect.ValueOf(map[string]interface{}{}) + d.storeSlot(&w, fresh) + pf = w + v = fresh + } + } else { + et := v.Type().Elem() + switch et.Kind() { + case reflect.Interface: + if !mapStringInterfaceType.AssignableTo(et) { + return unstable.NewParserError(d.p.Raw(expr.Raw), "cannot store a table in a %s", et) + } + fresh := reflect.ValueOf(map[string]interface{}{}) + d.storeSlot(&w, fresh) + pf = w + v = fresh + case reflect.Map: + nm := reflect.MakeMap(et) + d.storeSlot(&w, nm) + pf = w + v = nm + case reflect.Ptr: + np := reflect.New(et.Elem()) + d.storeSlot(&w, np) + pf = w + v = np + case reflect.Struct, reflect.Array, reflect.Slice: + tmp := reflect.New(et).Elem() + d.tableFlush = append(d.tableFlush, flushOp{w: w, val: tmp}) + pf = slotWriter{kind: 1, slot: tmp} + v = tmp + default: + return unstable.NewParserError(d.p.Raw(expr.Raw), "cannot store a table in a %s", et) + } + } + idx++ + case reflect.Struct: + plan := planForType(v.Type()) + f, found := plan.lookup(name) + if !found { + d.strict.MissingTable(expr) + d.skipUntilTable = true + return nil + } + fv := fieldByIndexAlloc(v, f.index) + pf = slotWriter{kind: 1, slot: fv} + v = fv + idx++ + default: + return unstable.NewParserError(d.p.Raw(expr.Raw), "cannot store a table in a %s", v.Kind()) + } + } + + if isArrayTable { + akey := d.joinPath(d.tableKey) + d.resetChildArrayCounts(akey) + + // Unwrap an interface container. + if v.Kind() == reflect.Interface { + var slice []interface{} + if !v.IsNil() { + if s, ok := v.Elem().Interface().([]interface{}); ok { + slice = s + } + } + if first { + slice = slice[:0] + } + m := map[string]interface{}{} + slice = append(slice, m) + sv := reflect.ValueOf(slice) + d.storeSlot(&pf, sv) + d.setArrayCount(akey, len(slice)) + d.segIdx[len(d.tableKey)] = len(slice) - 1 + d.tableTarget = reflect.ValueOf(m) + d.tableParentSlot = slotWriter{kind: 1, slot: sv.Index(len(slice) - 1)} + d.tableTargetValid = true + return nil + } + + switch v.Kind() { + case reflect.Slice: + if v.IsNil() { + v = reflect.MakeSlice(v.Type(), 0, 4) + } else if first { + v = v.Slice(0, 0) + } + v = reflect.Append(v, newContainerElem(v.Type().Elem())) + d.storeSlot(&pf, v) + n := v.Len() - 1 + d.setArrayCount(akey, n+1) + d.segIdx[len(d.tableKey)] = n + elem := v.Index(n) + if d.unmarshalerInterface && hasUnmarshaler(elem) { + d.startCapture(len(d.tableKey), expr) + return nil + } + pf = slotWriter{kind: 1, slot: elem} + v = elem + case reflect.Array: + cnt := d.arrayCount(akey) + if first { + cnt = 0 + } + if cnt >= v.Len() { + return unstable.NewParserError(d.p.Raw(expr.Raw), "array of size %d is too small to store this array table", v.Len()) + } + v.Index(cnt).Set(reflect.Zero(v.Type().Elem())) + d.setArrayCount(akey, cnt+1) + d.segIdx[len(d.tableKey)] = cnt + elem := v.Index(cnt) + if d.unmarshalerInterface && hasUnmarshaler(elem) { + d.startCapture(len(d.tableKey), expr) + return nil + } + pf = slotWriter{kind: 1, slot: elem} + v = elem + default: + return fmt.Errorf("toml: cannot store an array table in a %s", v.Kind()) + } + } + + // Settle on the concrete container for the key-values that follow. + for { + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + if !v.CanSet() { + return nil + } + v.Set(reflect.New(v.Type().Elem())) + } + elem := v.Elem() + pf = slotWriter{kind: 1, slot: elem} + v = elem + continue + case reflect.Interface: + if !v.IsNil() { + c := v.Elem() + if c.Type() == mapStringInterfaceType || c.Type() == sliceInterfaceType { + v = c + continue + } + } + if !mapStringInterfaceType.AssignableTo(v.Type()) { + return fmt.Errorf("toml: cannot store a table in a %s", v.Type()) + } + fresh := reflect.ValueOf(map[string]interface{}{}) + d.storeSlot(&pf, fresh) + v = fresh + continue + case reflect.Slice: + if v.Len() == 0 { + if v.IsNil() { + v = reflect.MakeSlice(v.Type(), 0, 4) + } + v = reflect.Append(v, newContainerElem(v.Type().Elem())) + d.storeSlot(&pf, v) + } + n := v.Len() - 1 + d.segIdx[len(d.tableKey)] = n + elem := v.Index(n) + pf = slotWriter{kind: 1, slot: elem} + v = elem + continue + case reflect.Map, reflect.Struct: + d.tableTarget = v + d.tableParentSlot = pf + d.tableTargetValid = true + return nil + default: + return fmt.Errorf("toml: cannot store a table in a %s", v.Kind()) + } + } +} + +// resumeCapture looks for an existing capture this table expression belongs +// to. It returns true if the expression was consumed. +func (d *decoder) resumeCapture(expr *unstable.Node) bool { + // Iterate in reverse, so that tables attach to the latest element of + // array tables. + for i := len(d.captures) - 1; i >= 0; i-- { + c := &d.captures[i] + if len(d.tableKey) < len(c.names) { + continue + } + if expr.Kind == unstable.ArrayTable && len(d.tableKey) == len(c.names) { + // A new element of an array table is not part of the capture of + // the previous element. + continue + } + match := true + for j, p := range c.names { + if d.tableKey[j] != p { + match = false + break + } + } + if !match { + continue + } + d.captureIdx = i + if len(d.tableKey) > len(c.names) { + d.appendCaptureHeader(c, expr, len(c.names)) + } + return true + } + return false +} + +// appendCaptureHeader writes the table header of expr in the capture buffer, +// adjusted to be relative to the capture root. +func (d *decoder) appendCaptureHeader(c *rawCapture, expr *unstable.Node, skip int) { + c.buf = append(c.buf, '[') + if expr.Kind == unstable.ArrayTable { + c.buf = append(c.buf, '[') + } + c.buf = append(c.buf, d.rawKeySuffix(expr, skip)...) + c.buf = append(c.buf, ']') + if expr.Kind == unstable.ArrayTable { + c.buf = append(c.buf, ']') + } + c.buf = append(c.buf, '\n') +} + +// rawKeySuffix returns the raw bytes of the key of the expression, skipping +// the first n parts. +func (d *decoder) rawKeySuffix(expr *unstable.Node, n int) []byte { + it := expr.Key() + idx := 0 + var start, end unstable.Range + for it.Next() { + if idx >= n { + r := it.Node().Raw + if start.Length == 0 && start.Offset == 0 && idx == n { + start = r + } + end = r + } + idx++ + } + return d.p.Data()[start.Offset : end.Offset+end.Length] +} + +// startCapture registers a new capture for the table at the given path +// (prefix of tableKey). +func (d *decoder) startCapture(pathLen int, expr *unstable.Node) { + names := make([]string, pathLen) + copy(names, d.tableKey[:pathLen]) + indexes := make([]int, pathLen+1) + copy(indexes, d.segIdx[:pathLen+1]) + d.captures = append(d.captures, rawCapture{ + names: names, + indexes: indexes, + }) + d.captureIdx = len(d.captures) - 1 + if pathLen < len(d.tableKey) { + d.appendCaptureHeader(&d.captures[d.captureIdx], expr, pathLen) + } +} + +// resolveCapture walks back to the target of a capture and delivers the +// accumulated raw bytes to its UnmarshalTOML implementation. +func (d *decoder) resolveCapture(v reflect.Value, c *rawCapture, idx int, indexed bool) (reflect.Value, error) { + if v.Kind() == reflect.Ptr { + if v.Type().Implements(unmarshalerType) && idx == len(c.names) { + u, _ := unmarshalerOf(v) + return v, u.UnmarshalTOML(c.buf) + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + nv, err := d.resolveCapture(v.Elem(), c, idx, indexed) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + v.Elem().Set(nv) + } + return v, nil + } + + if !indexed && (v.Kind() == reflect.Slice || v.Kind() == reflect.Array) && c.indexes[idx] >= 0 { + i := c.indexes[idx] + if i >= v.Len() { + return reflect.Value{}, errors.New("toml: internal error: capture index out of range") + } + elem := v.Index(i) + nv, err := d.resolveCapture(elem, c, idx, true) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + elem.Set(nv) + } + return v, nil + } + + if idx == len(c.names) { + u, ok := unmarshalerOf(v) + if !ok { + return reflect.Value{}, errors.New("toml: internal error: capture target does not implement UnmarshalTOML") + } + return v, u.UnmarshalTOML(c.buf) + } + + name := c.names[idx] + + switch v.Kind() { + case reflect.Struct: + plan := planForType(v.Type()) + f, found := plan.lookup(name) + if !found { + return v, nil + } + fv := fieldByIndexAlloc(v, f.index) + nv, err := d.resolveCapture(fv, c, idx+1, false) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() && fv.CanSet() { + fv.Set(nv) + } + return v, nil + case reflect.Map: + key, err := makeMapKey(v.Type().Key(), name) + if err != nil { + return reflect.Value{}, err + } + if v.IsNil() { + v = reflect.MakeMap(v.Type()) + } + elem := reflect.New(v.Type().Elem()).Elem() + if existing := v.MapIndex(key); existing.IsValid() { + elem.Set(existing) + } + nv, err := d.resolveCapture(elem, c, idx+1, false) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + v.SetMapIndex(key, nv) + } + return v, nil + case reflect.Interface: + elem := elemOrNewMap(v) + nv, err := d.resolveCapture(elem, c, idx, indexed) + if err != nil || !nv.IsValid() { + return reflect.Value{}, err + } + return nv, nil + default: + return reflect.Value{}, fmt.Errorf("toml: internal error: cannot resolve capture target through %s", v.Kind()) + } +} + +// captureKeyValue appends the raw bytes of a key-value expression to the +// current capture. +func (d *decoder) captureKeyValue(expr *unstable.Node) { + c := &d.captures[d.captureIdx] + c.buf = append(c.buf, d.p.Raw(expr.Raw)...) + c.buf = append(c.buf, '\n') +} + +// hasUnmarshaler reports whether v can provide an unstable.Unmarshaler, +// without allocating anything. +func hasUnmarshaler(v reflect.Value) bool { + t := v.Type() + return t.Implements(unmarshalerType) || (v.CanAddr() && reflect.PtrTo(t).Implements(unmarshalerType)) +} + +// makeMapKey converts a TOML key into a value usable as the given map key +// type. +func makeMapKey(kt reflect.Type, name string) (reflect.Value, error) { + switch kt.Kind() { + case reflect.String: + return reflect.ValueOf(name).Convert(kt), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i, err := strconv.ParseInt(name, 10, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err) + } + k := reflect.New(kt).Elem() + if k.OverflowInt(i) { + return reflect.Value{}, fmt.Errorf("toml: map key %q overflows %s", name, kt) + } + k.SetInt(i) + return k, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + u, err := strconv.ParseUint(name, 10, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err) + } + k := reflect.New(kt).Elem() + if k.OverflowUint(u) { + return reflect.Value{}, fmt.Errorf("toml: map key %q overflows %s", name, kt) + } + k.SetUint(u) + return k, nil + case reflect.Float32, reflect.Float64: + f, err := strconv.ParseFloat(name, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err) + } + k := reflect.New(kt).Elem() + k.SetFloat(f) + return k, nil + case reflect.Ptr: + if kt.Implements(textUnmarshalerType) { + k := reflect.New(kt.Elem()) + err := k.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(name)) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: error unmarshaling map key %q: %w", name, err) + } + return k, nil + } + default: + if reflect.PtrTo(kt).Implements(textUnmarshalerType) { + k := reflect.New(kt) + err := k.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(name)) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: error unmarshaling map key %q: %w", name, err) + } + return k.Elem(), nil + } + } + return reflect.Value{}, fmt.Errorf("toml: cannot decode a key into a map with key type %s", kt) +} + +// elemOrNewMap unwraps an interface value to descend into it. Contents that +// can hold a table (generic maps and slices) are kept; anything else is +// replaced by a fresh map[string]interface{}. Maps and slices are reference +// types: they are returned directly, not copied. +func elemOrNewMap(v reflect.Value) reflect.Value { + if !v.IsNil() { + concrete := v.Elem() + t := concrete.Type() + if t == mapStringInterfaceType || t == sliceInterfaceType { + return concrete + } + } + return reflect.ValueOf(map[string]interface{}{}) +} + +// handleKeyValueExpression stores the value of a top-level key-value +// expression, relative to the current table. +func (d *decoder) handleKeyValueExpression(expr *unstable.Node, root reflect.Value) error { + d.path = d.path[:0] + + target := root + useCache := d.tableTargetValid && len(d.tableKey) > 0 + if useCache { + target = d.tableTarget + } else { + for _, name := range d.tableKey { + d.path = append(d.path, pathPart{name: name}) + } + } + + it := expr.Key() + for it.Next() { + d.path = append(d.path, pathPart{node: it.Node()}) + } + + nv, err := d.descend(target, d.path, 0, expr, expr.Value()) + if err != nil { + return d.contextualizeError(err, useCache) + } + if !nv.IsValid() { + return nil + } + if useCache { + // The target may have been replaced (e.g. a nil map allocated): + // re-link it into its parent. + if nv.Kind() == reflect.Map && nv.Pointer() != d.tableTarget.Pointer() { + d.storeSlot(&d.tableParentSlot, nv) + d.tableTarget = nv + } + } else { + if root.CanSet() { + root.Set(nv) + } + } + return nil +} + +// descend walks the given key path into v, and assigns the value at the +// end. It returns the value to store back at this level. An invalid value +// means nothing should be stored (e.g. unknown field). +func (d *decoder) descend(v reflect.Value, path []pathPart, idx int, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { + if idx == len(path) { + return d.assignValue(v, expr, value) + } + + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + nv, err := d.descend(v.Elem(), path, idx, expr, value) + if err != nil || !nv.IsValid() { + return reflect.Value{}, err + } + v.Elem().Set(nv) + return v, nil + } + + // A target implementing the unmarshaler interface consumes the value, + // whatever the remaining parts of the key are. + if d.unmarshalerInterface { + if u, ok := unmarshalerOf(v); ok { + return v, u.UnmarshalTOML(d.rawValue(expr, value)) + } + } + + part := path[idx] + + switch v.Kind() { + case reflect.Map: + // Native fast path for the most common generic target: walk the + // remaining dotted-key path with plain Go map operations and decode + // the value directly, skipping the reflect.Value round-trips + // (stringMapKey, MapIndex, New, SetMapIndex) entirely. + if !d.unmarshalerInterface && v.Type() == mapStringInterfaceType { + return d.descendStrMap(v, path, idx, value) + } + var name string + var key reflect.Value + var err error + fastKey := v.Type().Key() == stringType + if fastKey { + name = d.partString(&part) + key = d.stringMapKey(name) + } else { + key, err = makeMapKey(v.Type().Key(), d.partString(&part)) + if err != nil { + return reflect.Value{}, err + } + } + if v.IsNil() { + v = reflect.MakeMap(v.Type()) + } + elemType := v.Type().Elem() + existing := v.MapIndex(key) + var elem reflect.Value + switch { + case existing.IsValid(): + elem = reflect.New(elemType).Elem() + elem.Set(existing) + case idx+1 == len(path) && elemType.Kind() == reflect.Interface: + // Fast path: a fresh interface element does not need to be + // materialized, the assigned value is stored directly. + elem = reflect.Zero(elemType) + default: + elem = reflect.New(elemType).Elem() + } + nv, err := d.descend(elem, path, idx+1, expr, value) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + if fastKey { + // The recursion may have overwritten the key buffer. + key = d.stringMapKey(name) + } + v.SetMapIndex(key, nv) + } + return v, nil + case reflect.Struct: + plan := planForType(v.Type()) + f, found := plan.lookupBytes(part.bytes()) + if !found { + if part.node != nil { + d.strict.MissingField(expr) + } + return v, nil + } + fv := fieldByIndexAlloc(v, f.index) + var nv reflect.Value + var err error + if idx+1 == len(path) { + // Leaf field: assign directly. descend's first action for a + // fully-consumed path is exactly this call, so skipping the extra + // frame is equivalent and avoids a call per scalar field. + nv, err = d.assignValue(fv, expr, value) + } else { + nv, err = d.descend(fv, path, idx+1, expr, value) + } + if err != nil { + var mm *typeMismatchError + if errors.As(err, &mm) { + err = &unstable.ParserError{ + Highlight: mm.highlight, + Message: fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s", + mm.toml, v.Type(), f.fieldName, mm.target), + } + } + return reflect.Value{}, err + } + if nv.IsValid() && fv.CanSet() { + fv.Set(nv) + } + return v, nil + case reflect.Interface: + elem := elemOrNewMap(v) + nv, err := d.descend(elem, path, idx, expr, value) + if err != nil || !nv.IsValid() { + return reflect.Value{}, err + } + return nv, nil + case reflect.Slice: + if v.Len() == 0 { + if v.IsNil() { + v = reflect.MakeSlice(v.Type(), 0, 4) + } + v = reflect.Append(v, reflect.New(v.Type().Elem()).Elem()) + } + elem := v.Index(v.Len() - 1) + nv, err := d.descend(elem, path, idx, expr, value) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + elem.Set(nv) + } + return v, nil + case reflect.Array: + names := make([]string, idx) + for i := range names { + names[i] = path[i].str() + } + cnt := d.arrayCount(d.joinPath(names)) + if cnt == 0 { + cnt = 1 + } + elemIdx := cnt - 1 + if elemIdx >= v.Len() { + return reflect.Value{}, unstable.NewParserError(keyHighlight(d.p.Data(), part.node), + "cannot reach element %d of array of size %d", elemIdx, v.Len()) + } + elem := v.Index(elemIdx) + nv, err := d.descend(elem, path, idx, expr, value) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + elem.Set(nv) + } + return v, nil + default: + return reflect.Value{}, d.typeMismatchError("table", v.Type(), keyHighlight(d.p.Data(), part.node)) + } +} + +// descendStrMap assigns into a native map[string]interface{} target, following +// the remaining dotted-key parts with plain Go map operations and decoding the +// value with decodeAny. It returns the map to store back at this level: a new +// map when v was nil, otherwise v unchanged, since maps are reference types and +// are mutated in place. +func (d *decoder) descendStrMap(v reflect.Value, path []pathPart, idx int, value *unstable.Node) (reflect.Value, error) { + var m map[string]interface{} + if v.IsNil() { + m = make(map[string]interface{}) + v = reflect.ValueOf(m) + } else { + m = v.Interface().(map[string]interface{}) + } + + // Walk intermediate parts, creating or reusing nested generic maps. A + // non-map value at an intermediate key can only occur in a document the + // seen-tracker has already rejected; replacing it mirrors the reflect + // path (elemOrNewMap). + for ; idx < len(path)-1; idx++ { + name := d.partString(&path[idx]) + child, _ := m[name].(map[string]interface{}) + if child == nil { + child = make(map[string]interface{}) + m[name] = child + } + m = child + } + + av, err := d.decodeAny(value) + if err != nil { + return reflect.Value{}, err + } + m[d.partString(&path[idx])] = av + return v, nil +} + +// keyHighlight returns a highlight for the given key part node, falling back +// to the start of the document. +func keyHighlight(doc []byte, node *unstable.Node) []byte { + if node == nil { + return doc[0:0] + } + return doc[node.Raw.Offset : node.Raw.Offset+node.Raw.Length] +} + +// rawValue returns the raw bytes of the value of a key-value expression. +func (d *decoder) rawValue(expr *unstable.Node, value *unstable.Node) []byte { + if value.Kind != unstable.InlineTable && value.Kind != unstable.Array { + return d.p.Raw(value.Raw) + } + if expr == nil || expr.Kind != unstable.KeyValue { + // Inline container nested in another container: best effort. + return d.p.Raw(value.Raw) + } + // Reconstruct the span of the value: it starts after the equal sign + // following the last part of the key, and stops at the end of the + // expression. + var last unstable.Range + it := expr.Key() + for it.Next() { + last = it.Node().Raw + } + doc := d.p.Data() + i := int(last.Offset + last.Length) + for i < len(doc) && (doc[i] == ' ' || doc[i] == '\t') { + i++ + } + i++ // equal sign + for i < len(doc) && (doc[i] == ' ' || doc[i] == '\t') { + i++ + } + end := int(expr.Raw.Offset + expr.Raw.Length) + return doc[i:end] +} + +// unmarshalerOf returns the unstable.Unmarshaler implementation of v, if +// any. It allocates intermediate pointers as needed. +func unmarshalerOf(v reflect.Value) (unstable.Unmarshaler, bool) { + t := v.Type() + if t.Implements(unmarshalerType) { + if v.Kind() == reflect.Ptr && v.IsNil() { + v.Set(reflect.New(t.Elem())) + } + return v.Interface().(unstable.Unmarshaler), true + } + if v.CanAddr() && reflect.PtrTo(t).Implements(unmarshalerType) { + return v.Addr().Interface().(unstable.Unmarshaler), true + } + return nil, false +} + +var unmarshalerType = reflect.TypeOf(new(unstable.Unmarshaler)).Elem() + +// assignValue stores the TOML value carried by the node into v. +func (d *decoder) assignValue(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { + if v.Kind() == reflect.Ptr { + if d.unmarshalerInterface { + if u, ok := unmarshalerOf(v); ok { + return v, u.UnmarshalTOML(d.rawValue(expr, value)) + } + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + nv, err := d.assignValue(v.Elem(), expr, value) + if err != nil || !nv.IsValid() { + return reflect.Value{}, err + } + v.Elem().Set(nv) + return v, nil + } + + if d.unmarshalerInterface { + if u, ok := unmarshalerOf(v); ok { + return v, u.UnmarshalTOML(d.rawValue(expr, value)) + } + } + + switch value.Kind { + case unstable.String: + return d.assignString(v, value) + case unstable.Integer: + return d.assignInteger(v, value) + case unstable.Float: + return d.assignFloat(v, value) + case unstable.Bool: + return d.assignBool(v, value) + case unstable.DateTime: + return d.assignDateTime(v, value) + case unstable.LocalDateTime: + return d.assignLocalDateTime(v, value) + case unstable.LocalDate: + return d.assignLocalDate(v, value) + case unstable.LocalTime: + return d.assignLocalTime(v, value) + case unstable.Array: + return d.assignArray(v, expr, value) + case unstable.InlineTable: + return d.assignInlineTable(v, expr, value) + default: + return reflect.Value{}, unstable.NewParserError(value.Data, "unsupported value kind %s", value.Kind) + } +} + +func (d *decoder) assignString(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + switch v.Kind() { + case reflect.String: + v.SetString(string(value.Data)) + return v, nil + case reflect.Interface: + return boxInto(v, reflect.ValueOf(string(value.Data))) + default: + } + if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { + err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(value.Data) + if err != nil { + return reflect.Value{}, unstable.NewParserError(d.p.Raw(value.Raw), "%s", err) + } + return v, nil + } + return reflect.Value{}, d.typeMismatchError("string", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignInteger(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + // Integer values targeting a float field are parsed as floats: they can + // represent (approximately) numbers beyond the int64 range. + if k := v.Kind(); k == reflect.Float32 || k == reflect.Float64 { + return d.assignFloat(v, value) + } + + i, err := parseInteger(value.Data) + if err != nil { + return reflect.Value{}, err + } + + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if v.OverflowInt(i) { + return reflect.Value{}, unstable.NewParserError(value.Data, "integer value %d cannot be stored in %s", i, v.Type()) + } + v.SetInt(i) + return v, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if i < 0 { + return reflect.Value{}, unstable.NewParserError(value.Data, "negative integer value %d cannot be stored in %s", i, v.Type()) + } + if v.OverflowUint(uint64(i)) { + return reflect.Value{}, unstable.NewParserError(value.Data, "integer value %d cannot be stored in %s", i, v.Type()) + } + v.SetUint(uint64(i)) + return v, nil + case reflect.Interface: + return boxInto(v, reflect.ValueOf(i)) + default: + } + if ok, err := tryTextUnmarshaler(v, value.Data); ok { + return v, err + } + return reflect.Value{}, d.typeMismatchError("integer", v.Type(), d.p.Raw(value.Raw)) +} + +// tryTextUnmarshaler attempts to deliver the raw text of a value to a target +// implementing encoding.TextUnmarshaler. +func tryTextUnmarshaler(v reflect.Value, text []byte) (bool, error) { + if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { + return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(text) + } + return false, nil +} + +func (d *decoder) assignFloat(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + f, err := parseFloat(value.Data) + if err != nil { + return reflect.Value{}, err + } + + switch v.Kind() { + case reflect.Float64: + v.SetFloat(f) + return v, nil + case reflect.Float32: + if !math.IsInf(f, 0) && math.Abs(f) > math.MaxFloat32 { + return reflect.Value{}, unstable.NewParserError(value.Data, "float value %f cannot be stored in float32", f) + } + v.SetFloat(f) + return v, nil + case reflect.Interface: + return boxInto(v, reflect.ValueOf(f)) + default: + } + if ok, err := tryTextUnmarshaler(v, value.Data); ok { + return v, err + } + return reflect.Value{}, d.typeMismatchError("float", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignBool(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + b := value.Data[0] == 't' + + switch v.Kind() { + case reflect.Bool: + v.SetBool(b) + return v, nil + case reflect.Interface: + return boxInto(v, reflect.ValueOf(b)) + default: + } + if ok, err := tryTextUnmarshaler(v, value.Data); ok { + return v, err + } + return reflect.Value{}, d.typeMismatchError("boolean", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignDateTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + t, err := parseDateTime(value.Data) + if err != nil { + return reflect.Value{}, err + } + + if v.Type() == timeType { + v.Set(reflect.ValueOf(t)) + return v, nil + } + if v.Kind() == reflect.Interface { + return boxInto(v, reflect.ValueOf(t)) + } + return reflect.Value{}, d.typeMismatchError("datetime", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignLocalDateTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + dt, rest, err := parseLocalDateTime(value.Data) + if err != nil { + return reflect.Value{}, err + } + if len(rest) > 0 { + return reflect.Value{}, unstable.NewParserError(rest, "extra characters at the end of a local date time") + } + + switch v.Type() { + case localDateTimeType: + v.Set(reflect.ValueOf(dt)) + return v, nil + case timeType: + v.Set(reflect.ValueOf(dt.AsTime(time.Local))) + return v, nil + } + if v.Kind() == reflect.Interface { + return boxInto(v, reflect.ValueOf(dt)) + } + return reflect.Value{}, d.typeMismatchError("local datetime", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignLocalDate(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + date, err := parseLocalDate(value.Data) + if err != nil { + return reflect.Value{}, err + } + + switch v.Type() { + case localDateType: + v.Set(reflect.ValueOf(date)) + return v, nil + case timeType: + v.Set(reflect.ValueOf(date.AsTime(time.Local))) + return v, nil + } + if v.Kind() == reflect.Interface { + return boxInto(v, reflect.ValueOf(date)) + } + return reflect.Value{}, d.typeMismatchError("local date", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignLocalTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) { + t, rest, err := parseLocalTime(value.Data) + if err != nil { + return reflect.Value{}, err + } + if len(rest) > 0 { + return reflect.Value{}, unstable.NewParserError(rest, "extra characters at the end of a local time") + } + + switch v.Type() { + case localTimeType: + v.Set(reflect.ValueOf(t)) + return v, nil + case timeType: + v.Set(reflect.ValueOf(time.Date(0, 1, 1, t.Hour, t.Minute, t.Second, t.Nanosecond, time.Local))) + return v, nil + } + if v.Kind() == reflect.Interface { + return boxInto(v, reflect.ValueOf(t)) + } + return reflect.Value{}, d.typeMismatchError("local time", v.Type(), d.p.Raw(value.Raw)) +} + +func (d *decoder) assignArray(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { + // Count the elements to allocate the target in one go. + count := 0 + cit := value.Children() + for cit.Next() { + if cit.Node().Kind != unstable.Comment { + count++ + } + } + + switch v.Kind() { + case reflect.Slice: + // Allocate the backing array once at its final length and assign each + // element in place. This avoids a reflect.New allocation per element + // and the repeated growth checks of reflect.Append. + slice := reflect.MakeSlice(v.Type(), count, count) + i := 0 + it := value.Children() + for it.Next() { + n := it.Node() + if n.Kind == unstable.Comment { + continue + } + elem := slice.Index(i) + nv, err := d.assignValue(elem, nil, n) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + elem.Set(nv) + } + i++ + } + return slice, nil + case reflect.Array: + it := value.Children() + i := 0 + for it.Next() { + n := it.Node() + if n.Kind == unstable.Comment { + continue + } + if i >= v.Len() { + // Extra elements are dropped when the target array is too + // small. + break + } + elem := v.Index(i) + nv, err := d.assignValue(elem, nil, n) + if err != nil { + return reflect.Value{}, err + } + elem.Set(nv) + i++ + } + return v, nil + case reflect.Interface: + // Build the []interface{} natively: each element is decoded straight + // into a Go value with no intermediate addressable reflect.Value and + // no reflect round-trip, and nested arrays recurse the same way. + slice := make([]interface{}, 0, count) + it := value.Children() + for it.Next() { + n := it.Node() + if n.Kind == unstable.Comment { + continue + } + ev, err := d.decodeAny(n) + if err != nil { + return reflect.Value{}, err + } + slice = append(slice, ev) + } + return boxInto(v, reflect.ValueOf(slice)) + default: + } + return reflect.Value{}, d.typeMismatchError("array", v.Type(), d.rawValue(expr, value)) +} + +// decodeAny decodes a value node into a native Go value (the representation +// used for interface{} targets), without going through reflect. Scalars and +// arrays are handled directly; inline tables still defer to the reflect-based +// path so that their dotted-key merge semantics remain identical. +func (d *decoder) decodeAny(n *unstable.Node) (interface{}, error) { + switch n.Kind { + case unstable.String: + return string(n.Data), nil + case unstable.Integer: + i, err := parseInteger(n.Data) + return i, err + case unstable.Float: + f, err := parseFloat(n.Data) + return f, err + case unstable.Bool: + return n.Data[0] == 't', nil + case unstable.Array: + count := 0 + cit := n.Children() + for cit.Next() { + if cit.Node().Kind != unstable.Comment { + count++ + } + } + slice := make([]interface{}, 0, count) + it := n.Children() + for it.Next() { + c := it.Node() + if c.Kind == unstable.Comment { + continue + } + ev, err := d.decodeAny(c) + if err != nil { + return nil, err + } + slice = append(slice, ev) + } + return slice, nil + case unstable.InlineTable: + // Build the map natively: navigate each (possibly dotted) key with + // plain Go map operations and decode each value with decodeAny. The + // seen-tracker has already rejected duplicate or conflicting keys, so + // intermediate parts can be created/merged without revalidation. + count := 0 + cit := n.Children() + for cit.Next() { + count++ + } + m := make(map[string]interface{}, count) + it := n.Children() + for it.Next() { + kv := it.Node() + if err := d.setAnyKey(m, kv.Key(), kv.Value()); err != nil { + return nil, err + } + } + return m, nil + case unstable.DateTime: + t, err := parseDateTime(n.Data) + return t, err + case unstable.LocalDateTime: + dt, rest, err := parseLocalDateTime(n.Data) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, unstable.NewParserError(rest, "extra characters at the end of a local date time") + } + return dt, nil + case unstable.LocalDate: + date, err := parseLocalDate(n.Data) + return date, err + case unstable.LocalTime: + t, rest, err := parseLocalTime(n.Data) + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, unstable.NewParserError(rest, "extra characters at the end of a local time") + } + return t, nil + default: + return nil, unstable.NewParserError(n.Data, "unsupported value kind %s", n.Kind) + } +} + +func (d *decoder) assignInlineTable(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { + switch v.Kind() { + case reflect.Map: + // Inline tables are self-contained: they fully replace the target. + v = reflect.MakeMap(v.Type()) + case reflect.Struct: + // fields are set in place + case reflect.Interface: + elem := reflect.ValueOf(map[string]interface{}{}) + nv, err := d.assignInlineTable(elem, expr, value) + if err != nil { + return reflect.Value{}, err + } + return boxInto(v, nv) + default: + return reflect.Value{}, d.typeMismatchError("inline table", v.Type(), d.rawValue(expr, value)) + } + + it := value.Children() + for it.Next() { + kv := it.Node() + // Build the path from the key of this key-value. Keys of inline + // tables rarely have more than a few parts. + var pathBuf [4]pathPart + path := pathBuf[:0] + kit := kv.Key() + for kit.Next() { + path = append(path, pathPart{node: kit.Node()}) + } + nv, err := d.descend(v, path, 0, kv, kv.Value()) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + v = nv + } + } + return v, nil +} + +// boxInto returns the value to store in place of the interface value v. The +// caller stores the result in the slot v was found in, which performs the +// interface conversion, so the concrete value can be returned as-is. +func boxInto(v reflect.Value, c reflect.Value) (reflect.Value, error) { + if !c.Type().AssignableTo(v.Type()) { + return reflect.Value{}, fmt.Errorf("toml: cannot store %s into %s", c.Type(), v.Type()) + } + return c, nil +} + +var ( + interfaceType = reflect.TypeOf(new(interface{})).Elem() + localDateType = reflect.TypeOf(LocalDate{}) + localTimeType = reflect.TypeOf(LocalTime{}) + localDateTimeType = reflect.TypeOf(LocalDateTime{}) +) + +// structPlan caches the mapping between TOML keys and the fields of a struct +// type. byFold, keyed by the lowercased name, resolves any key on its own when +// no two fields fold to the same name (the overwhelmingly common case, marked +// by hasCollision == false): TOML keys are usually lowercase and never match +// the exact (capitalized) Go field names, so the byName probe was always a +// wasted lookup. byName (the exact names) is only consulted, first, when +// fields do collide under folding, to preserve the exact-match-wins tiebreak. +type structPlan struct { + byName map[string]structField + byFold map[string]structField + hasCollision bool +} + +type structField struct { + index []int + fieldName string +} + +// foldBufSize bounds the stack buffer used to lowercase keys without +// allocating. Keys longer than this (extremely rare) take the strings.ToLower +// fallback. +const foldBufSize = 68 + +// lookup and lookupBytes keep the hot path to a single inlinable byFold lookup. +// byFold is indexed by both the exact field/tag names and their lowercased +// forms, so that lookup resolves the two common cases — a lowercase key, or a +// key matching the field's own casing — directly. byName is consulted first +// only for types whose fields collide under case-folding, to preserve the +// exact-match-wins tiebreak. The buffer-fold for other casings lives +// out-of-line so it does not bloat the hot path. +func (p *structPlan) lookup(name string) (structField, bool) { + if p.hasCollision { + if f, ok := p.byName[name]; ok { + return f, true + } + } + if f, ok := p.byFold[name]; ok { + return f, true + } + return p.lookupFoldStr(name) +} + +func (p *structPlan) lookupBytes(name []byte) (structField, bool) { + if p.hasCollision { + if f, ok := p.byName[string(name)]; ok { // does not allocate + return f, true + } + } + if f, ok := p.byFold[string(name)]; ok { // does not allocate + return f, true + } + return p.lookupFold(name) +} + +// lookupFold resolves keys whose casing matches neither the exact nor the +// lowercased index: it folds to lowercase (in a stack buffer for ASCII, so no +// allocation) and retries; only non-ASCII or oversized keys hit strings.ToLower. +func (p *structPlan) lookupFold(name []byte) (structField, bool) { + if len(name) <= foldBufSize { + // Fold into a stack buffer: len(name) <= cap(buf), so the append + // never reallocates and nothing escapes to the heap. + var buf [foldBufSize]byte + b := buf[:0] + ascii := true + for _, c := range name { + if c >= 0x80 { + ascii = false + break + } + if c >= 'A' && c <= 'Z' { + c += 'a' - 'A' + } + b = append(b, c) + } + if ascii { + f, ok := p.byFold[string(b)] // does not allocate + return f, ok + } + } + f, ok := p.byFold[strings.ToLower(string(name))] + return f, ok +} + +func (p *structPlan) lookupFoldStr(name string) (structField, bool) { + if len(name) <= foldBufSize { + // Fold into a stack buffer: len(name) <= cap(buf), so the append + // never reallocates and nothing escapes to the heap. + var buf [foldBufSize]byte + b := buf[:0] + ascii := true + for i := 0; i < len(name); i++ { + c := name[i] + if c >= 0x80 { + ascii = false + break + } + if c >= 'A' && c <= 'Z' { + c += 'a' - 'A' + } + b = append(b, c) + } + if ascii { + f, ok := p.byFold[string(b)] // does not allocate + return f, ok + } + } + f, ok := p.byFold[strings.ToLower(name)] + return f, ok +} + +var structPlans sync.Map // reflect.Type -> *structPlan + +func planForType(t reflect.Type) *structPlan { + if plan, ok := structPlans.Load(t); ok { + return plan.(*structPlan) + } + plan := buildPlan(t) + structPlans.Store(t, plan) + return plan +} + +func buildPlan(t reflect.Type) *structPlan { + plan := &structPlan{ + byName: map[string]structField{}, + byFold: map[string]structField{}, + } + addFields(plan, t, nil) + return plan +} + +func addFields(plan *structPlan, t reflect.Type, prefix []int) { + var embedded []reflect.StructField + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + tag, tagged := f.Tag.Lookup("toml") + name := f.Name + if tagged { + // A tag of exactly "-" drops the field. "-," names it "-". + if tag == "-" { + continue + } + parts := strings.SplitN(tag, ",", 2) + if parts[0] != "" { + name = parts[0] + } + } + if f.Anonymous { + ft := f.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if ft.Kind() != reflect.Struct { + // Embedded non-struct fields are not decoded into. + continue + } + if !tagged { + // Untagged embedded structs are flattened, even when their + // type is unexported: only their own exported fields are + // reachable. + embedded = append(embedded, f) + continue + } + // A tagged embedded struct acts as a regular named field. + } else if f.PkgPath != "" { + // unexported + continue + } + index := make([]int, 0, len(prefix)+1) + index = append(index, prefix...) + index = append(index, i) + sf := structField{index: index, fieldName: f.Name} + if _, ok := plan.byName[name]; !ok { + plan.byName[name] = sf + } + lower := strings.ToLower(name) + if _, ok := plan.byFold[lower]; !ok { + plan.byFold[lower] = sf + } else { + // Two distinct fields fold to the same name: case-insensitive + // matching is ambiguous, so lookups must consult byName first to + // keep the exact-match-wins tiebreak deterministic. + plan.hasCollision = true + } + // Index the exact (cased) name as well, so a key written with the + // field's own casing resolves in a single byFold lookup. Only fields + // whose name is not already lowercase need this extra entry. Any name + // that would conflict here also collides under folding (handled + // above), so byName-first preserves the exact tiebreak in that case. + if name != lower { + if _, ok := plan.byFold[name]; !ok { + plan.byFold[name] = sf + } + } + } + // Embedded structs are flattened after the regular fields, so that + // shallower fields win. + for _, f := range embedded { + ft := f.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + index := make([]int, 0, len(prefix)+1) + index = append(index, prefix...) + idx := f.Index[0] + index = append(index, idx) + addFields(plan, ft, index) + } +} + +// fieldByIndexAlloc returns the field of v at the given index path, +// allocating intermediate embedded pointers as needed. +func fieldByIndexAlloc(v reflect.Value, index []int) reflect.Value { + // Fast path for non-embedded fields, which have a single-element index: + // no intermediate pointer dereferencing is possible. + if len(index) == 1 { + return v.Field(index[0]) + } + for i, x := range index { + if i > 0 { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go b/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go new file mode 100644 index 0000000..2cf3bdb --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go @@ -0,0 +1,124 @@ +package unstable + +import ( + "errors" + "fmt" +) + +// Iterator over a sequence of nodes. +// +// Starts uninitialized, you need to call Next() first. +// +// For example: +// +// it := n.Children() +// for it.Next() { +// n := it.Node() +// // do something with n +// } +type Iterator struct { + started bool + node *Node +} + +// Next moves the iterator forward and returns true if points to a node, false +// otherwise. +func (c *Iterator) Next() bool { + if !c.started { + c.started = true + } else if c.node.Valid() { + c.node = c.node.Next() + } + return c.node.Valid() +} + +// IsLast returns true if the current node of the iterator is the last +// one. Subsequent calls to Next() will return false. +func (c *Iterator) IsLast() bool { + return c.node.next == 0 +} + +// Node returns a pointer to the node pointed at by the iterator. +func (c *Iterator) Node() *Node { + return c.node +} + +// Node in a TOML expression AST. +// +// Depending on Kind, its sequence of children should be interpreted +// differently. +// +// - Array have one child per element in the array. +// - InlineTable have one child per key-value in the table (each of kind +// InlineTable). +// - KeyValue have at least two children. The first one is the value. The +// rest make a potentially dotted key. +// - Table and ArrayTable's children represent a dotted key (same as +// KeyValue, but without the first node being the value). +// +// When relevant, Raw describes the range of bytes this node is referring to in +// the input document. Use Parser.Raw() to retrieve the actual bytes. +type Node struct { + Kind Kind + Raw Range // Raw bytes from the input. + Data []byte // Node value (either allocated or referencing the input). + + // References to other nodes, as 1-based indexes into the parser's arena. + // 0 means no node. + parser *Parser + next int32 + child int32 +} + +// Next returns a pointer to the next node, or nil if there is no next node. +func (n *Node) Next() *Node { + if n.next == 0 { + return nil + } + return &n.parser.nodes[n.next-1] +} + +// Child returns a pointer to the first child node of this node. Other children +// can be accessed calling Next on the first child. Returns nil if there is no +// child node. +func (n *Node) Child() *Node { + if n.child == 0 { + return nil + } + return &n.parser.nodes[n.child-1] +} + +// Valid returns true if the node's kind is set (not to Invalid). +func (n *Node) Valid() bool { + return n != nil && n.Kind != Invalid +} + +// Key returns the children nodes making the Key on a supported node. Panics +// otherwise. They are guaranteed to be all be of the Kind Key. A simple key +// would return just one element. +func (n *Node) Key() Iterator { + switch n.Kind { + case KeyValue: + value := n.Child() + if !value.Valid() { + panic(errors.New("KeyValue should have at least two children")) + } + return Iterator{node: value.Next()} + case Table, ArrayTable: + return Iterator{node: n.Child()} + default: + panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) + } +} + +// Value returns a pointer to the value node of a KeyValue. +// Guaranteed to be non-nil. Panics if not called on a KeyValue node, +// or if the Children are malformed. +func (n *Node) Value() *Node { + return n.Child() +} + +// Children returns an iterator over a node's children. +func (n *Node) Children() Iterator { + return Iterator{node: n.Child()} +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/bridge.go b/vendor/github.com/pelletier/go-toml/v2/unstable/bridge.go new file mode 100644 index 0000000..5482bde --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/bridge.go @@ -0,0 +1,21 @@ +package unstable + +import "github.com/pelletier/go-toml/v2/internal/parserbridge" + +// Expose the non-AST scanners to the root toml package without committing to +// them in the public API. See internal/parserbridge for the rationale. +// +//nolint:gochecknoinits // load-time wiring of an internal bridge (see internal/parserbridge) +func init() { + parserbridge.ScanScalar = func(p any, b []byte) (kind int, raw, value, rest []byte, err error) { + k, raw, value, rest, err := p.(*Parser).scanScalar(b) + return int(k), raw, value, rest, err + } + parserbridge.ScanKey = func(p any, b []byte, dst [][]byte) (parts [][]byte, raw, rest []byte, err error) { + return p.(*Parser).scanKey(b, dst) + } + parserbridge.ScanComment = scanComment + parserbridge.ParseValue = func(p any, b []byte) (node any, rest []byte, err error) { + return p.(*Parser).parseValue(b) + } +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go b/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go new file mode 100644 index 0000000..7ff26c5 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go @@ -0,0 +1,3 @@ +// Package unstable provides APIs that do not meet the backward compatibility +// guarantees yet. +package unstable diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go b/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go new file mode 100644 index 0000000..54b19f9 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go @@ -0,0 +1,83 @@ +package unstable + +import "fmt" + +// Kind represents the type of TOML structure contained in a given Node. +type Kind int + +const ( + // Invalid represents an invalid meta node. + Invalid Kind = iota + // Comment represents a comment meta node. + Comment + // Key represents a key meta node. + Key + + // Table represents a top-level table. + Table + // ArrayTable represents a top-level array table. + ArrayTable + // KeyValue represents a top-level key value. + KeyValue + + // Array represents an array container value. + Array + // InlineTable represents an inline table container value. + InlineTable + + // String represents a string value. + String + // Bool represents a boolean value. + Bool + // Float represents a floating point value. + Float + // Integer represents an integer value. + Integer + // LocalDate represents a a local date value. + LocalDate + // LocalTime represents a local time value. + LocalTime + // LocalDateTime represents a local date/time value. + LocalDateTime + // DateTime represents a data/time value. + DateTime +) + +// String implementation of fmt.Stringer. +func (k Kind) String() string { + switch k { + case Invalid: + return "Invalid" + case Comment: + return "Comment" + case Key: + return "Key" + case Table: + return "Table" + case ArrayTable: + return "ArrayTable" + case KeyValue: + return "KeyValue" + case Array: + return "Array" + case InlineTable: + return "InlineTable" + case String: + return "String" + case Bool: + return "Bool" + case Float: + return "Float" + case Integer: + return "Integer" + case LocalDate: + return "LocalDate" + case LocalTime: + return "LocalTime" + case LocalDateTime: + return "LocalDateTime" + case DateTime: + return "DateTime" + } + panic(fmt.Errorf("Kind.String() not implemented for kind %d", int(k))) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go b/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go new file mode 100644 index 0000000..91e2269 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go @@ -0,0 +1,1658 @@ +package unstable + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "unicode/utf8" +) + +// ParserError describes an error relative to the content of the document. +// +// It cannot outlive the instance of Parser it refers to, and may cause panics +// if the parser is reset. +type ParserError struct { + Highlight []byte + Message string + Key []string // optional +} + +// Error is the implementation of the error interface. +func (e *ParserError) Error() string { + return e.Message +} + +// NewParserError is a convenience function to create a ParserError +// +// Warning: Highlight needs to be a subslice of Parser.data, so only slices +// returned by Parser.Raw are valid candidates. +func NewParserError(highlight []byte, format string, args ...interface{}) error { + return &ParserError{ + Highlight: highlight, + Message: fmt.Errorf(format, args...).Error(), + } +} + +// Parser scans over a TOML-encoded document and generates an iterative AST. +// +// To prime the Parser, first reset it with the contents of a TOML document. +// Then, process all top-level expressions sequentially. See Example. +// +// Don't forget to check Error() after you're done parsing. +// +// Each top-level expression needs to be fully processed before calling +// NextExpression() again. Otherwise, calls to various Node methods may panic +// if the parser has moved on the next expression. +// +// For performance reasons, go-toml doesn't make a copy of the input bytes to +// the parser. Make sure to copy all the bytes you need to outlive the slice +// given to the parser. +type Parser struct { + KeepComments bool + + data []byte + left []byte + nodes []Node + err error +} + +// Data returns the slice provided to the last call to Reset. +func (p *Parser) Data() []byte { + return p.data +} + +// Range returns a range description that corresponds to a given slice of the +// input. If the argument is not a subslice of the parser input, this function +// panics. +func (p *Parser) Range(b []byte) Range { + // b is a subslice of p.data if and only if they share the same backing + // array. In that case, because subslicing cannot extend capacity, the + // number of bytes between the start of b and the end of the backing array + // (its capacity) identifies the offset of b within data. + offset := cap(p.data) - cap(b) + if offset < 0 || offset+len(b) > len(p.data) { + panic(errors.New("not a slice of the data slice")) + } + return Range{ + Offset: uint32(offset), //nolint:gosec // TOML documents are small + Length: uint32(len(b)), //nolint:gosec // TOML documents are small + } +} + +// Raw returns the slice corresponding to the bytes in the given range. +func (p *Parser) Raw(raw Range) []byte { + return p.data[raw.Offset : raw.Offset+raw.Length] +} + +// Reset brings the parser to its initial state for a given input. It wipes an +// reuses internal storage to reduce allocation. +func (p *Parser) Reset(b []byte) { + p.data = b + p.left = b + p.nodes = p.nodes[:0] + p.err = nil +} + +// Error returns any error that has occurred during parsing. +func (p *Parser) Error() error { + return p.err +} + +// Range of bytes in the document. +type Range struct { + Offset uint32 + Length uint32 +} + +// Position describes a position in the input. +type Position struct { + // Number of bytes from the beginning of the input. + Offset int + // Line number, starting at 1. + Line int + // Column number, starting at 1. + Column int +} + +// Shape describes the position of a range in the input. +type Shape struct { + Start Position + End Position +} + +func (p *Parser) position(offset int) Position { + pos := Position{ + Offset: offset, + Line: 1, + Column: 1, + } + b := p.data[:offset] + for { + idx := bytes.IndexByte(b, '\n') + if idx < 0 { + break + } + pos.Line++ + b = b[idx+1:] + } + pos.Column = len(b) + 1 + return pos +} + +// Shape returns the shape of the given range in the input. Will +// panic if the range is not a subslice of the input. +func (p *Parser) Shape(r Range) Shape { + raw := p.Raw(r) + return Shape{ + Start: p.position(int(r.Offset)), + End: p.position(int(r.Offset) + len(raw)), + } +} + +// Expression returns a pointer to the node representing the last successfully +// parsed expression. +func (p *Parser) Expression() *Node { + if len(p.nodes) == 0 { + return nil + } + return &p.nodes[0] +} + +// push appends a node to the arena and returns its handle (1-based index). +func (p *Parser) push(n Node) int32 { + if len(p.nodes) == cap(p.nodes) { + // Grow by 2x: large expressions (huge arrays) would otherwise grow + // the arena in small steps, copying it repeatedly. + newCap := 2 * cap(p.nodes) + if newCap < 64 { + newCap = 64 + } + nodes := make([]Node, len(p.nodes), newCap) + copy(nodes, p.nodes) + p.nodes = nodes + } + n.parser = p + p.nodes = append(p.nodes, n) + return int32(len(p.nodes)) //nolint:gosec // node counts are bounded by document size +} + +// at returns a pointer to the node with the given handle. Only valid until +// the next call to push. +func (p *Parser) at(handle int32) *Node { + return &p.nodes[handle-1] +} + +// offsetOf returns the offset of b within the parser's data. b must be a +// subslice of p.data. +func (p *Parser) offsetOf(b []byte) int { + return cap(p.data) - cap(b) +} + +// rangeFrom returns the Range covering bytes from the start of `from` to the +// start of `to`. Both must be subslices of p.data. +func (p *Parser) rangeFrom(from, to []byte) Range { + start := p.offsetOf(from) + end := p.offsetOf(to) + return Range{ + Offset: uint32(start), //nolint:gosec // TOML documents are small + Length: uint32(end - start), //nolint:gosec // TOML documents are small + } +} + +// NextExpression parses the next top-level expression. If an expression was +// successfully parsed, it returns true. If the parser is at the end of the +// document or an error occurred, it returns false. +// +// Retrieve the parsed expression with Expression(). +func (p *Parser) NextExpression() bool { + if p.err != nil { + return false + } + + p.nodes = p.nodes[:0] + + for { + b := skipWhitespace(p.left) + if len(b) == 0 { + p.left = b + return false + } + + var err error + switch b[0] { + case '\n': + p.left = b[1:] + continue + case '\r': + if len(b) > 1 && b[1] == '\n' { + p.left = b[2:] + continue + } + err = NewParserError(b[:1], "expected newline but got %#U", b[0]) + case '#': + var comment, rest []byte + comment, rest, err = scanComment(b) + if err == nil { + rest, err = consumeEOL(rest) + } + if err == nil { + if p.KeepComments { + p.push(Node{ + Kind: Comment, + Raw: p.Range(comment), + Data: comment, + }) + p.left = rest + return true + } + p.left = rest + continue + } + case '[': + var rest []byte + rest, err = p.parseExprTable(b) + if err == nil { + p.left = rest + return true + } + default: + var rest []byte + rest, err = p.parseExprKeyval(b) + if err == nil { + p.left = rest + return true + } + } + + // Errors at the end of the input have an empty highlight. Extend + // them to the last byte of the input so that they carry a usable + // position. + var perr *ParserError + if errors.As(err, &perr) && len(perr.Highlight) == 0 { + if offset := p.offsetOf(perr.Highlight); offset > 0 && offset == len(p.data) { + perr.Highlight = p.data[offset-1 : offset] + } + } + + p.err = err + return false + } +} + +// consumeEOL consumes a newline (LF or CRLF) or end of input. +func consumeEOL(b []byte) ([]byte, error) { + if len(b) == 0 { + return b, nil + } + switch b[0] { + case '\n': + return b[1:], nil + case '\r': + if len(b) > 1 && b[1] == '\n' { + return b[2:], nil + } + } + return nil, NewParserError(b[:1], "expected newline but got %#U", b[0]) +} + +// finishLine handles `ws [comment] (newline|eof)` after a top-level +// expression. If a comment is present and KeepComments is set, it is attached +// as the next sibling of the expression's root node. +func (p *Parser) finishLine(root int32, b []byte) ([]byte, error) { + b = skipWhitespace(b) + if len(b) > 0 && b[0] == '#' { + comment, rest, err := scanComment(b) + if err != nil { + return nil, err + } + if p.KeepComments { + h := p.push(Node{ + Kind: Comment, + Raw: p.Range(comment), + Data: comment, + }) + p.at(root).next = h + } + b = rest + } + return consumeEOL(b) +} + +// parseExprKeyval parses a top-level `key = value` expression, including its +// line termination. +func (p *Parser) parseExprKeyval(b []byte) ([]byte, error) { + root, rest, err := p.parseKeyval(b) + if err != nil { + return nil, err + } + return p.finishLine(root, rest) +} + +// parseExprTable parses a `[table]` or `[[array table]]` expression, +// including its line termination. b starts at '['. +func (p *Parser) parseExprTable(b []byte) ([]byte, error) { + var root int32 + var err error + var rest []byte + if len(b) > 1 && b[1] == '[' { + root, rest, err = p.parseArrayTableHeader(b) + } else { + root, rest, err = p.parseTableHeader(b) + } + if err != nil { + return nil, err + } + return p.finishLine(root, rest) +} + +// parseTableHeader parses `[ ws key ws ]`. b starts at '['. +func (p *Parser) parseTableHeader(b []byte) (int32, []byte, error) { + root := p.push(Node{Kind: Table}) + + first, b, err := p.parseKey(skipWhitespace(b[1:])) + if err != nil { + return 0, nil, err + } + p.at(root).child = first + + if len(b) == 0 || b[0] != ']' { + return 0, nil, NewParserError(highlight1(b), "expected ']' to close table name") + } + return root, b[1:], nil +} + +// parseArrayTableHeader parses `[[ ws key ws ]]`. b starts at '[['. +func (p *Parser) parseArrayTableHeader(b []byte) (int32, []byte, error) { + root := p.push(Node{Kind: ArrayTable}) + + first, b, err := p.parseKey(skipWhitespace(b[2:])) + if err != nil { + return 0, nil, err + } + p.at(root).child = first + + if len(b) < 2 || b[0] != ']' || b[1] != ']' { + return 0, nil, NewParserError(highlight1(b), "expected ']]' to close array table name") + } + return root, b[2:], nil +} + +// parseKeyval parses `key keyval-sep val`. Returns the handle to the KeyValue +// node. +func (p *Parser) parseKeyval(b []byte) (int32, []byte, error) { + root := p.push(Node{Kind: KeyValue}) + start := b + + firstKey, b, err := p.parseKey(b) + if err != nil { + return 0, nil, err + } + + if len(b) == 0 || b[0] != '=' { + return 0, nil, NewParserError(highlight1(b), "expected '=' after key") + } + b = skipWhitespace(b[1:]) + + value, b, err := p.parseVal(b) + if err != nil { + return 0, nil, err + } + + p.at(root).child = value + p.at(value).next = firstKey + p.at(root).Raw = p.rangeFrom(start, b) + return root, b, nil +} + +// parseKey parses a potentially dotted key. It consumes the whitespace +// following the key, so that the caller can directly check for the next +// expected character ('=', ']', ...). Returns the handle of the first Key +// node; subsequent parts are chained via next. +func (p *Parser) parseKey(b []byte) (int32, []byte, error) { + var first, last int32 + for { + h, rest, err := p.parseSimpleKey(b) + if err != nil { + return 0, nil, err + } + if first == 0 { + first = h + } else { + p.at(last).next = h + } + last = h + + b = skipWhitespace(rest) + if len(b) > 0 && b[0] == '.' { + b = skipWhitespace(b[1:]) + continue + } + return first, b, nil + } +} + +func isUnquotedKeyChar(c byte) bool { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' +} + +// parseSimpleKey parses one key part: either a bare key or a quoted key. +func (p *Parser) parseSimpleKey(b []byte) (int32, []byte, error) { + raw, value, rest, err := p.scanSimpleKey(b) + if err != nil { + return 0, nil, err + } + h := p.push(Node{Kind: Key, Raw: p.Range(raw), Data: value}) + return h, rest, nil +} + +// scanSimpleKey scans one key part (bare or quoted) without building an AST +// node. It returns the raw bytes, the decoded key value, and the rest of the +// input. +func (p *Parser) scanSimpleKey(b []byte) (raw, value, rest []byte, err error) { + if len(b) == 0 { + return nil, nil, nil, NewParserError(b, "expected key but reached end of input") + } + + switch b[0] { + case '\'': + return p.parseLiteralString(b) + case '"': + return p.parseBasicString(b) + default: + i := 0 + for i < len(b) && isUnquotedKeyChar(b[i]) { + i++ + } + if i == 0 { + return nil, nil, nil, NewParserError(b[:1], "invalid character at start of key: %#U", b[0]) + } + return b[:i], b[:i], b[i:], nil + } +} + +// parseVal parses a TOML value and returns the handle to its node. +func (p *Parser) parseVal(b []byte) (int32, []byte, error) { + if len(b) == 0 { + return 0, nil, NewParserError(b, "expected value, not end of input") + } + + c := b[0] + switch { + case c == '"': + var raw, value, rest []byte + var err error + if len(b) > 2 && b[1] == '"' && b[2] == '"' { + raw, value, rest, err = p.parseMultilineBasicString(b) + } else { + raw, value, rest, err = p.parseBasicString(b) + } + if err != nil { + return 0, nil, err + } + h := p.push(Node{Kind: String, Raw: p.Range(raw), Data: value}) + return h, rest, nil + case c == '\'': + var raw, value, rest []byte + var err error + if len(b) > 2 && b[1] == '\'' && b[2] == '\'' { + raw, value, rest, err = p.parseMultilineLiteralString(b) + } else { + raw, value, rest, err = p.parseLiteralString(b) + } + if err != nil { + return 0, nil, err + } + h := p.push(Node{Kind: String, Raw: p.Range(raw), Data: value}) + return h, rest, nil + case c == 't': + return p.parseKeyword(b, "true", Bool) + case c == 'f': + return p.parseKeyword(b, "false", Bool) + case c == 'i': + return p.parseKeyword(b, "inf", Float) + case c == 'n': + return p.parseKeyword(b, "nan", Float) + case c == '[': + return p.parseValArray(b) + case c == '{': + return p.parseInlineTable(b) + case c == '+' || c == '-': + return p.parseIntOrFloat(b) + case c >= '0' && c <= '9': + if isDateTimeStart(b) { + return p.parseDateTime(b) + } + return p.parseIntOrFloat(b) + default: + return 0, nil, NewParserError(b[:1], "unexpected character %#U at start of value", c) + } +} + +// scanScalar scans a single scalar TOML value (string, integer, float, +// boolean, or date/time) without building any AST node. It returns the kind of +// the value, its raw bytes, its decoded value bytes (for strings: quotes +// removed and escapes resolved; identical to raw for the other kinds), and the +// rest of the input. Arrays and inline tables are not scalars and produce an +// error: use parseValue for those. +// +// It is exposed to the root toml package through internal/parserbridge for the +// fused generic-decode path; it is not part of the public API. +func (p *Parser) scanScalar(b []byte) (kind Kind, raw, value, rest []byte, err error) { + if len(b) == 0 { + return Invalid, nil, nil, nil, NewParserError(b, "expected value, not end of input") + } + + c := b[0] + switch { + case c == '"': + if len(b) > 2 && b[1] == '"' && b[2] == '"' { + raw, value, rest, err = p.parseMultilineBasicString(b) + } else { + raw, value, rest, err = p.parseBasicString(b) + } + return String, raw, value, rest, err + case c == '\'': + if len(b) > 2 && b[1] == '\'' && b[2] == '\'' { + raw, value, rest, err = p.parseMultilineLiteralString(b) + } else { + raw, value, rest, err = p.parseLiteralString(b) + } + return String, raw, value, rest, err + case c == 't': + return scanKeyword(b, "true", Bool) + case c == 'f': + return scanKeyword(b, "false", Bool) + case c == 'i': + return scanKeyword(b, "inf", Float) + case c == 'n': + return scanKeyword(b, "nan", Float) + case c == '+' || c == '-': + return scanIntOrFloat(b) + case c >= '0' && c <= '9': + if isDateTimeStart(b) { + return scanDateTime(b) + } + return scanIntOrFloat(b) + default: + return Invalid, nil, nil, nil, NewParserError(b[:1], "unexpected character %#U at start of value", c) + } +} + +// scanKey scans a potentially dotted key without building AST nodes, +// appending the decoded value of each part to dst (pass dst[:0] to reuse a +// buffer). It consumes the whitespace following the key, so the caller can +// directly check for the next expected character ('=', ']', ...). It returns +// the parts, the raw bytes spanning the whole key (from the first part to the +// end of the last one, excluding trailing whitespace, usable as an error +// highlight), the rest of the input, and any error. +// +// It is exposed to the root toml package through internal/parserbridge for the +// fused generic-decode path; it is not part of the public API. +func (p *Parser) scanKey(b []byte, dst [][]byte) (parts [][]byte, raw, rest []byte, err error) { + parts = dst + start := b + for { + _, value, r, err := p.scanSimpleKey(b) + if err != nil { + return nil, nil, nil, err + } + parts = append(parts, value) + + // r points just past the current part: the key spans from start to + // here, ignoring any whitespace that follows. + raw = start[:len(start)-len(r)] + + b = skipWhitespace(r) + if len(b) > 0 && b[0] == '.' { + b = skipWhitespace(b[1:]) + continue + } + return parts, raw, b, nil + } +} + +// parseValue parses a single TOML value, which may be an array or inline table, +// into the parser's arena. It returns the root node of the value and the rest +// of the input. It resets the arena, so any node returned by a previous call to +// parseValue, Expression, or NextExpression is invalidated. +// +// It is exposed to the root toml package through internal/parserbridge for the +// fused generic-decode path; it is not part of the public API. +func (p *Parser) parseValue(b []byte) (*Node, []byte, error) { + p.nodes = p.nodes[:0] + h, rest, err := p.parseVal(b) + if err != nil { + return nil, nil, err + } + return &p.nodes[h-1], rest, nil +} + +func (p *Parser) parseKeyword(b []byte, kw string, kind Kind) (int32, []byte, error) { + k, raw, _, rest, err := scanKeyword(b, kw, kind) + if err != nil { + return 0, nil, err + } + h := p.push(Node{Kind: k, Raw: p.Range(raw), Data: raw}) + return h, rest, nil +} + +// scanKeyword scans a keyword value (true, false, inf, nan) without building +// an AST node. raw and value are identical (the keyword bytes). +func scanKeyword(b []byte, kw string, kind Kind) (Kind, []byte, []byte, []byte, error) { + if len(b) < len(kw) || string(b[:len(kw)]) != kw { + n := len(kw) + if len(b) < n { + n = len(b) + } + return Invalid, nil, nil, nil, NewParserError(b[:n], "expected keyword %q", kw) + } + return kind, b[:len(kw)], b[:len(kw)], b[len(kw):], nil +} + +// parseValArray parses an array value. b starts at '['. +func (p *Parser) parseValArray(b []byte) (int32, []byte, error) { + arr := p.push(Node{Kind: Array}) + b = b[1:] + + var lastChild int32 + appendChild := func(h int32) { + if lastChild == 0 { + p.at(arr).child = h + } else { + p.at(lastChild).next = h + } + lastChild = h + } + + // Comments inside the array are attached as follows: the first comment + // of a "run" (consecutive comments with no value in between) becomes a + // child of the array, interleaved with values; subsequent comments of the + // run are attached as children of the first one. + var runFirst, runLast int32 + + // afterValue is true when a value has been parsed and a comma (or the + // closing bracket) is expected before the next one. + afterValue := false + for { + b = skipWhitespace(b) + if len(b) == 0 { + return 0, nil, NewParserError(b, "array is incomplete") + } + + switch b[0] { + case ']': + return arr, b[1:], nil + case '\n': + b = b[1:] + continue + case '\r': + if len(b) > 1 && b[1] == '\n' { + b = b[2:] + continue + } + return 0, nil, NewParserError(b[:1], "expected newline but got %#U", b[0]) + case '#': + comment, rest, err := scanComment(b) + if err != nil { + return 0, nil, err + } + if p.KeepComments { + h := p.push(Node{Kind: Comment, Raw: p.Range(comment), Data: comment}) + switch { + case runFirst == 0: + appendChild(h) + runFirst = h + case runLast == runFirst: + p.at(runFirst).child = h + default: + p.at(runLast).next = h + } + runLast = h + } + b = rest + continue + case ',': + if !afterValue { + return 0, nil, NewParserError(b[:1], "expected value but got %#U", b[0]) + } + afterValue = false + b = b[1:] + continue + default: + if afterValue { + return 0, nil, NewParserError(b[:1], "expected ',' or ']' after array value") + } + h, rest, err := p.parseVal(b) + if err != nil { + return 0, nil, err + } + appendChild(h) + afterValue = true + runFirst, runLast = 0, 0 + b = rest + continue + } + } +} + +// parseInlineTable parses an inline table value. b starts at '{'. +// +// Per TOML v1.1.0, inline tables may span multiple lines (whitespace, +// comments and newlines are allowed between elements) and may contain a +// trailing comma. +func (p *Parser) parseInlineTable(b []byte) (int32, []byte, error) { + tbl := p.push(Node{Kind: InlineTable, Raw: p.Range(b[:1])}) + b = b[1:] + + var lastChild int32 + appendChild := func(h int32) { + if lastChild == 0 { + p.at(tbl).child = h + } else { + p.at(lastChild).next = h + } + lastChild = h + } + + // Comments are attached as in arrays: the first comment of a "run" + // (consecutive comments with no key-value in between) becomes a child of + // the table, interleaved with key-values; subsequent comments of the run + // hang off the first one. + var runFirst, runLast int32 + + // afterValue is true when a key-value has been parsed and a comma (or the + // closing brace) is expected before the next one. + afterValue := false + for { + b = skipWhitespace(b) + if len(b) == 0 { + return 0, nil, NewParserError(b, "inline table is incomplete") + } + + switch b[0] { + case '}': + return tbl, b[1:], nil + case '\n': + b = b[1:] + continue + case '\r': + if len(b) > 1 && b[1] == '\n' { + b = b[2:] + continue + } + return 0, nil, NewParserError(b[:1], "expected newline but got %#U", b[0]) + case '#': + comment, rest, err := scanComment(b) + if err != nil { + return 0, nil, err + } + if p.KeepComments { + h := p.push(Node{Kind: Comment, Raw: p.Range(comment), Data: comment}) + switch { + case runFirst == 0: + appendChild(h) + runFirst = h + case runLast == runFirst: + p.at(runFirst).child = h + default: + p.at(runLast).next = h + } + runLast = h + } + b = rest + continue + case ',': + if !afterValue { + return 0, nil, NewParserError(b[:1], "unexpected comma in inline table") + } + afterValue = false + b = b[1:] + continue + default: + if afterValue { + return 0, nil, NewParserError(b[:1], "expected ',' or '}' after inline table key-value") + } + h, rest, err := p.parseKeyval(b) + if err != nil { + return 0, nil, err + } + appendChild(h) + afterValue = true + runFirst, runLast = 0, 0 + b = rest + continue + } + } +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isHexDigit(c byte) bool { + return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') +} + +// isDateTimeStart reports whether b looks like the start of a date or time +// value instead of a number. Values starting with two digits followed by a +// colon are times; values starting with four digits followed by a dash are +// dates. +func isDateTimeStart(b []byte) bool { + if len(b) >= 3 && isDigit(b[1]) && b[2] == ':' { + return true + } + if len(b) >= 5 && isDigit(b[1]) && isDigit(b[2]) && isDigit(b[3]) && b[4] == '-' { + return true + } + return false +} + +// expectDigits checks that the n first bytes of b are digits. +// parseDateTime parses date and/or time values. b starts with a digit. +// +// The parser is lenient: it scans the characters that can be part of a date +// and/or time value to delimit and classify the token, but leaves the +// validation of its contents to the document consumer. This keeps the value +// in one piece, so that errors about its content can point at the right +// place. +func (p *Parser) parseDateTime(b []byte) (int32, []byte, error) { + kind, raw, _, rest, err := scanDateTime(b) + if err != nil { + return 0, nil, err + } + h := p.push(Node{Kind: kind, Raw: p.Range(raw), Data: raw}) + return h, rest, nil +} + +// scanDateTime classifies and delimits a date/time value without building an +// AST node. raw and value are identical (the token bytes). +func scanDateTime(b []byte) (Kind, []byte, []byte, []byte, error) { + // Greedily scan the characters that may compose a date/time value. A + // space is part of the value only when it serves as the delimiter + // between the date and the time, which is approximated by requiring a + // digit right after it. + i := 0 + delim := -1 + for i < len(b) { + c := b[i] + if isDigit(c) || c == ':' || c == '-' || c == '+' || c == '.' || c == 'Z' || c == 'z' { + i++ + continue + } + if c == 'T' || c == 't' || (c == ' ' && i+1 < len(b) && isDigit(b[i+1])) { + if delim < 0 { + delim = i + } + i++ + continue + } + break + } + tok := b[:i] + + var kind Kind + switch { + case tok[2] == ':': + kind = LocalTime + case delim < 0: + kind = LocalDate + case bytes.ContainsAny(tok[delim+1:], "Zz+-"): + kind = DateTime + default: + kind = LocalDateTime + } + + return kind, tok, tok, b[i:], nil +} + +// scanDigitsWithUnderscores scans a run of digits potentially separated by +// underscores. b starts right after the first digit of the run. isInRange +// selects the kind of digits. Returns the index after the run. +func scanDigitsWithUnderscores(b []byte, i int, isInRange func(byte) bool) (int, error) { + for i < len(b) { + c := b[i] + if isInRange(c) { + i++ + continue + } + if c == '_' { + if i+1 >= len(b) || !isInRange(b[i+1]) { + end := i + 2 + if end > len(b) { + end = len(b) + } + return 0, NewParserError(b[i:end], "number must have at least one digit between underscores") + } + i += 2 + continue + } + break + } + return i, nil +} + +// parseIntOrFloat parses integer and float values, including the special +// values inf and nan with an optional sign. +func (p *Parser) parseIntOrFloat(b []byte) (int32, []byte, error) { + kind, raw, _, rest, err := scanIntOrFloat(b) + if err != nil { + return 0, nil, err + } + h := p.push(Node{Kind: kind, Raw: p.Range(raw), Data: raw}) + return h, rest, nil +} + +// scanIntOrFloat delimits and classifies an integer or float value (including +// the special floats inf and nan with an optional sign) without building an +// AST node. raw and value are identical (the token bytes). +func scanIntOrFloat(b []byte) (Kind, []byte, []byte, []byte, error) { + i := 0 + if b[i] == '+' || b[i] == '-' { + i++ + } + if i >= len(b) { + return Invalid, nil, nil, nil, NewParserError(b, "expected number after sign") + } + + // special floats + if b[i] == 'i' || b[i] == 'n' { + kw := "inf" + if b[i] == 'n' { + kw = "nan" + } + if len(b) < i+3 || string(b[i:i+3]) != kw { + return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "expected %q", kw) + } + i += 3 + return Float, b[:i], b[:i], b[i:], nil + } + + if !isDigit(b[i]) { + return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "expected digit but got %#U", b[i]) + } + + // radix prefixes + if b[i] == '0' && i+1 < len(b) && (b[i+1] == 'x' || b[i+1] == 'o' || b[i+1] == 'b') { + if i != 0 { + return Invalid, nil, nil, nil, NewParserError(b[:2], "sign is not allowed on numbers with a radix prefix") + } + var isInRange func(byte) bool + switch b[1] { + case 'x': + isInRange = isHexDigit + case 'o': + isInRange = func(c byte) bool { return c >= '0' && c <= '7' } + case 'b': + isInRange = func(c byte) bool { return c == '0' || c == '1' } + } + i = 2 + if i >= len(b) || !isInRange(b[i]) { + return Invalid, nil, nil, nil, NewParserError(b[:2], "radix prefix must be followed by at least one digit") + } + i++ + var err error + i, err = scanDigitsWithUnderscores(b, i, isInRange) + if err != nil { + return Invalid, nil, nil, nil, err + } + return Integer, b[:i], b[:i], b[i:], nil + } + + // decimal integer part + leadingZero := b[i] == '0' + digitsStart := i + i++ + var err error + i, err = scanDigitsWithUnderscores(b, i, isDigit) + if err != nil { + return Invalid, nil, nil, nil, err + } + if leadingZero && i > digitsStart+1 { + return Invalid, nil, nil, nil, NewParserError(b[digitsStart:digitsStart+2], "integers cannot have leading zeroes") + } + + kind := Integer + + // fractional part + if i < len(b) && b[i] == '.' { + i++ + if i >= len(b) || !isDigit(b[i]) { + return Invalid, nil, nil, nil, NewParserError(highlight1(b[i:]), "decimal point must be followed by a digit") + } + i++ + i, err = scanDigitsWithUnderscores(b, i, isDigit) + if err != nil { + return Invalid, nil, nil, nil, err + } + kind = Float + } + + // exponent + if i < len(b) && (b[i] == 'e' || b[i] == 'E') { + i++ + if i < len(b) && (b[i] == '+' || b[i] == '-') { + i++ + } + if i >= len(b) || !isDigit(b[i]) { + return Invalid, nil, nil, nil, NewParserError(highlight1(b[i:]), "exponent must contain at least one digit") + } + i++ + i, err = scanDigitsWithUnderscores(b, i, isDigit) + if err != nil { + return Invalid, nil, nil, nil, err + } + kind = Float + } + + // A letter right after the number means it was meant to be a string that + // was left unquoted (e.g. "20s"). Report that instead of the misleading + // "expected newline" raised later (issue #413). + if i < len(b) { + if c := b[i]; (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { + return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "strings must be quoted") + } + } + + return kind, b[:i], b[:i], b[i:], nil +} + +// highlight1 returns a 1-byte highlight at the start of b, or b itself if it +// is empty. +func highlight1(b []byte) []byte { + if len(b) > 0 { + return b[:1] + } + return b +} + +func skipWhitespace(b []byte) []byte { + for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') { + b = b[1:] + } + return b +} + +// Word-at-a-time byte scanning helpers. These detect, within an 8-byte word, +// the presence of bytes that need special handling, so that runs of plain +// ASCII characters can be skipped 8 bytes at a time. +const ( + lsb = 0x0101010101010101 + msb = 0x8080808080808080 +) + +// hasByteBelow reports whether any byte of the word x is strictly below n. +// Only meaningful when combined with a check that no byte has its high bit +// set. +func hasByteBelow(x uint64, n uint64) uint64 { + return (x - n*lsb) & ^x & msb +} + +// hasByteEqual reports whether any byte of the word x equals c. Only +// meaningful for bytes without their high bit set. +func hasByteEqual(x uint64, c uint64) uint64 { + y := x ^ (c * lsb) + return (y - lsb) & ^y & msb +} + +// scanComment parses a comment, starting at the '#' character. It returns the +// comment bytes (including '#', excluding the line ending) and the rest of +// the input. +func scanComment(b []byte) ([]byte, []byte, error) { + i := 1 + for i < len(b) { + // Fast path: skip 8 bytes at a time as long as they are all plain + // printable ASCII. + for i+8 <= len(b) { + x := binary.LittleEndian.Uint64(b[i:]) + if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, 0x7f) != 0 { + break + } + i += 8 + } + if i >= len(b) { + break + } + + c := b[i] + if c >= 0x80 { + var ok bool + i, ok = scanUtf8Run(b, i) + if !ok { + return nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in comment") + } + continue + } + switch { + case c >= 0x20 && c < 0x7f: + i++ + case c == '\n': + return b[:i], b[i:], nil + case c == '\r': + if i+1 < len(b) && b[i+1] == '\n' { + return b[:i], b[i:], nil + } + return nil, nil, NewParserError(b[i:i+1], "carriage returns are not allowed in comments") + case c == '\t': + i++ + default: + return nil, nil, NewParserError(b[i:i+1], "control characters are not allowed in comments") + } + } + return b[:i], b[i:], nil +} + +// parseLiteralString parses a single-line literal string, starting at the +// opening quote. Returns the raw bytes (with quotes), the string value +// (without quotes) and the rest of the input. +func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { + i := 1 + for { + // Fast path over plain ASCII. + for i+8 <= len(b) { + x := binary.LittleEndian.Uint64(b[i:]) + if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, '\'')|hasByteEqual(x, 0x7f) != 0 { + break + } + i += 8 + } + if i >= len(b) { + return nil, nil, nil, NewParserError(b[len(b):], "unterminated literal string") + } + + c := b[i] + switch { + case c == '\'': + return b[:i+1], b[1:i], b[i+1:], nil + case c >= 0x20 && c < 0x7f: + i++ + case c == '\t': + i++ + case c == '\n' || c == '\r': + return nil, nil, nil, NewParserError(b[i:i+1], "literal strings cannot have new lines") + case c < 0x80: + return nil, nil, nil, NewParserError(b[i:i+1], "literal strings cannot have control characters") + default: + var ok bool + i, ok = scanUtf8Run(b, i) + if !ok { + return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in literal string") + } + } + } +} + +// parseMultilineLiteralString parses a multi-line literal string, starting at +// the opening triple quote. +func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) { + i := 3 + // trim the newline right after the opening delimiter + if i < len(b) && b[i] == '\n' { + i++ + } else if i+1 < len(b) && b[i] == '\r' && b[i+1] == '\n' { + i += 2 + } + contentStart := i + + for i < len(b) { + c := b[i] + switch { + case c == '\'': + // count consecutive quotes + j := i + for j < len(b) && b[j] == '\'' { + j++ + } + n := j - i + if n >= 3 { + if n > 5 { + return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline literal string") + } + // n-3 quotes belong to the content; the last 3 close the + // string. + contentEnd := i + n - 3 + return b[:j], b[contentStart:contentEnd], b[j:], nil + } + i = j + case c >= 0x20 && c < 0x7f: + i++ + case c == '\t' || c == '\n': + i++ + case c == '\r': + if i+1 < len(b) && b[i+1] == '\n' { + i += 2 + continue + } + return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character") + case c < 0x80: + return nil, nil, nil, NewParserError(b[i:i+1], "multiline literal strings cannot have control characters") + default: + var ok bool + i, ok = scanUtf8Run(b, i) + if !ok { + return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline literal string") + } + } + } + return nil, nil, nil, NewParserError(b[len(b):], "multiline literal string not terminated by '''") +} + +// parseBasicString parses a single-line basic string, starting at the opening +// quote. The value is a subslice of the input if the string contains no +// escape sequence, or a new allocation otherwise. +func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { + i := 1 + // First pass: handle strings without escape sequences without allocating. + for { + for i+8 <= len(b) { + x := binary.LittleEndian.Uint64(b[i:]) + if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, '"')|hasByteEqual(x, '\\')|hasByteEqual(x, 0x7f) != 0 { + break + } + i += 8 + } + if i >= len(b) { + return nil, nil, nil, NewParserError(b[len(b):], "unterminated basic string") + } + + c := b[i] + switch { + case c == '"': + return b[:i+1], b[1:i], b[i+1:], nil + case c == '\\': + // switch to the escape-aware parser, copying what has been + // scanned so far + return p.parseBasicStringEscaped(b, i) + case c >= 0x20 && c < 0x7f: + i++ + case c == '\t': + i++ + case c == '\n' || c == '\r': + return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines") + case c < 0x80: + return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have control characters") + default: + var ok bool + i, ok = scanUtf8Run(b, i) + if !ok { + return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in basic string") + } + } + } +} + +// findBasicStringEnd returns the index of the quote closing a basic string, +// or -1 if the string is not terminated. i is the index of the first +// character after the opening quote. It does not validate the content: it +// only skips over escape sequences so that escaped quotes do not terminate +// the string. +func findBasicStringEnd(b []byte, i int) int { + for i < len(b) { + switch b[i] { + case '"': + return i + case '\\': + i += 2 + default: + i++ + } + } + return -1 +} + +// parseBasicStringEscaped continues parsing a basic string that contains +// escape sequences. i is the index of the first backslash. +func (p *Parser) parseBasicStringEscaped(b []byte, i int) ([]byte, []byte, []byte, error) { + // Escape sequences only ever shrink, so the content length before + // unescaping is enough to never reallocate. + bufCap := len(b) - 1 + if end := findBasicStringEnd(b, i); end >= 0 { + bufCap = end - 1 + } + buf := make([]byte, i-1, bufCap) + copy(buf, b[1:i]) + + for i < len(b) { + c := b[i] + switch { + case c == '"': + return b[:i+1], buf, b[i+1:], nil + case c == '\\': + i++ + if i >= len(b) { + return nil, nil, nil, NewParserError(b[i-1:], `need a character after \`) + } + var err error + buf, i, err = unescape(buf, b, i) + if err != nil { + return nil, nil, nil, err + } + case c >= 0x20 && c < 0x7f: + buf = append(buf, c) + i++ + case c == '\t': + buf = append(buf, c) + i++ + case c == '\n' || c == '\r': + return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines") + case c < 0x80: + return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have control characters") + default: + j, ok := scanUtf8Run(b, i) + if !ok { + return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in basic string") + } + buf = append(buf, b[i:j]...) + i = j + } + } + return nil, nil, nil, NewParserError(b[len(b):], "unterminated basic string") +} + +// unescape processes one escape sequence. i is the index of the character +// right after the backslash. It returns the updated buffer and index. +func unescape(buf []byte, b []byte, i int) ([]byte, int, error) { + c := b[i] + switch c { + case '"': + return append(buf, '"'), i + 1, nil + case '\\': + return append(buf, '\\'), i + 1, nil + case 'b': + return append(buf, '\b'), i + 1, nil + case 'f': + return append(buf, '\f'), i + 1, nil + case 'n': + return append(buf, '\n'), i + 1, nil + case 'r': + return append(buf, '\r'), i + 1, nil + case 't': + return append(buf, '\t'), i + 1, nil + case 'e': + // TOML v1.1.0: \e is the escape character (U+001B). + return append(buf, 0x1B), i + 1, nil + case 'x': + // TOML v1.1.0: \xHH is a two-digit hexadecimal code point. + return unescapeUnicode(buf, b, i+1, 2) + case 'u': + return unescapeUnicode(buf, b, i+1, 4) + case 'U': + return unescapeUnicode(buf, b, i+1, 8) + default: + return nil, 0, NewParserError(b[i-1:i+1], "invalid escape character %#U", c) + } +} + +// unescapeUnicode handles \uXXXX and \UXXXXXXXX escape sequences. i is the +// index of the first hex digit. +func unescapeUnicode(buf []byte, b []byte, i int, n int) ([]byte, int, error) { + if i+n > len(b) { + return nil, 0, NewParserError(b[i-2:], "unicode escape sequence is too short") + } + var r uint32 + for k := 0; k < n; k++ { + c := b[i+k] + var d uint32 + switch { + case c >= '0' && c <= '9': + d = uint32(c - '0') + case c >= 'a' && c <= 'f': + d = uint32(c-'a') + 10 + case c >= 'A' && c <= 'F': + d = uint32(c-'A') + 10 + default: + return nil, 0, NewParserError(b[i+k:i+k+1], "invalid hexadecimal digit in unicode escape sequence") + } + r = r<<4 | d + } + if r > utf8.MaxRune || (r >= 0xD800 && r <= 0xDFFF) { + return nil, 0, NewParserError(b[i-2:i+n], "escape sequence is not a valid unicode code point") + } + return utf8.AppendRune(buf, rune(r)), i + n, nil +} + +// parseMultilineBasicString parses a multi-line basic string, starting at the +// opening triple quote. +func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { + i := 3 + // trim the newline right after the opening delimiter + if i < len(b) && b[i] == '\n' { + i++ + } else if i+1 < len(b) && b[i] == '\r' && b[i+1] == '\n' { + i += 2 + } + contentStart := i + + // First pass without allocating, until an escape sequence is found. + for i < len(b) { + c := b[i] + switch { + case c == '"': + j := i + for j < len(b) && b[j] == '"' { + j++ + } + n := j - i + if n >= 3 { + if n > 5 { + return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline basic string") + } + contentEnd := i + n - 3 + return b[:j], b[contentStart:contentEnd], b[j:], nil + } + i = j + case c == '\\': + return p.parseMultilineBasicStringEscaped(b, contentStart, i) + case c >= 0x20 && c < 0x7f: + i++ + case c == '\t' || c == '\n': + i++ + case c == '\r': + if i+1 < len(b) && b[i+1] == '\n' { + i += 2 + continue + } + return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character") + case c < 0x80: + return nil, nil, nil, NewParserError(b[i:i+1], "multiline basic strings cannot have control characters") + default: + var ok bool + i, ok = scanUtf8Run(b, i) + if !ok { + return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline basic string") + } + } + } + return nil, nil, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`) +} + +// findMultilineBasicStringEnd returns the index of the first quote of the +// run of quotes closing a multi-line basic string, or -1 if the string is +// not terminated. It does not validate the content: it only skips over +// escape sequences so that escaped quotes do not terminate the string. +func findMultilineBasicStringEnd(b []byte, i int) int { + for { + j := bytes.IndexAny(b[i:], "\"\\") + if j < 0 { + return -1 + } + i += j + if b[i] == '\\' { + i += 2 + if i > len(b) { + return -1 + } + continue + } + j = i + for j < len(b) && b[j] == '"' { + j++ + } + if j-i >= 3 { + return i + } + i = j + } +} + +// parseMultilineBasicStringEscaped continues parsing a multi-line basic +// string that contains escape sequences. i is the index of the first +// backslash; content starts at contentStart. +func (p *Parser) parseMultilineBasicStringEscaped(b []byte, contentStart, i int) ([]byte, []byte, []byte, error) { + // Escape sequences only ever shrink, so the content length before + // unescaping is enough to never reallocate. The closing run of quotes + // can lend up to two quotes to the content. + bufCap := len(b) - contentStart + if end := findMultilineBasicStringEnd(b, i); end >= 0 { + bufCap = end + 2 - contentStart + } + buf := make([]byte, i-contentStart, bufCap) + copy(buf, b[contentStart:i]) + + for i < len(b) { + c := b[i] + switch { + case c == '"': + j := i + for j < len(b) && b[j] == '"' { + j++ + } + n := j - i + if n >= 3 { + if n > 5 { + return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline basic string") + } + buf = append(buf, b[i:i+n-3]...) + return b[:j], buf, b[j:], nil + } + buf = append(buf, b[i:j]...) + i = j + case c == '\\': + i++ + if i >= len(b) { + return nil, nil, nil, NewParserError(b[i-1:], `need a character after \`) + } + // Escaped newline: backslash, optional whitespace, newline, + // then all following whitespace and newlines are trimmed. + if b[i] == ' ' || b[i] == '\t' || b[i] == '\n' || b[i] == '\r' { + j := i + for j < len(b) && (b[j] == ' ' || b[j] == '\t') { + j++ + } + if j < len(b) && b[j] == '\r' { + j++ + } + if j >= len(b) || b[j] != '\n' { + return nil, nil, nil, NewParserError(b[i-1:i+1], "invalid escape character %#U", b[i]) + } + j++ + for j < len(b) && (b[j] == ' ' || b[j] == '\t' || b[j] == '\n' || b[j] == '\r') { + // note: a lone \r not followed by \n will be caught on + // the next iteration of the outer loop. + if b[j] == '\r' { + if j+1 >= len(b) || b[j+1] != '\n' { + break + } + j++ + } + j++ + } + i = j + continue + } + var err error + buf, i, err = unescape(buf, b, i) + if err != nil { + return nil, nil, nil, err + } + case c >= 0x20 && c < 0x7f: + buf = append(buf, c) + i++ + case c == '\t' || c == '\n': + buf = append(buf, c) + i++ + case c == '\r': + if i+1 < len(b) && b[i+1] == '\n' { + buf = append(buf, '\r', '\n') + i += 2 + continue + } + return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character") + case c < 0x80: + return nil, nil, nil, NewParserError(b[i:i+1], "multiline basic strings cannot have control characters") + default: + j, ok := scanUtf8Run(b, i) + if !ok { + return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline basic string") + } + buf = append(buf, b[i:j]...) + i = j + } + } + return nil, nil, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`) +} + +// scanUtf8Run consumes a run of valid non-ASCII UTF-8 runes starting at +// b[i]. It returns the index of the first byte after the run, and whether +// the run was entirely valid. Processing whole runs amortizes the cost of +// the call compared to validating rune by rune. +func scanUtf8Run(b []byte, i int) (int, bool) { + for i < len(b) { + c := b[i] + switch { + case c < 0x80: + return i, true + case c < 0xC2: + return i, false + case c < 0xE0: + if i+1 >= len(b) || b[i+1]&0xC0 != 0x80 { + return i, false + } + i += 2 + case c < 0xF0: + if i+2 >= len(b) || b[i+2]&0xC0 != 0x80 { + return i, false + } + b1 := b[i+1] + switch c { + case 0xE0: + if b1 < 0xA0 || b1 > 0xBF { + return i, false + } + case 0xED: + // exclude surrogates + if b1 < 0x80 || b1 > 0x9F { + return i, false + } + default: + if b1&0xC0 != 0x80 { + return i, false + } + } + i += 3 + case c < 0xF5: + if i+3 >= len(b) || b[i+2]&0xC0 != 0x80 || b[i+3]&0xC0 != 0x80 { + return i, false + } + b1 := b[i+1] + switch c { + case 0xF0: + if b1 < 0x90 || b1 > 0xBF { + return i, false + } + case 0xF4: + if b1 < 0x80 || b1 > 0x8F { + return i, false + } + default: + if b1&0xC0 != 0x80 { + return i, false + } + } + i += 4 + default: + return i, false + } + } + return i, true +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go b/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go new file mode 100644 index 0000000..e417040 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go @@ -0,0 +1,32 @@ +package unstable + +// Unmarshaler is implemented by types that can unmarshal a TOML description +// of themselves. The input is a valid TOML document containing the relevant +// portion of the parsed document. +// +// For tables (including split tables defined in multiple places), the data +// contains the raw key-value bytes from the original document with adjusted +// table headers to be relative to the unmarshaling target. +type Unmarshaler interface { + UnmarshalTOML(data []byte) error +} + +// RawMessage is a raw encoded TOML value. It implements Unmarshaler and can +// be used to delay TOML decoding or capture raw content. +// +// Example usage: +// +// type Config struct { +// Plugin RawMessage `toml:"plugin"` +// } +// +// var cfg Config +// toml.NewDecoder(r).EnableUnmarshalerInterface().Decode(&cfg) +// // cfg.Plugin now contains the raw TOML bytes for [plugin] +type RawMessage []byte + +// UnmarshalTOML implements Unmarshaler. +func (m *RawMessage) UnmarshalTOML(data []byte) error { + *m = append((*m)[:0], data...) + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 739228d..dc6c4e6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -265,9 +265,12 @@ github.com/opencontainers/go-digest ## explicit; go 1.18 github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/pelletier/go-toml v1.9.5 -## explicit; go 1.12 -github.com/pelletier/go-toml +# github.com/pelletier/go-toml/v2 v2.4.1 +## explicit; go 1.21.0 +github.com/pelletier/go-toml/v2 +github.com/pelletier/go-toml/v2/internal/parserbridge +github.com/pelletier/go-toml/v2/internal/tracker +github.com/pelletier/go-toml/v2/unstable # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors