pax_global_header00006660000000000000000000000064141653241740014521gustar00rootroot0000000000000052 comment=fed1464066413075eac02cd4dc368b5221845541 go-toml-1.9.5/000077500000000000000000000000001416532417400131135ustar00rootroot00000000000000go-toml-1.9.5/.dockerignore000066400000000000000000000000461416532417400155670ustar00rootroot00000000000000cmd/tomll/tomll cmd/tomljson/tomljson go-toml-1.9.5/.github/000077500000000000000000000000001416532417400144535ustar00rootroot00000000000000go-toml-1.9.5/.github/ISSUE_TEMPLATE/000077500000000000000000000000001416532417400166365ustar00rootroot00000000000000go-toml-1.9.5/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015021416532417400213260ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior. Including TOML files. **Expected behavior** A clear and concise description of what you expected to happen, if other than "should work". **Versions** - go-toml: version (or git sha) - go: version - operating system: e.g. macOS, Windows, Linux **Additional context** Add any other context about the problem here that you think may help to diagnose. go-toml-1.9.5/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002721416532417400206270ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Questions and discussions url: https://github.com/pelletier/go-toml/discussions about: Please ask and answer questions here. go-toml-1.9.5/.github/dependabot.yml000066400000000000000000000002211416532417400172760ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily time: "13:00" open-pull-requests-limit: 10 go-toml-1.9.5/.github/workflows/000077500000000000000000000000001416532417400165105ustar00rootroot00000000000000go-toml-1.9.5/.github/workflows/codeql-analysis.yml000066400000000000000000000044651416532417400223340ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '26 19 * * 0' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 go-toml-1.9.5/.gitignore000066400000000000000000000001461416532417400151040ustar00rootroot00000000000000test_program/test_program_bin fuzz/ cmd/tomll/tomll cmd/tomljson/tomljson cmd/tomltestgen/tomltestgen go-toml-1.9.5/CONTRIBUTING.md000066400000000000000000000116201416532417400153440ustar00rootroot00000000000000## 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 go-toml-1.9.5/Dockerfile000066400000000000000000000004771416532417400151150ustar00rootroot00000000000000FROM 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 go-toml-1.9.5/LICENSE000066400000000000000000000321131416532417400141200ustar00rootroot00000000000000The 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. go-toml-1.9.5/Makefile000066400000000000000000000011671416532417400145600ustar00rootroot00000000000000export 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) go-toml-1.9.5/PULL_REQUEST_TEMPLATE.md000066400000000000000000000003131416532417400167110ustar00rootroot00000000000000**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). go-toml-1.9.5/README.md000066400000000000000000000120511416532417400143710ustar00rootroot00000000000000# 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). go-toml-1.9.5/SECURITY.md000066400000000000000000000012431416532417400147040ustar00rootroot00000000000000# Security Policy ## 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: | | All 1.x | :x: | | All 0.x | :x: | ## Reporting a Vulnerability Email a vulnerability report to `security@pelletier.codes`. Make sure to include as many details as possible to reproduce the vulnerability. This is a side-project: I will try to get back to you as quickly as possible, time permitting in my personal life. Providing a working patch helps very much! go-toml-1.9.5/azure-pipelines.yml000066400000000000000000000112761416532417400167610ustar00rootroot00000000000000trigger: - 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' go-toml-1.9.5/benchmark.sh000077500000000000000000000017031416532417400154050ustar00rootroot00000000000000#!/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} go-toml-1.9.5/benchmark/000077500000000000000000000000001416532417400150455ustar00rootroot00000000000000go-toml-1.9.5/benchmark/benchmark.json000066400000000000000000000071121416532417400176730ustar00rootroot00000000000000{ "array": { "key1": [ 1, 2, 3 ], "key2": [ "red", "yellow", "green" ], "key3": [ [ 1, 2 ], [ 3, 4, 5 ] ], "key4": [ [ 1, 2 ], [ "a", "b", "c" ] ], "key5": [ 1, 2, 3 ], "key6": [ 1, 2 ] }, "boolean": { "False": false, "True": true }, "datetime": { "key1": "1979-05-27T07:32:00Z", "key2": "1979-05-27T00:32:00-07:00", "key3": "1979-05-27T00:32:00.999999-07:00" }, "float": { "both": { "key": 6.626e-34 }, "exponent": { "key1": 5e+22, "key2": 1000000, "key3": -0.02 }, "fractional": { "key1": 1, "key2": 3.1415, "key3": -0.01 }, "underscores": { "key1": 9224617.445991227, "key2": 1e+100 } }, "fruit": [{ "name": "apple", "physical": { "color": "red", "shape": "round" }, "variety": [{ "name": "red delicious" }, { "name": "granny smith" } ] }, { "name": "banana", "variety": [{ "name": "plantain" }] } ], "integer": { "key1": 99, "key2": 42, "key3": 0, "key4": -17, "underscores": { "key1": 1000, "key2": 5349221, "key3": 12345 } }, "products": [{ "name": "Hammer", "sku": 738594937 }, {}, { "color": "gray", "name": "Nail", "sku": 284758393 } ], "string": { "basic": { "basic": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." }, "literal": { "multiline": { "lines": "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", "regex2": "I [dw]on't need \\d{2} apples" }, "quoted": "Tom \"Dubs\" Preston-Werner", "regex": "\u003c\\i\\c*\\s*\u003e", "winpath": "C:\\Users\\nodejs\\templates", "winpath2": "\\\\ServerX\\admin$\\system32\\" }, "multiline": { "continued": { "key1": "The quick brown fox jumps over the lazy dog.", "key2": "The quick brown fox jumps over the lazy dog.", "key3": "The quick brown fox jumps over the lazy dog." }, "key1": "One\nTwo", "key2": "One\nTwo", "key3": "One\nTwo" } }, "table": { "inline": { "name": { "first": "Tom", "last": "Preston-Werner" }, "point": { "x": 1, "y": 2 } }, "key": "value", "subtable": { "key": "another value" } }, "x": { "y": { "z": { "w": {} } } } } go-toml-1.9.5/benchmark/benchmark.toml000066400000000000000000000121721416532417400176770ustar00rootroot00000000000000################################################################################ ## Comment # Speak your mind with the hash symbol. They go from the symbol to the end of # the line. ################################################################################ ## Table # Tables (also known as hash tables or dictionaries) are collections of # key/value pairs. They appear in square brackets on a line by themselves. [table] key = "value" # Yeah, you can do this. # Nested tables are denoted by table names with dots in them. Name your tables # whatever crap you please, just don't use #, ., [ or ]. [table.subtable] key = "another value" # You don't need to specify all the super-tables if you don't want to. TOML # knows how to do it for you. # [x] you # [x.y] don't # [x.y.z] need these [x.y.z.w] # for this to work ################################################################################ ## Inline Table # Inline tables provide a more compact syntax for expressing tables. They are # especially useful for grouped data that can otherwise quickly become verbose. # Inline tables are enclosed in curly braces `{` and `}`. No newlines are # allowed between the curly braces unless they are valid within a value. [table.inline] name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } ################################################################################ ## String # There are four ways to express strings: basic, multi-line basic, literal, and # multi-line literal. All strings must contain only valid UTF-8 characters. [string.basic] basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." [string.multiline] # The following strings are byte-for-byte equivalent: key1 = "One\nTwo" key2 = """One\nTwo""" key3 = """ One Two""" [string.multiline.continued] # The following strings are byte-for-byte equivalent: key1 = "The quick brown fox jumps over the lazy dog." key2 = """ The quick brown \ fox jumps over \ the lazy dog.""" key3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ """ [string.literal] # What you see is what you get. winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' regex = '<\i\c*\s*>' [string.literal.multiline] regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. ''' ################################################################################ ## Integer # Integers are whole numbers. Positive numbers may be prefixed with a plus sign. # Negative numbers are prefixed with a minus sign. [integer] key1 = +99 key2 = 42 key3 = 0 key4 = -17 [integer.underscores] # For large numbers, you may use underscores to enhance readability. Each # underscore must be surrounded by at least one digit. key1 = 1_000 key2 = 5_349_221 key3 = 1_2_3_4_5 # valid but inadvisable ################################################################################ ## Float # A float consists of an integer part (which may be prefixed with a plus or # minus sign) followed by a fractional part and/or an exponent part. [float.fractional] key1 = +1.0 key2 = 3.1415 key3 = -0.01 [float.exponent] key1 = 5e+22 key2 = 1e6 key3 = -2E-2 [float.both] key = 6.626e-34 [float.underscores] key1 = 9_224_617.445_991_228_313 key2 = 1e1_00 ################################################################################ ## Boolean # Booleans are just the tokens you're used to. Always lowercase. [boolean] True = true False = false ################################################################################ ## Datetime # Datetimes are RFC 3339 dates. [datetime] key1 = 1979-05-27T07:32:00Z key2 = 1979-05-27T00:32:00-07:00 key3 = 1979-05-27T00:32:00.999999-07:00 ################################################################################ ## Array # Arrays are square brackets with other primitives inside. Whitespace is # ignored. Elements are separated by commas. Data types may not be mixed. [array] key1 = [ 1, 2, 3 ] key2 = [ "red", "yellow", "green" ] key3 = [ [ 1, 2 ], [3, 4, 5] ] #key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok # Arrays can also be multiline. So in addition to ignoring whitespace, arrays # also ignore newlines between the brackets. Terminating commas are ok before # the closing bracket. key5 = [ 1, 2, 3 ] key6 = [ 1, 2, # this is ok ] ################################################################################ ## Array of Tables # These can be expressed by using a table name in double brackets. Each table # with the same double bracketed name will be an element in the array. The # tables are inserted in the order encountered. [[products]] name = "Hammer" sku = 738594937 [[products]] [[products]] name = "Nail" sku = 284758393 color = "gray" # You can create nested arrays of tables as well. [[fruit]] name = "apple" [fruit.physical] color = "red" shape = "round" [[fruit.variety]] name = "red delicious" [[fruit.variety]] name = "granny smith" [[fruit]] name = "banana" [[fruit.variety]] name = "plantain" go-toml-1.9.5/benchmark/benchmark.yml000066400000000000000000000035501416532417400175250ustar00rootroot00000000000000--- array: key1: - 1 - 2 - 3 key2: - red - yellow - green key3: - - 1 - 2 - - 3 - 4 - 5 key4: - - 1 - 2 - - a - b - c key5: - 1 - 2 - 3 key6: - 1 - 2 boolean: 'False': false 'True': true datetime: key1: '1979-05-27T07:32:00Z' key2: '1979-05-27T00:32:00-07:00' key3: '1979-05-27T00:32:00.999999-07:00' float: both: key: 6.626e-34 exponent: key1: 5.0e+22 key2: 1000000 key3: -0.02 fractional: key1: 1 key2: 3.1415 key3: -0.01 underscores: key1: 9224617.445991227 key2: 1.0e+100 fruit: - name: apple physical: color: red shape: round variety: - name: red delicious - name: granny smith - name: banana variety: - name: plantain integer: key1: 99 key2: 42 key3: 0 key4: -17 underscores: key1: 1000 key2: 5349221 key3: 12345 products: - name: Hammer sku: 738594937 - {} - color: gray name: Nail sku: 284758393 string: basic: basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." literal: multiline: lines: | The first newline is trimmed in raw strings. All other whitespace is preserved. regex2: I [dw]on't need \d{2} apples quoted: Tom "Dubs" Preston-Werner regex: "<\\i\\c*\\s*>" winpath: C:\Users\nodejs\templates winpath2: "\\\\ServerX\\admin$\\system32\\" multiline: continued: key1: The quick brown fox jumps over the lazy dog. key2: The quick brown fox jumps over the lazy dog. key3: The quick brown fox jumps over the lazy dog. key1: |- One Two key2: |- One Two key3: |- One Two table: inline: name: first: Tom last: Preston-Werner point: x: 1 y: 2 key: value subtable: key: another value x: y: z: w: {} go-toml-1.9.5/benchmark/benchmark_test.go000066400000000000000000000061051416532417400203670ustar00rootroot00000000000000package benchmark import ( "bytes" "encoding/json" "io/ioutil" "testing" "time" burntsushi "github.com/BurntSushi/toml" "github.com/pelletier/go-toml" "gopkg.in/yaml.v2" ) type benchmarkDoc struct { Table struct { Key string Subtable struct { Key string } Inline struct { Name struct { First string Last string } Point struct { X int64 U int64 } } } String struct { Basic struct { Basic string } Multiline struct { Key1 string Key2 string Key3 string Continued struct { Key1 string Key2 string Key3 string } } Literal struct { Winpath string Winpath2 string Quoted string Regex string Multiline struct { Regex2 string Lines string } } } Integer struct { Key1 int64 Key2 int64 Key3 int64 Key4 int64 Underscores struct { Key1 int64 Key2 int64 Key3 int64 } } Float struct { Fractional struct { Key1 float64 Key2 float64 Key3 float64 } Exponent struct { Key1 float64 Key2 float64 Key3 float64 } Both struct { Key float64 } Underscores struct { Key1 float64 Key2 float64 } } Boolean struct { True bool False bool } Datetime struct { Key1 time.Time Key2 time.Time Key3 time.Time } Array struct { Key1 []int64 Key2 []string Key3 [][]int64 // TODO: Key4 not supported by go-toml's Unmarshal Key5 []int64 Key6 []int64 } Products []struct { Name string Sku int64 Color string } Fruit []struct { Name string Physical struct { Color string Shape string Variety []struct { Name string } } } } func BenchmarkParseToml(b *testing.B) { fileBytes, err := ioutil.ReadFile("benchmark.toml") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := toml.LoadReader(bytes.NewReader(fileBytes)) if err != nil { b.Fatal(err) } } } func BenchmarkUnmarshalToml(b *testing.B) { bytes, err := ioutil.ReadFile("benchmark.toml") if err != nil { b.Fatal(err) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { target := benchmarkDoc{} err := toml.Unmarshal(bytes, &target) if err != nil { b.Fatal(err) } } } func BenchmarkUnmarshalBurntSushiToml(b *testing.B) { bytes, err := ioutil.ReadFile("benchmark.toml") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { target := benchmarkDoc{} err := burntsushi.Unmarshal(bytes, &target) if err != nil { b.Fatal(err) } } } func BenchmarkUnmarshalJson(b *testing.B) { bytes, err := ioutil.ReadFile("benchmark.json") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { target := benchmarkDoc{} err := json.Unmarshal(bytes, &target) if err != nil { b.Fatal(err) } } } func BenchmarkUnmarshalYaml(b *testing.B) { bytes, err := ioutil.ReadFile("benchmark.yml") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { target := benchmarkDoc{} err := yaml.Unmarshal(bytes, &target) if err != nil { b.Fatal(err) } } } go-toml-1.9.5/benchmark/go.mod000066400000000000000000000003221416532417400161500ustar00rootroot00000000000000module github.com/pelletier/go-toml/benchmark go 1.12 require ( github.com/BurntSushi/toml v0.3.1 github.com/pelletier/go-toml v0.0.0 gopkg.in/yaml.v2 v2.3.0 ) replace github.com/pelletier/go-toml => ../ go-toml-1.9.5/benchmark/go.sum000066400000000000000000000010231416532417400161740ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= go-toml-1.9.5/cmd/000077500000000000000000000000001416532417400136565ustar00rootroot00000000000000go-toml-1.9.5/cmd/jsontoml/000077500000000000000000000000001416532417400155235ustar00rootroot00000000000000go-toml-1.9.5/cmd/jsontoml/main.go000066400000000000000000000033141416532417400167770ustar00rootroot00000000000000// Jsontoml reads JSON and converts to TOML. // // Usage: // cat file.toml | jsontoml > file.json // jsontoml file1.toml > file.json package main import ( "encoding/json" "flag" "fmt" "io" "io/ioutil" "os" "github.com/pelletier/go-toml" ) func main() { flag.Usage = func() { fmt.Fprintln(os.Stderr, "jsontoml can be used in two ways:") fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Reading from a file name:") fmt.Fprintln(os.Stderr, " tomljson file.toml") } flag.Parse() os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) } func processMain(files []string, defaultInput io.Reader, output io.Writer, errorOutput io.Writer) int { // read from stdin and print to stdout inputReader := defaultInput if len(files) > 0 { file, err := os.Open(files[0]) if err != nil { printError(err, errorOutput) return -1 } inputReader = file defer file.Close() } s, err := reader(inputReader) if err != nil { printError(err, errorOutput) return -1 } io.WriteString(output, s) return 0 } func printError(err error, output io.Writer) { io.WriteString(output, err.Error()+"\n") } func reader(r io.Reader) (string, error) { jsonMap := make(map[string]interface{}) jsonBytes, err := ioutil.ReadAll(r) if err != nil { return "", err } err = json.Unmarshal(jsonBytes, &jsonMap) if err != nil { return "", err } tree, err := toml.TreeFromMap(jsonMap) if err != nil { return "", err } return mapToTOML(tree) } func mapToTOML(t *toml.Tree) (string, error) { tomlBytes, err := t.ToTomlString() if err != nil { return "", err } return string(tomlBytes[:]), nil } go-toml-1.9.5/cmd/jsontoml/main_test.go000066400000000000000000000040641416532417400200410ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "os" "runtime" "strings" "testing" ) func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) { output := buffer.String() if output != expected { t.Errorf("incorrect %s: \n%sexpected %s: \n%s", name, output, name, expected) t.Log([]rune(output)) t.Log([]rune(expected)) } } func expectProcessMainResults(t *testing.T, input string, args []string, exitCode int, expectedOutput string, expectedError string) { inputReader := strings.NewReader(input) outputBuffer := new(bytes.Buffer) errorBuffer := new(bytes.Buffer) returnCode := processMain(args, inputReader, outputBuffer, errorBuffer) expectBufferEquality(t, "output", outputBuffer, expectedOutput) expectBufferEquality(t, "error", errorBuffer, expectedError) if returnCode != exitCode { t.Error("incorrect return code:", returnCode, "expected", exitCode) } } func TestProcessMainReadFromStdin(t *testing.T) { expectedOutput := ` [mytoml] a = 42.0 ` input := `{ "mytoml": { "a": 42 } } ` expectedError := `` expectedExitCode := 0 expectProcessMainResults(t, input, []string{}, expectedExitCode, expectedOutput, expectedError) } func TestProcessMainReadFromFile(t *testing.T) { input := `{ "mytoml": { "a": 42 } } ` tmpfile, err := ioutil.TempFile("", "example.json") if err != nil { t.Fatal(err) } if _, err := tmpfile.Write([]byte(input)); err != nil { t.Fatal(err) } defer os.Remove(tmpfile.Name()) expectedOutput := ` [mytoml] a = 42.0 ` expectedError := `` expectedExitCode := 0 expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError) } func TestProcessMainReadFromMissingFile(t *testing.T) { var expectedError string if runtime.GOOS == "windows" { expectedError = `open /this/file/does/not/exist: The system cannot find the path specified. ` } else { expectedError = `open /this/file/does/not/exist: no such file or directory ` } expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError) } go-toml-1.9.5/cmd/tomljson/000077500000000000000000000000001416532417400155235ustar00rootroot00000000000000go-toml-1.9.5/cmd/tomljson/main.go000066400000000000000000000030651416532417400170020ustar00rootroot00000000000000// Tomljson reads TOML and converts to JSON. // // Usage: // cat file.toml | tomljson > file.json // tomljson file1.toml > file.json package main import ( "encoding/json" "flag" "fmt" "io" "os" "github.com/pelletier/go-toml" ) func main() { flag.Usage = func() { fmt.Fprintln(os.Stderr, "tomljson can be used in two ways:") fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") fmt.Fprintln(os.Stderr, " cat file.toml | tomljson > file.json") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Reading from a file name:") fmt.Fprintln(os.Stderr, " tomljson file.toml") } flag.Parse() os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) } func processMain(files []string, defaultInput io.Reader, output io.Writer, errorOutput io.Writer) int { // read from stdin and print to stdout inputReader := defaultInput if len(files) > 0 { var err error inputReader, err = os.Open(files[0]) if err != nil { printError(err, errorOutput) return -1 } } s, err := reader(inputReader) if err != nil { printError(err, errorOutput) return -1 } io.WriteString(output, s+"\n") return 0 } func printError(err error, output io.Writer) { io.WriteString(output, err.Error()+"\n") } func reader(r io.Reader) (string, error) { tree, err := toml.LoadReader(r) if err != nil { return "", err } return mapToJSON(tree) } func mapToJSON(tree *toml.Tree) (string, error) { treeMap := tree.ToMap() bytes, err := json.MarshalIndent(treeMap, "", " ") if err != nil { return "", err } return string(bytes[:]), nil } go-toml-1.9.5/cmd/tomljson/main_test.go000066400000000000000000000040641416532417400200410ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "os" "runtime" "strings" "testing" ) func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) { output := buffer.String() if output != expected { t.Errorf("incorrect %s:\n%s\n\nexpected %s:\n%s", name, output, name, expected) t.Log([]rune(output)) t.Log([]rune(expected)) } } func expectProcessMainResults(t *testing.T, input string, args []string, exitCode int, expectedOutput string, expectedError string) { inputReader := strings.NewReader(input) outputBuffer := new(bytes.Buffer) errorBuffer := new(bytes.Buffer) returnCode := processMain(args, inputReader, outputBuffer, errorBuffer) expectBufferEquality(t, "output", outputBuffer, expectedOutput) expectBufferEquality(t, "error", errorBuffer, expectedError) if returnCode != exitCode { t.Error("incorrect return code:", returnCode, "expected", exitCode) } } func TestProcessMainReadFromStdin(t *testing.T) { input := ` [mytoml] a = 42` expectedOutput := `{ "mytoml": { "a": 42 } } ` expectedError := `` expectedExitCode := 0 expectProcessMainResults(t, input, []string{}, expectedExitCode, expectedOutput, expectedError) } func TestProcessMainReadFromFile(t *testing.T) { input := ` [mytoml] a = 42` tmpfile, err := ioutil.TempFile("", "example.toml") if err != nil { t.Fatal(err) } if _, err := tmpfile.Write([]byte(input)); err != nil { t.Fatal(err) } defer os.Remove(tmpfile.Name()) expectedOutput := `{ "mytoml": { "a": 42 } } ` expectedError := `` expectedExitCode := 0 expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError) } func TestProcessMainReadFromMissingFile(t *testing.T) { var expectedError string if runtime.GOOS == "windows" { expectedError = `open /this/file/does/not/exist: The system cannot find the path specified. ` } else { expectedError = `open /this/file/does/not/exist: no such file or directory ` } expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError) } go-toml-1.9.5/cmd/tomll/000077500000000000000000000000001416532417400150055ustar00rootroot00000000000000go-toml-1.9.5/cmd/tomll/main.go000066400000000000000000000045611416532417400162660ustar00rootroot00000000000000// Tomll is a linter for TOML // // Usage: // cat file.toml | tomll > file_linted.toml // tomll file1.toml file2.toml # lint the two files in place package main import ( "bytes" "flag" "fmt" "io" "io/ioutil" "os" "github.com/pelletier/go-toml" ) func main() { multiLineArray := flag.Bool("multiLineArray", false, "sets up the linter to encode arrays with more than one element on multiple lines instead of one.") flag.Usage = func() { fmt.Fprintln(os.Stderr, "tomll can be used in two ways:") fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") fmt.Fprintln(os.Stderr, " cat file.toml | tomll > file.toml") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Reading and updating a list of files:") fmt.Fprintln(os.Stderr, " tomll a.toml b.toml c.toml") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "When given a list of files, tomll will modify all files in place without asking.") fmt.Fprintln(os.Stderr, "When given a list of files, tomll will modify all files in place without asking.") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, "Flags:") fmt.Fprintln(os.Stderr, "-multiLineArray sets up the linter to encode arrays with more than one element on multiple lines instead of one.") } flag.Parse() // read from stdin and print to stdout if flag.NArg() == 0 { s, err := lintReader(os.Stdin, *multiLineArray) if err != nil { io.WriteString(os.Stderr, err.Error()) os.Exit(-1) } io.WriteString(os.Stdout, s) } else { // otherwise modify a list of files for _, filename := range flag.Args() { s, err := lintFile(filename, *multiLineArray) if err != nil { io.WriteString(os.Stderr, err.Error()) os.Exit(-1) } ioutil.WriteFile(filename, []byte(s), 0644) } } } func lintFile(filename string, multiLineArray bool) (string, error) { tree, err := toml.LoadFile(filename) if err != nil { return "", err } buf := new(bytes.Buffer) if err := toml.NewEncoder(buf).ArraysWithOneElementPerLine(multiLineArray).Encode(tree); err != nil { panic(err) } return buf.String(), nil } func lintReader(r io.Reader, multiLineArray bool) (string, error) { tree, err := toml.LoadReader(r) if err != nil { return "", err } buf := new(bytes.Buffer) if err := toml.NewEncoder(buf).ArraysWithOneElementPerLine(multiLineArray).Encode(tree); err != nil { panic(err) } return buf.String(), nil } go-toml-1.9.5/cmd/tomltestgen/000077500000000000000000000000001416532417400162235ustar00rootroot00000000000000go-toml-1.9.5/cmd/tomltestgen/main.go000066400000000000000000000111151416532417400174750ustar00rootroot00000000000000// Tomltestgen is a program that retrieves a given version of // https://github.com/BurntSushi/toml-test and generates go code for go-toml's unit tests // based on the test files. // // Usage: go run github.com/pelletier/go-toml/cmd/tomltestgen > toml_testgen_test.go package main import ( "archive/zip" "bytes" "flag" "fmt" "go/format" "io" "io/ioutil" "log" "net/http" "os" "regexp" "strconv" "strings" "text/template" "time" ) type invalid struct { Name string Input string } type valid struct { Name string Input string JsonRef string } type testsCollection struct { Ref string Timestamp string Invalid []invalid Valid []valid Count int } const srcTemplate = "// Generated by tomltestgen for toml-test ref {{.Ref}} on {{.Timestamp}}\n" + "package toml\n" + " import (\n" + " \"testing\"\n" + ")\n" + "{{range .Invalid}}\n" + "func TestInvalid{{.Name}}(t *testing.T) {\n" + " input := {{.Input|gostr}}\n" + " testgenInvalid(t, input)\n" + "}\n" + "{{end}}\n" + "\n" + "{{range .Valid}}\n" + "func TestValid{{.Name}}(t *testing.T) {\n" + " input := {{.Input|gostr}}\n" + " jsonRef := {{.JsonRef|gostr}}\n" + " testgenValid(t, input, jsonRef)\n" + "}\n" + "{{end}}\n" func downloadTmpFile(url string) string { log.Println("starting to download file from", url) resp, err := http.Get(url) if err != nil { panic(err) } defer resp.Body.Close() tmpfile, err := ioutil.TempFile("", "toml-test-*.zip") if err != nil { panic(err) } defer tmpfile.Close() copiedLen, err := io.Copy(tmpfile, resp.Body) if err != nil { panic(err) } if resp.ContentLength > 0 && copiedLen != resp.ContentLength { panic(fmt.Errorf("copied %d bytes, request body had %d", copiedLen, resp.ContentLength)) } return tmpfile.Name() } func kebabToCamel(kebab string) string { camel := "" nextUpper := true for _, c := range kebab { if nextUpper { camel += strings.ToUpper(string(c)) nextUpper = false } else if c == '-' { nextUpper = true } else { camel += string(c) } } return camel } func readFileFromZip(f *zip.File) string { reader, err := f.Open() if err != nil { panic(err) } defer reader.Close() bytes, err := ioutil.ReadAll(reader) if err != nil { panic(err) } return string(bytes) } func templateGoStr(input string) string { if len(input) > 0 && input[len(input)-1] == '\n' { input = input[0 : len(input)-1] } if strings.Contains(input, "`") { lines := strings.Split(input, "\n") for idx, line := range lines { lines[idx] = strconv.Quote(line + "\n") } return strings.Join(lines, " + \n") } return "`" + input + "`" } var ( ref = flag.String("r", "master", "git reference") ) func usage() { _, _ = fmt.Fprintf(os.Stderr, "usage: tomltestgen [flags]\n") flag.PrintDefaults() } func main() { flag.Usage = usage flag.Parse() url := "https://codeload.github.com/BurntSushi/toml-test/zip/" + *ref resultFile := downloadTmpFile(url) defer os.Remove(resultFile) log.Println("file written to", resultFile) zipReader, err := zip.OpenReader(resultFile) if err != nil { panic(err) } defer zipReader.Close() collection := testsCollection{ Ref: *ref, Timestamp: time.Now().Format(time.RFC3339), } zipFilesMap := map[string]*zip.File{} for _, f := range zipReader.File { zipFilesMap[f.Name] = f } testFileRegexp := regexp.MustCompile(`([^/]+/tests/(valid|invalid)/(.+))\.(toml)`) for _, f := range zipReader.File { groups := testFileRegexp.FindStringSubmatch(f.Name) if len(groups) > 0 { name := kebabToCamel(groups[3]) testType := groups[2] log.Printf("> [%s] %s\n", testType, name) tomlContent := readFileFromZip(f) switch testType { case "invalid": collection.Invalid = append(collection.Invalid, invalid{ Name: name, Input: tomlContent, }) collection.Count++ case "valid": baseFilePath := groups[1] jsonFilePath := baseFilePath + ".json" jsonContent := readFileFromZip(zipFilesMap[jsonFilePath]) collection.Valid = append(collection.Valid, valid{ Name: name, Input: tomlContent, JsonRef: jsonContent, }) collection.Count++ default: panic(fmt.Sprintf("unknown test type: %s", testType)) } } } log.Printf("Collected %d tests from toml-test\n", collection.Count) funcMap := template.FuncMap{ "gostr": templateGoStr, } t := template.Must(template.New("src").Funcs(funcMap).Parse(srcTemplate)) buf := new(bytes.Buffer) err = t.Execute(buf, collection) if err != nil { panic(err) } outputBytes, err := format.Source(buf.Bytes()) if err != nil { panic(err) } fmt.Println(string(outputBytes)) } go-toml-1.9.5/doc.go000066400000000000000000000014251416532417400142110ustar00rootroot00000000000000// 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 go-toml-1.9.5/doc_test.go000066400000000000000000000067251416532417400152600ustar00rootroot00000000000000// code examples for godoc package toml_test import ( "fmt" "log" "os" toml "github.com/pelletier/go-toml" ) func Example_tree() { config, err := toml.LoadFile("config.toml") if err != nil { fmt.Println("Error ", err.Error()) } else { // retrieve data directly directUser := config.Get("postgres.user").(string) directPassword := config.Get("postgres.password").(string) fmt.Println("User is", directUser, " and password is", directPassword) // or using an intermediate object configTree := config.Get("postgres").(*toml.Tree) user := configTree.Get("user").(string) password := configTree.Get("password").(string) fmt.Println("User is", user, " and password is", password) // show where elements are in the file fmt.Printf("User position: %v\n", configTree.GetPosition("user")) fmt.Printf("Password position: %v\n", configTree.GetPosition("password")) } } func Example_unmarshal() { type Employer struct { Name string Phone string } type Person struct { Name string Age int64 Employer Employer } document := []byte(` name = "John" age = 30 [employer] name = "Company Inc." phone = "+1 234 567 89012" `) person := Person{} toml.Unmarshal(document, &person) fmt.Println(person.Name, "is", person.Age, "and works at", person.Employer.Name) // Output: // John is 30 and works at Company Inc. } func ExampleMarshal() { type Postgres struct { User string `toml:"user"` Password string `toml:"password"` Database string `toml:"db" commented:"true" comment:"not used anymore"` } type Config struct { Postgres Postgres `toml:"postgres" comment:"Postgres configuration"` } config := Config{Postgres{User: "pelletier", Password: "mypassword", Database: "old_database"}} b, err := toml.Marshal(config) if err != nil { log.Fatal(err) } fmt.Println(string(b)) // Output: // # Postgres configuration // [postgres] // // # not used anymore // # db = "old_database" // password = "mypassword" // user = "pelletier" } func ExampleUnmarshal() { 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) // Output: // user= pelletier } func ExampleEncoder_anonymous() { type Credentials struct { User string `toml:"user"` Password string `toml:"password"` } type Protocol struct { Name string `toml:"name"` } type Config struct { Version int `toml:"version"` Credentials Protocol `toml:"Protocol"` } config := Config{ Version: 2, Credentials: Credentials{ User: "pelletier", Password: "mypassword", }, Protocol: Protocol{ Name: "tcp", }, } fmt.Println("Default:") fmt.Println("---------------") def := toml.NewEncoder(os.Stdout) if err := def.Encode(config); err != nil { log.Fatal(err) } fmt.Println("---------------") fmt.Println("With promotion:") fmt.Println("---------------") prom := toml.NewEncoder(os.Stdout).PromoteAnonymous(true) if err := prom.Encode(config); err != nil { log.Fatal(err) } // Output: // Default: // --------------- // password = "mypassword" // user = "pelletier" // version = 2 // // [Protocol] // name = "tcp" // --------------- // With promotion: // --------------- // version = 2 // // [Credentials] // password = "mypassword" // user = "pelletier" // // [Protocol] // name = "tcp" } go-toml-1.9.5/example-crlf.toml000066400000000000000000000013321416532417400163660ustar00rootroot00000000000000# 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 supportedgo-toml-1.9.5/example.toml000066400000000000000000000012751416532417400154500ustar00rootroot00000000000000# 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 supportedgo-toml-1.9.5/fuzz.go000066400000000000000000000007301416532417400144400ustar00rootroot00000000000000// +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 } go-toml-1.9.5/fuzz.sh000077500000000000000000000004431416532417400144510ustar00rootroot00000000000000#! /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 go-toml-1.9.5/go.mod000066400000000000000000000000551416532417400142210ustar00rootroot00000000000000module github.com/pelletier/go-toml go 1.12 go-toml-1.9.5/keysparsing.go000066400000000000000000000046771416532417400160170ustar00rootroot00000000000000// 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) } go-toml-1.9.5/keysparsing_test.go000066400000000000000000000041111416532417400170350ustar00rootroot00000000000000package toml import ( "fmt" "testing" ) func testResult(t *testing.T, key string, expected []string) { parsed, err := parseKey(key) t.Logf("key=%s expected=%s parsed=%s", key, expected, parsed) if err != nil { t.Fatal("Unexpected error:", err) } if len(expected) != len(parsed) { t.Fatal("Expected length", len(expected), "but", len(parsed), "parsed") } for index, expectedKey := range expected { if expectedKey != parsed[index] { t.Fatal("Expected", expectedKey, "at index", index, "but found", parsed[index]) } } } func testError(t *testing.T, key string, expectedError string) { res, err := parseKey(key) if err == nil { t.Fatalf("Expected error, but successfully parsed key %s", res) } if fmt.Sprintf("%s", err) != expectedError { t.Fatalf("Expected error \"%s\", but got \"%s\".", expectedError, err) } } func TestBareKeyBasic(t *testing.T) { testResult(t, "test", []string{"test"}) } func TestBareKeyDotted(t *testing.T) { testResult(t, "this.is.a.key", []string{"this", "is", "a", "key"}) } func TestDottedKeyBasic(t *testing.T) { testResult(t, "\"a.dotted.key\"", []string{"a.dotted.key"}) } func TestBaseKeyPound(t *testing.T) { testError(t, "hello#world", "invalid bare key character: #") } func TestUnclosedSingleQuotedKey(t *testing.T) { testError(t, "'", "unclosed single-quoted key") } func TestUnclosedDoubleQuotedKey(t *testing.T) { testError(t, "\"", "unclosed double-quoted key") } func TestInvalidStartKeyCharacter(t *testing.T) { testError(t, "/", "invalid key character: /") } func TestInvalidSpaceInKey(t *testing.T) { testError(t, "invalid key", "invalid key character after whitespace: k") } func TestQuotedKeys(t *testing.T) { testResult(t, `hello."foo".bar`, []string{"hello", "foo", "bar"}) testResult(t, `"hello!"`, []string{"hello!"}) testResult(t, `foo."ba.r".baz`, []string{"foo", "ba.r", "baz"}) // escape sequences must not be converted testResult(t, `"hello\tworld"`, []string{`hello\tworld`}) } func TestEmptyKey(t *testing.T) { testError(t, ``, "empty key") testError(t, ` `, "empty key") testResult(t, `""`, []string{""}) } go-toml-1.9.5/lexer.go000066400000000000000000000466341416532417400145760ustar00rootroot00000000000000// 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 } go-toml-1.9.5/lexer_test.go000066400000000000000000001145701416532417400156300ustar00rootroot00000000000000package toml import ( "bytes" "fmt" "reflect" "testing" "text/tabwriter" ) func testFlow(t *testing.T, input string, expectedFlow []token) { tokens := lexToml([]byte(input)) if !reflect.DeepEqual(tokens, expectedFlow) { diffFlowsColumnsFatal(t, expectedFlow, tokens) } } func diffFlowsColumnsFatal(t *testing.T, expectedFlow []token, actualFlow []token) { max := len(expectedFlow) if len(actualFlow) > max { max = len(actualFlow) } b := &bytes.Buffer{} w := tabwriter.NewWriter(b, 0, 0, 1, ' ', tabwriter.Debug) fmt.Fprintln(w, "expected\tT\tP\tactual\tT\tP\tdiff") for i := 0; i < max; i++ { expected := "" expectedType := "" expectedPos := "" if i < len(expectedFlow) { expected = fmt.Sprintf("%s", expectedFlow[i]) expectedType = fmt.Sprintf("%s", expectedFlow[i].typ) expectedPos = expectedFlow[i].Position.String() } actual := "" actualType := "" actualPos := "" if i < len(actualFlow) { actual = fmt.Sprintf("%s", actualFlow[i]) actualType = fmt.Sprintf("%s", actualFlow[i].typ) actualPos = actualFlow[i].Position.String() } different := "" if i >= len(expectedFlow) { different = "+" } else if i >= len(actualFlow) { different = "-" } else if !reflect.DeepEqual(expectedFlow[i], actualFlow[i]) { different = "x" } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", expected, expectedType, expectedPos, actual, actualType, actualPos, different) } w.Flush() t.Errorf("Different flows:\n%s", b.String()) } func TestValidKeyGroup(t *testing.T) { testFlow(t, "[hello world]", []token{ {Position{1, 1}, tokenLeftBracket, "["}, {Position{1, 2}, tokenKeyGroup, "hello world"}, {Position{1, 13}, tokenRightBracket, "]"}, {Position{1, 14}, tokenEOF, ""}, }) } func TestNestedQuotedUnicodeKeyGroup(t *testing.T) { testFlow(t, `[ j . "ʞ" . l . 'ɯ' ]`, []token{ {Position{1, 1}, tokenLeftBracket, "["}, {Position{1, 2}, tokenKeyGroup, ` j . "ʞ" . l . 'ɯ' `}, {Position{1, 21}, tokenRightBracket, "]"}, {Position{1, 22}, tokenEOF, ""}, }) } func TestNestedQuotedUnicodeKeyAssign(t *testing.T) { testFlow(t, ` j . "ʞ" . l . 'ɯ' = 3`, []token{ {Position{1, 2}, tokenKey, `j . "ʞ" . l . 'ɯ'`}, {Position{1, 20}, tokenEqual, "="}, {Position{1, 22}, tokenInteger, "3"}, {Position{1, 23}, tokenEOF, ""}, }) } func TestUnclosedKeyGroup(t *testing.T) { testFlow(t, "[hello world", []token{ {Position{1, 1}, tokenLeftBracket, "["}, {Position{1, 2}, tokenError, "unclosed table key"}, }) } func TestComment(t *testing.T) { testFlow(t, "# blahblah", []token{ {Position{1, 11}, tokenEOF, ""}, }) } func TestKeyGroupComment(t *testing.T) { testFlow(t, "[hello world] # blahblah", []token{ {Position{1, 1}, tokenLeftBracket, "["}, {Position{1, 2}, tokenKeyGroup, "hello world"}, {Position{1, 13}, tokenRightBracket, "]"}, {Position{1, 25}, tokenEOF, ""}, }) } func TestMultipleKeyGroupsComment(t *testing.T) { testFlow(t, "[hello world] # blahblah\n[test]", []token{ {Position{1, 1}, tokenLeftBracket, "["}, {Position{1, 2}, tokenKeyGroup, "hello world"}, {Position{1, 13}, tokenRightBracket, "]"}, {Position{2, 1}, tokenLeftBracket, "["}, {Position{2, 2}, tokenKeyGroup, "test"}, {Position{2, 6}, tokenRightBracket, "]"}, {Position{2, 7}, tokenEOF, ""}, }) } func TestSimpleWindowsCRLF(t *testing.T) { testFlow(t, "a=4\r\nb=2", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 2}, tokenEqual, "="}, {Position{1, 3}, tokenInteger, "4"}, {Position{2, 1}, tokenKey, "b"}, {Position{2, 2}, tokenEqual, "="}, {Position{2, 3}, tokenInteger, "2"}, {Position{2, 4}, tokenEOF, ""}, }) } func TestBasicKey(t *testing.T) { testFlow(t, "hello", []token{ {Position{1, 1}, tokenKey, "hello"}, {Position{1, 6}, tokenEOF, ""}, }) } func TestBasicKeyWithUnderscore(t *testing.T) { testFlow(t, "hello_hello", []token{ {Position{1, 1}, tokenKey, "hello_hello"}, {Position{1, 12}, tokenEOF, ""}, }) } func TestBasicKeyWithDash(t *testing.T) { testFlow(t, "hello-world", []token{ {Position{1, 1}, tokenKey, "hello-world"}, {Position{1, 12}, tokenEOF, ""}, }) } func TestBasicKeyWithUppercaseMix(t *testing.T) { testFlow(t, "helloHELLOHello", []token{ {Position{1, 1}, tokenKey, "helloHELLOHello"}, {Position{1, 16}, tokenEOF, ""}, }) } func TestBasicKeyWithInternationalCharacters(t *testing.T) { testFlow(t, "'héllÖ'", []token{ {Position{1, 1}, tokenKey, "'héllÖ'"}, {Position{1, 8}, tokenEOF, ""}, }) } func TestBasicKeyAndEqual(t *testing.T) { testFlow(t, "hello =", []token{ {Position{1, 1}, tokenKey, "hello"}, {Position{1, 7}, tokenEqual, "="}, {Position{1, 8}, tokenEOF, ""}, }) } func TestKeyWithSharpAndEqual(t *testing.T) { testFlow(t, "key#name = 5", []token{ {Position{1, 1}, tokenError, "keys cannot contain # character"}, }) } func TestKeyWithSymbolsAndEqual(t *testing.T) { testFlow(t, "~!@$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{ {Position{1, 1}, tokenError, "keys cannot contain ~ character"}, }) } func TestKeyEqualStringEscape(t *testing.T) { testFlow(t, `foo = "hello\""`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "hello\""}, {Position{1, 16}, tokenEOF, ""}, }) } func TestKeyEqualStringUnfinished(t *testing.T) { testFlow(t, `foo = "bar`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "unclosed string"}, }) } func TestKeyEqualString(t *testing.T) { testFlow(t, `foo = "bar"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "bar"}, {Position{1, 12}, tokenEOF, ""}, }) } func TestKeyEqualTrue(t *testing.T) { testFlow(t, "foo = true", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenTrue, "true"}, {Position{1, 11}, tokenEOF, ""}, }) } func TestKeyEqualFalse(t *testing.T) { testFlow(t, "foo = false", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenFalse, "false"}, {Position{1, 12}, tokenEOF, ""}, }) } func TestArrayNestedString(t *testing.T) { testFlow(t, `a = [ ["hello", "world"] ]`, []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenLeftBracket, "["}, {Position{1, 7}, tokenLeftBracket, "["}, {Position{1, 9}, tokenString, "hello"}, {Position{1, 15}, tokenComma, ","}, {Position{1, 18}, tokenString, "world"}, {Position{1, 24}, tokenRightBracket, "]"}, {Position{1, 26}, tokenRightBracket, "]"}, {Position{1, 27}, tokenEOF, ""}, }) } func TestArrayNestedInts(t *testing.T) { testFlow(t, "a = [ [42, 21], [10] ]", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenLeftBracket, "["}, {Position{1, 7}, tokenLeftBracket, "["}, {Position{1, 8}, tokenInteger, "42"}, {Position{1, 10}, tokenComma, ","}, {Position{1, 12}, tokenInteger, "21"}, {Position{1, 14}, tokenRightBracket, "]"}, {Position{1, 15}, tokenComma, ","}, {Position{1, 17}, tokenLeftBracket, "["}, {Position{1, 18}, tokenInteger, "10"}, {Position{1, 20}, tokenRightBracket, "]"}, {Position{1, 22}, tokenRightBracket, "]"}, {Position{1, 23}, tokenEOF, ""}, }) } func TestArrayInts(t *testing.T) { testFlow(t, "a = [ 42, 21, 10, ]", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenLeftBracket, "["}, {Position{1, 7}, tokenInteger, "42"}, {Position{1, 9}, tokenComma, ","}, {Position{1, 11}, tokenInteger, "21"}, {Position{1, 13}, tokenComma, ","}, {Position{1, 15}, tokenInteger, "10"}, {Position{1, 17}, tokenComma, ","}, {Position{1, 19}, tokenRightBracket, "]"}, {Position{1, 20}, tokenEOF, ""}, }) } func TestMultilineArrayComments(t *testing.T) { testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenLeftBracket, "["}, {Position{1, 6}, tokenInteger, "1"}, {Position{1, 7}, tokenComma, ","}, {Position{2, 1}, tokenInteger, "2"}, {Position{2, 2}, tokenComma, ","}, {Position{3, 1}, tokenInteger, "3"}, {Position{3, 2}, tokenComma, ","}, {Position{4, 1}, tokenRightBracket, "]"}, {Position{4, 2}, tokenEOF, ""}, }) } func TestNestedArraysComment(t *testing.T) { toml := ` someArray = [ # does not work ["entry1"] ]` testFlow(t, toml, []token{ {Position{2, 1}, tokenKey, "someArray"}, {Position{2, 11}, tokenEqual, "="}, {Position{2, 13}, tokenLeftBracket, "["}, {Position{4, 1}, tokenLeftBracket, "["}, {Position{4, 3}, tokenString, "entry1"}, {Position{4, 10}, tokenRightBracket, "]"}, {Position{5, 1}, tokenRightBracket, "]"}, {Position{5, 2}, tokenEOF, ""}, }) } func TestKeyEqualArrayBools(t *testing.T) { testFlow(t, "foo = [true, false, true]", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftBracket, "["}, {Position{1, 8}, tokenTrue, "true"}, {Position{1, 12}, tokenComma, ","}, {Position{1, 14}, tokenFalse, "false"}, {Position{1, 19}, tokenComma, ","}, {Position{1, 21}, tokenTrue, "true"}, {Position{1, 25}, tokenRightBracket, "]"}, {Position{1, 26}, tokenEOF, ""}, }) } func TestKeyEqualArrayBoolsWithComments(t *testing.T) { testFlow(t, "foo = [true, false, true] # YEAH", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftBracket, "["}, {Position{1, 8}, tokenTrue, "true"}, {Position{1, 12}, tokenComma, ","}, {Position{1, 14}, tokenFalse, "false"}, {Position{1, 19}, tokenComma, ","}, {Position{1, 21}, tokenTrue, "true"}, {Position{1, 25}, tokenRightBracket, "]"}, {Position{1, 33}, tokenEOF, ""}, }) } func TestKeyEqualDate(t *testing.T) { t.Run("local date time", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T07:32:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "07:32:00"}, {Position{1, 26}, tokenEOF, ""}, }) }) t.Run("local date time space", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 07:32:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "07:32:00"}, {Position{1, 26}, tokenEOF, ""}, }) }) t.Run("local date time fraction", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T00:32:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, {Position{1, 33}, tokenEOF, ""}, }) }) t.Run("local date time fraction space", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:32:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, {Position{1, 33}, tokenEOF, ""}, }) }) t.Run("offset date-time utc", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "07:32:00"}, {Position{1, 26}, tokenTimeOffset, "Z"}, {Position{1, 27}, tokenEOF, ""}, }) }) t.Run("offset date-time -07:00", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T00:32:00-07:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00"}, {Position{1, 26}, tokenTimeOffset, "-07:00"}, {Position{1, 32}, tokenEOF, ""}, }) }) t.Run("offset date-time fractions -07:00", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T00:32:00.999999-07:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, {Position{1, 33}, tokenTimeOffset, "-07:00"}, {Position{1, 39}, tokenEOF, ""}, }) }) t.Run("offset date-time space separated utc", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 07:32:00Z", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "07:32:00"}, {Position{1, 26}, tokenTimeOffset, "Z"}, {Position{1, 27}, tokenEOF, ""}, }) }) t.Run("offset date-time space separated offset", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:32:00-07:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00"}, {Position{1, 26}, tokenTimeOffset, "-07:00"}, {Position{1, 32}, tokenEOF, ""}, }) }) t.Run("offset date-time space separated fraction offset", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:32:00.999999-07:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, {Position{1, 33}, tokenTimeOffset, "-07:00"}, {Position{1, 39}, tokenEOF, ""}, }) }) t.Run("local date", func(t *testing.T) { testFlow(t, "foo = 1979-05-27", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 17}, tokenEOF, ""}, }) }) t.Run("local time", func(t *testing.T) { testFlow(t, "foo = 07:32:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalTime, "07:32:00"}, {Position{1, 15}, tokenEOF, ""}, }) }) t.Run("local time fraction", func(t *testing.T) { testFlow(t, "foo = 00:32:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalTime, "00:32:00.999999"}, {Position{1, 22}, tokenEOF, ""}, }) }) t.Run("local time invalid minute digit", func(t *testing.T) { testFlow(t, "foo = 00:3x:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "invalid minute digit in time: x"}, }) }) t.Run("local time invalid minute/second digit", func(t *testing.T) { testFlow(t, "foo = 00:30x00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "time minute/second separator should be :, not x"}, }) }) t.Run("local time invalid second digit", func(t *testing.T) { testFlow(t, "foo = 00:30:x0.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "invalid second digit in time: x"}, }) }) t.Run("local time invalid second digit", func(t *testing.T) { testFlow(t, "foo = 00:30:00.F", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "expected at least one digit in time's fraction, not F"}, }) }) t.Run("local date-time invalid minute digit", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:3x:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenError, "invalid minute digit in time: x"}, }) }) t.Run("local date-time invalid hour digit", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T0x:30:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenError, "invalid hour digit in time: x"}, }) }) t.Run("local date-time invalid hour digit", func(t *testing.T) { testFlow(t, "foo = 1979-05-27T00x30:00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenError, "time hour/minute separator should be :, not x"}, }) }) t.Run("local date-time invalid minute/second digit", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:30x00.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenError, "time minute/second separator should be :, not x"}, }) }) t.Run("local date-time invalid second digit", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:30:x0.999999", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenError, "invalid second digit in time: x"}, }) }) t.Run("local date-time invalid fraction", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:30:00.F", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenError, "expected at least one digit in time's fraction, not F"}, }) }) t.Run("local date-time invalid month-date separator", func(t *testing.T) { testFlow(t, "foo = 1979-05X27 00:30:00.F", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "expected - to separate month of a date, not X"}, }) }) t.Run("local date-time extra whitespace", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 ", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 19}, tokenEOF, ""}, }) }) t.Run("local date-time extra whitespace", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 ", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 22}, tokenEOF, ""}, }) }) t.Run("offset date-time space separated offset", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:32:00-0x:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00"}, {Position{1, 26}, tokenError, "invalid hour digit in time offset: x"}, }) }) t.Run("offset date-time space separated offset", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:32:00-07x00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00"}, {Position{1, 26}, tokenError, "time offset hour/minute separator should be :, not x"}, }) }) t.Run("offset date-time space separated offset", func(t *testing.T) { testFlow(t, "foo = 1979-05-27 00:32:00-07:x0", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLocalDate, "1979-05-27"}, {Position{1, 18}, tokenLocalTime, "00:32:00"}, {Position{1, 26}, tokenError, "invalid minute digit in time offset: x"}, }) }) } func TestFloatEndingWithDot(t *testing.T) { testFlow(t, "foo = 42.", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "float cannot end with a dot"}, }) } func TestFloatWithTwoDots(t *testing.T) { testFlow(t, "foo = 4.2.", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "cannot have two dots in one float"}, }) } func TestFloatWithExponent1(t *testing.T) { testFlow(t, "a = 5e+22", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenFloat, "5e+22"}, {Position{1, 10}, tokenEOF, ""}, }) } func TestFloatWithExponent2(t *testing.T) { testFlow(t, "a = 5E+22", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenFloat, "5E+22"}, {Position{1, 10}, tokenEOF, ""}, }) } func TestFloatWithExponent3(t *testing.T) { testFlow(t, "a = -5e+22", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenFloat, "-5e+22"}, {Position{1, 11}, tokenEOF, ""}, }) } func TestFloatWithExponent4(t *testing.T) { testFlow(t, "a = -5e-22", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenFloat, "-5e-22"}, {Position{1, 11}, tokenEOF, ""}, }) } func TestFloatWithExponent5(t *testing.T) { testFlow(t, "a = 6.626e-34", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenFloat, "6.626e-34"}, {Position{1, 14}, tokenEOF, ""}, }) } func TestInvalidEsquapeSequence(t *testing.T) { testFlow(t, `foo = "\x"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "invalid escape sequence: \\x"}, }) } func TestNestedArrays(t *testing.T) { testFlow(t, "foo = [[[]]]", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftBracket, "["}, {Position{1, 8}, tokenLeftBracket, "["}, {Position{1, 9}, tokenLeftBracket, "["}, {Position{1, 10}, tokenRightBracket, "]"}, {Position{1, 11}, tokenRightBracket, "]"}, {Position{1, 12}, tokenRightBracket, "]"}, {Position{1, 13}, tokenEOF, ""}, }) } func TestKeyEqualNumber(t *testing.T) { testFlow(t, "foo = 42", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "42"}, {Position{1, 9}, tokenEOF, ""}, }) testFlow(t, "foo = +42", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "+42"}, {Position{1, 10}, tokenEOF, ""}, }) testFlow(t, "foo = -42", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "-42"}, {Position{1, 10}, tokenEOF, ""}, }) testFlow(t, "foo = 4.2", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenFloat, "4.2"}, {Position{1, 10}, tokenEOF, ""}, }) testFlow(t, "foo = +4.2", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenFloat, "+4.2"}, {Position{1, 11}, tokenEOF, ""}, }) testFlow(t, "foo = -4.2", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenFloat, "-4.2"}, {Position{1, 11}, tokenEOF, ""}, }) testFlow(t, "foo = 1_000", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "1_000"}, {Position{1, 12}, tokenEOF, ""}, }) testFlow(t, "foo = 5_349_221", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "5_349_221"}, {Position{1, 16}, tokenEOF, ""}, }) testFlow(t, "foo = 1_2_3_4_5", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "1_2_3_4_5"}, {Position{1, 16}, tokenEOF, ""}, }) testFlow(t, "flt8 = 9_224_617.445_991_228_313", []token{ {Position{1, 1}, tokenKey, "flt8"}, {Position{1, 6}, tokenEqual, "="}, {Position{1, 8}, tokenFloat, "9_224_617.445_991_228_313"}, {Position{1, 33}, tokenEOF, ""}, }) testFlow(t, "foo = +", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenError, "no digit in that number"}, }) } func TestMultiline(t *testing.T) { testFlow(t, "foo = 42\nbar=21", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenInteger, "42"}, {Position{2, 1}, tokenKey, "bar"}, {Position{2, 4}, tokenEqual, "="}, {Position{2, 5}, tokenInteger, "21"}, {Position{2, 7}, tokenEOF, ""}, }) } func TestKeyEqualStringUnicodeEscape(t *testing.T) { testFlow(t, `foo = "hello \u2665"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "hello ♥"}, {Position{1, 21}, tokenEOF, ""}, }) testFlow(t, `foo = "hello \U000003B4"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "hello δ"}, {Position{1, 25}, tokenEOF, ""}, }) testFlow(t, `foo = "\uabcd"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "\uabcd"}, {Position{1, 15}, tokenEOF, ""}, }) testFlow(t, `foo = "\uABCD"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "\uABCD"}, {Position{1, 15}, tokenEOF, ""}, }) testFlow(t, `foo = "\U000bcdef"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "\U000bcdef"}, {Position{1, 19}, tokenEOF, ""}, }) testFlow(t, `foo = "\U000BCDEF"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "\U000BCDEF"}, {Position{1, 19}, tokenEOF, ""}, }) testFlow(t, `foo = "\u2"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "unfinished unicode escape"}, }) testFlow(t, `foo = "\U2"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "unfinished unicode escape"}, }) } func TestKeyEqualStringNoEscape(t *testing.T) { testFlow(t, "foo = \"hello \u0002\"", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "unescaped control character U+0002"}, }) testFlow(t, "foo = \"hello \u001F\"", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "unescaped control character U+001F"}, }) } func TestLiteralString(t *testing.T) { testFlow(t, `foo = 'C:\Users\nodejs\templates'`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, `C:\Users\nodejs\templates`}, {Position{1, 34}, tokenEOF, ""}, }) testFlow(t, `foo = '\\ServerX\admin$\system32\'`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, `\\ServerX\admin$\system32\`}, {Position{1, 35}, tokenEOF, ""}, }) testFlow(t, `foo = 'Tom "Dubs" Preston-Werner'`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, `Tom "Dubs" Preston-Werner`}, {Position{1, 34}, tokenEOF, ""}, }) testFlow(t, `foo = '<\i\c*\s*>'`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, `<\i\c*\s*>`}, {Position{1, 19}, tokenEOF, ""}, }) testFlow(t, `foo = 'C:\Users\nodejs\unfinis`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenError, "unclosed string"}, }) } func TestMultilineLiteralString(t *testing.T) { testFlow(t, `foo = '''hello 'literal' world'''`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 10}, tokenString, `hello 'literal' world`}, {Position{1, 34}, tokenEOF, ""}, }) testFlow(t, "foo = '''\nhello\n'literal'\nworld'''", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{2, 1}, tokenString, "hello\n'literal'\nworld"}, {Position{4, 9}, tokenEOF, ""}, }) testFlow(t, "foo = '''\r\nhello\r\n'literal'\r\nworld'''", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{2, 1}, tokenString, "hello\r\n'literal'\r\nworld"}, {Position{4, 9}, tokenEOF, ""}, }) } func TestMultilineString(t *testing.T) { testFlow(t, `foo = """hello "literal" world"""`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 10}, tokenString, `hello "literal" world`}, {Position{1, 34}, tokenEOF, ""}, }) testFlow(t, "foo = \"\"\"\r\nhello\\\r\n\"literal\"\\\nworld\"\"\"", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{2, 1}, tokenString, "hello\"literal\"world"}, {Position{4, 9}, tokenEOF, ""}, }) testFlow(t, "foo = \"\"\"\\\n \\\n \\\n hello\\\nmultiline\\\nworld\"\"\"", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 10}, tokenString, "hellomultilineworld"}, {Position{6, 9}, tokenEOF, ""}, }) testFlow(t, `foo = """hello world"""`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 10}, tokenString, "hello\tworld"}, {Position{1, 24}, tokenEOF, ""}, }) testFlow(t, "key2 = \"\"\"\nThe quick brown \\\n\n\n fox jumps over \\\n the lazy dog.\"\"\"", []token{ {Position{1, 1}, tokenKey, "key2"}, {Position{1, 6}, tokenEqual, "="}, {Position{2, 1}, tokenString, "The quick brown fox jumps over the lazy dog."}, {Position{6, 21}, tokenEOF, ""}, }) testFlow(t, "key2 = \"\"\"\\\n The quick brown \\\n fox jumps over \\\n the lazy dog.\\\n \"\"\"", []token{ {Position{1, 1}, tokenKey, "key2"}, {Position{1, 6}, tokenEqual, "="}, {Position{1, 11}, tokenString, "The quick brown fox jumps over the lazy dog."}, {Position{5, 11}, tokenEOF, ""}, }) testFlow(t, `key2 = "Roses are red\nViolets are blue"`, []token{ {Position{1, 1}, tokenKey, "key2"}, {Position{1, 6}, tokenEqual, "="}, {Position{1, 9}, tokenString, "Roses are red\nViolets are blue"}, {Position{1, 41}, tokenEOF, ""}, }) testFlow(t, "key2 = \"\"\"\nRoses are red\nViolets are blue\"\"\"", []token{ {Position{1, 1}, tokenKey, "key2"}, {Position{1, 6}, tokenEqual, "="}, {Position{2, 1}, tokenString, "Roses are red\nViolets are blue"}, {Position{3, 20}, tokenEOF, ""}, }) } func TestUnicodeString(t *testing.T) { testFlow(t, `foo = "hello ♥ world"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "hello ♥ world"}, {Position{1, 22}, tokenEOF, ""}, }) } func TestEscapeInString(t *testing.T) { testFlow(t, `foo = "\b\f\/"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "\b\f/"}, {Position{1, 15}, tokenEOF, ""}, }) } func TestTabInString(t *testing.T) { testFlow(t, `foo = "hello world"`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 8}, tokenString, "hello\tworld"}, {Position{1, 20}, tokenEOF, ""}, }) } func TestKeyGroupArray(t *testing.T) { testFlow(t, "[[foo]]", []token{ {Position{1, 1}, tokenDoubleLeftBracket, "[["}, {Position{1, 3}, tokenKeyGroupArray, "foo"}, {Position{1, 6}, tokenDoubleRightBracket, "]]"}, {Position{1, 8}, tokenEOF, ""}, }) } func TestQuotedKey(t *testing.T) { testFlow(t, "\"a b\" = 42", []token{ {Position{1, 1}, tokenKey, "\"a b\""}, {Position{1, 7}, tokenEqual, "="}, {Position{1, 9}, tokenInteger, "42"}, {Position{1, 11}, tokenEOF, ""}, }) } func TestQuotedKeyTab(t *testing.T) { testFlow(t, "\"num\tber\" = 123", []token{ {Position{1, 1}, tokenKey, "\"num\tber\""}, {Position{1, 11}, tokenEqual, "="}, {Position{1, 13}, tokenInteger, "123"}, {Position{1, 16}, tokenEOF, ""}, }) } func TestKeyNewline(t *testing.T) { testFlow(t, "a\n= 4", []token{ {Position{1, 1}, tokenError, "keys cannot contain new lines"}, }) } func TestInvalidFloat(t *testing.T) { testFlow(t, "a=7e1_", []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 2}, tokenEqual, "="}, {Position{1, 3}, tokenFloat, "7e1_"}, {Position{1, 7}, tokenEOF, ""}, }) } func TestLexUnknownRvalue(t *testing.T) { testFlow(t, `a = !b`, []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenError, "no value can start with !"}, }) testFlow(t, `a = \b`, []token{ {Position{1, 1}, tokenKey, "a"}, {Position{1, 3}, tokenEqual, "="}, {Position{1, 5}, tokenError, `no value can start with \`}, }) } func TestLexInlineTableEmpty(t *testing.T) { testFlow(t, `foo = {}`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 8}, tokenRightCurlyBrace, "}"}, {Position{1, 9}, tokenEOF, ""}, }) } func TestLexInlineTableBareKey(t *testing.T) { testFlow(t, `foo = { bar = "baz" }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "bar"}, {Position{1, 13}, tokenEqual, "="}, {Position{1, 16}, tokenString, "baz"}, {Position{1, 21}, tokenRightCurlyBrace, "}"}, {Position{1, 22}, tokenEOF, ""}, }) } func TestLexInlineTableBareKeyDash(t *testing.T) { testFlow(t, `foo = { -bar = "baz" }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "-bar"}, {Position{1, 14}, tokenEqual, "="}, {Position{1, 17}, tokenString, "baz"}, {Position{1, 22}, tokenRightCurlyBrace, "}"}, {Position{1, 23}, tokenEOF, ""}, }) } func TestLexInlineTableBareKeyInArray(t *testing.T) { testFlow(t, `foo = [{ -bar_ = "baz" }]`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftBracket, "["}, {Position{1, 8}, tokenLeftCurlyBrace, "{"}, {Position{1, 10}, tokenKey, "-bar_"}, {Position{1, 16}, tokenEqual, "="}, {Position{1, 19}, tokenString, "baz"}, {Position{1, 24}, tokenRightCurlyBrace, "}"}, {Position{1, 25}, tokenRightBracket, "]"}, {Position{1, 26}, tokenEOF, ""}, }) } func TestLexInlineTableError1(t *testing.T) { testFlow(t, `foo = { 123 = 0 ]`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "123"}, {Position{1, 13}, tokenEqual, "="}, {Position{1, 15}, tokenInteger, "0"}, {Position{1, 17}, tokenRightBracket, "]"}, {Position{1, 18}, tokenError, "cannot have ']' here"}, }) } func TestLexInlineTableError2(t *testing.T) { testFlow(t, `foo = { 123 = 0 }}`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "123"}, {Position{1, 13}, tokenEqual, "="}, {Position{1, 15}, tokenInteger, "0"}, {Position{1, 17}, tokenRightCurlyBrace, "}"}, {Position{1, 18}, tokenRightCurlyBrace, "}"}, {Position{1, 19}, tokenError, "cannot have '}' here"}, }) } func TestLexInlineTableDottedKey1(t *testing.T) { testFlow(t, `foo = { a = 0, 123.45abc = 0 }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "a"}, {Position{1, 11}, tokenEqual, "="}, {Position{1, 13}, tokenInteger, "0"}, {Position{1, 14}, tokenComma, ","}, {Position{1, 16}, tokenKey, "123.45abc"}, {Position{1, 26}, tokenEqual, "="}, {Position{1, 28}, tokenInteger, "0"}, {Position{1, 30}, tokenRightCurlyBrace, "}"}, {Position{1, 31}, tokenEOF, ""}, }) } func TestLexInlineTableDottedKey2(t *testing.T) { testFlow(t, `foo = { a = 0, '123'.'45abc' = 0 }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "a"}, {Position{1, 11}, tokenEqual, "="}, {Position{1, 13}, tokenInteger, "0"}, {Position{1, 14}, tokenComma, ","}, {Position{1, 16}, tokenKey, "'123'.'45abc'"}, {Position{1, 30}, tokenEqual, "="}, {Position{1, 32}, tokenInteger, "0"}, {Position{1, 34}, tokenRightCurlyBrace, "}"}, {Position{1, 35}, tokenEOF, ""}, }) } func TestLexInlineTableDottedKey3(t *testing.T) { testFlow(t, `foo = { a = 0, "123"."45ʎǝʞ" = 0 }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "a"}, {Position{1, 11}, tokenEqual, "="}, {Position{1, 13}, tokenInteger, "0"}, {Position{1, 14}, tokenComma, ","}, {Position{1, 16}, tokenKey, `"123"."45ʎǝʞ"`}, {Position{1, 30}, tokenEqual, "="}, {Position{1, 32}, tokenInteger, "0"}, {Position{1, 34}, tokenRightCurlyBrace, "}"}, {Position{1, 35}, tokenEOF, ""}, }) } func TestLexInlineTableBareKeyWithComma(t *testing.T) { testFlow(t, `foo = { -bar1 = "baz", -bar_ = "baz" }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "-bar1"}, {Position{1, 15}, tokenEqual, "="}, {Position{1, 18}, tokenString, "baz"}, {Position{1, 22}, tokenComma, ","}, {Position{1, 24}, tokenKey, "-bar_"}, {Position{1, 30}, tokenEqual, "="}, {Position{1, 33}, tokenString, "baz"}, {Position{1, 38}, tokenRightCurlyBrace, "}"}, {Position{1, 39}, tokenEOF, ""}, }) } func TestLexInlineTableBareKeyUnderscore(t *testing.T) { testFlow(t, `foo = { _bar = "baz" }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "_bar"}, {Position{1, 14}, tokenEqual, "="}, {Position{1, 17}, tokenString, "baz"}, {Position{1, 22}, tokenRightCurlyBrace, "}"}, {Position{1, 23}, tokenEOF, ""}, }) } func TestLexInlineTableQuotedKey(t *testing.T) { testFlow(t, `foo = { "bar" = "baz" }`, []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenLeftCurlyBrace, "{"}, {Position{1, 9}, tokenKey, "\"bar\""}, {Position{1, 15}, tokenEqual, "="}, {Position{1, 18}, tokenString, "baz"}, {Position{1, 23}, tokenRightCurlyBrace, "}"}, {Position{1, 24}, tokenEOF, ""}, }) } func BenchmarkLexer(b *testing.B) { sample := `title = "Hugo: A Fast and Flexible Website Generator" baseurl = "http://gohugo.io/" MetaDataFormat = "yaml" pluralizeListTitles = false [params] description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go" author = "Steve Francia (spf13) and friends" release = "0.22-DEV" [[menu.main]] name = "Download Hugo" pre = "" url = "https://github.com/spf13/hugo/releases" weight = -200 ` b.ResetTimer() for i := 0; i < b.N; i++ { lexToml([]byte(sample)) } } go-toml-1.9.5/localtime.go000066400000000000000000000236401416532417400154200ustar00rootroot00000000000000// 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 } go-toml-1.9.5/localtime_test.go000066400000000000000000000315521416532417400164600ustar00rootroot00000000000000// 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_test.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 toml import ( "encoding/json" "reflect" "testing" "time" ) func cmpEqual(x, y interface{}) bool { return reflect.DeepEqual(x, y) } func TestDates(t *testing.T) { for _, test := range []struct { date LocalDate loc *time.Location wantStr string wantTime time.Time }{ { date: LocalDate{2014, 7, 29}, loc: time.Local, wantStr: "2014-07-29", wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local), }, { date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)), loc: time.UTC, wantStr: "2014-08-20", wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC), }, { date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)), loc: time.UTC, wantStr: "0999-01-26", wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC), }, } { if got := test.date.String(); got != test.wantStr { t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) } if got := test.date.In(test.loc); !got.Equal(test.wantTime) { t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) } } } func TestDateIsValid(t *testing.T) { for _, test := range []struct { date LocalDate want bool }{ {LocalDate{2014, 7, 29}, true}, {LocalDate{2000, 2, 29}, true}, {LocalDate{10000, 12, 31}, true}, {LocalDate{1, 1, 1}, true}, {LocalDate{0, 1, 1}, true}, // year zero is OK {LocalDate{-1, 1, 1}, true}, // negative year is OK {LocalDate{1, 0, 1}, false}, {LocalDate{1, 1, 0}, false}, {LocalDate{2016, 1, 32}, false}, {LocalDate{2016, 13, 1}, false}, {LocalDate{1, -1, 1}, false}, {LocalDate{1, 1, -1}, false}, } { got := test.date.IsValid() if got != test.want { t.Errorf("%#v: got %t, want %t", test.date, got, test.want) } } } func TestParseDate(t *testing.T) { for _, test := range []struct { str string want LocalDate // if empty, expect an error }{ {"2016-01-02", LocalDate{2016, 1, 2}}, {"2016-12-31", LocalDate{2016, 12, 31}}, {"0003-02-04", LocalDate{3, 2, 4}}, {"999-01-26", LocalDate{}}, {"", LocalDate{}}, {"2016-01-02x", LocalDate{}}, } { got, err := ParseLocalDate(test.str) if got != test.want { t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) } if err != nil && test.want != (LocalDate{}) { t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) } } } func TestDateArithmetic(t *testing.T) { for _, test := range []struct { desc string start LocalDate end LocalDate days int }{ { desc: "zero days noop", start: LocalDate{2014, 5, 9}, end: LocalDate{2014, 5, 9}, days: 0, }, { desc: "crossing a year boundary", start: LocalDate{2014, 12, 31}, end: LocalDate{2015, 1, 1}, days: 1, }, { desc: "negative number of days", start: LocalDate{2015, 1, 1}, end: LocalDate{2014, 12, 31}, days: -1, }, { desc: "full leap year", start: LocalDate{2004, 1, 1}, end: LocalDate{2005, 1, 1}, days: 366, }, { desc: "full non-leap year", start: LocalDate{2001, 1, 1}, end: LocalDate{2002, 1, 1}, days: 365, }, { desc: "crossing a leap second", start: LocalDate{1972, 6, 30}, end: LocalDate{1972, 7, 1}, days: 1, }, { desc: "dates before the unix epoch", start: LocalDate{101, 1, 1}, end: LocalDate{102, 1, 1}, days: 365, }, } { if got := test.start.AddDays(test.days); got != test.end { t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) } if got := test.end.DaysSince(test.start); got != test.days { t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) } } } func TestDateBefore(t *testing.T) { for _, test := range []struct { d1, d2 LocalDate want bool }{ {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true}, {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true}, {LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true}, } { if got := test.d1.Before(test.d2); got != test.want { t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want) } } } func TestDateAfter(t *testing.T) { for _, test := range []struct { d1, d2 LocalDate want bool }{ {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false}, {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false}, } { if got := test.d1.After(test.d2); got != test.want { t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want) } } } func TestTimeToString(t *testing.T) { for _, test := range []struct { str string time LocalTime roundTrip bool // ParseLocalTime(str).String() == str? }{ {"13:26:33", LocalTime{13, 26, 33, 0}, true}, {"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true}, {"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true}, {"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false}, {"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false}, } { gotTime, err := ParseLocalTime(test.str) if err != nil { t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) continue } if gotTime != test.time { t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) } if test.roundTrip { gotStr := test.time.String() if gotStr != test.str { t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str) } } } } func TestTimeOf(t *testing.T) { for _, test := range []struct { time time.Time want LocalTime }{ {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}}, {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}}, } { if got := LocalTimeOf(test.time); got != test.want { t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want) } } } func TestTimeIsValid(t *testing.T) { for _, test := range []struct { time LocalTime want bool }{ {LocalTime{0, 0, 0, 0}, true}, {LocalTime{23, 0, 0, 0}, true}, {LocalTime{23, 59, 59, 999999999}, true}, {LocalTime{24, 59, 59, 999999999}, false}, {LocalTime{23, 60, 59, 999999999}, false}, {LocalTime{23, 59, 60, 999999999}, false}, {LocalTime{23, 59, 59, 1000000000}, false}, {LocalTime{-1, 0, 0, 0}, false}, {LocalTime{0, -1, 0, 0}, false}, {LocalTime{0, 0, -1, 0}, false}, {LocalTime{0, 0, 0, -1}, false}, } { got := test.time.IsValid() if got != test.want { t.Errorf("%#v: got %t, want %t", test.time, got, test.want) } } } func TestDateTimeToString(t *testing.T) { for _, test := range []struct { str string dateTime LocalDateTime roundTrip bool // ParseLocalDateTime(str).String() == str? }{ {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true}, {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true}, {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false}, } { gotDateTime, err := ParseLocalDateTime(test.str) if err != nil { t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) continue } if gotDateTime != test.dateTime { t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) } if test.roundTrip { gotStr := test.dateTime.String() if gotStr != test.str { t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str) } } } } func TestParseDateTimeErrors(t *testing.T) { for _, str := range []string{ "", "2016-03-22", // just a date "13:26:33", // just a time "2016-03-22 13:26:33", // wrong separating character "2016-03-22T13:26:33x", // extra at end } { if _, err := ParseLocalDateTime(str); err == nil { t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str) } } } func TestDateTimeOf(t *testing.T) { for _, test := range []struct { time time.Time want LocalDateTime }{ {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}}, {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}}, } { if got := LocalDateTimeOf(test.time); got != test.want { t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) } } } func TestDateTimeIsValid(t *testing.T) { // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. for _, test := range []struct { dt LocalDateTime want bool }{ {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true}, {LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false}, {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false}, } { got := test.dt.IsValid() if got != test.want { t.Errorf("%#v: got %t, want %t", test.dt, got, test.want) } } } func TestDateTimeIn(t *testing.T) { dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} got := dt.In(time.UTC) want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) if !got.Equal(want) { t.Errorf("got %v, want %v", got, want) } } func TestDateTimeBefore(t *testing.T) { d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} t1 := LocalTime{5, 6, 7, 8} t2 := LocalTime{5, 6, 7, 9} for _, test := range []struct { dt1, dt2 LocalDateTime want bool }{ {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true}, {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true}, {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false}, {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, } { if got := test.dt1.Before(test.dt2); got != test.want { t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) } } } func TestDateTimeAfter(t *testing.T) { d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} t1 := LocalTime{5, 6, 7, 8} t2 := LocalTime{5, 6, 7, 9} for _, test := range []struct { dt1, dt2 LocalDateTime want bool }{ {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false}, {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false}, {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true}, {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, } { if got := test.dt1.After(test.dt2); got != test.want { t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) } } } func TestMarshalJSON(t *testing.T) { for _, test := range []struct { value interface{} want string }{ {LocalDate{1987, 4, 15}, `"1987-04-15"`}, {LocalTime{18, 54, 2, 0}, `"18:54:02"`}, {LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`}, } { bgot, err := json.Marshal(test.value) if err != nil { t.Fatal(err) } if got := string(bgot); got != test.want { t.Errorf("%#v: got %s, want %s", test.value, got, test.want) } } } func TestUnmarshalJSON(t *testing.T) { var d LocalDate var tm LocalTime var dt LocalDateTime for _, test := range []struct { data string ptr interface{} want interface{} }{ {`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}}, {`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}}, {`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}}, {`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}}, } { if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { t.Fatalf("%s: %v", test.data, err) } if !cmpEqual(test.ptr, test.want) { t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) } } for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`, `19870415`, // a JSON number `11987-04-15x`, // not a JSON string } { if json.Unmarshal([]byte(bad), &d) == nil { t.Errorf("%q, LocalDate: got nil, want error", bad) } if json.Unmarshal([]byte(bad), &tm) == nil { t.Errorf("%q, LocalTime: got nil, want error", bad) } if json.Unmarshal([]byte(bad), &dt) == nil { t.Errorf("%q, LocalDateTime: got nil, want error", bad) } } } go-toml-1.9.5/marshal.go000066400000000000000000001106041416532417400150730ustar00rootroot00000000000000package 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{}{} } } } go-toml-1.9.5/marshal_OrderPreserve_test.toml000066400000000000000000000011561416532417400213500ustar00rootroot00000000000000title = "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" go-toml-1.9.5/marshal_test.go000066400000000000000000002755371416532417400161530ustar00rootroot00000000000000package toml import ( "bytes" "encoding/json" "errors" "fmt" "io/ioutil" "os" "reflect" "strconv" "strings" "testing" "time" ) type basicMarshalTestStruct struct { String string `toml:"Zstring"` StringList []string `toml:"Ystrlist"` BasicMarshalTestSubAnonymousStruct Sub basicMarshalTestSubStruct `toml:"Xsubdoc"` SubList []basicMarshalTestSubStruct `toml:"Wsublist"` } type basicMarshalTestSubStruct struct { String2 string } type BasicMarshalTestSubAnonymousStruct struct { String3 string } var basicTestData = basicMarshalTestStruct{ String: "Hello", StringList: []string{"Howdy", "Hey There"}, BasicMarshalTestSubAnonymousStruct: BasicMarshalTestSubAnonymousStruct{"One"}, Sub: basicMarshalTestSubStruct{"Two"}, SubList: []basicMarshalTestSubStruct{{"Three"}, {"Four"}}, } var basicTestToml = []byte(`String3 = "One" Ystrlist = ["Howdy", "Hey There"] Zstring = "Hello" [[Wsublist]] String2 = "Three" [[Wsublist]] String2 = "Four" [Xsubdoc] String2 = "Two" `) var basicTestTomlCustomIndentation = []byte(`String3 = "One" Ystrlist = ["Howdy", "Hey There"] Zstring = "Hello" [[Wsublist]] String2 = "Three" [[Wsublist]] String2 = "Four" [Xsubdoc] String2 = "Two" `) var basicTestTomlOrdered = []byte(`Zstring = "Hello" Ystrlist = ["Howdy", "Hey There"] String3 = "One" [Xsubdoc] String2 = "Two" [[Wsublist]] String2 = "Three" [[Wsublist]] String2 = "Four" `) var marshalTestToml = []byte(`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" `) var marshalOrderPreserveToml = []byte(`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" `) var mashalOrderPreserveMapToml = []byte(`title = "TOML Marshal Testing" [basic_map] one = "one" two = "two" [long_map] a7 = "1" b3 = "2" c8 = "3" d4 = "4" e6 = "5" f5 = "6" g10 = "7" h1 = "8" i2 = "9" j9 = "10" `) type Conf struct { Name string Age int Inter interface{} } type NestedStruct struct { FirstName string LastName string Age int } var doc = []byte(`Name = "rui" Age = 18 [Inter] FirstName = "wang" LastName = "jl" Age = 100`) func TestInterface(t *testing.T) { var config Conf config.Inter = &NestedStruct{} err := Unmarshal(doc, &config) expected := Conf{ Name: "rui", Age: 18, Inter: &NestedStruct{ FirstName: "wang", LastName: "jl", Age: 100, }, } if err != nil || !reflect.DeepEqual(config, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, config) } } func TestBasicMarshal(t *testing.T) { result, err := Marshal(basicTestData) if err != nil { t.Fatal(err) } expected := basicTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestBasicMarshalCustomIndentation(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Indentation("\t").Encode(basicTestData) if err != nil { t.Fatal(err) } expected := basicTestTomlCustomIndentation if !bytes.Equal(result.Bytes(), expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) } } func TestBasicMarshalWrongIndentation(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Indentation(" \n").Encode(basicTestData) if err.Error() != "invalid indentation: must only contains space or tab characters" { t.Error("expect err:invalid indentation: must only contains space or tab characters but got:", err) } } func TestBasicMarshalOrdered(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Order(OrderPreserve).Encode(basicTestData) if err != nil { t.Fatal(err) } expected := basicTestTomlOrdered if !bytes.Equal(result.Bytes(), expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) } } func TestBasicMarshalWithPointer(t *testing.T) { result, err := Marshal(&basicTestData) if err != nil { t.Fatal(err) } expected := basicTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestBasicMarshalOrderedWithPointer(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Order(OrderPreserve).Encode(&basicTestData) if err != nil { t.Fatal(err) } expected := basicTestTomlOrdered if !bytes.Equal(result.Bytes(), expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) } } func TestBasicUnmarshal(t *testing.T) { result := basicMarshalTestStruct{} err := Unmarshal(basicTestToml, &result) expected := basicTestData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) } } type quotedKeyMarshalTestStruct struct { String string `toml:"Z.string-àéù"` Float float64 `toml:"Yfloat-𝟘"` Sub basicMarshalTestSubStruct `toml:"Xsubdoc-àéù"` SubList []basicMarshalTestSubStruct `toml:"W.sublist-𝟘"` } var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ String: "Hello", Float: 3.5, Sub: basicMarshalTestSubStruct{"One"}, SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, } var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 "Z.string-àéù" = "Hello" [["W.sublist-𝟘"]] String2 = "Two" [["W.sublist-𝟘"]] String2 = "Three" ["Xsubdoc-àéù"] String2 = "One" `) func TestBasicMarshalQuotedKey(t *testing.T) { result, err := Marshal(quotedKeyMarshalTestData) if err != nil { t.Fatal(err) } expected := quotedKeyMarshalTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestBasicUnmarshalQuotedKey(t *testing.T) { tree, err := LoadBytes(quotedKeyMarshalTestToml) if err != nil { t.Fatal(err) } var q quotedKeyMarshalTestStruct tree.Unmarshal(&q) fmt.Println(q) if !reflect.DeepEqual(quotedKeyMarshalTestData, q) { t.Errorf("Bad unmarshal: expected\n-----\n%v\n-----\ngot\n-----\n%v\n-----\n", quotedKeyMarshalTestData, q) } } type testDoc struct { Title string `toml:"title"` BasicLists testDocBasicLists `toml:"basic_lists"` SubDocPtrs []*testSubDoc `toml:"subdocptrs"` BasicMap map[string]string `toml:"basic_map"` Subdocs testDocSubs `toml:"subdoc"` Basics testDocBasics `toml:"basic"` SubDocList []testSubDoc `toml:"subdoclist"` err int `toml:"shouldntBeHere"` unexported int `toml:"shouldntBeHere"` Unexported2 int `toml:"-"` } type testMapDoc struct { Title string `toml:"title"` BasicMap map[string]string `toml:"basic_map"` LongMap map[string]string `toml:"long_map"` } type testDocBasics struct { Uint uint `toml:"uint"` Bool bool `toml:"bool"` Float32 float32 `toml:"float"` Float64 float64 `toml:"float64"` Int int `toml:"int"` String *string `toml:"string"` Date time.Time `toml:"date"` unexported int `toml:"shouldntBeHere"` } type testDocBasicLists struct { Floats []*float32 `toml:"floats"` Bools []bool `toml:"bools"` Dates []time.Time `toml:"dates"` Ints []int `toml:"ints"` UInts []uint `toml:"uints"` Strings []string `toml:"strings"` } type testDocSubs struct { Second *testSubDoc `toml:"second"` First testSubDoc `toml:"first"` } type testSubDoc struct { Name string `toml:"name"` unexported int `toml:"shouldntBeHere"` } var biteMe = "Bite me" var float1 float32 = 12.3 var float2 float32 = 45.6 var float3 float32 = 78.9 var subdoc = testSubDoc{"Second", 0} var docData = testDoc{ Title: "TOML Marshal Testing", unexported: 0, Unexported2: 0, Basics: testDocBasics{ Bool: true, Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), Float32: 123.4, Float64: 123.456782132399, Int: 5000, Uint: 5001, String: &biteMe, unexported: 0, }, BasicLists: testDocBasicLists{ Bools: []bool{true, false, true}, Dates: []time.Time{ time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC), }, Floats: []*float32{&float1, &float2, &float3}, Ints: []int{8001, 8001, 8002}, Strings: []string{"One", "Two", "Three"}, UInts: []uint{5002, 5003}, }, BasicMap: map[string]string{ "one": "one", "two": "two", }, Subdocs: testDocSubs{ First: testSubDoc{"First", 0}, Second: &subdoc, }, SubDocList: []testSubDoc{ {"List.First", 0}, {"List.Second", 0}, }, SubDocPtrs: []*testSubDoc{&subdoc}, } var mapTestDoc = testMapDoc{ Title: "TOML Marshal Testing", BasicMap: map[string]string{ "one": "one", "two": "two", }, LongMap: map[string]string{ "h1": "8", "i2": "9", "b3": "2", "d4": "4", "f5": "6", "e6": "5", "a7": "1", "c8": "3", "j9": "10", "g10": "7", }, } func TestDocMarshal(t *testing.T) { result, err := Marshal(docData) if err != nil { t.Fatal(err) } if !bytes.Equal(result, marshalTestToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalTestToml, result) } } func TestDocMarshalOrdered(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Order(OrderPreserve).Encode(docData) if err != nil { t.Fatal(err) } if !bytes.Equal(result.Bytes(), marshalOrderPreserveToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalOrderPreserveToml, result.Bytes()) } } func TestDocMarshalMaps(t *testing.T) { result, err := Marshal(mapTestDoc) if err != nil { t.Fatal(err) } if !bytes.Equal(result, mashalOrderPreserveMapToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", mashalOrderPreserveMapToml, result) } } func TestDocMarshalOrderedMaps(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Order(OrderPreserve).Encode(mapTestDoc) if err != nil { t.Fatal(err) } if !bytes.Equal(result.Bytes(), mashalOrderPreserveMapToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", mashalOrderPreserveMapToml, result.Bytes()) } } func TestDocMarshalPointer(t *testing.T) { result, err := Marshal(&docData) if err != nil { t.Fatal(err) } if !bytes.Equal(result, marshalTestToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalTestToml, result) } } func TestDocUnmarshal(t *testing.T) { result := testDoc{} err := Unmarshal(marshalTestToml, &result) expected := docData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { resStr, _ := json.MarshalIndent(result, "", " ") expStr, _ := json.MarshalIndent(expected, "", " ") t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) } } func TestDocPartialUnmarshal(t *testing.T) { file, err := ioutil.TempFile("", "test-*.toml") if err != nil { t.Fatal(err) } defer os.Remove(file.Name()) err = ioutil.WriteFile(file.Name(), marshalTestToml, 0) if err != nil { t.Fatal(err) } tree, _ := LoadFile(file.Name()) subTree := tree.Get("subdoc").(*Tree) result := testDocSubs{} err = subTree.Unmarshal(&result) expected := docData.Subdocs if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { resStr, _ := json.MarshalIndent(result, "", " ") expStr, _ := json.MarshalIndent(expected, "", " ") t.Errorf("Bad partial unmartial: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) } } type tomlTypeCheckTest struct { name string item interface{} typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree } func TestTypeChecks(t *testing.T) { tests := []tomlTypeCheckTest{ {"bool", true, 0}, {"bool", false, 0}, {"int", int(2), 0}, {"int8", int8(2), 0}, {"int16", int16(2), 0}, {"int32", int32(2), 0}, {"int64", int64(2), 0}, {"uint", uint(2), 0}, {"uint8", uint8(2), 0}, {"uint16", uint16(2), 0}, {"uint32", uint32(2), 0}, {"uint64", uint64(2), 0}, {"float32", float32(3.14), 0}, {"float64", float64(3.14), 0}, {"string", "lorem ipsum", 0}, {"time", time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), 0}, {"stringlist", []string{"hello", "hi"}, 1}, {"stringlistptr", &[]string{"hello", "hi"}, 1}, {"stringarray", [2]string{"hello", "hi"}, 1}, {"stringarrayptr", &[2]string{"hello", "hi"}, 1}, {"timelist", []time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, {"timelistptr", &[]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, {"timearray", [1]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, {"timearrayptr", &[1]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, {"objectlist", []tomlTypeCheckTest{}, 2}, {"objectlistptr", &[]tomlTypeCheckTest{}, 2}, {"objectarray", [2]tomlTypeCheckTest{{}, {}}, 2}, {"objectlistptr", &[2]tomlTypeCheckTest{{}, {}}, 2}, {"object", tomlTypeCheckTest{}, 3}, {"objectptr", &tomlTypeCheckTest{}, 3}, } for _, test := range tests { expected := []bool{false, false, false, false} expected[test.typ] = true result := []bool{ isPrimitive(reflect.TypeOf(test.item)), isOtherSequence(reflect.TypeOf(test.item)), isTreeSequence(reflect.TypeOf(test.item)), isTree(reflect.TypeOf(test.item)), } if !reflect.DeepEqual(expected, result) { t.Errorf("Bad type check on %q: expected %v, got %v", test.name, expected, result) } } } type unexportedMarshalTestStruct struct { String string `toml:"string"` StringList []string `toml:"strlist"` Sub basicMarshalTestSubStruct `toml:"subdoc"` SubList []basicMarshalTestSubStruct `toml:"sublist"` unexported int `toml:"shouldntBeHere"` Unexported2 int `toml:"-"` } var unexportedTestData = unexportedMarshalTestStruct{ String: "Hello", StringList: []string{"Howdy", "Hey There"}, Sub: basicMarshalTestSubStruct{"One"}, SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, unexported: 0, Unexported2: 0, } var unexportedTestToml = []byte(`string = "Hello" strlist = ["Howdy","Hey There"] unexported = 1 shouldntBeHere = 2 [subdoc] String2 = "One" [[sublist]] String2 = "Two" [[sublist]] String2 = "Three" `) func TestUnexportedUnmarshal(t *testing.T) { result := unexportedMarshalTestStruct{} err := Unmarshal(unexportedTestToml, &result) expected := unexportedTestData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result) } } type errStruct struct { Bool bool `toml:"bool"` Date time.Time `toml:"date"` Float float64 `toml:"float"` Int int16 `toml:"int"` String *string `toml:"string"` } var errTomls = []string{ "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", } type mapErr struct { Vals map[string]float64 } type intErr struct { Int1 int Int2 int8 Int3 int16 Int4 int32 Int5 int64 UInt1 uint UInt2 uint8 UInt3 uint16 UInt4 uint32 UInt5 uint64 Flt1 float32 Flt2 float64 } var intErrTomls = []string{ "Int1 = []\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = []\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = []\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = []\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = []\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = []\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = []\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = []\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = []\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = []\nFlt1 = 1.0\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = []\nFlt2 = 2.0", "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = []", } func TestErrUnmarshal(t *testing.T) { for ind, toml := range errTomls { result := errStruct{} err := Unmarshal([]byte(toml), &result) if err == nil { t.Errorf("Expected err from case %d\n", ind) } } result2 := mapErr{} err := Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2) if err == nil { t.Errorf("Expected err from map") } for ind, toml := range intErrTomls { result3 := intErr{} err := Unmarshal([]byte(toml), &result3) if err == nil { t.Errorf("Expected int err from case %d\n", ind) } } } type emptyMarshalTestStruct struct { Title string `toml:"title"` Bool bool `toml:"bool"` Int int `toml:"int"` String string `toml:"string"` StringList []string `toml:"stringlist"` Ptr *basicMarshalTestStruct `toml:"ptr"` Map map[string]string `toml:"map"` } var emptyTestData = emptyMarshalTestStruct{ Title: "Placeholder", Bool: false, Int: 0, String: "", StringList: []string{}, Ptr: nil, Map: map[string]string{}, } var emptyTestToml = []byte(`bool = false int = 0 string = "" stringlist = [] title = "Placeholder" [map] `) type emptyMarshalTestStruct2 struct { Title string `toml:"title"` Bool bool `toml:"bool,omitempty"` Int int `toml:"int, omitempty"` String string `toml:"string,omitempty "` StringList []string `toml:"stringlist,omitempty"` Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` Map map[string]string `toml:"map,omitempty"` } var emptyTestData2 = emptyMarshalTestStruct2{ Title: "Placeholder", Bool: false, Int: 0, String: "", StringList: []string{}, Ptr: nil, Map: map[string]string{}, } var emptyTestToml2 = []byte(`title = "Placeholder" `) func TestEmptyMarshal(t *testing.T) { result, err := Marshal(emptyTestData) if err != nil { t.Fatal(err) } expected := emptyTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad empty marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestEmptyMarshalOmit(t *testing.T) { result, err := Marshal(emptyTestData2) if err != nil { t.Fatal(err) } expected := emptyTestToml2 if !bytes.Equal(result, expected) { t.Errorf("Bad empty omit marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestEmptyUnmarshal(t *testing.T) { result := emptyMarshalTestStruct{} err := Unmarshal(emptyTestToml, &result) expected := emptyTestData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result) } } func TestEmptyUnmarshalOmit(t *testing.T) { result := emptyMarshalTestStruct2{} err := Unmarshal(emptyTestToml, &result) expected := emptyTestData2 if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result) } } type pointerMarshalTestStruct struct { Str *string List *[]string ListPtr *[]*string Map *map[string]string MapPtr *map[string]*string EmptyStr *string EmptyList *[]string EmptyMap *map[string]string DblPtr *[]*[]*string } var pointerStr = "Hello" var pointerList = []string{"Hello back"} var pointerListPtr = []*string{&pointerStr} var pointerMap = map[string]string{"response": "Goodbye"} var pointerMapPtr = map[string]*string{"alternate": &pointerStr} var pointerTestData = pointerMarshalTestStruct{ Str: &pointerStr, List: &pointerList, ListPtr: &pointerListPtr, Map: &pointerMap, MapPtr: &pointerMapPtr, EmptyStr: nil, EmptyList: nil, EmptyMap: nil, } var pointerTestToml = []byte(`List = ["Hello back"] ListPtr = ["Hello"] Str = "Hello" [Map] response = "Goodbye" [MapPtr] alternate = "Hello" `) func TestPointerMarshal(t *testing.T) { result, err := Marshal(pointerTestData) if err != nil { t.Fatal(err) } expected := pointerTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad pointer marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestPointerUnmarshal(t *testing.T) { result := pointerMarshalTestStruct{} err := Unmarshal(pointerTestToml, &result) expected := pointerTestData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result) } } func TestUnmarshalTypeMismatch(t *testing.T) { result := pointerMarshalTestStruct{} err := Unmarshal([]byte("List = 123"), &result) if !strings.HasPrefix(err.Error(), "(1, 1): Can't convert 123(int64) to []string(slice)") { t.Errorf("Type mismatch must be reported: got %v", err.Error()) } } type nestedMarshalTestStruct struct { String [][]string //Struct [][]basicMarshalTestSubStruct StringPtr *[]*[]*string // StructPtr *[]*[]*basicMarshalTestSubStruct } var str1 = "Three" var str2 = "Four" var strPtr = []*string{&str1, &str2} var strPtr2 = []*[]*string{&strPtr} var nestedTestData = nestedMarshalTestStruct{ String: [][]string{{"Five", "Six"}, {"One", "Two"}}, StringPtr: &strPtr2, } var nestedTestToml = []byte(`String = [["Five", "Six"], ["One", "Two"]] StringPtr = [["Three", "Four"]] `) func TestNestedMarshal(t *testing.T) { result, err := Marshal(nestedTestData) if err != nil { t.Fatal(err) } expected := nestedTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad nested marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestNestedUnmarshal(t *testing.T) { result := nestedMarshalTestStruct{} err := Unmarshal(nestedTestToml, &result) expected := nestedTestData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result) } } type customMarshalerParent struct { Self customMarshaler `toml:"me"` Friends []customMarshaler `toml:"friends"` } type customMarshaler struct { FirstName string LastName string } func (c customMarshaler) MarshalTOML() ([]byte, error) { fullName := fmt.Sprintf("%s %s", c.FirstName, c.LastName) return []byte(fullName), nil } var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} var customMarshalerToml = []byte(`Sally Fields`) var nestedCustomMarshalerData = customMarshalerParent{ Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, Friends: []customMarshaler{customMarshalerData}, } var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] me = "Maiku Suteda" `) var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends] FirstName = "Sally" LastName = "Fields"`) func TestCustomMarshaler(t *testing.T) { result, err := Marshal(customMarshalerData) if err != nil { t.Fatal(err) } expected := customMarshalerToml if !bytes.Equal(result, expected) { t.Errorf("Bad custom marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } type IntOrString string func (x *IntOrString) MarshalTOML() ([]byte, error) { s := *(*string)(x) _, err := strconv.Atoi(s) if err != nil { return []byte(fmt.Sprintf(`"%s"`, s)), nil } return []byte(s), nil } func TestNestedCustomMarshaler(t *testing.T) { num := IntOrString("100") str := IntOrString("hello") var parent = struct { IntField *IntOrString `toml:"int"` StringField *IntOrString `toml:"string"` }{ &num, &str, } result, err := Marshal(parent) if err != nil { t.Fatal(err) } expected := `int = 100 string = "hello" ` if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } type textMarshaler struct { FirstName string LastName string } func (m textMarshaler) MarshalText() ([]byte, error) { fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) return []byte(fullName), nil } func TestTextMarshaler(t *testing.T) { m := textMarshaler{FirstName: "Sally", LastName: "Fields"} result, err := Marshal(m) if err != nil { t.Fatal(err) } expected := `Sally Fields` if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestUnmarshalTextMarshaler(t *testing.T) { var nested = struct { Friends textMarshaler `toml:"friends"` }{} var expected = struct { Friends textMarshaler `toml:"friends"` }{ Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"}, } err := Unmarshal(nestedCustomMarshalerTomlForUnmarshal, &nested) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(nested, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, nested) } } func TestNestedTextMarshaler(t *testing.T) { var parent = struct { Self textMarshaler `toml:"me"` Friends []textMarshaler `toml:"friends"` Stranger *textMarshaler `toml:"stranger"` }{ Self: textMarshaler{FirstName: "Maiku", LastName: "Suteda"}, Friends: []textMarshaler{textMarshaler{FirstName: "Sally", LastName: "Fields"}}, Stranger: &textMarshaler{FirstName: "Earl", LastName: "Henson"}, } result, err := Marshal(parent) if err != nil { t.Fatal(err) } expected := `friends = ["Sally Fields"] me = "Maiku Suteda" stranger = "Earl Henson" ` if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } type precedentMarshaler struct { FirstName string LastName string } func (m precedentMarshaler) MarshalText() ([]byte, error) { return []byte("shadowed"), nil } func (m precedentMarshaler) MarshalTOML() ([]byte, error) { fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) return []byte(fullName), nil } func TestPrecedentMarshaler(t *testing.T) { m := textMarshaler{FirstName: "Sally", LastName: "Fields"} result, err := Marshal(m) if err != nil { t.Fatal(err) } expected := `Sally Fields` if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } type customPointerMarshaler struct { FirstName string LastName string } func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) { return []byte(`"hidden"`), nil } type textPointerMarshaler struct { FirstName string LastName string } func (m *textPointerMarshaler) MarshalText() ([]byte, error) { return []byte("hidden"), nil } func TestPointerMarshaler(t *testing.T) { var parent = struct { Self customPointerMarshaler `toml:"me"` Stranger *customPointerMarshaler `toml:"stranger"` Friend textPointerMarshaler `toml:"friend"` Fiend *textPointerMarshaler `toml:"fiend"` }{ Self: customPointerMarshaler{FirstName: "Maiku", LastName: "Suteda"}, Stranger: &customPointerMarshaler{FirstName: "Earl", LastName: "Henson"}, Friend: textPointerMarshaler{FirstName: "Sally", LastName: "Fields"}, Fiend: &textPointerMarshaler{FirstName: "Casper", LastName: "Snider"}, } result, err := Marshal(parent) if err != nil { t.Fatal(err) } expected := `fiend = "hidden" stranger = "hidden" [friend] FirstName = "Sally" LastName = "Fields" [me] FirstName = "Maiku" LastName = "Suteda" ` if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestPointerCustomMarshalerSequence(t *testing.T) { var customPointerMarshalerSlice *[]*customPointerMarshaler var customPointerMarshalerArray *[2]*customPointerMarshaler if !isCustomMarshalerSequence(reflect.TypeOf(customPointerMarshalerSlice)) { t.Errorf("error: should be a sequence of custom marshaler interfaces") } if !isCustomMarshalerSequence(reflect.TypeOf(customPointerMarshalerArray)) { t.Errorf("error: should be a sequence of custom marshaler interfaces") } } func TestPointerTextMarshalerSequence(t *testing.T) { var textPointerMarshalerSlice *[]*textPointerMarshaler var textPointerMarshalerArray *[2]*textPointerMarshaler if !isTextMarshalerSequence(reflect.TypeOf(textPointerMarshalerSlice)) { t.Errorf("error: should be a sequence of text marshaler interfaces") } if !isTextMarshalerSequence(reflect.TypeOf(textPointerMarshalerArray)) { t.Errorf("error: should be a sequence of text marshaler interfaces") } } var commentTestToml = []byte(` # it's a comment on type [postgres] # isCommented = "dvalue" noComment = "cvalue" # A comment on AttrB with a # break line password = "bvalue" # A comment on AttrA user = "avalue" [[postgres.My]] # a comment on my on typeC My = "Foo" [[postgres.My]] # a comment on my on typeC My = "Baar" `) func TestMarshalComment(t *testing.T) { type TypeC struct { My string `comment:"a comment on my on typeC"` } type TypeB struct { AttrA string `toml:"user" comment:"A comment on AttrA"` AttrB string `toml:"password" comment:"A comment on AttrB with a\n break line"` AttrC string `toml:"noComment"` AttrD string `toml:"isCommented" commented:"true"` My []TypeC } type TypeA struct { TypeB TypeB `toml:"postgres" comment:"it's a comment on type"` } ta := []TypeC{{My: "Foo"}, {My: "Baar"}} config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue", AttrC: "cvalue", AttrD: "dvalue", My: ta}} result, err := Marshal(config) if err != nil { t.Fatal(err) } expected := commentTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestMarshalMultilineCommented(t *testing.T) { expectedToml := []byte(`# MultilineArray = [ # 100, # 200, # 300, # ] # MultilineNestedArray = [ # [ # "a", # "b", # "c", # ], # [ # "d", # "e", # "f", # ], # ] # MultilineString = """ # I # am # Allen""" NonCommented = "Not commented line" `) type StructWithMultiline struct { NonCommented string MultilineString string `commented:"true" multiline:"true"` MultilineArray []int `commented:"true"` MultilineNestedArray [][]string `commented:"true"` } var buf bytes.Buffer enc := NewEncoder(&buf) if err := enc.ArraysWithOneElementPerLine(true).Encode(StructWithMultiline{ NonCommented: "Not commented line", MultilineString: "I\nam\nAllen", MultilineArray: []int{100, 200, 300}, MultilineNestedArray: [][]string{ {"a", "b", "c"}, {"d", "e", "f"}, }, }); err == nil { result := buf.Bytes() if !bytes.Equal(result, expectedToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result) } } else { t.Fatal(err) } } func TestMarshalMultilineLiteral(t *testing.T) { type Doc struct { Value string `multiline:"true" literal:"true"` } d := Doc{ Value: "hello\nworld\ttest\nend", } expected := []byte(`Value = ''' hello world test end ''' `) b, err := Marshal(d) if err != nil { t.Fatal("unexpected error") } if !bytes.Equal(b, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) } } func TestMarshalNonPrimitiveTypeCommented(t *testing.T) { expectedToml := []byte(` # [CommentedMapField] # [CommentedMapField.CommentedMapField1] # SingleLineString = "This line should be commented out" # [CommentedMapField.CommentedMapField2] # SingleLineString = "This line should be commented out" # [CommentedStructField] # [CommentedStructField.CommentedStructField] # MultilineArray = [ # 1, # 2, # ] # MultilineNestedArray = [ # [ # 10, # 20, # ], # [ # 100, # 200, # ], # ] # MultilineString = """ # This line # should be # commented out""" # [CommentedStructField.NotCommentedStructField] # MultilineArray = [ # 1, # 2, # ] # MultilineNestedArray = [ # [ # 10, # 20, # ], # [ # 100, # 200, # ], # ] # MultilineString = """ # This line # should be # commented out""" [NotCommentedStructField] # [NotCommentedStructField.CommentedStructField] # MultilineArray = [ # 1, # 2, # ] # MultilineNestedArray = [ # [ # 10, # 20, # ], # [ # 100, # 200, # ], # ] # MultilineString = """ # This line # should be # commented out""" [NotCommentedStructField.NotCommentedStructField] MultilineArray = [ 3, 4, ] MultilineNestedArray = [ [ 30, 40, ], [ 300, 400, ], ] MultilineString = """ This line should NOT be commented out""" `) type InnerStruct struct { MultilineString string `multiline:"true"` MultilineArray []int MultilineNestedArray [][]int } type MiddleStruct struct { NotCommentedStructField InnerStruct CommentedStructField InnerStruct `commented:"true"` } type OuterStruct struct { CommentedStructField MiddleStruct `commented:"true"` NotCommentedStructField MiddleStruct CommentedMapField map[string]struct{ SingleLineString string } `commented:"true"` } commentedTestStruct := OuterStruct{ CommentedStructField: MiddleStruct{ NotCommentedStructField: InnerStruct{ MultilineString: "This line\nshould be\ncommented out", MultilineArray: []int{1, 2}, MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, }, CommentedStructField: InnerStruct{ MultilineString: "This line\nshould be\ncommented out", MultilineArray: []int{1, 2}, MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, }, }, NotCommentedStructField: MiddleStruct{ NotCommentedStructField: InnerStruct{ MultilineString: "This line\nshould NOT be\ncommented out", MultilineArray: []int{3, 4}, MultilineNestedArray: [][]int{{30, 40}, {300, 400}}, }, CommentedStructField: InnerStruct{ MultilineString: "This line\nshould be\ncommented out", MultilineArray: []int{1, 2}, MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, }, }, CommentedMapField: map[string]struct{ SingleLineString string }{ "CommentedMapField1": { SingleLineString: "This line should be commented out", }, "CommentedMapField2": { SingleLineString: "This line should be commented out", }, }, } var buf bytes.Buffer enc := NewEncoder(&buf) if err := enc.ArraysWithOneElementPerLine(true).Encode(commentedTestStruct); err == nil { result := buf.Bytes() if !bytes.Equal(result, expectedToml) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result) } } else { t.Fatal(err) } } func TestCompactComments(t *testing.T) { expected := []byte(` [first-section] # comment for first-key first-key = 1 # comment for second-key second-key = "value" # comment for commented third-key # third-key = [] [second-section] # comment for first-key first-key = 2 # comment for second-key second-key = "another value" # comment for commented third-key # third-key = ["value1", "value2"] `) type Settings struct { FirstKey int `toml:"first-key" comment:"comment for first-key"` SecondKey string `toml:"second-key" comment:"comment for second-key"` ThirdKey []string `toml:"third-key" comment:"comment for commented third-key" commented:"true"` } type Config struct { FirstSection Settings `toml:"first-section"` SecondSection Settings `toml:"second-section"` } data := Config{ FirstSection: Settings{1, "value", []string{}}, SecondSection: Settings{2, "another value", []string{"value1", "value2"}}, } buf := new(bytes.Buffer) if err := NewEncoder(buf).CompactComments(true).Encode(data); err != nil { t.Fatal(err) } if !bytes.Equal(expected, buf.Bytes()) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, buf.Bytes()) } } type mapsTestStruct struct { Simple map[string]string Paths map[string]string Other map[string]float64 X struct { Y struct { Z map[string]bool } } } var mapsTestData = mapsTestStruct{ Simple: map[string]string{ "one plus one": "two", "next": "three", }, Paths: map[string]string{ "/this/is/a/path": "/this/is/also/a/path", "/heloo.txt": "/tmp/lololo.txt", }, Other: map[string]float64{ "testing": 3.9999, }, X: struct{ Y struct{ Z map[string]bool } }{ Y: struct{ Z map[string]bool }{ Z: map[string]bool{ "is.Nested": true, }, }, }, } var mapsTestToml = []byte(` [Other] "testing" = 3.9999 [Paths] "/heloo.txt" = "/tmp/lololo.txt" "/this/is/a/path" = "/this/is/also/a/path" [Simple] "next" = "three" "one plus one" = "two" [X] [X.Y] [X.Y.Z] "is.Nested" = true `) func TestEncodeQuotedMapKeys(t *testing.T) { var buf bytes.Buffer if err := NewEncoder(&buf).QuoteMapKeys(true).Encode(mapsTestData); err != nil { t.Fatal(err) } result := buf.Bytes() expected := mapsTestToml if !bytes.Equal(result, expected) { t.Errorf("Bad maps marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestDecodeQuotedMapKeys(t *testing.T) { result := mapsTestStruct{} err := NewDecoder(bytes.NewBuffer(mapsTestToml)).Decode(&result) expected := mapsTestData if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad maps unmarshal: expected %v, got %v", expected, result) } } type structArrayNoTag struct { A struct { B []int64 C []int64 } } func TestMarshalArray(t *testing.T) { expected := []byte(` [A] B = [1, 2, 3] C = [1] `) m := structArrayNoTag{ A: struct { B []int64 C []int64 }{ B: []int64{1, 2, 3}, C: []int64{1}, }, } b, err := Marshal(m) if err != nil { t.Fatal(err) } if !bytes.Equal(b, expected) { t.Errorf("Bad arrays marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) } } func TestMarshalArrayOnePerLine(t *testing.T) { expected := []byte(` [A] B = [ 1, 2, 3, ] C = [1] `) m := structArrayNoTag{ A: struct { B []int64 C []int64 }{ B: []int64{1, 2, 3}, C: []int64{1}, }, } var buf bytes.Buffer encoder := NewEncoder(&buf).ArraysWithOneElementPerLine(true) err := encoder.Encode(m) if err != nil { t.Fatal(err) } b := buf.Bytes() if !bytes.Equal(b, expected) { t.Errorf("Bad arrays marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) } } var customTagTestToml = []byte(` [postgres] password = "bvalue" user = "avalue" [[postgres.My]] My = "Foo" [[postgres.My]] My = "Baar" `) func TestMarshalCustomTag(t *testing.T) { type TypeC struct { My string } type TypeB struct { AttrA string `file:"user"` AttrB string `file:"password"` My []TypeC } type TypeA struct { TypeB TypeB `file:"postgres"` } ta := []TypeC{{My: "Foo"}, {My: "Baar"}} config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue", My: ta}} var buf bytes.Buffer err := NewEncoder(&buf).SetTagName("file").Encode(config) if err != nil { t.Fatal(err) } expected := customTagTestToml result := buf.Bytes() if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } var customCommentTagTestToml = []byte(` # db connection [postgres] # db pass password = "bvalue" # db user user = "avalue" `) func TestMarshalCustomComment(t *testing.T) { type TypeB struct { AttrA string `toml:"user" descr:"db user"` AttrB string `toml:"password" descr:"db pass"` } type TypeA struct { TypeB TypeB `toml:"postgres" descr:"db connection"` } config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue"}} var buf bytes.Buffer err := NewEncoder(&buf).SetTagComment("descr").Encode(config) if err != nil { t.Fatal(err) } expected := customCommentTagTestToml result := buf.Bytes() if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } var customCommentedTagTestToml = []byte(` [postgres] # password = "bvalue" # user = "avalue" `) func TestMarshalCustomCommented(t *testing.T) { type TypeB struct { AttrA string `toml:"user" disable:"true"` AttrB string `toml:"password" disable:"true"` } type TypeA struct { TypeB TypeB `toml:"postgres"` } config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue"}} var buf bytes.Buffer err := NewEncoder(&buf).SetTagCommented("disable").Encode(config) if err != nil { t.Fatal(err) } expected := customCommentedTagTestToml result := buf.Bytes() if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestMarshalDirectMultilineString(t *testing.T) { tree := newTree() tree.SetWithOptions("mykey", SetOptions{ Multiline: true, }, "my\x11multiline\nstring\ba\tb\fc\rd\"e\\!") result, err := tree.Marshal() if err != nil { t.Fatal("marshal should not error:", err) } expected := []byte("mykey = \"\"\"\nmy\\u0011multiline\nstring\\ba\tb\\fc\rd\"e\\!\"\"\"\n") if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { type Test struct { Field1 string `toml:"Fie ld1"` Field2 string } type TestCase struct { desc string input []byte expected Test } testCases := []TestCase{ { desc: "multiline string with tab", input: []byte("Field2 = \"\"\"\nhello\tworld\"\"\""), expected: Test{ Field2: "hello\tworld", }, }, { desc: "quoted key with tab", input: []byte("\"Fie\tld1\" = \"key with tab\""), expected: Test{ Field1: "key with tab", }, }, { desc: "basic string tab", input: []byte("Field2 = \"hello\tworld\""), expected: Test{ Field2: "hello\tworld", }, }, } for i := range testCases { result := Test{} err := Unmarshal(testCases[i].input, &result) if err != nil { t.Errorf("%s test error:%v", testCases[i].desc, err) continue } if !reflect.DeepEqual(result, testCases[i].expected) { t.Errorf("%s test error: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n", testCases[i].desc, testCases[i].expected, result) } } } var customMultilineTagTestToml = []byte(`int_slice = [ 1, 2, 3, ] `) func TestMarshalCustomMultiline(t *testing.T) { type TypeA struct { AttrA []int `toml:"int_slice" mltln:"true"` } config := TypeA{AttrA: []int{1, 2, 3}} var buf bytes.Buffer err := NewEncoder(&buf).ArraysWithOneElementPerLine(true).SetTagMultiline("mltln").Encode(config) if err != nil { t.Fatal(err) } expected := customMultilineTagTestToml result := buf.Bytes() if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestMultilineWithAdjacentQuotationMarks(t *testing.T) { type testStruct struct { Str string `multiline:"true"` } type testCase struct { expected []byte data testStruct } testCases := []testCase{ { expected: []byte(`Str = """ hello\"""" `), data: testStruct{ Str: "hello\"", }, }, { expected: []byte(`Str = """ ""\"""\"""\"""" `), data: testStruct{ Str: "\"\"\"\"\"\"\"\"\"", }, }, } for i := range testCases { result, err := Marshal(testCases[i].data) if err != nil { t.Fatal(err) } if !bytes.Equal(result, testCases[i].expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", testCases[i].expected, result) } else { var data testStruct if err = Unmarshal(result, &data); err != nil { t.Fatal(err) } if data.Str != testCases[i].data.Str { t.Errorf("Round trip test fail: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", testCases[i].data.Str, data.Str) } } } } func TestMarshalEmbedTree(t *testing.T) { expected := []byte(`OuterField1 = "Out" OuterField2 = 1024 [TreeField] InnerField1 = "In" InnerField2 = 2048 [TreeField.EmbedStruct] EmbedField = "Embed" `) type InnerStruct struct { InnerField1 string InnerField2 int EmbedStruct struct { EmbedField string } } type OuterStruct struct { OuterField1 string OuterField2 int TreeField *Tree } tree, err := Load(` InnerField1 = "In" InnerField2 = 2048 [EmbedStruct] EmbedField = "Embed" `) if err != nil { t.Fatal(err) } out := OuterStruct{ "Out", 1024, tree, } actual, _ := Marshal(out) if !bytes.Equal(actual, expected) { t.Errorf("Bad marshal: expected %s, got %s", expected, actual) } } var testDocBasicToml = []byte(` [document] bool_val = true date_val = 1979-05-27T07:32:00Z float_val = 123.4 int_val = 5000 string_val = "Bite me" uint_val = 5001 `) type testDocCustomTag struct { Doc testDocBasicsCustomTag `file:"document"` } type testDocBasicsCustomTag struct { Bool bool `file:"bool_val"` Date time.Time `file:"date_val"` Float float32 `file:"float_val"` Int int `file:"int_val"` Uint uint `file:"uint_val"` String *string `file:"string_val"` unexported int `file:"shouldntBeHere"` } var testDocCustomTagData = testDocCustomTag{ Doc: testDocBasicsCustomTag{ Bool: true, Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), Float: 123.4, Int: 5000, Uint: 5001, String: &biteMe, unexported: 0, }, } func TestUnmarshalCustomTag(t *testing.T) { buf := bytes.NewBuffer(testDocBasicToml) result := testDocCustomTag{} err := NewDecoder(buf).SetTagName("file").Decode(&result) if err != nil { t.Fatal(err) } expected := testDocCustomTagData if !reflect.DeepEqual(result, expected) { resStr, _ := json.MarshalIndent(result, "", " ") expStr, _ := json.MarshalIndent(expected, "", " ") t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) } } func TestUnmarshalMap(t *testing.T) { testToml := []byte(` a = 1 b = 2 c = 3 `) var result map[string]int err := Unmarshal(testToml, &result) if err != nil { t.Errorf("Received unexpected error: %s", err) return } expected := map[string]int{ "a": 1, "b": 2, "c": 3, } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) } } func TestUnmarshalMapWithTypedKey(t *testing.T) { testToml := []byte(` a = 1 b = 2 c = 3 `) type letter string var result map[letter]int err := Unmarshal(testToml, &result) if err != nil { t.Errorf("Received unexpected error: %s", err) return } expected := map[letter]int{ "a": 1, "b": 2, "c": 3, } if !reflect.DeepEqual(result, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) } } func TestUnmarshalNonPointer(t *testing.T) { a := 1 err := Unmarshal([]byte{}, a) if err == nil { t.Fatal("unmarshal should err when given a non pointer") } } func TestUnmarshalInvalidPointerKind(t *testing.T) { a := 1 err := Unmarshal([]byte{}, &a) if err == nil { t.Fatal("unmarshal should err when given an invalid pointer type") } } func TestMarshalSlice(t *testing.T) { m := make([]int, 1) m[0] = 1 var buf bytes.Buffer err := NewEncoder(&buf).Encode(&m) if err == nil { t.Error("expected error, got nil") return } if err.Error() != "Only pointer to struct can be marshaled to TOML" { t.Fail() } } func TestMarshalSlicePointer(t *testing.T) { m := make([]int, 1) m[0] = 1 var buf bytes.Buffer err := NewEncoder(&buf).Encode(m) if err == nil { t.Error("expected error, got nil") return } if err.Error() != "Only a struct or map can be marshaled to TOML" { t.Fail() } } func TestMarshalNestedArrayInlineTables(t *testing.T) { type table struct { Value1 int `toml:"ZValue1"` Value2 int `toml:"YValue2"` Value3 int `toml:"XValue3"` } type nestedTable struct { Table table } nestedArray := struct { Simple [][]table SimplePointer *[]*[]table Nested [][]nestedTable NestedPointer *[]*[]nestedTable }{ Simple: [][]table{{{Value1: 1}, {Value1: 10}}}, SimplePointer: &[]*[]table{{{Value2: 2}}}, Nested: [][]nestedTable{{{Table: table{Value3: 3}}}}, NestedPointer: &[]*[]nestedTable{{{Table: table{Value3: -3}}}}, } expectedPreserve := `Simple = [[{ ZValue1 = 1, YValue2 = 0, XValue3 = 0 }, { ZValue1 = 10, YValue2 = 0, XValue3 = 0 }]] SimplePointer = [[{ ZValue1 = 0, YValue2 = 2, XValue3 = 0 }]] Nested = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = 3 } }]] NestedPointer = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = -3 } }]] ` expectedAlphabetical := `Nested = [[{ Table = { XValue3 = 3, YValue2 = 0, ZValue1 = 0 } }]] NestedPointer = [[{ Table = { XValue3 = -3, YValue2 = 0, ZValue1 = 0 } }]] Simple = [[{ XValue3 = 0, YValue2 = 0, ZValue1 = 1 }, { XValue3 = 0, YValue2 = 0, ZValue1 = 10 }]] SimplePointer = [[{ XValue3 = 0, YValue2 = 2, ZValue1 = 0 }]] ` var bufPreserve bytes.Buffer if err := NewEncoder(&bufPreserve).Order(OrderPreserve).Encode(nestedArray); err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if !bytes.Equal(bufPreserve.Bytes(), []byte(expectedPreserve)) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedPreserve, bufPreserve.String()) } var bufAlphabetical bytes.Buffer if err := NewEncoder(&bufAlphabetical).Order(OrderAlphabetical).Encode(nestedArray); err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if !bytes.Equal(bufAlphabetical.Bytes(), []byte(expectedAlphabetical)) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedAlphabetical, bufAlphabetical.String()) } } type testDuration struct { Nanosec time.Duration `toml:"nanosec"` Microsec1 time.Duration `toml:"microsec1"` Microsec2 *time.Duration `toml:"microsec2"` Millisec time.Duration `toml:"millisec"` Sec time.Duration `toml:"sec"` Min time.Duration `toml:"min"` Hour time.Duration `toml:"hour"` Mixed time.Duration `toml:"mixed"` AString string `toml:"a_string"` } var testDurationToml = []byte(` nanosec = "1ns" microsec1 = "1us" microsec2 = "1µs" millisec = "1ms" sec = "1s" min = "1m" hour = "1h" mixed = "1h1m1s1ms1µs1ns" a_string = "15s" `) func TestUnmarshalDuration(t *testing.T) { buf := bytes.NewBuffer(testDurationToml) result := testDuration{} err := NewDecoder(buf).Decode(&result) if err != nil { t.Fatal(err) } ms := time.Duration(1) * time.Microsecond expected := testDuration{ Nanosec: 1, Microsec1: time.Microsecond, Microsec2: &ms, Millisec: time.Millisecond, Sec: time.Second, Min: time.Minute, Hour: time.Hour, Mixed: time.Hour + time.Minute + time.Second + time.Millisecond + time.Microsecond + time.Nanosecond, AString: "15s", } if !reflect.DeepEqual(result, expected) { resStr, _ := json.MarshalIndent(result, "", " ") expStr, _ := json.MarshalIndent(expected, "", " ") t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) } } var testDurationToml2 = []byte(`a_string = "15s" hour = "1h0m0s" microsec1 = "1µs" microsec2 = "1µs" millisec = "1ms" min = "1m0s" mixed = "1h1m1.001001001s" nanosec = "1ns" sec = "1s" `) func TestMarshalDuration(t *testing.T) { ms := time.Duration(1) * time.Microsecond data := testDuration{ Nanosec: 1, Microsec1: time.Microsecond, Microsec2: &ms, Millisec: time.Millisecond, Sec: time.Second, Min: time.Minute, Hour: time.Hour, Mixed: time.Hour + time.Minute + time.Second + time.Millisecond + time.Microsecond + time.Nanosecond, AString: "15s", } var buf bytes.Buffer err := NewEncoder(&buf).Encode(data) if err != nil { t.Fatal(err) } expected := testDurationToml2 result := buf.Bytes() if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } type testBadDuration struct { Val time.Duration `toml:"val"` } var testBadDurationToml = []byte(`val = "1z"`) func TestUnmarshalBadDuration(t *testing.T) { buf := bytes.NewBuffer(testBadDurationToml) result := testBadDuration{} err := NewDecoder(buf).Decode(&result) if err == nil { t.Fatal("expected bad duration error") } } var testCamelCaseKeyToml = []byte(`fooBar = 10`) func TestUnmarshalCamelCaseKey(t *testing.T) { var x struct { FooBar int B int } if err := Unmarshal(testCamelCaseKeyToml, &x); err != nil { t.Fatal(err) } if x.FooBar != 10 { t.Fatal("Did not set camelCase'd key") } } func TestUnmarshalNegativeUint(t *testing.T) { type check struct{ U uint } tree, _ := Load("u = -1") err := tree.Unmarshal(&check{}) if err.Error() != "(1, 1): -1(int64) is negative so does not fit in uint" { t.Error("expect err:(1, 1): -1(int64) is negative so does not fit in uint but got:", err) } } func TestUnmarshalCheckConversionFloatInt(t *testing.T) { type conversionCheck struct { U uint I int F float64 } treeU, _ := Load("u = 1e300") treeI, _ := Load("i = 1e300") treeF, _ := Load("f = 9223372036854775806") errU := treeU.Unmarshal(&conversionCheck{}) errI := treeI.Unmarshal(&conversionCheck{}) errF := treeF.Unmarshal(&conversionCheck{}) if errU.Error() != "(1, 1): Can't convert 1e+300(float64) to uint" { t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to uint but got:", errU) } if errI.Error() != "(1, 1): Can't convert 1e+300(float64) to int" { t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to int but got:", errI) } if errF.Error() != "(1, 1): Can't convert 9223372036854775806(int64) to float64" { t.Error("expect err:(1, 1): Can't convert 9223372036854775806(int64) to float64 but got:", errF) } } func TestUnmarshalOverflow(t *testing.T) { type overflow struct { U8 uint8 I8 int8 F32 float32 } treeU8, _ := Load("u8 = 300") treeI8, _ := Load("i8 = 300") treeF32, _ := Load("f32 = 1e300") errU8 := treeU8.Unmarshal(&overflow{}) errI8 := treeI8.Unmarshal(&overflow{}) errF32 := treeF32.Unmarshal(&overflow{}) if errU8.Error() != "(1, 1): 300(int64) would overflow uint8" { t.Error("expect err:(1, 1): 300(int64) would overflow uint8 but got:", errU8) } if errI8.Error() != "(1, 1): 300(int64) would overflow int8" { t.Error("expect err:(1, 1): 300(int64) would overflow int8 but got:", errI8) } if errF32.Error() != "(1, 1): 1e+300(float64) would overflow float32" { t.Error("expect err:(1, 1): 1e+300(float64) would overflow float32 but got:", errF32) } } func TestUnmarshalDefault(t *testing.T) { type EmbeddedStruct struct { StringField string `default:"c"` } type aliasUint uint var doc struct { StringField string `default:"a"` BoolField bool `default:"true"` UintField uint `default:"1"` Uint8Field uint8 `default:"8"` Uint16Field uint16 `default:"16"` Uint32Field uint32 `default:"32"` Uint64Field uint64 `default:"64"` IntField int `default:"-1"` Int8Field int8 `default:"-8"` Int16Field int16 `default:"-16"` Int32Field int32 `default:"-32"` Int64Field int64 `default:"-64"` Float32Field float32 `default:"32.1"` Float64Field float64 `default:"64.1"` DurationField time.Duration `default:"120ms"` DurationField2 time.Duration `default:"120000000"` NonEmbeddedStruct struct { StringField string `default:"b"` } EmbeddedStruct AliasUintField aliasUint `default:"1000"` } err := Unmarshal([]byte(``), &doc) if err != nil { t.Fatal(err) } if doc.BoolField != true { t.Errorf("BoolField should be true, not %t", doc.BoolField) } if doc.StringField != "a" { t.Errorf("StringField should be \"a\", not %s", doc.StringField) } if doc.UintField != 1 { t.Errorf("UintField should be 1, not %d", doc.UintField) } if doc.Uint8Field != 8 { t.Errorf("Uint8Field should be 8, not %d", doc.Uint8Field) } if doc.Uint16Field != 16 { t.Errorf("Uint16Field should be 16, not %d", doc.Uint16Field) } if doc.Uint32Field != 32 { t.Errorf("Uint32Field should be 32, not %d", doc.Uint32Field) } if doc.Uint64Field != 64 { t.Errorf("Uint64Field should be 64, not %d", doc.Uint64Field) } if doc.IntField != -1 { t.Errorf("IntField should be -1, not %d", doc.IntField) } if doc.Int8Field != -8 { t.Errorf("Int8Field should be -8, not %d", doc.Int8Field) } if doc.Int16Field != -16 { t.Errorf("Int16Field should be -16, not %d", doc.Int16Field) } if doc.Int32Field != -32 { t.Errorf("Int32Field should be -32, not %d", doc.Int32Field) } if doc.Int64Field != -64 { t.Errorf("Int64Field should be -64, not %d", doc.Int64Field) } if doc.Float32Field != 32.1 { t.Errorf("Float32Field should be 32.1, not %f", doc.Float32Field) } if doc.Float64Field != 64.1 { t.Errorf("Float64Field should be 64.1, not %f", doc.Float64Field) } if doc.DurationField != 120*time.Millisecond { t.Errorf("DurationField should be 120ms, not %s", doc.DurationField.String()) } if doc.DurationField2 != 120*time.Millisecond { t.Errorf("DurationField2 should be 120000000, not %d", doc.DurationField2) } if doc.NonEmbeddedStruct.StringField != "b" { t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField) } if doc.EmbeddedStruct.StringField != "c" { t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField) } if doc.AliasUintField != 1000 { t.Errorf("AliasUintField should be 1000, not %d", doc.AliasUintField) } } func TestUnmarshalDefaultFailureBool(t *testing.T) { var doc struct { Field bool `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } func TestUnmarshalDefaultFailureInt(t *testing.T) { var doc struct { Field int `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } func TestUnmarshalDefaultFailureInt64(t *testing.T) { var doc struct { Field int64 `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } func TestUnmarshalDefaultFailureFloat64(t *testing.T) { var doc struct { Field float64 `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } func TestUnmarshalDefaultFailureDuration(t *testing.T) { var doc struct { Field time.Duration `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } func TestUnmarshalDefaultFailureUnsupported(t *testing.T) { var doc struct { Field struct{} `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } func TestMarshalNestedAnonymousStructs(t *testing.T) { type Embedded struct { Value string `toml:"value"` Top struct { Value string `toml:"value"` } `toml:"top"` } type Named struct { Value string `toml:"value"` } var doc struct { Embedded Named `toml:"named"` Anonymous struct { Value string `toml:"value"` } `toml:"anonymous"` } expected := `value = "" [anonymous] value = "" [named] value = "" [top] value = "" ` result, err := Marshal(doc) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, string(result)) } } func TestEncoderPromoteNestedAnonymousStructs(t *testing.T) { type Embedded struct { Value string `toml:"value"` } var doc struct { Embedded } expected := ` [Embedded] value = "" ` var buf bytes.Buffer if err := NewEncoder(&buf).PromoteAnonymous(true).Encode(doc); err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if !bytes.Equal(buf.Bytes(), []byte(expected)) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, buf.String()) } } func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) { type Embedded struct { Value string `toml:"value"` Top struct { Value string `toml:"value"` } `toml:"top"` } var doc struct { Value string `toml:"value"` Embedded } doc.Embedded.Value = "shadowed" doc.Value = "shadows" expected := `value = "shadows" [top] value = "" ` result, err := Marshal(doc) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if !bytes.Equal(result, []byte(expected)) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, string(result)) } } func TestUnmarshalNestedAnonymousStructs(t *testing.T) { type Nested struct { Value string `toml:"nested_field"` } type Deep struct { Nested } type Document struct { Deep Value string `toml:"own_field"` } var doc Document err := Unmarshal([]byte(`nested_field = "nested value"`+"\n"+`own_field = "own value"`), &doc) if err != nil { t.Fatal("should not error") } if doc.Value != "own value" || doc.Nested.Value != "nested value" { t.Fatal("unexpected values") } } func TestUnmarshalNestedAnonymousStructs_Controversial(t *testing.T) { type Nested struct { Value string `toml:"nested"` } type Deep struct { Nested } type Document struct { Deep Value string `toml:"own"` } var doc Document err := Unmarshal([]byte(`nested = "nested value"`+"\n"+`own = "own value"`), &doc) if err == nil { t.Fatal("should error") } } type unexportedFieldPreservationTest struct { Exported string `toml:"exported"` unexported string Nested1 unexportedFieldPreservationTestNested `toml:"nested1"` Nested2 *unexportedFieldPreservationTestNested `toml:"nested2"` Nested3 *unexportedFieldPreservationTestNested `toml:"nested3"` Slice1 []unexportedFieldPreservationTestNested `toml:"slice1"` Slice2 []*unexportedFieldPreservationTestNested `toml:"slice2"` } type unexportedFieldPreservationTestNested struct { Exported1 string `toml:"exported1"` unexported1 string } func TestUnmarshalPreservesUnexportedFields(t *testing.T) { toml := ` exported = "visible" unexported = "ignored" [nested1] exported1 = "visible1" unexported1 = "ignored1" [nested2] exported1 = "visible2" unexported1 = "ignored2" [nested3] exported1 = "visible3" unexported1 = "ignored3" [[slice1]] exported1 = "visible3" [[slice1]] exported1 = "visible4" [[slice2]] exported1 = "visible5" ` t.Run("unexported field should not be set from toml", func(t *testing.T) { var actual unexportedFieldPreservationTest err := Unmarshal([]byte(toml), &actual) if err != nil { t.Fatal("did not expect an error") } expect := unexportedFieldPreservationTest{ Exported: "visible", unexported: "", Nested1: unexportedFieldPreservationTestNested{"visible1", ""}, Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, Nested3: &unexportedFieldPreservationTestNested{"visible3", ""}, Slice1: []unexportedFieldPreservationTestNested{ {Exported1: "visible3"}, {Exported1: "visible4"}, }, Slice2: []*unexportedFieldPreservationTestNested{ {Exported1: "visible5"}, }, } if !reflect.DeepEqual(actual, expect) { t.Fatalf("%+v did not equal %+v", actual, expect) } }) t.Run("unexported field should be preserved", func(t *testing.T) { actual := unexportedFieldPreservationTest{ Exported: "foo", unexported: "bar", Nested1: unexportedFieldPreservationTestNested{"baz", "bax"}, Nested2: nil, Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"}, } err := Unmarshal([]byte(toml), &actual) if err != nil { t.Fatal("did not expect an error") } expect := unexportedFieldPreservationTest{ Exported: "visible", unexported: "bar", Nested1: unexportedFieldPreservationTestNested{"visible1", "bax"}, Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, Nested3: &unexportedFieldPreservationTestNested{"visible3", "bax"}, Slice1: []unexportedFieldPreservationTestNested{ {Exported1: "visible3"}, {Exported1: "visible4"}, }, Slice2: []*unexportedFieldPreservationTestNested{ {Exported1: "visible5"}, }, } if !reflect.DeepEqual(actual, expect) { t.Fatalf("%+v did not equal %+v", actual, expect) } }) } func TestTreeMarshal(t *testing.T) { cases := [][]byte{ basicTestToml, marshalTestToml, emptyTestToml, pointerTestToml, } for _, expected := range cases { t.Run("", func(t *testing.T) { tree, err := LoadBytes(expected) if err != nil { t.Fatal(err) } result, err := tree.Marshal() if err != nil { t.Fatal(err) } if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } }) } } func TestMarshalArrays(t *testing.T) { cases := []struct { Data interface{} Expected string }{ { Data: struct { XY [2]int }{ XY: [2]int{1, 2}, }, Expected: `XY = [1, 2] `, }, { Data: struct { XY [1][2]int }{ XY: [1][2]int{{1, 2}}, }, Expected: `XY = [[1, 2]] `, }, { Data: struct { XY [1][]int }{ XY: [1][]int{{1, 2}}, }, Expected: `XY = [[1, 2]] `, }, { Data: struct { XY [][2]int }{ XY: [][2]int{{1, 2}}, }, Expected: `XY = [[1, 2]] `, }, } for _, tc := range cases { t.Run("", func(t *testing.T) { result, err := Marshal(tc.Data) if err != nil { t.Fatal(err) } if !bytes.Equal(result, []byte(tc.Expected)) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", []byte(tc.Expected), result) } }) } } func TestUnmarshalLocalDate(t *testing.T) { t.Run("ToLocalDate", func(t *testing.T) { type dateStruct struct { Date LocalDate } toml := `date = 1979-05-27` var obj dateStruct err := Unmarshal([]byte(toml), &obj) if err != nil { t.Fatal(err) } if obj.Date.Year != 1979 { t.Errorf("expected year 1979, got %d", obj.Date.Year) } if obj.Date.Month != 5 { t.Errorf("expected month 5, got %d", obj.Date.Month) } if obj.Date.Day != 27 { t.Errorf("expected day 27, got %d", obj.Date.Day) } }) t.Run("ToLocalDate", func(t *testing.T) { type dateStruct struct { Date time.Time } toml := `date = 1979-05-27` var obj dateStruct err := Unmarshal([]byte(toml), &obj) if err != nil { t.Fatal(err) } if obj.Date.Year() != 1979 { t.Errorf("expected year 1979, got %d", obj.Date.Year()) } if obj.Date.Month() != 5 { t.Errorf("expected month 5, got %d", obj.Date.Month()) } if obj.Date.Day() != 27 { t.Errorf("expected day 27, got %d", obj.Date.Day()) } }) } func TestMarshalLocalDate(t *testing.T) { type dateStruct struct { Date LocalDate } obj := dateStruct{Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }} b, err := Marshal(obj) if err != nil { t.Fatalf("unexpected error: %v", err) } got := string(b) expected := `Date = 1979-05-27 ` if got != expected { t.Errorf("expected '%s', got '%s'", expected, got) } } func TestUnmarshalLocalDateTime(t *testing.T) { examples := []struct { name string in string out LocalDateTime }{ { name: "normal", in: "1979-05-27T07:32:00", out: LocalDateTime{ Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }, Time: LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 0, }, }}, { name: "with nanoseconds", in: "1979-05-27T00:32:00.999999", out: LocalDateTime{ Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }, Time: LocalTime{ Hour: 0, Minute: 32, Second: 0, Nanosecond: 999999000, }, }, }, } for i, example := range examples { toml := fmt.Sprintf(`date = %s`, example.in) t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct { Date LocalDateTime } var obj dateStruct err := Unmarshal([]byte(toml), &obj) if err != nil { t.Fatal(err) } if obj.Date != example.out { t.Errorf("expected '%s', got '%s'", example.out, obj.Date) } }) t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct { Date time.Time } var obj dateStruct err := Unmarshal([]byte(toml), &obj) if err != nil { t.Fatal(err) } if obj.Date.Year() != example.out.Date.Year { t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) } if obj.Date.Month() != example.out.Date.Month { t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) } if obj.Date.Day() != example.out.Date.Day { t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) } if obj.Date.Hour() != example.out.Time.Hour { t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) } if obj.Date.Minute() != example.out.Time.Minute { t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) } if obj.Date.Second() != example.out.Time.Second { t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) } if obj.Date.Nanosecond() != example.out.Time.Nanosecond { t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) } }) } } func TestMarshalLocalDateTime(t *testing.T) { type dateStruct struct { DateTime LocalDateTime } examples := []struct { name string in LocalDateTime out string }{ { name: "normal", out: "DateTime = 1979-05-27T07:32:00\n", in: LocalDateTime{ Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }, Time: LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 0, }, }}, { name: "with nanoseconds", out: "DateTime = 1979-05-27T00:32:00.999999000\n", in: LocalDateTime{ Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }, Time: LocalTime{ Hour: 0, Minute: 32, Second: 0, Nanosecond: 999999000, }, }, }, } for i, example := range examples { t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { obj := dateStruct{ DateTime: example.in, } b, err := Marshal(obj) if err != nil { t.Fatalf("unexpected error: %v", err) } got := string(b) if got != example.out { t.Errorf("expected '%s', got '%s'", example.out, got) } }) } } func TestUnmarshalLocalTime(t *testing.T) { examples := []struct { name string in string out LocalTime }{ { name: "normal", in: "07:32:00", out: LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 0, }, }, { name: "with nanoseconds", in: "00:32:00.999999", out: LocalTime{ Hour: 0, Minute: 32, Second: 0, Nanosecond: 999999000, }, }, } for i, example := range examples { toml := fmt.Sprintf(`Time = %s`, example.in) t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct { Time LocalTime } var obj dateStruct err := Unmarshal([]byte(toml), &obj) if err != nil { t.Fatal(err) } if obj.Time != example.out { t.Errorf("expected '%s', got '%s'", example.out, obj.Time) } }) } } func TestMarshalLocalTime(t *testing.T) { type timeStruct struct { Time LocalTime } examples := []struct { name string in LocalTime out string }{ { name: "normal", out: "Time = 07:32:00\n", in: LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 0, }}, { name: "with nanoseconds", out: "Time = 00:32:00.999999000\n", in: LocalTime{ Hour: 0, Minute: 32, Second: 0, Nanosecond: 999999000, }, }, } for i, example := range examples { t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { obj := timeStruct{ Time: example.in, } b, err := Marshal(obj) if err != nil { t.Fatalf("unexpected error: %v", err) } got := string(b) if got != example.out { t.Errorf("expected '%s', got '%s'", example.out, got) } }) } } // test case for issue #339 func TestUnmarshalSameInnerField(t *testing.T) { type InterStruct2 struct { Test string Name string Age int } type Inter2 struct { Name string Age int InterStruct2 InterStruct2 } type Server struct { Name string `toml:"name"` Inter2 Inter2 `toml:"inter2"` } var server Server if err := Unmarshal([]byte(`name = "123" [inter2] name = "inter2" age = 222`), &server); err == nil { expected := Server{ Name: "123", Inter2: Inter2{ Name: "inter2", Age: 222, }, } if !reflect.DeepEqual(server, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, server) } } else { t.Fatalf("unexpected error: %v", err) } } func TestMarshalInterface(t *testing.T) { type InnerStruct struct { InnerField string } type OuterStruct struct { PrimitiveField interface{} ArrayField interface{} StructArrayField interface{} MapField map[string]interface{} StructField interface{} PointerField interface{} NilField interface{} InterfacePointerField *interface{} } expected := []byte(`ArrayField = [1, 2, 3] InterfacePointerField = "hello world" PrimitiveField = "string" [MapField] key1 = "value1" key2 = false [MapField.key3] InnerField = "value3" [PointerField] InnerField = "yyy" [[StructArrayField]] InnerField = "s1" [[StructArrayField]] InnerField = "s2" [StructField] InnerField = "xxx" `) var h interface{} = "hello world" if result, err := Marshal(OuterStruct{ "string", []int{1, 2, 3}, []InnerStruct{{"s1"}, {"s2"}}, map[string]interface{}{ "key1": "value1", "key2": false, "key3": InnerStruct{"value3"}, "nil value": nil, }, InnerStruct{ "xxx", }, &InnerStruct{ "yyy", }, nil, &h, }); err == nil { if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) } } else { t.Fatal(err) } } func TestUnmarshalToNilInterface(t *testing.T) { toml := []byte(` PrimitiveField = "Hello" ArrayField = [1,2,3] InterfacePointerField = "World" [StructField] Field1 = 123 Field2 = "Field2" [MapField] MapField1 = [4,5,6] MapField2 = {A = "A"} MapField3 = false [[StructArrayField]] Name = "Allen" Age = 20 [[StructArrayField]] Name = "Jack" Age = 23 `) type OuterStruct struct { PrimitiveField interface{} ArrayField interface{} StructArrayField interface{} MapField map[string]interface{} StructField interface{} NilField interface{} InterfacePointerField *interface{} } var s interface{} = "World" expected := OuterStruct{ PrimitiveField: "Hello", ArrayField: []interface{}{int64(1), int64(2), int64(3)}, StructField: map[string]interface{}{ "Field1": int64(123), "Field2": "Field2", }, MapField: map[string]interface{}{ "MapField1": []interface{}{int64(4), int64(5), int64(6)}, "MapField2": map[string]interface{}{ "A": "A", }, "MapField3": false, }, NilField: nil, InterfacePointerField: &s, StructArrayField: []map[string]interface{}{ { "Name": "Allen", "Age": int64(20), }, { "Name": "Jack", "Age": int64(23), }, }, } actual := OuterStruct{} if err := Unmarshal(toml, &actual); err == nil { if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) } } else { t.Fatal(err) } } func TestUnmarshalToNonNilInterface(t *testing.T) { toml := []byte(` PrimitiveField = "Allen" ArrayField = [1,2,3] [StructField] InnerField = "After1" [PointerField] InnerField = "After2" [InterfacePointerField] InnerField = "After" [MapField] MapField1 = [4,5,6] MapField2 = {A = "A"} MapField3 = false [[StructArrayField]] InnerField = "After3" [[StructArrayField]] InnerField = "After4" `) type InnerStruct struct { InnerField interface{} } type OuterStruct struct { PrimitiveField interface{} ArrayField interface{} StructArrayField interface{} MapField map[string]interface{} StructField interface{} PointerField interface{} NilField interface{} InterfacePointerField *interface{} } var s interface{} = InnerStruct{"After"} expected := OuterStruct{ PrimitiveField: "Allen", ArrayField: []int{1, 2, 3}, StructField: InnerStruct{InnerField: "After1"}, MapField: map[string]interface{}{ "MapField1": []interface{}{int64(4), int64(5), int64(6)}, "MapField2": map[string]interface{}{ "A": "A", }, "MapField3": false, }, PointerField: &InnerStruct{InnerField: "After2"}, NilField: nil, InterfacePointerField: &s, StructArrayField: []InnerStruct{ {InnerField: "After3"}, {InnerField: "After4"}, }, } actual := OuterStruct{ PrimitiveField: "aaa", ArrayField: []int{100, 200, 300, 400}, StructField: InnerStruct{InnerField: "Before1"}, MapField: map[string]interface{}{ "MapField1": []int{4, 5, 6}, "MapField2": map[string]string{ "B": "BBB", }, "MapField3": true, }, PointerField: &InnerStruct{InnerField: "Before2"}, NilField: nil, InterfacePointerField: &s, StructArrayField: []InnerStruct{ {InnerField: "Before3"}, {InnerField: "Before4"}, }, } if err := Unmarshal(toml, &actual); err == nil { if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) } } else { t.Fatal(err) } } func TestUnmarshalEmbedTree(t *testing.T) { toml := []byte(` OuterField1 = "Out" OuterField2 = 1024 [TreeField] InnerField1 = "In" InnerField2 = 2048 [TreeField.EmbedStruct] EmbedField = "Embed" `) type InnerStruct struct { InnerField1 string InnerField2 int EmbedStruct struct { EmbedField string } } type OuterStruct struct { OuterField1 string OuterField2 int TreeField *Tree } out := OuterStruct{} actual := InnerStruct{} expected := InnerStruct{ "In", 2048, struct { EmbedField string }{ EmbedField: "Embed", }, } if err := Unmarshal(toml, &out); err != nil { t.Fatal(err) } if err := out.TreeField.Unmarshal(&actual); err != nil { t.Fatal(err) } if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) } } func TestMarshalNil(t *testing.T) { if _, err := Marshal(nil); err == nil { t.Errorf("Expected err from nil marshal") } if _, err := Marshal((*struct{})(nil)); err == nil { t.Errorf("Expected err from nil marshal") } } func TestUnmarshalNil(t *testing.T) { if err := Unmarshal([]byte(`whatever = "whatever"`), nil); err == nil { t.Errorf("Expected err from nil marshal") } if err := Unmarshal([]byte(`whatever = "whatever"`), (*struct{})(nil)); err == nil { t.Errorf("Expected err from nil marshal") } } var sliceTomlDemo = []byte(`str_slice = ["Howdy","Hey There"] str_slice_ptr= ["Howdy","Hey There"] int_slice=[1,2] int_slice_ptr=[1,2] [[struct_slice]] String2="1" [[struct_slice]] String2="2" [[struct_slice_ptr]] String2="1" [[struct_slice_ptr]] String2="2" `) type sliceStruct struct { Slice []string ` toml:"str_slice" ` SlicePtr *[]string ` toml:"str_slice_ptr" ` IntSlice []int ` toml:"int_slice" ` IntSlicePtr *[]int ` toml:"int_slice_ptr" ` StructSlice []basicMarshalTestSubStruct ` toml:"struct_slice" ` StructSlicePtr *[]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` } type arrayStruct struct { Slice [4]string ` toml:"str_slice" ` SlicePtr *[4]string ` toml:"str_slice_ptr" ` IntSlice [4]int ` toml:"int_slice" ` IntSlicePtr *[4]int ` toml:"int_slice_ptr" ` StructSlice [4]basicMarshalTestSubStruct ` toml:"struct_slice" ` StructSlicePtr *[4]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` } type arrayTooSmallStruct struct { Slice [1]string ` toml:"str_slice" ` StructSlice [1]basicMarshalTestSubStruct ` toml:"struct_slice" ` } func TestUnmarshalSlice(t *testing.T) { tree, _ := LoadBytes(sliceTomlDemo) tree, _ = TreeFromMap(tree.ToMap()) var actual sliceStruct err := tree.Unmarshal(&actual) if err != nil { t.Error("shound not err", err) } expected := sliceStruct{ Slice: []string{"Howdy", "Hey There"}, SlicePtr: &[]string{"Howdy", "Hey There"}, IntSlice: []int{1, 2}, IntSlicePtr: &[]int{1, 2}, StructSlice: []basicMarshalTestSubStruct{{"1"}, {"2"}}, StructSlicePtr: &[]basicMarshalTestSubStruct{{"1"}, {"2"}}, } if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) } } func TestUnmarshalSliceFail(t *testing.T) { tree, _ := TreeFromMap(map[string]interface{}{ "str_slice": []int{1, 2}, }) var actual sliceStruct err := tree.Unmarshal(&actual) if err.Error() != "(0, 0): Can't convert 1(int64) to string" { t.Error("expect err:(0, 0): Can't convert 1(int64) to string but got ", err) } } func TestUnmarshalSliceFail2(t *testing.T) { tree, _ := Load(`str_slice=[1,2]`) var actual sliceStruct err := tree.Unmarshal(&actual) if err.Error() != "(1, 1): Can't convert 1(int64) to string" { t.Error("expect err:(1, 1): Can't convert 1(int64) to string but got ", err) } } func TestMarshalMixedTypeArray(t *testing.T) { type InnerStruct struct { IntField int StrField string } type TestStruct struct { ArrayField []interface{} } expected := []byte(`ArrayField = [3.14, 100, true, "hello world", { IntField = 100, StrField = "inner1" }, [{ IntField = 200, StrField = "inner2" }, { IntField = 300, StrField = "inner3" }]] `) if result, err := Marshal(TestStruct{ ArrayField: []interface{}{ 3.14, 100, true, "hello world", InnerStruct{ IntField: 100, StrField: "inner1", }, []InnerStruct{ {IntField: 200, StrField: "inner2"}, {IntField: 300, StrField: "inner3"}, }, }, }); err == nil { if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) } } else { t.Fatal(err) } } func TestUnmarshalMixedTypeArray(t *testing.T) { type TestStruct struct { ArrayField []interface{} } toml := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] `) actual := TestStruct{} expected := TestStruct{ ArrayField: []interface{}{ 3.14, int64(100), true, "hello world", map[string]interface{}{ "Field": "inner1", }, []map[string]interface{}{ {"Field": "inner2"}, {"Field": "inner3"}, }, }, } if err := Unmarshal(toml, &actual); err == nil { if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual) } } else { t.Fatal(err) } } func TestUnmarshalArray(t *testing.T) { var tree *Tree var err error tree, _ = LoadBytes(sliceTomlDemo) var actual1 arrayStruct err = tree.Unmarshal(&actual1) if err != nil { t.Error("shound not err", err) } tree, _ = TreeFromMap(tree.ToMap()) var actual2 arrayStruct err = tree.Unmarshal(&actual2) if err != nil { t.Error("shound not err", err) } expected := arrayStruct{ Slice: [4]string{"Howdy", "Hey There"}, SlicePtr: &[4]string{"Howdy", "Hey There"}, IntSlice: [4]int{1, 2}, IntSlicePtr: &[4]int{1, 2}, StructSlice: [4]basicMarshalTestSubStruct{{"1"}, {"2"}}, StructSlicePtr: &[4]basicMarshalTestSubStruct{{"1"}, {"2"}}, } if !reflect.DeepEqual(actual1, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual1) } if !reflect.DeepEqual(actual2, expected) { t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual2) } } func TestUnmarshalArrayFail(t *testing.T) { tree, _ := TreeFromMap(map[string]interface{}{ "str_slice": []string{"Howdy", "Hey There"}, }) var actual arrayTooSmallStruct err := tree.Unmarshal(&actual) if err.Error() != "(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1)" { t.Error("expect err:(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) } } func TestUnmarshalArrayFail2(t *testing.T) { tree, _ := Load(`str_slice=["Howdy","Hey There"]`) var actual arrayTooSmallStruct err := tree.Unmarshal(&actual) if err.Error() != "(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { t.Error("expect err:(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) } } func TestUnmarshalArrayFail3(t *testing.T) { tree, _ := Load(`[[struct_slice]] String2="1" [[struct_slice]] String2="2"`) var actual arrayTooSmallStruct err := tree.Unmarshal(&actual) if err.Error() != "(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { t.Error("expect err:(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) } } func TestDecoderStrict(t *testing.T) { input := ` [decoded] key = "" [undecoded] key = "" [undecoded.inner] key = "" [[undecoded.array]] key = "" [[undecoded.array]] key = "" ` var doc struct { Decoded struct { Key string } } expected := `undecoded keys: ["undecoded.array.0.key" "undecoded.array.1.key" "undecoded.inner.key" "undecoded.key"]` err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) if err == nil { t.Error("expected error, got none") } else if err.Error() != expected { t.Errorf("expect err: %s, got: %s", expected, err.Error()) } if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&doc); err != nil { t.Errorf("unexpected err: %s", err) } var m map[string]interface{} if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&m); err != nil { t.Errorf("unexpected err: %s", err) } } func TestDecoderStrictValid(t *testing.T) { input := ` [decoded] key = "" ` var doc struct { Decoded struct { Key string } } err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) if err != nil { t.Fatal("unexpected error:", err) } } type docUnmarshalTOML struct { Decoded struct { Key string } } func (d *docUnmarshalTOML) UnmarshalTOML(i interface{}) error { if iMap, ok := i.(map[string]interface{}); !ok { return fmt.Errorf("type assertion error: wants %T, have %T", map[string]interface{}{}, i) } else if key, ok := iMap["key"]; !ok { return fmt.Errorf("key '%s' not in map", "key") } else if keyString, ok := key.(string); !ok { return fmt.Errorf("type assertion error: wants %T, have %T", "", key) } else { d.Decoded.Key = keyString } return nil } func TestDecoderStrictCustomUnmarshal(t *testing.T) { input := `key = "ok"` var doc docUnmarshalTOML err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) if err != nil { t.Fatal("unexpected error:", err) } if doc.Decoded.Key != "ok" { t.Errorf("Bad unmarshal: expected ok, got %v", doc.Decoded.Key) } } type parent struct { Doc docUnmarshalTOML DocPointer *docUnmarshalTOML } func TestCustomUnmarshal(t *testing.T) { input := ` [Doc] key = "ok1" [DocPointer] key = "ok2" ` var d parent if err := Unmarshal([]byte(input), &d); err != nil { t.Fatalf("unexpected err: %s", err.Error()) } if d.Doc.Decoded.Key != "ok1" { t.Errorf("Bad unmarshal: expected ok, got %v", d.Doc.Decoded.Key) } if d.DocPointer.Decoded.Key != "ok2" { t.Errorf("Bad unmarshal: expected ok, got %v", d.DocPointer.Decoded.Key) } } func TestCustomUnmarshalError(t *testing.T) { input := ` [Doc] key = 1 [DocPointer] key = "ok2" ` expected := "(2, 1): unmarshal toml: type assertion error: wants string, have int64" var d parent err := Unmarshal([]byte(input), &d) if err == nil { t.Error("expected error, got none") } else if err.Error() != expected { t.Errorf("expect err: %s, got: %s", expected, err.Error()) } } type intWrapper struct { Value int } func (w *intWrapper) UnmarshalText(text []byte) error { var err error if w.Value, err = strconv.Atoi(string(text)); err == nil { return nil } if b, err := strconv.ParseBool(string(text)); err == nil { if b { w.Value = 1 } return nil } if f, err := strconv.ParseFloat(string(text), 32); err == nil { w.Value = int(f) return nil } return fmt.Errorf("unsupported: %s", text) } func TestTextUnmarshal(t *testing.T) { var doc struct { UnixTime intWrapper Version *intWrapper Bool intWrapper Int intWrapper Float intWrapper } input := ` UnixTime = "12" Version = "42" Bool = true Int = 21 Float = 2.0 ` if err := Unmarshal([]byte(input), &doc); err != nil { t.Fatalf("unexpected err: %s", err.Error()) } if doc.UnixTime.Value != 12 { t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) } if doc.Version.Value != 42 { t.Fatalf("expected Version: 42 got: %d", doc.Version.Value) } if doc.Bool.Value != 1 { t.Fatalf("expected Bool: 1 got: %d", doc.Bool.Value) } if doc.Int.Value != 21 { t.Fatalf("expected Int: 21 got: %d", doc.Int.Value) } if doc.Float.Value != 2 { t.Fatalf("expected Float: 2 got: %d", doc.Float.Value) } } func TestTextUnmarshalError(t *testing.T) { var doc struct { Failer intWrapper } input := `Failer = "hello"` if err := Unmarshal([]byte(input), &doc); err == nil { t.Fatalf("expected err, got none") } } // issue406 func TestPreserveNotEmptyField(t *testing.T) { toml := []byte(`Field1 = "ccc"`) type Inner struct { InnerField1 string InnerField2 int } type TestStruct struct { Field1 string Field2 int Field3 Inner } actual := TestStruct{ "aaa", 100, Inner{ "bbb", 200, }, } expected := TestStruct{ "ccc", 100, Inner{ "bbb", 200, }, } err := Unmarshal(toml, &actual) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) } } // github issue 432 func TestUnmarshalEmptyInterface(t *testing.T) { doc := []byte(`User = "pelletier"`) var v interface{} err := Unmarshal(doc, &v) if err != nil { t.Fatal(err) } x, ok := v.(map[string]interface{}) if !ok { t.Fatal(err) } if x["User"] != "pelletier" { t.Fatalf("expected User=pelletier, but got %v", x) } } func TestUnmarshalEmptyInterfaceDeep(t *testing.T) { doc := []byte(` User = "pelletier" Age = 99 [foo] bar = 42 `) var v interface{} err := Unmarshal(doc, &v) if err != nil { t.Fatal(err) } x, ok := v.(map[string]interface{}) if !ok { t.Fatal(err) } expected := map[string]interface{}{ "User": "pelletier", "Age": 99, "foo": map[string]interface{}{ "bar": 42, }, } reflect.DeepEqual(x, expected) } type Config struct { Key string `toml:"key"` Obj Custom `toml:"obj"` } type Custom struct { v string } func (c *Custom) UnmarshalTOML(v interface{}) error { c.v = "called" return nil } func TestGithubIssue431(t *testing.T) { doc := `key = "value"` tree, err := LoadBytes([]byte(doc)) if err != nil { t.Fatalf("unexpected error: %s", err) } var c Config if err := tree.Unmarshal(&c); err != nil { t.Fatalf("unexpected error: %s", err) } if c.Key != "value" { t.Errorf("expected c.Key='value', not '%s'", c.Key) } if c.Obj.v == "called" { t.Errorf("UnmarshalTOML should not have been called") } } type durationString struct { time.Duration } func (d *durationString) UnmarshalTOML(v interface{}) error { d.Duration = 10 * time.Second return nil } type config437Error struct { } func (e *config437Error) UnmarshalTOML(v interface{}) error { return errors.New("expected") } type config437 struct { HTTP struct { PingTimeout durationString `toml:"PingTimeout"` ErrorField config437Error } `toml:"HTTP"` } func TestGithubIssue437(t *testing.T) { src := ` [HTTP] PingTimeout = "32m" ` cfg := &config437{} cfg.HTTP.PingTimeout = durationString{time.Second} r := strings.NewReader(src) err := NewDecoder(r).Decode(cfg) if err != nil { t.Fatalf("unexpected errors %s", err) } expected := durationString{10 * time.Second} if cfg.HTTP.PingTimeout != expected { t.Fatalf("expected '%s', got '%s'", expected, cfg.HTTP.PingTimeout) } } func TestLeafUnmarshalerError(t *testing.T) { src := ` [HTTP] ErrorField = "foo" ` cfg := &config437{} r := strings.NewReader(src) err := NewDecoder(r).Decode(cfg) if err == nil { t.Fatalf("error was expected") } } func TestGithubIssue732(t *testing.T) { var v interface{} data := []byte("a=\nb=0") err := Unmarshal(data, &v) if err == nil { t.Fatalf("error was expected") } } go-toml-1.9.5/marshal_test.toml000066400000000000000000000011561416532417400165010ustar00rootroot00000000000000title = "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" go-toml-1.9.5/parser.go000066400000000000000000000277441416532417400147540ustar00rootroot00000000000000// 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 } go-toml-1.9.5/parser_test.go000066400000000000000000000710201416532417400157750ustar00rootroot00000000000000package toml import ( "fmt" "math" "reflect" "testing" "time" ) func assertSubTree(t *testing.T, path []string, tree *Tree, err error, ref map[string]interface{}) { if err != nil { t.Error("Non-nil error:", err.Error()) return } for k, v := range ref { nextPath := append(path, k) t.Log("asserting path", nextPath) // NOTE: directly access key instead of resolve by path // NOTE: see TestSpecialKV switch node := tree.GetPath([]string{k}).(type) { case []*Tree: t.Log("\tcomparing key", nextPath, "by array iteration") for idx, item := range node { assertSubTree(t, nextPath, item, err, v.([]map[string]interface{})[idx]) } case *Tree: t.Log("\tcomparing key", nextPath, "by subtree assestion") assertSubTree(t, nextPath, node, err, v.(map[string]interface{})) default: t.Log("\tcomparing key", nextPath, "by string representation because it's of type", reflect.TypeOf(node)) if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) { t.Errorf("was expecting %v at %v but got %v", v, k, node) } } } } func assertTree(t *testing.T, tree *Tree, err error, ref map[string]interface{}) { t.Logf("Asserting tree:\n (%T)(%p)(%+v)", tree, tree, tree) assertSubTree(t, []string{}, tree, err, ref) t.Log("Finished tree assertion.") } func TestCreateSubTree(t *testing.T) { tree := newTree() tree.createSubTree([]string{"a", "b", "c"}, Position{}) tree.Set("a.b.c", 42) if tree.Get("a.b.c") != 42 { t.Fail() } } func TestSimpleKV(t *testing.T) { tree, err := Load("a = 42") assertTree(t, tree, err, map[string]interface{}{ "a": int64(42), }) tree, _ = Load("a = 42\nb = 21") assertTree(t, tree, err, map[string]interface{}{ "a": int64(42), "b": int64(21), }) } func TestNumberInKey(t *testing.T) { tree, err := Load("hello2 = 42") assertTree(t, tree, err, map[string]interface{}{ "hello2": int64(42), }) } func TestIncorrectKeyExtraSquareBracket(t *testing.T) { _, err := Load(`[a]b] zyx = 42`) if err == nil { t.Error("Error should have been returned.") } if err.Error() != "(1, 4): parsing error: keys cannot contain ] character" { t.Error("Bad error message:", err.Error()) } } func TestSimpleNumbers(t *testing.T) { tree, err := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1") assertTree(t, tree, err, map[string]interface{}{ "a": int64(42), "b": int64(-21), "c": float64(4.2), "d": float64(-2.1), }) } func TestSpecialFloats(t *testing.T) { tree, err := Load(` normalinf = inf plusinf = +inf minusinf = -inf normalnan = nan plusnan = +nan minusnan = -nan `) assertTree(t, tree, err, map[string]interface{}{ "normalinf": math.Inf(1), "plusinf": math.Inf(1), "minusinf": math.Inf(-1), "normalnan": math.NaN(), "plusnan": math.NaN(), "minusnan": math.NaN(), }) } func TestHexIntegers(t *testing.T) { tree, err := Load(`a = 0xDEADBEEF`) assertTree(t, tree, err, map[string]interface{}{"a": int64(3735928559)}) tree, err = Load(`a = 0xdeadbeef`) assertTree(t, tree, err, map[string]interface{}{"a": int64(3735928559)}) tree, err = Load(`a = 0xdead_beef`) assertTree(t, tree, err, map[string]interface{}{"a": int64(3735928559)}) _, err = Load(`a = 0x_1`) if err.Error() != "(1, 5): invalid use of _ in hex number" { t.Error("Bad error message:", err.Error()) } } func TestOctIntegers(t *testing.T) { tree, err := Load(`a = 0o01234567`) assertTree(t, tree, err, map[string]interface{}{"a": int64(342391)}) tree, err = Load(`a = 0o755`) assertTree(t, tree, err, map[string]interface{}{"a": int64(493)}) _, err = Load(`a = 0o_1`) if err.Error() != "(1, 5): invalid use of _ in number" { t.Error("Bad error message:", err.Error()) } } func TestBinIntegers(t *testing.T) { tree, err := Load(`a = 0b11010110`) assertTree(t, tree, err, map[string]interface{}{"a": int64(214)}) _, err = Load(`a = 0b_1`) if err.Error() != "(1, 5): invalid use of _ in number" { t.Error("Bad error message:", err.Error()) } } func TestBadIntegerBase(t *testing.T) { _, err := Load(`a = 0k1`) if err.Error() != "(1, 5): unknown number base: k. possible options are x (hex) o (octal) b (binary)" { t.Error("Error should have been returned.") } } func TestIntegerNoDigit(t *testing.T) { _, err := Load(`a = 0b`) if err.Error() != "(1, 5): number needs at least one digit" { t.Error("Bad error message:", err.Error()) } } func TestNumbersWithUnderscores(t *testing.T) { tree, err := Load("a = 1_000") assertTree(t, tree, err, map[string]interface{}{ "a": int64(1000), }) tree, err = Load("a = 5_349_221") assertTree(t, tree, err, map[string]interface{}{ "a": int64(5349221), }) tree, err = Load("a = 1_2_3_4_5") assertTree(t, tree, err, map[string]interface{}{ "a": int64(12345), }) tree, err = Load("flt8 = 9_224_617.445_991_228_313") assertTree(t, tree, err, map[string]interface{}{ "flt8": float64(9224617.445991228313), }) tree, err = Load("flt9 = 1e1_00") assertTree(t, tree, err, map[string]interface{}{ "flt9": float64(1e100), }) } func TestFloatsWithExponents(t *testing.T) { tree, err := Load("a = 5e+22\nb = 5E+22\nc = -5e+22\nd = -5e-22\ne = 6.626e-34") assertTree(t, tree, err, map[string]interface{}{ "a": float64(5e+22), "b": float64(5e+22), "c": float64(-5e+22), "d": float64(-5e-22), "e": float64(6.626e-34), }) } func TestSimpleDate(t *testing.T) { tree, err := Load("a = 1979-05-27T07:32:00Z") assertTree(t, tree, err, map[string]interface{}{ "a": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), }) } func TestDateOffset(t *testing.T) { tree, err := Load("a = 1979-05-27T00:32:00-07:00") assertTree(t, tree, err, map[string]interface{}{ "a": time.Date(1979, time.May, 27, 0, 32, 0, 0, time.FixedZone("", -7*60*60)), }) } func TestDateNano(t *testing.T) { tree, err := Load("a = 1979-05-27T00:32:00.999999999-07:00") assertTree(t, tree, err, map[string]interface{}{ "a": time.Date(1979, time.May, 27, 0, 32, 0, 999999999, time.FixedZone("", -7*60*60)), }) } func TestLocalDateTime(t *testing.T) { tree, err := Load("a = 1979-05-27T07:32:00") assertTree(t, tree, err, map[string]interface{}{ "a": LocalDateTime{ Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }, Time: LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 0, }, }, }) } func TestLocalDateTimeNano(t *testing.T) { tree, err := Load("a = 1979-05-27T07:32:00.999999") assertTree(t, tree, err, map[string]interface{}{ "a": LocalDateTime{ Date: LocalDate{ Year: 1979, Month: 5, Day: 27, }, Time: LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 999999000, }, }, }) } func TestLocalDate(t *testing.T) { tree, err := Load("a = 1979-05-27") assertTree(t, tree, err, map[string]interface{}{ "a": LocalDate{ Year: 1979, Month: 5, Day: 27, }, }) } func TestLocalDateError(t *testing.T) { _, err := Load("a = 2020-09-31") if err == nil { t.Fatalf("should error") } } func TestLocalTimeError(t *testing.T) { _, err := Load("a = 07:99:00") if err == nil { t.Fatalf("should error") } } func TestLocalDateTimeError(t *testing.T) { _, err := Load("a = 2020-09-31T07:99:00") if err == nil { t.Fatalf("should error") } } func TestDateTimeOffsetError(t *testing.T) { _, err := Load("a = 2020-09-31T07:99:00Z") if err == nil { t.Fatalf("should error") } } func TestLocalTime(t *testing.T) { tree, err := Load("a = 07:32:00") assertTree(t, tree, err, map[string]interface{}{ "a": LocalTime{ Hour: 7, Minute: 32, Second: 0, Nanosecond: 0, }, }) } func TestLocalTimeNano(t *testing.T) { tree, err := Load("a = 00:32:00.999999") assertTree(t, tree, err, map[string]interface{}{ "a": LocalTime{ Hour: 0, Minute: 32, Second: 0, Nanosecond: 999999000, }, }) } func TestSimpleString(t *testing.T) { tree, err := Load("a = \"hello world\"") assertTree(t, tree, err, map[string]interface{}{ "a": "hello world", }) } func TestSpaceKey(t *testing.T) { tree, err := Load("\"a b\" = \"hello world\"") assertTree(t, tree, err, map[string]interface{}{ "a b": "hello world", }) } func TestDoubleQuotedKey(t *testing.T) { tree, err := Load(` "key" = "a" "\t" = "b" "\U0001F914" = "c" "\u2764" = "d" `) assertTree(t, tree, err, map[string]interface{}{ "key": "a", "\t": "b", "\U0001F914": "c", "\u2764": "d", }) } func TestSingleQuotedKey(t *testing.T) { tree, err := Load(` 'key' = "a" '\t' = "b" '\U0001F914' = "c" '\u2764' = "d" `) assertTree(t, tree, err, map[string]interface{}{ `key`: "a", `\t`: "b", `\U0001F914`: "c", `\u2764`: "d", }) } func TestStringEscapables(t *testing.T) { tree, err := Load("a = \"a \\n b\"") assertTree(t, tree, err, map[string]interface{}{ "a": "a \n b", }) tree, err = Load("a = \"a \\t b\"") assertTree(t, tree, err, map[string]interface{}{ "a": "a \t b", }) tree, err = Load("a = \"a \\r b\"") assertTree(t, tree, err, map[string]interface{}{ "a": "a \r b", }) tree, err = Load("a = \"a \\\\ b\"") assertTree(t, tree, err, map[string]interface{}{ "a": "a \\ b", }) } func TestEmptyQuotedString(t *testing.T) { tree, err := Load(`[""] "" = 1`) assertTree(t, tree, err, map[string]interface{}{ "": map[string]interface{}{ "": int64(1), }, }) } func TestBools(t *testing.T) { tree, err := Load("a = true\nb = false") assertTree(t, tree, err, map[string]interface{}{ "a": true, "b": false, }) } func TestNestedKeys(t *testing.T) { tree, err := Load("[a.b.c]\nd = 42") assertTree(t, tree, err, map[string]interface{}{ "a": map[string]interface{}{ "b": map[string]interface{}{ "c": map[string]interface{}{ "d": int64(42), }, }, }, }) } func TestNestedQuotedUnicodeKeys(t *testing.T) { tree, err := Load("[ j . \"ʞ\" . l ]\nd = 42") assertTree(t, tree, err, map[string]interface{}{ "j": map[string]interface{}{ "ʞ": map[string]interface{}{ "l": map[string]interface{}{ "d": int64(42), }, }, }, }) tree, err = Load("[ g . h . i ]\nd = 42") assertTree(t, tree, err, map[string]interface{}{ "g": map[string]interface{}{ "h": map[string]interface{}{ "i": map[string]interface{}{ "d": int64(42), }, }, }, }) tree, err = Load("[ d.e.f ]\nk = 42") assertTree(t, tree, err, map[string]interface{}{ "d": map[string]interface{}{ "e": map[string]interface{}{ "f": map[string]interface{}{ "k": int64(42), }, }, }, }) } func TestArrayOne(t *testing.T) { tree, err := Load("a = [1]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(1)}, }) } func TestArrayZero(t *testing.T) { tree, err := Load("a = []") assertTree(t, tree, err, map[string]interface{}{ "a": []interface{}{}, }) } func TestArraySimple(t *testing.T) { tree, err := Load("a = [42, 21, 10]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(42), int64(21), int64(10)}, }) tree, _ = Load("a = [42, 21, 10,]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(42), int64(21), int64(10)}, }) } func TestArrayMultiline(t *testing.T) { tree, err := Load("a = [42,\n21, 10,]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(42), int64(21), int64(10)}, }) } func TestArrayNested(t *testing.T) { tree, err := Load("a = [[42, 21], [10]]") assertTree(t, tree, err, map[string]interface{}{ "a": [][]int64{{int64(42), int64(21)}, {int64(10)}}, }) } func TestNestedArrayComment(t *testing.T) { tree, err := Load(` someArray = [ # does not work ["entry1"] ]`) assertTree(t, tree, err, map[string]interface{}{ "someArray": [][]string{{"entry1"}}, }) } func TestNestedEmptyArrays(t *testing.T) { tree, err := Load("a = [[[]]]") assertTree(t, tree, err, map[string]interface{}{ "a": [][][]interface{}{{{}}}, }) } func TestArrayNestedStrings(t *testing.T) { tree, err := Load("data = [ [\"gamma\", \"delta\"], [\"Foo\"] ]") assertTree(t, tree, err, map[string]interface{}{ "data": [][]string{{"gamma", "delta"}, {"Foo"}}, }) } func TestParseUnknownRvalue(t *testing.T) { _, err := Load("a = !bssss") if err == nil { t.Error("Expecting a parse error") } _, err = Load("a = /b") if err == nil { t.Error("Expecting a parse error") } } func TestMissingValue(t *testing.T) { _, err := Load("a = ") if err.Error() != "(1, 5): expecting a value" { t.Error("Bad error message:", err.Error()) } } func TestUnterminatedArray(t *testing.T) { _, err := Load("a = [1,") if err.Error() != "(1, 8): unterminated array" { t.Error("Bad error message:", err.Error()) } _, err = Load("a = [1") if err.Error() != "(1, 7): unterminated array" { t.Error("Bad error message:", err.Error()) } _, err = Load("a = [1 2") if err.Error() != "(1, 8): missing comma" { t.Error("Bad error message:", err.Error()) } } func TestNewlinesInArrays(t *testing.T) { tree, err := Load("a = [1,\n2,\n3]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(1), int64(2), int64(3)}, }) } func TestArrayWithExtraComma(t *testing.T) { tree, err := Load("a = [1,\n2,\n3,\n]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(1), int64(2), int64(3)}, }) } func TestArrayWithExtraCommaComment(t *testing.T) { tree, err := Load("a = [1, # wow\n2, # such items\n3, # so array\n]") assertTree(t, tree, err, map[string]interface{}{ "a": []int64{int64(1), int64(2), int64(3)}, }) } func TestSimpleInlineGroup(t *testing.T) { tree, err := Load("key = {a = 42}") assertTree(t, tree, err, map[string]interface{}{ "key": map[string]interface{}{ "a": int64(42), }, }) } func TestDoubleInlineGroup(t *testing.T) { tree, err := Load("key = {a = 42, b = \"foo\"}") assertTree(t, tree, err, map[string]interface{}{ "key": map[string]interface{}{ "a": int64(42), "b": "foo", }, }) } func TestNestedInlineGroup(t *testing.T) { tree, err := Load("out = {block0 = {x = 99, y = 100}, block1 = {p = \"999\", q = \"1000\"}}") assertTree(t, tree, err, map[string]interface{}{ "out": map[string]interface{}{ "block0": map[string]interface{}{ "x": int64(99), "y": int64(100), }, "block1": map[string]interface{}{ "p": "999", "q": "1000", }, }, }) } func TestArrayInNestedInlineGroup(t *testing.T) { tree, err := Load(`image = {name = "xxx", palette = {id = 100, colors = ["red", "blue", "green"]}}`) assertTree(t, tree, err, map[string]interface{}{ "image": map[string]interface{}{ "name": "xxx", "palette": map[string]interface{}{ "id": int64(100), "colors": []string{ "red", "blue", "green", }, }, }, }) } func TestExampleInlineGroup(t *testing.T) { tree, err := Load(`name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 }`) assertTree(t, tree, err, map[string]interface{}{ "name": map[string]interface{}{ "first": "Tom", "last": "Preston-Werner", }, "point": map[string]interface{}{ "x": int64(1), "y": int64(2), }, }) } func TestInlineGroupBareKeysUnderscore(t *testing.T) { tree, err := Load(`foo = { _bar = "buz" }`) assertTree(t, tree, err, map[string]interface{}{ "foo": map[string]interface{}{ "_bar": "buz", }, }) } func TestInlineGroupBareKeysDash(t *testing.T) { tree, err := Load(`foo = { -bar = "buz" }`) assertTree(t, tree, err, map[string]interface{}{ "foo": map[string]interface{}{ "-bar": "buz", }, }) } func TestInlineGroupKeyQuoted(t *testing.T) { tree, err := Load(`foo = { "bar" = "buz" }`) assertTree(t, tree, err, map[string]interface{}{ "foo": map[string]interface{}{ "bar": "buz", }, }) } func TestExampleInlineGroupInArray(t *testing.T) { tree, err := Load(`points = [{ x = 1, y = 2 }]`) assertTree(t, tree, err, map[string]interface{}{ "points": []map[string]interface{}{ { "x": int64(1), "y": int64(2), }, }, }) } func TestInlineTableUnterminated(t *testing.T) { _, err := Load("foo = {") if err.Error() != "(1, 8): unterminated inline table" { t.Error("Bad error message:", err.Error()) } } func TestInlineTableCommaExpected(t *testing.T) { _, err := Load("foo = {hello = 53 test = foo}") if err.Error() != "(1, 19): unexpected token type in inline table: no value can start with t" { t.Error("Bad error message:", err.Error()) } } func TestInlineTableCommaStart(t *testing.T) { _, err := Load("foo = {, hello = 53}") if err.Error() != "(1, 8): unexpected token type in inline table: keys cannot contain , character" { t.Error("Bad error message:", err.Error()) } } func TestInlineTableDoubleComma(t *testing.T) { _, err := Load("foo = {hello = 53,, foo = 17}") if err.Error() != "(1, 19): unexpected token type in inline table: keys cannot contain , character" { t.Error("Bad error message:", err.Error()) } } func TestInlineTableTrailingComma(t *testing.T) { _, err := Load("foo = {hello = 53, foo = 17,}") if err.Error() != "(1, 28): trailing comma at the end of inline table" { t.Error("Bad error message:", err.Error()) } } func TestAddKeyToInlineTable(t *testing.T) { _, err := Load("type = { name = \"Nail\" }\ntype.edible = false") if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : type" { t.Error("Bad error message:", err.Error()) } } func TestAddSubTableToInlineTable(t *testing.T) { _, err := Load("a = { b = \"c\" }\na.d.e = \"f\"") if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : a.d" { t.Error("Bad error message:", err.Error()) } } func TestAddKeyToSubTableOfInlineTable(t *testing.T) { _, err := Load("a = { b = { c = \"d\" } }\na.b.e = \"f\"") if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : a.b" { t.Error("Bad error message:", err.Error()) } } func TestReDefineInlineTable(t *testing.T) { _, err := Load("a = { b = \"c\" }\n[a]\n d = \"e\"") if err.Error() != "(2, 2): could not re-define exist inline table or its sub-table : a" { t.Error("Bad error message:", err.Error()) } } func TestDuplicateGroups(t *testing.T) { _, err := Load("[foo]\na=2\n[foo]b=3") if err.Error() != "(3, 2): duplicated tables" { t.Error("Bad error message:", err.Error()) } } func TestDuplicateKeys(t *testing.T) { _, err := Load("foo = 2\nfoo = 3") if err.Error() != "(2, 1): The following key was defined twice: foo" { t.Error("Bad error message:", err.Error()) } } func TestEmptyIntermediateTable(t *testing.T) { _, err := Load("[foo..bar]") if err.Error() != "(1, 2): invalid table array key: expecting key part after dot" { t.Error("Bad error message:", err.Error()) } } func TestImplicitDeclarationBefore(t *testing.T) { tree, err := Load("[a.b.c]\nanswer = 42\n[a]\nbetter = 43") assertTree(t, tree, err, map[string]interface{}{ "a": map[string]interface{}{ "b": map[string]interface{}{ "c": map[string]interface{}{ "answer": int64(42), }, }, "better": int64(43), }, }) } func TestFloatsWithoutLeadingZeros(t *testing.T) { _, err := Load("a = .42") if err.Error() != "(1, 5): cannot start float with a dot" { t.Error("Bad error message:", err.Error()) } _, err = Load("a = -.42") if err.Error() != "(1, 5): cannot start float with a dot" { t.Error("Bad error message:", err.Error()) } } func TestMissingFile(t *testing.T) { _, err := LoadFile("foo.toml") if err.Error() != "open foo.toml: no such file or directory" && err.Error() != "open foo.toml: The system cannot find the file specified." { t.Error("Bad error message:", err.Error()) } } func TestParseFile(t *testing.T) { tree, err := LoadFile("example.toml") assertTree(t, tree, err, map[string]interface{}{ "title": "TOML Example", "owner": map[string]interface{}{ "name": "Tom Preston-Werner", "organization": "GitHub", "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), }, "database": map[string]interface{}{ "server": "192.168.1.1", "ports": []int64{8001, 8001, 8002}, "connection_max": 5000, "enabled": true, }, "servers": map[string]interface{}{ "alpha": map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, "beta": map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, }, "clients": map[string]interface{}{ "data": []interface{}{ []string{"gamma", "delta"}, []int64{1, 2}, }, "score": 4e-08, }, }) } func TestParseFileCRLF(t *testing.T) { tree, err := LoadFile("example-crlf.toml") assertTree(t, tree, err, map[string]interface{}{ "title": "TOML Example", "owner": map[string]interface{}{ "name": "Tom Preston-Werner", "organization": "GitHub", "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), }, "database": map[string]interface{}{ "server": "192.168.1.1", "ports": []int64{8001, 8001, 8002}, "connection_max": 5000, "enabled": true, }, "servers": map[string]interface{}{ "alpha": map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, "beta": map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, }, "clients": map[string]interface{}{ "data": []interface{}{ []string{"gamma", "delta"}, []int64{1, 2}, }, "score": 4e-08, }, }) } func TestParseKeyGroupArray(t *testing.T) { tree, err := Load("[[foo.bar]] a = 42\n[[foo.bar]] a = 69") assertTree(t, tree, err, map[string]interface{}{ "foo": map[string]interface{}{ "bar": []map[string]interface{}{ {"a": int64(42)}, {"a": int64(69)}, }, }, }) } func TestParseKeyGroupArrayUnfinished(t *testing.T) { _, err := Load("[[foo.bar]\na = 42") if err.Error() != "(1, 10): was expecting token [[, but got unclosed table array key instead" { t.Error("Bad error message:", err.Error()) } _, err = Load("[[foo.[bar]\na = 42") if err.Error() != "(1, 3): unexpected token table array key cannot contain ']', was expecting a table array key" { t.Error("Bad error message:", err.Error()) } } func TestParseKeyGroupArrayQueryExample(t *testing.T) { tree, err := Load(` [[book]] title = "The Stand" author = "Stephen King" [[book]] title = "For Whom the Bell Tolls" author = "Ernest Hemmingway" [[book]] title = "Neuromancer" author = "William Gibson" `) assertTree(t, tree, err, map[string]interface{}{ "book": []map[string]interface{}{ {"title": "The Stand", "author": "Stephen King"}, {"title": "For Whom the Bell Tolls", "author": "Ernest Hemmingway"}, {"title": "Neuromancer", "author": "William Gibson"}, }, }) } func TestParseKeyGroupArraySpec(t *testing.T) { tree, err := Load("[[fruit]]\n name=\"apple\"\n [fruit.physical]\n color=\"red\"\n shape=\"round\"\n [[fruit]]\n name=\"banana\"") assertTree(t, tree, err, map[string]interface{}{ "fruit": []map[string]interface{}{ {"name": "apple", "physical": map[string]interface{}{"color": "red", "shape": "round"}}, {"name": "banana"}, }, }) } func TestTomlValueStringRepresentation(t *testing.T) { for idx, item := range []struct { Value interface{} Expect string }{ {int64(12345), "12345"}, {uint64(50), "50"}, {float64(123.45), "123.45"}, {true, "true"}, {"hello world", "\"hello world\""}, {"\b\t\n\f\r\"\\", "\"\\b\\t\\n\\f\\r\\\"\\\\\""}, {"\x05", "\"\\u0005\""}, {time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), "1979-05-27T07:32:00Z"}, {[]interface{}{"gamma", "delta"}, "[\"gamma\", \"delta\"]"}, {nil, ""}, } { result, err := tomlValueStringRepresentation(item.Value, "", "", OrderAlphabetical, false) if err != nil { t.Errorf("Test %d - unexpected error: %s", idx, err) } if result != item.Expect { t.Errorf("Test %d - got '%s', expected '%s'", idx, result, item.Expect) } } } func TestToStringMapStringString(t *testing.T) { tree, err := TreeFromMap(map[string]interface{}{"m": map[string]interface{}{"v": "abc"}}) if err != nil { t.Fatalf("unexpected error: %s", err) } want := "\n[m]\n v = \"abc\"\n" got := tree.String() if got != want { t.Errorf("want:\n%q\ngot:\n%q", want, got) } } func assertPosition(t *testing.T, text string, ref map[string]Position) { tree, err := Load(text) if err != nil { t.Errorf("Error loading document text: `%v`", text) t.Errorf("Error: %v", err) } for path, pos := range ref { testPos := tree.GetPosition(path) if testPos.Invalid() { t.Errorf("Failed to query tree path or path has invalid position: %s", path) } else if pos != testPos { t.Errorf("Expected position %v, got %v instead", pos, testPos) } } } func TestDocumentPositions(t *testing.T) { assertPosition(t, "[foo]\nbar=42\nbaz=69", map[string]Position{ "": {1, 1}, "foo": {1, 1}, "foo.bar": {2, 1}, "foo.baz": {3, 1}, }) } func TestDocumentPositionsWithSpaces(t *testing.T) { assertPosition(t, " [foo]\n bar=42\n baz=69", map[string]Position{ "": {1, 1}, "foo": {1, 3}, "foo.bar": {2, 3}, "foo.baz": {3, 3}, }) } func TestDocumentPositionsWithGroupArray(t *testing.T) { assertPosition(t, "[[foo]]\nbar=42\nbaz=69", map[string]Position{ "": {1, 1}, "foo": {1, 1}, "foo.bar": {2, 1}, "foo.baz": {3, 1}, }) } func TestNestedTreePosition(t *testing.T) { assertPosition(t, "[foo.bar]\na=42\nb=69", map[string]Position{ "": {1, 1}, "foo": {1, 1}, "foo.bar": {1, 1}, "foo.bar.a": {2, 1}, "foo.bar.b": {3, 1}, }) } func TestInvalidGroupArray(t *testing.T) { _, err := Load("[table#key]\nanswer = 42") if err == nil { t.Error("Should error") } _, err = Load("[foo.[bar]\na = 42") if err.Error() != "(1, 2): unexpected token table key cannot contain ']', was expecting a table key" { t.Error("Bad error message:", err.Error()) } } func TestDoubleEqual(t *testing.T) { _, err := Load("foo= = 2") if err.Error() != "(1, 6): cannot have multiple equals for the same key" { t.Error("Bad error message:", err.Error()) } } func TestGroupArrayReassign(t *testing.T) { _, err := Load("[hello]\n[[hello]]") if err.Error() != "(2, 3): key \"hello\" is already assigned and not of type table array" { t.Error("Bad error message:", err.Error()) } } func TestInvalidFloatParsing(t *testing.T) { _, err := Load("a=1e_2") if err.Error() != "(1, 3): invalid use of _ in number" { t.Error("Bad error message:", err.Error()) } _, err = Load("a=1e2_") if err.Error() != "(1, 3): invalid use of _ in number" { t.Error("Bad error message:", err.Error()) } _, err = Load("a=1__2") if err.Error() != "(1, 3): invalid use of _ in number" { t.Error("Bad error message:", err.Error()) } _, err = Load("a=_1_2") if err.Error() != "(1, 3): no value can start with _" { t.Error("Bad error message:", err.Error()) } } func TestMapKeyIsNum(t *testing.T) { _, err := Load("table={2018=1,2019=2}") if err != nil { t.Error("should be passed") } _, err = Load(`table={"2018"=1,"2019"=2}`) if err != nil { t.Error("should be passed") } } func TestInvalidKeyInlineTable(t *testing.T) { _, err := Load("table={invalid..key = 1}") if err.Error() != "(1, 8): invalid key: expecting key part after dot" { t.Error("Bad error message:", err.Error()) } } func TestDottedKeys(t *testing.T) { tree, err := Load(` name = "Orange" physical.color = "orange" physical.shape = "round" site."google.com" = true`) assertTree(t, tree, err, map[string]interface{}{ "name": "Orange", "physical": map[string]interface{}{ "color": "orange", "shape": "round", }, "site": map[string]interface{}{ "google.com": true, }, }) } func TestInvalidDottedKeyEmptyGroup(t *testing.T) { _, err := Load(`a..b = true`) if err == nil { t.Fatal("should return an error") } if err.Error() != "(1, 1): invalid key: expecting key part after dot" { t.Fatalf("invalid error message: %s", err) } } func TestAccidentalNewlines(t *testing.T) { expected := "The quick brown fox jumps over the lazy dog." tree, err := Load(`str1 = "The quick brown fox jumps over the lazy dog." str2 = """ The quick brown \ fox jumps over \ the lazy dog.""" str3 = """\ The quick brown \` + " " + ` fox jumps over \` + " " + ` the lazy dog.\` + " " + ` """`) if err != nil { t.Fatalf("unexpected error: %v", err) } got := tree.Get("str1") if got != expected { t.Errorf("expected '%s', got '%s'", expected, got) } got = tree.Get("str2") if got != expected { t.Errorf("expected '%s', got '%s'", expected, got) } got = tree.Get("str3") if got != expected { t.Errorf("expected '%s', got '%s'", expected, got) } } func TestUint(t *testing.T) { tree, err := Load("hello = 18446744073709551615") assertTree(t, tree, err, map[string]interface{}{ "hello": uint64(math.MaxUint64), }) } go-toml-1.9.5/position.go000066400000000000000000000013501416532417400153050ustar00rootroot00000000000000// 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 } go-toml-1.9.5/position_test.go000066400000000000000000000007061416532417400163500ustar00rootroot00000000000000// Testing support for go-toml package toml import ( "testing" ) func TestPositionString(t *testing.T) { p := Position{123, 456} expected := "(123, 456)" value := p.String() if value != expected { t.Errorf("Expected %v, got %v instead", expected, value) } } func TestInvalid(t *testing.T) { for i, v := range []Position{ {0, 1234}, {1234, 0}, {0, 0}, } { if !v.Invalid() { t.Errorf("Position at %v is valid: %v", i, v) } } } go-toml-1.9.5/query/000077500000000000000000000000001416532417400142605ustar00rootroot00000000000000go-toml-1.9.5/query/README.md000066400000000000000000000132051416532417400155400ustar00rootroot00000000000000# Query package ## Overview Package query performs JSONPath-like queries on a TOML document. The query path implementation is based loosely on the JSONPath specification: http://goessner.net/articles/JsonPath/. The idea behind a query path is to allow quick access to any element, or set of elements within TOML document, with a single expression. ```go result, err := query.CompileAndExecute("$.foo.bar.baz", tree) ``` This is roughly equivalent to: ```go next := tree.Get("foo") if next != nil { next = next.Get("bar") if next != nil { next = next.Get("baz") } } result := next ``` err is nil if any parsing exception occurs. If no node in the tree matches the query, result will simply contain an empty list of items. As illustrated above, the query path is much more efficient, especially since the structure of the TOML file can vary. Rather than making assumptions about a document's structure, a query allows the programmer to make structured requests into the document, and get zero or more values as a result. ## Query syntax The syntax of a query begins with a root token, followed by any number sub-expressions: ``` $ Root of the TOML tree. This must always come first. .name Selects child of this node, where 'name' is a TOML key name. ['name'] Selects child of this node, where 'name' is a string containing a TOML key name. [index] Selcts child array element at 'index'. ..expr Recursively selects all children, filtered by an a union, index, or slice expression. ..* Recursive selection of all nodes at this point in the tree. .* Selects all children of the current node. [expr,expr] Union operator - a logical 'or' grouping of two or more sub-expressions: index, key name, or filter. [start:end:step] Slice operator - selects array elements from start to end-1, at the given step. All three arguments are optional. [?(filter)] Named filter expression - the function 'filter' is used to filter children at this node. ``` ## Query Indexes And Slices Index expressions perform no bounds checking, and will contribute no values to the result set if the provided index or index range is invalid. Negative indexes represent values from the end of the array, counting backwards. ```go // select the last index of the array named 'foo' query.CompileAndExecute("$.foo[-1]", tree) ``` Slice expressions are supported, by using ':' to separate a start/end index pair. ```go // select up to the first five elements in the array query.CompileAndExecute("$.foo[0:5]", tree) ``` Slice expressions also allow negative indexes for the start and stop arguments. ```go // select all array elements except the last one. query.CompileAndExecute("$.foo[0:-1]", tree) ``` Slice expressions may have an optional stride/step parameter: ```go // select every other element query.CompileAndExecute("$.foo[0::2]", tree) ``` Slice start and end parameters are also optional: ```go // these are all equivalent and select all the values in the array query.CompileAndExecute("$.foo[:]", tree) query.CompileAndExecute("$.foo[::]", tree) query.CompileAndExecute("$.foo[::1]", tree) query.CompileAndExecute("$.foo[0:]", tree) query.CompileAndExecute("$.foo[0::]", tree) query.CompileAndExecute("$.foo[0::1]", tree) ``` ## Query Filters Query filters are used within a Union [,] or single Filter [] expression. A filter only allows nodes that qualify through to the next expression, and/or into the result set. ```go // returns children of foo that are permitted by the 'bar' filter. query.CompileAndExecute("$.foo[?(bar)]", tree) ``` There are several filters provided with the library: ``` tree Allows nodes of type Tree. int Allows nodes of type int64. float Allows nodes of type float64. string Allows nodes of type string. time Allows nodes of type time.Time. bool Allows nodes of type bool. ``` ## Query Results An executed query returns a Result object. This contains the nodes in the TOML tree that qualify the query expression. Position information is also available for each value in the set. ```go // display the results of a query results := query.CompileAndExecute("$.foo.bar.baz", tree) for idx, value := results.Values() { fmt.Println("%v: %v", results.Positions()[idx], value) } ``` ## Compiled Queries Queries may be executed directly on a Tree object, or compiled ahead of time and executed discretely. The former is more convenient, but has the penalty of having to recompile the query expression each time. ```go // basic query results := query.CompileAndExecute("$.foo.bar.baz", tree) // compiled query query, err := toml.Compile("$.foo.bar.baz") results := query.Execute(tree) // run the compiled query again on a different tree moreResults := query.Execute(anotherTree) ``` ## User Defined Query Filters Filter expressions may also be user defined by using the SetFilter() function on the Query object. The function must return true/false, which signifies if the passed node is kept or discarded, respectively. ```go // create a query that references a user-defined filter query, _ := query.Compile("$[?(bazOnly)]") // define the filter, and assign it to the query query.SetFilter("bazOnly", func(node interface{}) bool{ if tree, ok := node.(*Tree); ok { return tree.Has("baz") } return false // reject all other node types }) // run the query query.Execute(tree) ``` go-toml-1.9.5/query/doc.go000066400000000000000000000140551416532417400153610ustar00rootroot00000000000000// Package query performs JSONPath-like queries on a TOML document. // // The query path implementation is based loosely on the JSONPath specification: // http://goessner.net/articles/JsonPath/. // // The idea behind a query path is to allow quick access to any element, or set // of elements within TOML document, with a single expression. // // result, err := query.CompileAndExecute("$.foo.bar.baz", tree) // // This is roughly equivalent to: // // next := tree.Get("foo") // if next != nil { // next = next.Get("bar") // if next != nil { // next = next.Get("baz") // } // } // result := next // // err is nil if any parsing exception occurs. // // If no node in the tree matches the query, result will simply contain an empty list of // items. // // As illustrated above, the query path is much more efficient, especially since // the structure of the TOML file can vary. Rather than making assumptions about // a document's structure, a query allows the programmer to make structured // requests into the document, and get zero or more values as a result. // // Query syntax // // The syntax of a query begins with a root token, followed by any number // sub-expressions: // // $ // Root of the TOML tree. This must always come first. // .name // Selects child of this node, where 'name' is a TOML key // name. // ['name'] // Selects child of this node, where 'name' is a string // containing a TOML key name. // [index] // Selcts child array element at 'index'. // ..expr // Recursively selects all children, filtered by an a union, // index, or slice expression. // ..* // Recursive selection of all nodes at this point in the // tree. // .* // Selects all children of the current node. // [expr,expr] // Union operator - a logical 'or' grouping of two or more // sub-expressions: index, key name, or filter. // [start:end:step] // Slice operator - selects array elements from start to // end-1, at the given step. All three arguments are // optional. // [?(filter)] // Named filter expression - the function 'filter' is // used to filter children at this node. // // Query Indexes And Slices // // Index expressions perform no bounds checking, and will contribute no // values to the result set if the provided index or index range is invalid. // Negative indexes represent values from the end of the array, counting backwards. // // // select the last index of the array named 'foo' // query.CompileAndExecute("$.foo[-1]", tree) // // Slice expressions are supported, by using ':' to separate a start/end index pair. // // // select up to the first five elements in the array // query.CompileAndExecute("$.foo[0:5]", tree) // // Slice expressions also allow negative indexes for the start and stop // arguments. // // // select all array elements except the last one. // query.CompileAndExecute("$.foo[0:-1]", tree) // // Slice expressions may have an optional stride/step parameter: // // // select every other element // query.CompileAndExecute("$.foo[0::2]", tree) // // Slice start and end parameters are also optional: // // // these are all equivalent and select all the values in the array // query.CompileAndExecute("$.foo[:]", tree) // query.CompileAndExecute("$.foo[::]", tree) // query.CompileAndExecute("$.foo[::1]", tree) // query.CompileAndExecute("$.foo[0:]", tree) // query.CompileAndExecute("$.foo[0::]", tree) // query.CompileAndExecute("$.foo[0::1]", tree) // // Query Filters // // Query filters are used within a Union [,] or single Filter [] expression. // A filter only allows nodes that qualify through to the next expression, // and/or into the result set. // // // returns children of foo that are permitted by the 'bar' filter. // query.CompileAndExecute("$.foo[?(bar)]", tree) // // There are several filters provided with the library: // // tree // Allows nodes of type Tree. // int // Allows nodes of type int64. // float // Allows nodes of type float64. // string // Allows nodes of type string. // time // Allows nodes of type time.Time. // bool // Allows nodes of type bool. // // Query Results // // An executed query returns a Result object. This contains the nodes // in the TOML tree that qualify the query expression. Position information // is also available for each value in the set. // // // display the results of a query // results := query.CompileAndExecute("$.foo.bar.baz", tree) // for idx, value := results.Values() { // fmt.Println("%v: %v", results.Positions()[idx], value) // } // // Compiled Queries // // Queries may be executed directly on a Tree object, or compiled ahead // of time and executed discretely. The former is more convenient, but has the // penalty of having to recompile the query expression each time. // // // basic query // results := query.CompileAndExecute("$.foo.bar.baz", tree) // // // compiled query // query, err := toml.Compile("$.foo.bar.baz") // results := query.Execute(tree) // // // run the compiled query again on a different tree // moreResults := query.Execute(anotherTree) // // User Defined Query Filters // // Filter expressions may also be user defined by using the SetFilter() // function on the Query object. The function must return true/false, which // signifies if the passed node is kept or discarded, respectively. // // // create a query that references a user-defined filter // query, _ := query.Compile("$[?(bazOnly)]") // // // define the filter, and assign it to the query // query.SetFilter("bazOnly", func(node interface{}) bool{ // if tree, ok := node.(*Tree); ok { // return tree.Has("baz") // } // return false // reject all other node types // }) // // // run the query // query.Execute(tree) // package query go-toml-1.9.5/query/lexer.go000066400000000000000000000144431416532417400157340ustar00rootroot00000000000000// TOML JSONPath lexer. // // Written using the principles developed by Rob Pike in // http://www.youtube.com/watch?v=HxaD_trXwRE package query import ( "fmt" "github.com/pelletier/go-toml" "strconv" "strings" "unicode/utf8" ) // Lexer state function type queryLexStateFn func() queryLexStateFn // Lexer definition type queryLexer struct { input string start int pos int width int tokens chan token depth int line int col int stringTerm string } func (l *queryLexer) run() { for state := l.lexVoid; state != nil; { state = state() } close(l.tokens) } func (l *queryLexer) nextStart() { // iterate by runes (utf8 characters) // search for newlines and advance line/col counts for i := l.start; i < l.pos; { r, width := utf8.DecodeRuneInString(l.input[i:]) if r == '\n' { l.line++ l.col = 1 } else { l.col++ } i += width } // advance start position to next token l.start = l.pos } func (l *queryLexer) emit(t tokenType) { l.tokens <- token{ Position: toml.Position{Line: l.line, Col: l.col}, typ: t, val: l.input[l.start:l.pos], } l.nextStart() } func (l *queryLexer) emitWithValue(t tokenType, value string) { l.tokens <- token{ Position: toml.Position{Line: l.line, Col: l.col}, typ: t, val: value, } l.nextStart() } func (l *queryLexer) next() rune { if l.pos >= len(l.input) { l.width = 0 return eof } var r rune r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) l.pos += l.width return r } func (l *queryLexer) ignore() { l.nextStart() } func (l *queryLexer) backup() { l.pos -= l.width } func (l *queryLexer) errorf(format string, args ...interface{}) queryLexStateFn { l.tokens <- token{ Position: toml.Position{Line: l.line, Col: l.col}, typ: tokenError, val: fmt.Sprintf(format, args...), } return nil } func (l *queryLexer) peek() rune { r := l.next() l.backup() return r } func (l *queryLexer) accept(valid string) bool { if strings.ContainsRune(valid, l.next()) { return true } l.backup() return false } func (l *queryLexer) follow(next string) bool { return strings.HasPrefix(l.input[l.pos:], next) } func (l *queryLexer) lexVoid() queryLexStateFn { for { next := l.peek() switch next { case '$': l.pos++ l.emit(tokenDollar) continue case '.': if l.follow("..") { l.pos += 2 l.emit(tokenDotDot) } else { l.pos++ l.emit(tokenDot) } continue case '[': l.pos++ l.emit(tokenLeftBracket) continue case ']': l.pos++ l.emit(tokenRightBracket) continue case ',': l.pos++ l.emit(tokenComma) continue case '*': l.pos++ l.emit(tokenStar) continue case '(': l.pos++ l.emit(tokenLeftParen) continue case ')': l.pos++ l.emit(tokenRightParen) continue case '?': l.pos++ l.emit(tokenQuestion) continue case ':': l.pos++ l.emit(tokenColon) continue case '\'': l.ignore() l.stringTerm = string(next) return l.lexString case '"': l.ignore() l.stringTerm = string(next) return l.lexString } if isSpace(next) { l.next() l.ignore() continue } if isAlphanumeric(next) { return l.lexKey } if next == '+' || next == '-' || isDigit(next) { return l.lexNumber } if l.next() == eof { break } return l.errorf("unexpected char: '%v'", next) } l.emit(tokenEOF) return nil } func (l *queryLexer) lexKey() queryLexStateFn { for { next := l.peek() if !isAlphanumeric(next) { l.emit(tokenKey) return l.lexVoid } if l.next() == eof { break } } l.emit(tokenEOF) return nil } func (l *queryLexer) lexString() queryLexStateFn { l.pos++ l.ignore() growingString := "" for { if l.follow(l.stringTerm) { l.emitWithValue(tokenString, growingString) l.pos++ l.ignore() return l.lexVoid } if l.follow("\\\"") { l.pos++ growingString += "\"" } else if l.follow("\\'") { l.pos++ growingString += "'" } else if l.follow("\\n") { l.pos++ growingString += "\n" } else if l.follow("\\b") { l.pos++ growingString += "\b" } else if l.follow("\\f") { l.pos++ growingString += "\f" } else if l.follow("\\/") { l.pos++ growingString += "/" } else if l.follow("\\t") { l.pos++ growingString += "\t" } else if l.follow("\\r") { l.pos++ growingString += "\r" } else if l.follow("\\\\") { l.pos++ growingString += "\\" } else if l.follow("\\u") { l.pos += 2 code := "" for i := 0; i < 4; i++ { c := l.peek() l.pos++ if !isHexDigit(c) { return l.errorf("unfinished unicode escape") } code = code + string(c) } l.pos-- intcode, err := strconv.ParseInt(code, 16, 32) if err != nil { return l.errorf("invalid unicode escape: \\u" + code) } growingString += string(rune(intcode)) } else if l.follow("\\U") { l.pos += 2 code := "" for i := 0; i < 8; i++ { c := l.peek() l.pos++ if !isHexDigit(c) { return l.errorf("unfinished unicode escape") } code = code + string(c) } l.pos-- intcode, err := strconv.ParseInt(code, 16, 32) if err != nil { return l.errorf("invalid unicode escape: \\u" + code) } growingString += string(rune(intcode)) } else if l.follow("\\") { l.pos++ return l.errorf("invalid escape sequence: \\" + string(l.peek())) } else { growingString += string(l.peek()) } if l.next() == eof { break } } return l.errorf("unclosed string") } func (l *queryLexer) lexNumber() queryLexStateFn { l.ignore() if !l.accept("+") { l.accept("-") } pointSeen := false digitSeen := false for { next := l.next() if next == '.' { if pointSeen { return l.errorf("cannot have two dots in one float") } if !isDigit(l.peek()) { return l.errorf("float cannot end with a dot") } pointSeen = true } else if isDigit(next) { digitSeen = true } else { l.backup() 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 { l.emit(tokenFloat) } else { l.emit(tokenInteger) } return l.lexVoid } // Entry point func lexQuery(input string) chan token { l := &queryLexer{ input: input, tokens: make(chan token), line: 1, col: 1, } go l.run() return l.tokens } go-toml-1.9.5/query/lexer_test.go000066400000000000000000000114411416532417400167660ustar00rootroot00000000000000package query import ( "github.com/pelletier/go-toml" "testing" ) func testQLFlow(t *testing.T, input string, expectedFlow []token) { ch := lexQuery(input) for idx, expected := range expectedFlow { token := <-ch if token != expected { t.Log("While testing #", idx, ":", input) t.Log("compared (got)", token, "to (expected)", expected) t.Log("\tvalue:", token.val, "<->", expected.val) t.Log("\tvalue as bytes:", []byte(token.val), "<->", []byte(expected.val)) t.Log("\ttype:", token.typ.String(), "<->", expected.typ.String()) t.Log("\tline:", token.Line, "<->", expected.Line) t.Log("\tcolumn:", token.Col, "<->", expected.Col) t.Log("compared", token, "to", expected) t.FailNow() } } tok, ok := <-ch if ok { t.Log("channel is not closed!") t.Log(len(ch)+1, "tokens remaining:") t.Log("token ->", tok) for token := range ch { t.Log("token ->", token) } t.FailNow() } } func TestLexSpecialChars(t *testing.T) { testQLFlow(t, " .$[]..()?*", []token{ {toml.Position{1, 2}, tokenDot, "."}, {toml.Position{1, 3}, tokenDollar, "$"}, {toml.Position{1, 4}, tokenLeftBracket, "["}, {toml.Position{1, 5}, tokenRightBracket, "]"}, {toml.Position{1, 6}, tokenDotDot, ".."}, {toml.Position{1, 8}, tokenLeftParen, "("}, {toml.Position{1, 9}, tokenRightParen, ")"}, {toml.Position{1, 10}, tokenQuestion, "?"}, {toml.Position{1, 11}, tokenStar, "*"}, {toml.Position{1, 12}, tokenEOF, ""}, }) } func TestLexString(t *testing.T) { testQLFlow(t, "'foo\n'", []token{ {toml.Position{1, 2}, tokenString, "foo\n"}, {toml.Position{2, 2}, tokenEOF, ""}, }) } func TestLexDoubleString(t *testing.T) { testQLFlow(t, `"bar"`, []token{ {toml.Position{1, 2}, tokenString, "bar"}, {toml.Position{1, 6}, tokenEOF, ""}, }) } func TestLexStringEscapes(t *testing.T) { testQLFlow(t, `"foo \" \' \b \f \/ \t \r \\ \u03A9 \U00012345 \n bar"`, []token{ {toml.Position{1, 2}, tokenString, "foo \" ' \b \f / \t \r \\ \u03A9 \U00012345 \n bar"}, {toml.Position{1, 55}, tokenEOF, ""}, }) } func TestLexStringUnfinishedUnicode4(t *testing.T) { testQLFlow(t, `"\u000"`, []token{ {toml.Position{1, 2}, tokenError, "unfinished unicode escape"}, }) } func TestLexStringUnfinishedUnicode8(t *testing.T) { testQLFlow(t, `"\U0000"`, []token{ {toml.Position{1, 2}, tokenError, "unfinished unicode escape"}, }) } func TestLexStringInvalidEscape(t *testing.T) { testQLFlow(t, `"\x"`, []token{ {toml.Position{1, 2}, tokenError, "invalid escape sequence: \\x"}, }) } func TestLexStringUnfinished(t *testing.T) { testQLFlow(t, `"bar`, []token{ {toml.Position{1, 2}, tokenError, "unclosed string"}, }) } func TestLexKey(t *testing.T) { testQLFlow(t, "foo", []token{ {toml.Position{1, 1}, tokenKey, "foo"}, {toml.Position{1, 4}, tokenEOF, ""}, }) } func TestLexRecurse(t *testing.T) { testQLFlow(t, "$..*", []token{ {toml.Position{1, 1}, tokenDollar, "$"}, {toml.Position{1, 2}, tokenDotDot, ".."}, {toml.Position{1, 4}, tokenStar, "*"}, {toml.Position{1, 5}, tokenEOF, ""}, }) } func TestLexBracketKey(t *testing.T) { testQLFlow(t, "$[foo]", []token{ {toml.Position{1, 1}, tokenDollar, "$"}, {toml.Position{1, 2}, tokenLeftBracket, "["}, {toml.Position{1, 3}, tokenKey, "foo"}, {toml.Position{1, 6}, tokenRightBracket, "]"}, {toml.Position{1, 7}, tokenEOF, ""}, }) } func TestLexSpace(t *testing.T) { testQLFlow(t, "foo bar baz", []token{ {toml.Position{1, 1}, tokenKey, "foo"}, {toml.Position{1, 5}, tokenKey, "bar"}, {toml.Position{1, 9}, tokenKey, "baz"}, {toml.Position{1, 12}, tokenEOF, ""}, }) } func TestLexInteger(t *testing.T) { testQLFlow(t, "100 +200 -300", []token{ {toml.Position{1, 1}, tokenInteger, "100"}, {toml.Position{1, 5}, tokenInteger, "+200"}, {toml.Position{1, 10}, tokenInteger, "-300"}, {toml.Position{1, 14}, tokenEOF, ""}, }) } func TestLexFloat(t *testing.T) { testQLFlow(t, "100.0 +200.0 -300.0", []token{ {toml.Position{1, 1}, tokenFloat, "100.0"}, {toml.Position{1, 7}, tokenFloat, "+200.0"}, {toml.Position{1, 14}, tokenFloat, "-300.0"}, {toml.Position{1, 20}, tokenEOF, ""}, }) } func TestLexFloatWithMultipleDots(t *testing.T) { testQLFlow(t, "4.2.", []token{ {toml.Position{1, 1}, tokenError, "cannot have two dots in one float"}, }) } func TestLexFloatLeadingDot(t *testing.T) { testQLFlow(t, "+.1", []token{ {toml.Position{1, 1}, tokenError, "cannot start float with a dot"}, }) } func TestLexFloatWithTrailingDot(t *testing.T) { testQLFlow(t, "42.", []token{ {toml.Position{1, 1}, tokenError, "float cannot end with a dot"}, }) } func TestLexNumberWithoutDigit(t *testing.T) { testQLFlow(t, "+", []token{ {toml.Position{1, 1}, tokenError, "no digit in that number"}, }) } func TestLexUnknown(t *testing.T) { testQLFlow(t, "^", []token{ {toml.Position{1, 1}, tokenError, "unexpected char: '94'"}, }) } go-toml-1.9.5/query/match.go000066400000000000000000000136271416532417400157140ustar00rootroot00000000000000package query import ( "fmt" "reflect" "github.com/pelletier/go-toml" ) // base match type matchBase struct { next pathFn } func (f *matchBase) setNext(next pathFn) { f.next = next } // terminating functor - gathers results type terminatingFn struct { // empty } func newTerminatingFn() *terminatingFn { return &terminatingFn{} } func (f *terminatingFn) setNext(next pathFn) { // do nothing } func (f *terminatingFn) call(node interface{}, ctx *queryContext) { ctx.result.appendResult(node, ctx.lastPosition) } // match single key type matchKeyFn struct { matchBase Name string } func newMatchKeyFn(name string) *matchKeyFn { return &matchKeyFn{Name: name} } func (f *matchKeyFn) call(node interface{}, ctx *queryContext) { if array, ok := node.([]*toml.Tree); ok { for _, tree := range array { item := tree.GetPath([]string{f.Name}) if item != nil { ctx.lastPosition = tree.GetPositionPath([]string{f.Name}) f.next.call(item, ctx) } } } else if tree, ok := node.(*toml.Tree); ok { item := tree.GetPath([]string{f.Name}) if item != nil { ctx.lastPosition = tree.GetPositionPath([]string{f.Name}) f.next.call(item, ctx) } } } // match single index type matchIndexFn struct { matchBase Idx int } func newMatchIndexFn(idx int) *matchIndexFn { return &matchIndexFn{Idx: idx} } func (f *matchIndexFn) call(node interface{}, ctx *queryContext) { v := reflect.ValueOf(node) if v.Kind() == reflect.Slice { if v.Len() == 0 { return } // Manage negative values idx := f.Idx if idx < 0 { idx += v.Len() } if 0 <= idx && idx < v.Len() { callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) } } } func callNextIndexSlice(next pathFn, node interface{}, ctx *queryContext, value interface{}) { if treesArray, ok := node.([]*toml.Tree); ok { ctx.lastPosition = treesArray[0].Position() } next.call(value, ctx) } // filter by slicing type matchSliceFn struct { matchBase Start, End, Step *int } func newMatchSliceFn() *matchSliceFn { return &matchSliceFn{} } func (f *matchSliceFn) setStart(start int) *matchSliceFn { f.Start = &start return f } func (f *matchSliceFn) setEnd(end int) *matchSliceFn { f.End = &end return f } func (f *matchSliceFn) setStep(step int) *matchSliceFn { f.Step = &step return f } func (f *matchSliceFn) call(node interface{}, ctx *queryContext) { v := reflect.ValueOf(node) if v.Kind() == reflect.Slice { if v.Len() == 0 { return } var start, end, step int // Initialize step if f.Step != nil { step = *f.Step } else { step = 1 } // Initialize start if f.Start != nil { start = *f.Start // Manage negative values if start < 0 { start += v.Len() } // Manage out of range values start = max(start, 0) start = min(start, v.Len()-1) } else if step > 0 { start = 0 } else { start = v.Len() - 1 } // Initialize end if f.End != nil { end = *f.End // Manage negative values if end < 0 { end += v.Len() } // Manage out of range values end = max(end, -1) end = min(end, v.Len()) } else if step > 0 { end = v.Len() } else { end = -1 } // Loop on values if step > 0 { for idx := start; idx < end; idx += step { callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) } } else { for idx := start; idx > end; idx += step { callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) } } } } func min(a, b int) int { if a < b { return a } return b } func max(a, b int) int { if a > b { return a } return b } // match anything type matchAnyFn struct { matchBase } func newMatchAnyFn() *matchAnyFn { return &matchAnyFn{} } func (f *matchAnyFn) call(node interface{}, ctx *queryContext) { if tree, ok := node.(*toml.Tree); ok { for _, k := range tree.Keys() { v := tree.GetPath([]string{k}) ctx.lastPosition = tree.GetPositionPath([]string{k}) f.next.call(v, ctx) } } } // filter through union type matchUnionFn struct { Union []pathFn } func (f *matchUnionFn) setNext(next pathFn) { for _, fn := range f.Union { fn.setNext(next) } } func (f *matchUnionFn) call(node interface{}, ctx *queryContext) { for _, fn := range f.Union { fn.call(node, ctx) } } // match every single last node in the tree type matchRecursiveFn struct { matchBase } func newMatchRecursiveFn() *matchRecursiveFn { return &matchRecursiveFn{} } func (f *matchRecursiveFn) call(node interface{}, ctx *queryContext) { originalPosition := ctx.lastPosition if tree, ok := node.(*toml.Tree); ok { var visit func(tree *toml.Tree) visit = func(tree *toml.Tree) { for _, k := range tree.Keys() { v := tree.GetPath([]string{k}) ctx.lastPosition = tree.GetPositionPath([]string{k}) f.next.call(v, ctx) switch node := v.(type) { case *toml.Tree: visit(node) case []*toml.Tree: for _, subtree := range node { visit(subtree) } } } } ctx.lastPosition = originalPosition f.next.call(tree, ctx) visit(tree) } } // match based on an externally provided functional filter type matchFilterFn struct { matchBase Pos toml.Position Name string } func newMatchFilterFn(name string, pos toml.Position) *matchFilterFn { return &matchFilterFn{Name: name, Pos: pos} } func (f *matchFilterFn) call(node interface{}, ctx *queryContext) { fn, ok := (*ctx.filters)[f.Name] if !ok { panic(fmt.Sprintf("%s: query context does not have filter '%s'", f.Pos.String(), f.Name)) } switch castNode := node.(type) { case *toml.Tree: for _, k := range castNode.Keys() { v := castNode.GetPath([]string{k}) if fn(v) { ctx.lastPosition = castNode.GetPositionPath([]string{k}) f.next.call(v, ctx) } } case []*toml.Tree: for _, v := range castNode { if fn(v) { if len(castNode) > 0 { ctx.lastPosition = castNode[0].Position() } f.next.call(v, ctx) } } case []interface{}: for _, v := range castNode { if fn(v) { f.next.call(v, ctx) } } } } go-toml-1.9.5/query/match_test.go000066400000000000000000000074501416532417400167500ustar00rootroot00000000000000package query import ( "fmt" "strconv" "testing" "github.com/pelletier/go-toml" ) // dump path tree to a string func pathString(root pathFn) string { result := fmt.Sprintf("%T:", root) switch fn := root.(type) { case *terminatingFn: result += "{}" case *matchKeyFn: result += fmt.Sprintf("{%s}", fn.Name) result += pathString(fn.next) case *matchIndexFn: result += fmt.Sprintf("{%d}", fn.Idx) result += pathString(fn.next) case *matchSliceFn: startString, endString, stepString := "nil", "nil", "nil" if fn.Start != nil { startString = strconv.Itoa(*fn.Start) } if fn.End != nil { endString = strconv.Itoa(*fn.End) } if fn.Step != nil { stepString = strconv.Itoa(*fn.Step) } result += fmt.Sprintf("{%s:%s:%s}", startString, endString, stepString) result += pathString(fn.next) case *matchAnyFn: result += "{}" result += pathString(fn.next) case *matchUnionFn: result += "{[" for _, v := range fn.Union { result += pathString(v) + ", " } result += "]}" case *matchRecursiveFn: result += "{}" result += pathString(fn.next) case *matchFilterFn: result += fmt.Sprintf("{%s}", fn.Name) result += pathString(fn.next) } return result } func assertPathMatch(t *testing.T, path, ref *Query) bool { pathStr := pathString(path.root) refStr := pathString(ref.root) if pathStr != refStr { t.Errorf("paths do not match") t.Log("test:", pathStr) t.Log("ref: ", refStr) return false } return true } func assertPath(t *testing.T, query string, ref *Query) { path, _ := parseQuery(lexQuery(query)) assertPathMatch(t, path, ref) } func buildPath(parts ...pathFn) *Query { query := newQuery() for _, v := range parts { query.appendPath(v) } return query } func TestPathRoot(t *testing.T) { assertPath(t, "$", buildPath( // empty )) } func TestPathKey(t *testing.T) { assertPath(t, "$.foo", buildPath( newMatchKeyFn("foo"), )) } func TestPathBracketKey(t *testing.T) { assertPath(t, "$[foo]", buildPath( newMatchKeyFn("foo"), )) } func TestPathBracketStringKey(t *testing.T) { assertPath(t, "$['foo']", buildPath( newMatchKeyFn("foo"), )) } func TestPathIndex(t *testing.T) { assertPath(t, "$[123]", buildPath( newMatchIndexFn(123), )) } func TestPathSliceStart(t *testing.T) { assertPath(t, "$[123:]", buildPath( newMatchSliceFn().setStart(123), )) } func TestPathSliceStartEnd(t *testing.T) { assertPath(t, "$[123:456]", buildPath( newMatchSliceFn().setStart(123).setEnd(456), )) } func TestPathSliceStartEndColon(t *testing.T) { assertPath(t, "$[123:456:]", buildPath( newMatchSliceFn().setStart(123).setEnd(456), )) } func TestPathSliceStartStep(t *testing.T) { assertPath(t, "$[123::7]", buildPath( newMatchSliceFn().setStart(123).setStep(7), )) } func TestPathSliceEndStep(t *testing.T) { assertPath(t, "$[:456:7]", buildPath( newMatchSliceFn().setEnd(456).setStep(7), )) } func TestPathSliceStep(t *testing.T) { assertPath(t, "$[::7]", buildPath( newMatchSliceFn().setStep(7), )) } func TestPathSliceAll(t *testing.T) { assertPath(t, "$[123:456:7]", buildPath( newMatchSliceFn().setStart(123).setEnd(456).setStep(7), )) } func TestPathAny(t *testing.T) { assertPath(t, "$.*", buildPath( newMatchAnyFn(), )) } func TestPathUnion(t *testing.T) { assertPath(t, "$[foo, bar, baz]", buildPath( &matchUnionFn{[]pathFn{ newMatchKeyFn("foo"), newMatchKeyFn("bar"), newMatchKeyFn("baz"), }}, )) } func TestPathRecurse(t *testing.T) { assertPath(t, "$..*", buildPath( newMatchRecursiveFn(), )) } func TestPathFilterExpr(t *testing.T) { assertPath(t, "$[?('foo'),?(bar)]", buildPath( &matchUnionFn{[]pathFn{ newMatchFilterFn("foo", toml.Position{}), newMatchFilterFn("bar", toml.Position{}), }}, )) } go-toml-1.9.5/query/parser.go000066400000000000000000000140451416532417400161070ustar00rootroot00000000000000/* Based on the "jsonpath" spec/concept. http://goessner.net/articles/JsonPath/ https://code.google.com/p/json-path/ */ package query import ( "fmt" ) const maxInt = int(^uint(0) >> 1) type queryParser struct { flow chan token tokensBuffer []token query *Query union []pathFn err error } type queryParserStateFn func() queryParserStateFn // Formats and panics an error message based on a token func (p *queryParser) parseError(tok *token, msg string, args ...interface{}) queryParserStateFn { p.err = fmt.Errorf(tok.Position.String()+": "+msg, args...) return nil // trigger parse to end } func (p *queryParser) run() { for state := p.parseStart; state != nil; { state = state() } } func (p *queryParser) backup(tok *token) { p.tokensBuffer = append(p.tokensBuffer, *tok) } func (p *queryParser) peek() *token { if len(p.tokensBuffer) != 0 { return &(p.tokensBuffer[0]) } tok, ok := <-p.flow if !ok { return nil } p.backup(&tok) return &tok } func (p *queryParser) lookahead(types ...tokenType) bool { result := true buffer := []token{} for _, typ := range types { tok := p.getToken() if tok == nil { result = false break } buffer = append(buffer, *tok) if tok.typ != typ { result = false break } } // add the tokens back to the buffer, and return p.tokensBuffer = append(p.tokensBuffer, buffer...) return result } func (p *queryParser) getToken() *token { if len(p.tokensBuffer) != 0 { tok := p.tokensBuffer[0] p.tokensBuffer = p.tokensBuffer[1:] return &tok } tok, ok := <-p.flow if !ok { return nil } return &tok } func (p *queryParser) parseStart() queryParserStateFn { tok := p.getToken() if tok == nil || tok.typ == tokenEOF { return nil } if tok.typ != tokenDollar { return p.parseError(tok, "Expected '$' at start of expression") } return p.parseMatchExpr } // handle '.' prefix, '[]', and '..' func (p *queryParser) parseMatchExpr() queryParserStateFn { tok := p.getToken() switch tok.typ { case tokenDotDot: p.query.appendPath(&matchRecursiveFn{}) // nested parse for '..' tok := p.getToken() switch tok.typ { case tokenKey: p.query.appendPath(newMatchKeyFn(tok.val)) return p.parseMatchExpr case tokenLeftBracket: return p.parseBracketExpr case tokenStar: // do nothing - the recursive predicate is enough return p.parseMatchExpr } case tokenDot: // nested parse for '.' tok := p.getToken() switch tok.typ { case tokenKey: p.query.appendPath(newMatchKeyFn(tok.val)) return p.parseMatchExpr case tokenStar: p.query.appendPath(&matchAnyFn{}) return p.parseMatchExpr } case tokenLeftBracket: return p.parseBracketExpr case tokenEOF: return nil // allow EOF at this stage } return p.parseError(tok, "expected match expression") } func (p *queryParser) parseBracketExpr() queryParserStateFn { if p.lookahead(tokenInteger, tokenColon) { return p.parseSliceExpr } if p.peek().typ == tokenColon { return p.parseSliceExpr } return p.parseUnionExpr } func (p *queryParser) parseUnionExpr() queryParserStateFn { var tok *token // this state can be traversed after some sub-expressions // so be careful when setting up state in the parser if p.union == nil { p.union = []pathFn{} } loop: // labeled loop for easy breaking for { if len(p.union) > 0 { // parse delimiter or terminator tok = p.getToken() switch tok.typ { case tokenComma: // do nothing case tokenRightBracket: break loop default: return p.parseError(tok, "expected ',' or ']', not '%s'", tok.val) } } // parse sub expression tok = p.getToken() switch tok.typ { case tokenInteger: p.union = append(p.union, newMatchIndexFn(tok.Int())) case tokenKey: p.union = append(p.union, newMatchKeyFn(tok.val)) case tokenString: p.union = append(p.union, newMatchKeyFn(tok.val)) case tokenQuestion: return p.parseFilterExpr default: return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union)) } } // if there is only one sub-expression, use that instead if len(p.union) == 1 { p.query.appendPath(p.union[0]) } else { p.query.appendPath(&matchUnionFn{p.union}) } p.union = nil // clear out state return p.parseMatchExpr } func (p *queryParser) parseSliceExpr() queryParserStateFn { // init slice to grab all elements var start, end, step *int = nil, nil, nil // parse optional start tok := p.getToken() if tok.typ == tokenInteger { v := tok.Int() start = &v tok = p.getToken() } if tok.typ != tokenColon { return p.parseError(tok, "expected ':'") } // parse optional end tok = p.getToken() if tok.typ == tokenInteger { v := tok.Int() end = &v tok = p.getToken() } if tok.typ == tokenRightBracket { p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step}) return p.parseMatchExpr } if tok.typ != tokenColon { return p.parseError(tok, "expected ']' or ':'") } // parse optional step tok = p.getToken() if tok.typ == tokenInteger { v := tok.Int() if v == 0 { return p.parseError(tok, "step cannot be zero") } step = &v tok = p.getToken() } if tok.typ != tokenRightBracket { return p.parseError(tok, "expected ']'") } p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step}) return p.parseMatchExpr } func (p *queryParser) parseFilterExpr() queryParserStateFn { tok := p.getToken() if tok.typ != tokenLeftParen { return p.parseError(tok, "expected left-parenthesis for filter expression") } tok = p.getToken() if tok.typ != tokenKey && tok.typ != tokenString { return p.parseError(tok, "expected key or string for filter function name") } name := tok.val tok = p.getToken() if tok.typ != tokenRightParen { return p.parseError(tok, "expected right-parenthesis for filter expression") } p.union = append(p.union, newMatchFilterFn(name, tok.Position)) return p.parseUnionExpr } func parseQuery(flow chan token) (*Query, error) { parser := &queryParser{ flow: flow, tokensBuffer: []token{}, query: newQuery(), } parser.run() return parser.query, parser.err } go-toml-1.9.5/query/parser_test.go000066400000000000000000000345711416532417400171540ustar00rootroot00000000000000package query import ( "fmt" "io/ioutil" "sort" "strings" "testing" "time" "github.com/pelletier/go-toml" ) type queryTestNode struct { value interface{} position toml.Position } func valueString(root interface{}) string { result := "" //fmt.Sprintf("%T:", root) switch node := root.(type) { case *Result: items := []string{} for i, v := range node.Values() { items = append(items, fmt.Sprintf("%s:%s", node.Positions()[i].String(), valueString(v))) } sort.Strings(items) result = "[" + strings.Join(items, ", ") + "]" case queryTestNode: result = fmt.Sprintf("%s:%s", node.position.String(), valueString(node.value)) case []interface{}: items := []string{} for _, v := range node { items = append(items, valueString(v)) } sort.Strings(items) result = "[" + strings.Join(items, ", ") + "]" case *toml.Tree: // workaround for unreliable map key ordering items := []string{} for _, k := range node.Keys() { v := node.GetPath([]string{k}) items = append(items, k+":"+valueString(v)) } sort.Strings(items) result = "{" + strings.Join(items, ", ") + "}" case map[string]interface{}: // workaround for unreliable map key ordering items := []string{} for k, v := range node { items = append(items, k+":"+valueString(v)) } sort.Strings(items) result = "{" + strings.Join(items, ", ") + "}" case int64: result += fmt.Sprintf("%d", node) case string: result += "'" + node + "'" case float64: result += fmt.Sprintf("%f", node) case bool: result += fmt.Sprintf("%t", node) case time.Time: result += fmt.Sprintf("'%v'", node) } return result } func assertValue(t *testing.T, result, ref interface{}) { pathStr := valueString(result) refStr := valueString(ref) if pathStr != refStr { t.Errorf("values do not match") t.Log("test:", pathStr) t.Log("ref: ", refStr) } } func assertParseError(t *testing.T, query string, errString string) { _, err := Compile(query) if err == nil { t.Error("error should be non-nil") return } if err.Error() != errString { t.Errorf("error does not match") t.Log("test:", err.Error()) t.Log("ref: ", errString) } } func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) { tree, err := toml.Load(tomlDoc) if err != nil { t.Errorf("Non-nil toml parse error: %v", err) return } q, err := Compile(query) if err != nil { t.Error(err) return } results := q.Execute(tree) assertValue(t, results, ref) } func TestQueryRoot(t *testing.T) { assertQueryPositions(t, "a = 42", "$", []interface{}{ queryTestNode{ map[string]interface{}{ "a": int64(42), }, toml.Position{1, 1}, }, }) } func TestQueryKey(t *testing.T) { assertQueryPositions(t, "[foo]\na = 42", "$.foo.a", []interface{}{ queryTestNode{ int64(42), toml.Position{2, 1}, }, }) } func TestQueryKeyString(t *testing.T) { assertQueryPositions(t, "[foo]\na = 42", "$.foo['a']", []interface{}{ queryTestNode{ int64(42), toml.Position{2, 1}, }, }) } func TestQueryKeyUnicodeString(t *testing.T) { assertQueryPositions(t, "['f𝟘.o']\na = 42", "$['f𝟘.o']['a']", []interface{}{ queryTestNode{ int64(42), toml.Position{2, 1}, }, }) } func TestQueryIndexError1(t *testing.T) { assertParseError(t, "$.foo.a[5", "(1, 10): expected ',' or ']', not ''") } func TestQueryIndexError2(t *testing.T) { assertParseError(t, "$.foo.a[]", "(1, 9): expected union sub expression, not ']', 0") } func TestQueryIndex(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[5]", []interface{}{ queryTestNode{int64(5), toml.Position{2, 1}}, }) } func TestQueryIndexNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[-2]", []interface{}{ queryTestNode{int64(8), toml.Position{2, 1}}, }) } func TestQueryIndexWrong(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[99]", []interface{}{}) } func TestQueryIndexEmpty(t *testing.T) { assertQueryPositions(t, "[foo]\na = []", "$.foo.a[5]", []interface{}{}) } func TestQueryIndexTree(t *testing.T) { assertQueryPositions(t, "[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\nb = 3", "$.foo[1].b", []interface{}{ queryTestNode{int64(3), toml.Position{4, 1}}, }) } func TestQuerySliceError1(t *testing.T) { assertParseError(t, "$.foo.a[3:?]", "(1, 11): expected ']' or ':'") } func TestQuerySliceError2(t *testing.T) { assertParseError(t, "$.foo.a[:::]", "(1, 11): expected ']'") } func TestQuerySliceError3(t *testing.T) { assertParseError(t, "$.foo.a[::0]", "(1, 11): step cannot be zero") } func TestQuerySliceRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[:5]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(3), toml.Position{2, 1}}, queryTestNode{int64(4), toml.Position{2, 1}}, }) } func TestQuerySliceStep(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[0:5:2]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(4), toml.Position{2, 1}}, }) } func TestQuerySliceStartNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[-3:]", []interface{}{ queryTestNode{int64(7), toml.Position{2, 1}}, queryTestNode{int64(8), toml.Position{2, 1}}, queryTestNode{int64(9), toml.Position{2, 1}}, }) } func TestQuerySliceEndNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[:-6]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(3), toml.Position{2, 1}}, }) } func TestQuerySliceStepNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[::-2]", []interface{}{ queryTestNode{int64(9), toml.Position{2, 1}}, queryTestNode{int64(7), toml.Position{2, 1}}, queryTestNode{int64(5), toml.Position{2, 1}}, queryTestNode{int64(3), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, }) } func TestQuerySliceStartOverRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[-99:3]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, }) } func TestQuerySliceStartOverRangeNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[99:7:-1]", []interface{}{ queryTestNode{int64(9), toml.Position{2, 1}}, queryTestNode{int64(8), toml.Position{2, 1}}, }) } func TestQuerySliceEndOverRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[7:99]", []interface{}{ queryTestNode{int64(7), toml.Position{2, 1}}, queryTestNode{int64(8), toml.Position{2, 1}}, queryTestNode{int64(9), toml.Position{2, 1}}, }) } func TestQuerySliceEndOverRangeNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[2:-99:-1]", []interface{}{ queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(0), toml.Position{2, 1}}, }) } func TestQuerySliceWrongRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[5:3]", []interface{}{}) } func TestQuerySliceWrongRangeNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[3:5:-1]", []interface{}{}) } func TestQuerySliceEmpty(t *testing.T) { assertQueryPositions(t, "[foo]\na = []", "$.foo.a[5:]", []interface{}{}) } func TestQuerySliceTree(t *testing.T) { assertQueryPositions(t, "[[foo]]\na='nok'\n[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\na='ok'\nb = 3", "$.foo[1:].a", []interface{}{ queryTestNode{ []interface{}{ int64(0), int64(1), int64(2), int64(3), int64(4), int64(5), int64(6), int64(7), int64(8), int64(9)}, toml.Position{4, 1}}, queryTestNode{"ok", toml.Position{6, 1}}, }) } func TestQueryAny(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4", "$.foo.*", []interface{}{ queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, }) } func TestQueryUnionSimple(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", "$.*[bar,foo]", []interface{}{ queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(5), "b": int64(6), }, toml.Position{7, 1}, }, }) } func TestQueryRecursionAll(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", "$..*", []interface{}{ queryTestNode{ map[string]interface{}{ "foo": map[string]interface{}{ "bar": map[string]interface{}{ "a": int64(1), "b": int64(2), }, }, "baz": map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(3), "b": int64(4), }, }, "gorf": map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(5), "b": int64(6), }, }, }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "bar": map[string]interface{}{ "a": int64(1), "b": int64(2), }, }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{3, 1}}, queryTestNode{ map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(3), "b": int64(4), }, }, toml.Position{4, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, queryTestNode{int64(3), toml.Position{5, 1}}, queryTestNode{int64(4), toml.Position{6, 1}}, queryTestNode{ map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(5), "b": int64(6), }, }, toml.Position{7, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(5), "b": int64(6), }, toml.Position{7, 1}, }, queryTestNode{int64(5), toml.Position{8, 1}}, queryTestNode{int64(6), toml.Position{9, 1}}, }) } func TestQueryRecursionUnionSimple(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", "$..['foo','bar']", []interface{}{ queryTestNode{ map[string]interface{}{ "bar": map[string]interface{}{ "a": int64(1), "b": int64(2), }, }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(5), "b": int64(6), }, toml.Position{7, 1}, }, }) } func TestQueryFilterFn(t *testing.T) { buff, err := ioutil.ReadFile("../example.toml") if err != nil { t.Error(err) return } assertQueryPositions(t, string(buff), "$..[?(int)]", []interface{}{ queryTestNode{int64(8001), toml.Position{13, 1}}, queryTestNode{int64(8001), toml.Position{13, 1}}, queryTestNode{int64(8002), toml.Position{13, 1}}, queryTestNode{int64(5000), toml.Position{14, 1}}, }) assertQueryPositions(t, string(buff), "$..[?(string)]", []interface{}{ queryTestNode{"TOML Example", toml.Position{3, 1}}, queryTestNode{"Tom Preston-Werner", toml.Position{6, 1}}, queryTestNode{"GitHub", toml.Position{7, 1}}, queryTestNode{"GitHub Cofounder & CEO\nLikes tater tots and beer.", toml.Position{8, 1}}, queryTestNode{"192.168.1.1", toml.Position{12, 1}}, queryTestNode{"10.0.0.1", toml.Position{21, 3}}, queryTestNode{"eqdc10", toml.Position{22, 3}}, queryTestNode{"10.0.0.2", toml.Position{25, 3}}, queryTestNode{"eqdc10", toml.Position{26, 3}}, }) assertQueryPositions(t, string(buff), "$..[?(float)]", []interface{}{ queryTestNode{4e-08, toml.Position{30, 1}}, }) tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") assertQueryPositions(t, string(buff), "$..[?(tree)]", []interface{}{ queryTestNode{ map[string]interface{}{ "name": "Tom Preston-Werner", "organization": "GitHub", "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "dob": tv, }, toml.Position{5, 1}, }, queryTestNode{ map[string]interface{}{ "server": "192.168.1.1", "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, "connection_max": int64(5000), "enabled": true, }, toml.Position{11, 1}, }, queryTestNode{ map[string]interface{}{ "alpha": map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, "beta": map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, }, toml.Position{17, 1}, }, queryTestNode{ map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, toml.Position{20, 3}, }, queryTestNode{ map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, toml.Position{24, 3}, }, queryTestNode{ map[string]interface{}{ "data": []interface{}{ []interface{}{"gamma", "delta"}, []interface{}{int64(1), int64(2)}, }, "score": 4e-08, }, toml.Position{28, 1}, }, }) assertQueryPositions(t, string(buff), "$..[?(time)]", []interface{}{ queryTestNode{tv, toml.Position{9, 1}}, }) assertQueryPositions(t, string(buff), "$..[?(bool)]", []interface{}{ queryTestNode{true, toml.Position{15, 1}}, }) } go-toml-1.9.5/query/query.go000066400000000000000000000100631416532417400157540ustar00rootroot00000000000000package query import ( "time" "github.com/pelletier/go-toml" ) // NodeFilterFn represents a user-defined filter function, for use with // Query.SetFilter(). // // The return value of the function must indicate if 'node' is to be included // at this stage of the TOML path. Returning true will include the node, and // returning false will exclude it. // // NOTE: Care should be taken to write script callbacks such that they are safe // to use from multiple goroutines. type NodeFilterFn func(node interface{}) bool // Result is the result of Executing a Query. type Result struct { items []interface{} positions []toml.Position } // appends a value/position pair to the result set. func (r *Result) appendResult(node interface{}, pos toml.Position) { r.items = append(r.items, node) r.positions = append(r.positions, pos) } // Values is a set of values within a Result. The order of values is not // guaranteed to be in document order, and may be different each time a query is // executed. func (r Result) Values() []interface{} { return r.items } // Positions is a set of positions for values within a Result. Each index // in Positions() corresponds to the entry in Value() of the same index. func (r Result) Positions() []toml.Position { return r.positions } // runtime context for executing query paths type queryContext struct { result *Result filters *map[string]NodeFilterFn lastPosition toml.Position } // generic path functor interface type pathFn interface { setNext(next pathFn) // it is the caller's responsibility to set the ctx.lastPosition before invoking call() // node can be one of: *toml.Tree, []*toml.Tree, or a scalar call(node interface{}, ctx *queryContext) } // A Query is the representation of a compiled TOML path. A Query is safe // for concurrent use by multiple goroutines. type Query struct { root pathFn tail pathFn filters *map[string]NodeFilterFn } func newQuery() *Query { return &Query{ root: nil, tail: nil, filters: &defaultFilterFunctions, } } func (q *Query) appendPath(next pathFn) { if q.root == nil { q.root = next } else { q.tail.setNext(next) } q.tail = next next.setNext(newTerminatingFn()) // init the next functor } // Compile compiles a TOML path expression. The returned Query can be used // to match elements within a Tree and its descendants. See Execute. func Compile(path string) (*Query, error) { return parseQuery(lexQuery(path)) } // Execute executes a query against a Tree, and returns the result of the query. func (q *Query) Execute(tree *toml.Tree) *Result { result := &Result{ items: []interface{}{}, positions: []toml.Position{}, } if q.root == nil { result.appendResult(tree, tree.GetPosition("")) } else { ctx := &queryContext{ result: result, filters: q.filters, } ctx.lastPosition = tree.Position() q.root.call(tree, ctx) } return result } // CompileAndExecute is a shorthand for Compile(path) followed by Execute(tree). func CompileAndExecute(path string, tree *toml.Tree) (*Result, error) { query, err := Compile(path) if err != nil { return nil, err } return query.Execute(tree), nil } // SetFilter sets a user-defined filter function. These may be used inside // "?(..)" query expressions to filter TOML document elements within a query. func (q *Query) SetFilter(name string, fn NodeFilterFn) { if q.filters == &defaultFilterFunctions { // clone the static table q.filters = &map[string]NodeFilterFn{} for k, v := range defaultFilterFunctions { (*q.filters)[k] = v } } (*q.filters)[name] = fn } var defaultFilterFunctions = map[string]NodeFilterFn{ "tree": func(node interface{}) bool { _, ok := node.(*toml.Tree) return ok }, "int": func(node interface{}) bool { _, ok := node.(int64) return ok }, "float": func(node interface{}) bool { _, ok := node.(float64) return ok }, "string": func(node interface{}) bool { _, ok := node.(string) return ok }, "time": func(node interface{}) bool { _, ok := node.(time.Time) return ok }, "bool": func(node interface{}) bool { _, ok := node.(bool) return ok }, } go-toml-1.9.5/query/query_test.go000066400000000000000000000100301416532417400170050ustar00rootroot00000000000000package query import ( "fmt" "testing" "github.com/pelletier/go-toml" ) func assertArrayContainsInOrder(t *testing.T, array []interface{}, objects ...interface{}) { if len(array) != len(objects) { t.Fatalf("array contains %d objects but %d are expected", len(array), len(objects)) } for i := 0; i < len(array); i++ { if array[i] != objects[i] { t.Fatalf("wanted '%s', have '%s'", objects[i], array[i]) } } } func checkQuery(t *testing.T, tree *toml.Tree, query string, objects ...interface{}) { results, err := CompileAndExecute(query, tree) if err != nil { t.Fatal("unexpected error:", err) } assertArrayContainsInOrder(t, results.Values(), objects...) } func TestQueryExample(t *testing.T) { config, _ := toml.Load(` [[book]] title = "The Stand" author = "Stephen King" [[book]] title = "For Whom the Bell Tolls" author = "Ernest Hemmingway" [[book]] title = "Neuromancer" author = "William Gibson" `) checkQuery(t, config, "$.book.author", "Stephen King", "Ernest Hemmingway", "William Gibson") checkQuery(t, config, "$.book[0].author", "Stephen King") checkQuery(t, config, "$.book[-1].author", "William Gibson") checkQuery(t, config, "$.book[1:].author", "Ernest Hemmingway", "William Gibson") checkQuery(t, config, "$.book[-1:].author", "William Gibson") checkQuery(t, config, "$.book[::2].author", "Stephen King", "William Gibson") checkQuery(t, config, "$.book[::-1].author", "William Gibson", "Ernest Hemmingway", "Stephen King") checkQuery(t, config, "$.book[:].author", "Stephen King", "Ernest Hemmingway", "William Gibson") checkQuery(t, config, "$.book[::].author", "Stephen King", "Ernest Hemmingway", "William Gibson") } func TestQueryReadmeExample(t *testing.T) { config, _ := toml.Load(` [postgres] user = "pelletier" password = "mypassword" `) checkQuery(t, config, "$..[user,password]", "pelletier", "mypassword") } func TestQueryPathNotPresent(t *testing.T) { config, _ := toml.Load(`a = "hello"`) query, err := Compile("$.foo.bar") if err != nil { t.Fatal("unexpected error:", err) } results := query.Execute(config) if err != nil { t.Fatalf("err should be nil. got %s instead", err) } if len(results.items) != 0 { t.Fatalf("no items should be matched. %d matched instead", len(results.items)) } } func ExampleNodeFilterFn_filterExample() { tree, _ := toml.Load(` [struct_one] foo = "foo" bar = "bar" [struct_two] baz = "baz" gorf = "gorf" `) // create a query that references a user-defined-filter query, _ := Compile("$[?(bazOnly)]") // define the filter, and assign it to the query query.SetFilter("bazOnly", func(node interface{}) bool { if tree, ok := node.(*toml.Tree); ok { return tree.Has("baz") } return false // reject all other node types }) // results contain only the 'struct_two' Tree query.Execute(tree) } func ExampleQuery_queryExample() { config, _ := toml.Load(` [[book]] title = "The Stand" author = "Stephen King" [[book]] title = "For Whom the Bell Tolls" author = "Ernest Hemmingway" [[book]] title = "Neuromancer" author = "William Gibson" `) // find and print all the authors in the document query, _ := Compile("$.book.author") authors := query.Execute(config) for _, name := range authors.Values() { fmt.Println(name) } } func TestTomlQuery(t *testing.T) { tree, err := toml.Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") if err != nil { t.Error(err) return } query, err := Compile("$.foo.bar") if err != nil { t.Error(err) return } result := query.Execute(tree) values := result.Values() if len(values) != 1 { t.Errorf("Expected resultset of 1, got %d instead: %v", len(values), values) } if tt, ok := values[0].(*toml.Tree); !ok { t.Errorf("Expected type of Tree: %T", values[0]) } else if tt.Get("a") != int64(1) { t.Errorf("Expected 'a' with a value 1: %v", tt.Get("a")) } else if tt.Get("b") != int64(2) { t.Errorf("Expected 'b' with a value 2: %v", tt.Get("b")) } } go-toml-1.9.5/query/tokens.go000066400000000000000000000025361416532417400161200ustar00rootroot00000000000000package query import ( "fmt" "strconv" "github.com/pelletier/go-toml" ) // Define tokens type tokenType int const ( eof = -(iota + 1) ) const ( tokenError tokenType = iota tokenEOF tokenKey tokenString tokenInteger tokenFloat tokenLeftBracket tokenRightBracket tokenLeftParen tokenRightParen tokenComma tokenColon tokenDollar tokenStar tokenQuestion tokenDot tokenDotDot ) var tokenTypeNames = []string{ "Error", "EOF", "Key", "String", "Integer", "Float", "[", "]", "(", ")", ",", ":", "$", "*", "?", ".", "..", } type token struct { toml.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) Int() int { if result, err := strconv.Atoi(t.val); err != nil { panic(err) } else { return result } } 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 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') } go-toml-1.9.5/token.go000066400000000000000000000037111416532417400145640ustar00rootroot00000000000000package 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') } go-toml-1.9.5/token_test.go000066400000000000000000000032331416532417400156220ustar00rootroot00000000000000package toml import "testing" func TestTokenStringer(t *testing.T) { var tests = []struct { tt tokenType expect string }{ {tokenError, "Error"}, {tokenEOF, "EOF"}, {tokenComment, "Comment"}, {tokenKey, "Key"}, {tokenString, "String"}, {tokenInteger, "Integer"}, {tokenTrue, "True"}, {tokenFalse, "False"}, {tokenFloat, "Float"}, {tokenEqual, "="}, {tokenLeftBracket, "["}, {tokenRightBracket, "]"}, {tokenLeftCurlyBrace, "{"}, {tokenRightCurlyBrace, "}"}, {tokenLeftParen, "("}, {tokenRightParen, ")"}, {tokenDoubleLeftBracket, "]]"}, {tokenDoubleRightBracket, "[["}, {tokenLocalDate, "LocalDate"}, {tokenLocalTime, "LocalTime"}, {tokenTimeOffset, "TimeOffset"}, {tokenKeyGroup, "KeyGroup"}, {tokenKeyGroupArray, "KeyGroupArray"}, {tokenComma, ","}, {tokenColon, ":"}, {tokenDollar, "$"}, {tokenStar, "*"}, {tokenQuestion, "?"}, {tokenDot, "."}, {tokenDotDot, ".."}, {tokenEOL, "EOL"}, {tokenEOL + 1, "Unknown"}, } for i, test := range tests { got := test.tt.String() if got != test.expect { t.Errorf("[%d] invalid string of token type; got %q, expected %q", i, got, test.expect) } } } func TestTokenString(t *testing.T) { var tests = []struct { tok token expect string }{ {token{Position{1, 1}, tokenEOF, ""}, "EOF"}, {token{Position{1, 1}, tokenError, "Δt"}, "Δt"}, {token{Position{1, 1}, tokenString, "bar"}, `"bar"`}, {token{Position{1, 1}, tokenString, "123456789012345"}, `"123456789012345"`}, } for i, test := range tests { got := test.tok.String() if got != test.expect { t.Errorf("[%d] invalid of string token; got %q, expected %q", i, got, test.expect) } } } go-toml-1.9.5/toml.go000066400000000000000000000323421416532417400144210ustar00rootroot00000000000000package 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) } go-toml-1.9.5/toml_test.go000066400000000000000000000131671416532417400154640ustar00rootroot00000000000000// Testing support for go-toml package toml import ( "reflect" "testing" ) func TestTomlHas(t *testing.T) { tree, _ := Load(` [test] key = "value" `) if !tree.Has("test.key") { t.Errorf("Has - expected test.key to exists") } if tree.Has("") { t.Errorf("Should return false if the key is not provided") } } func TestTomlGet(t *testing.T) { tree, _ := Load(` [test] key = "value" `) if tree.Get("") != tree { t.Errorf("Get should return the tree itself when given an empty path") } if tree.Get("test.key") != "value" { t.Errorf("Get should return the value") } if tree.Get(`\`) != nil { t.Errorf("should return nil when the key is malformed") } } func TestTomlGetArray(t *testing.T) { tree, _ := Load(` [test] key = ["one", "two"] key2 = [true, false, false] key3 = [1.5,2.5] `) if tree.GetArray("") != tree { t.Errorf("GetArray should return the tree itself when given an empty path") } expect := []string{"one", "two"} actual := tree.GetArray("test.key").([]string) if !reflect.DeepEqual(actual, expect) { t.Errorf("GetArray should return the []string value") } expect2 := []bool{true, false, false} actual2 := tree.GetArray("test.key2").([]bool) if !reflect.DeepEqual(actual2, expect2) { t.Errorf("GetArray should return the []bool value") } expect3 := []float64{1.5, 2.5} actual3 := tree.GetArray("test.key3").([]float64) if !reflect.DeepEqual(actual3, expect3) { t.Errorf("GetArray should return the []float64 value") } if tree.GetArray(`\`) != nil { t.Errorf("should return nil when the key is malformed") } } func TestTomlGetDefault(t *testing.T) { tree, _ := Load(` [test] key = "value" `) if tree.GetDefault("", "hello") != tree { t.Error("GetDefault should return the tree itself when given an empty path") } if tree.GetDefault("test.key", "hello") != "value" { t.Error("Get should return the value") } if tree.GetDefault("whatever", "hello") != "hello" { t.Error("GetDefault should return the default value if the key does not exist") } } func TestTomlHasPath(t *testing.T) { tree, _ := Load(` [test] key = "value" `) if !tree.HasPath([]string{"test", "key"}) { t.Errorf("HasPath - expected test.key to exists") } } func TestTomlDelete(t *testing.T) { tree, _ := Load(` key = "value" `) err := tree.Delete("key") if err != nil { t.Errorf("Delete - unexpected error while deleting key: %s", err.Error()) } if tree.Get("key") != nil { t.Errorf("Delete should have removed key but did not.") } } func TestTomlDeleteUnparsableKey(t *testing.T) { tree, _ := Load(` key = "value" `) err := tree.Delete(".") if err == nil { t.Errorf("Delete should error") } } func TestTomlDeleteNestedKey(t *testing.T) { tree, _ := Load(` [foo] [foo.bar] key = "value" `) err := tree.Delete("foo.bar.key") if err != nil { t.Errorf("Error while deleting nested key: %s", err.Error()) } if tree.Get("key") != nil { t.Errorf("Delete should have removed nested key but did not.") } } func TestTomlDeleteNonexistentNestedKey(t *testing.T) { tree, _ := Load(` [foo] [foo.bar] key = "value" `) err := tree.Delete("foo.not.there.key") if err == nil { t.Errorf("Delete should have thrown an error trying to delete key in nonexistent tree") } } func TestTomlGetPath(t *testing.T) { node := newTree() //TODO: set other node data for idx, item := range []struct { Path []string Expected *Tree }{ { // empty path test []string{}, node, }, } { result := node.GetPath(item.Path) if result != item.Expected { t.Errorf("GetPath[%d] %v - expected %v, got %v instead.", idx, item.Path, item.Expected, result) } } tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") if tree.GetPath([]string{"whatever"}) != nil { t.Error("GetPath should return nil when the key does not exist") } } func TestTomlGetArrayPath(t *testing.T) { for idx, item := range []struct { Name string Path []string Make func() (tree *Tree, expected interface{}) }{ { Name: "empty", Path: []string{}, Make: func() (tree *Tree, expected interface{}) { tree = newTree() expected = tree return }, }, { Name: "int64", Path: []string{"a"}, Make: func() (tree *Tree, expected interface{}) { var err error tree, err = Load(`a = [1,2,3]`) if err != nil { panic(err) } expected = []int64{1, 2, 3} return }, }, } { t.Run(item.Name, func(t *testing.T) { tree, expected := item.Make() result := tree.GetArrayPath(item.Path) if !reflect.DeepEqual(result, expected) { t.Errorf("GetArrayPath[%d] %v - expected %#v, got %#v instead.", idx, item.Path, expected, result) } }) } tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") if tree.GetArrayPath([]string{"whatever"}) != nil { t.Error("GetArrayPath should return nil when the key does not exist") } } func TestTomlFromMap(t *testing.T) { simpleMap := map[string]interface{}{"hello": 42} tree, err := TreeFromMap(simpleMap) if err != nil { t.Fatal("unexpected error:", err) } if tree.Get("hello") != int64(42) { t.Fatal("hello should be 42, not", tree.Get("hello")) } } func TestLoadBytesBOM(t *testing.T) { payloads := [][]byte{ []byte("\xFE\xFFhello=1"), []byte("\xFF\xFEhello=1"), []byte("\xEF\xBB\xBFhello=1"), []byte("\x00\x00\xFE\xFFhello=1"), []byte("\xFF\xFE\x00\x00hello=1"), } for _, data := range payloads { tree, err := LoadBytes(data) if err != nil { t.Fatal("unexpected error:", err, "for:", data) } v := tree.Get("hello") if v != int64(1) { t.Fatal("hello should be 1, not", v) } } } go-toml-1.9.5/toml_testgen_support_test.go000066400000000000000000000057141416532417400210100ustar00rootroot00000000000000// This is a support file for toml_testgen_test.go package toml import ( "bytes" "encoding/json" "fmt" "reflect" "testing" "time" ) func testgenInvalid(t *testing.T, input string) { t.Logf("Input TOML:\n%s", input) tree, err := Load(input) if err != nil { return } typedTree := testgenTranslate(*tree) buf := new(bytes.Buffer) if err := json.NewEncoder(buf).Encode(typedTree); err != nil { return } t.Fatalf("test did not fail. resulting tree:\n%s", buf.String()) } func testgenValid(t *testing.T, input string, jsonRef string) { t.Logf("Input TOML:\n%s", input) tree, err := Load(input) if err != nil { t.Fatalf("failed parsing toml: %s", err) } typedTree := testgenTranslate(*tree) buf := new(bytes.Buffer) if err := json.NewEncoder(buf).Encode(typedTree); err != nil { t.Fatalf("failed translating to JSON: %s", err) } var jsonTest interface{} if err := json.NewDecoder(buf).Decode(&jsonTest); err != nil { t.Logf("translated JSON:\n%s", buf.String()) t.Fatalf("failed decoding translated JSON: %s", err) } var jsonExpected interface{} if err := json.NewDecoder(bytes.NewBufferString(jsonRef)).Decode(&jsonExpected); err != nil { t.Logf("reference JSON:\n%s", jsonRef) t.Fatalf("failed decoding reference JSON: %s", err) } if !reflect.DeepEqual(jsonExpected, jsonTest) { t.Logf("Diff:\n%#+v\n%#+v", jsonExpected, jsonTest) t.Fatal("parsed TOML tree is different than expected structure") } } func testgenTranslate(tomlData interface{}) interface{} { switch orig := tomlData.(type) { case map[string]interface{}: typed := make(map[string]interface{}, len(orig)) for k, v := range orig { typed[k] = testgenTranslate(v) } return typed case *Tree: return testgenTranslate(*orig) case Tree: keys := orig.Keys() typed := make(map[string]interface{}, len(keys)) for _, k := range keys { typed[k] = testgenTranslate(orig.GetPath([]string{k})) } return typed case []*Tree: typed := make([]map[string]interface{}, len(orig)) for i, v := range orig { typed[i] = testgenTranslate(v).(map[string]interface{}) } return typed case []map[string]interface{}: typed := make([]map[string]interface{}, len(orig)) for i, v := range orig { typed[i] = testgenTranslate(v).(map[string]interface{}) } return typed case []interface{}: typed := make([]interface{}, len(orig)) for i, v := range orig { typed[i] = testgenTranslate(v) } return testgenTag("array", typed) case time.Time: return testgenTag("datetime", orig.Format("2006-01-02T15:04:05Z")) case bool: return testgenTag("bool", fmt.Sprintf("%v", orig)) case int64: return testgenTag("integer", fmt.Sprintf("%d", orig)) case float64: return testgenTag("float", fmt.Sprintf("%v", orig)) case string: return testgenTag("string", orig) } panic(fmt.Sprintf("Unknown type: %T", tomlData)) } func testgenTag(typeName string, data interface{}) map[string]interface{} { return map[string]interface{}{ "type": typeName, "value": data, } } go-toml-1.9.5/toml_testgen_test.go000066400000000000000000000531131416532417400172100ustar00rootroot00000000000000// Generated by tomltestgen for toml-test ref 39e37e6 on 2019-03-19T23:58:45-07:00 package toml import ( "testing" ) func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { input := `no-leads = 1987-7-05T17:45:00Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { input := `no-secs = 1987-07-05T17:45Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedNoT(t *testing.T) { input := `no-t = 1987-07-0517:45:00Z` testgenInvalid(t, input) } func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { input := `with-milli = 1987-07-5T17:45:00.12Z` testgenInvalid(t, input) } func TestInvalidDuplicateKeyTable(t *testing.T) { input := `[fruit] type = "apple" [fruit.type] apple = "yes"` testgenInvalid(t, input) } func TestInvalidDuplicateKeys(t *testing.T) { input := `dupe = false dupe = true` testgenInvalid(t, input) } func TestInvalidDuplicateTables(t *testing.T) { input := `[a] [a]` testgenInvalid(t, input) } func TestInvalidEmptyImplicitTable(t *testing.T) { input := `[naughty..naughty]` testgenInvalid(t, input) } func TestInvalidEmptyTable(t *testing.T) { input := `[]` testgenInvalid(t, input) } func TestInvalidFloatNoLeadingZero(t *testing.T) { input := `answer = .12345 neganswer = -.12345` testgenInvalid(t, input) } func TestInvalidFloatNoTrailingDigits(t *testing.T) { input := `answer = 1. neganswer = -1.` testgenInvalid(t, input) } func TestInvalidKeyEmpty(t *testing.T) { input := ` = 1` testgenInvalid(t, input) } func TestInvalidKeyHash(t *testing.T) { input := `a# = 1` testgenInvalid(t, input) } func TestInvalidKeyNewline(t *testing.T) { input := `a = 1` testgenInvalid(t, input) } func TestInvalidKeyOpenBracket(t *testing.T) { input := `[abc = 1` testgenInvalid(t, input) } func TestInvalidKeySingleOpenBracket(t *testing.T) { input := `[` testgenInvalid(t, input) } func TestInvalidKeySpace(t *testing.T) { input := `a b = 1` testgenInvalid(t, input) } func TestInvalidKeyStartBracket(t *testing.T) { input := `[a] [xyz = 5 [b]` testgenInvalid(t, input) } func TestInvalidKeyTwoEquals(t *testing.T) { input := `key= = 1` testgenInvalid(t, input) } func TestInvalidStringBadByteEscape(t *testing.T) { input := `naughty = "\xAg"` testgenInvalid(t, input) } func TestInvalidStringBadEscape(t *testing.T) { input := `invalid-escape = "This string has a bad \a escape character."` testgenInvalid(t, input) } func TestInvalidStringByteEscapes(t *testing.T) { input := `answer = "\x33"` testgenInvalid(t, input) } func TestInvalidStringNoClose(t *testing.T) { input := `no-ending-quote = "One time, at band camp` testgenInvalid(t, input) } func TestInvalidTableArrayImplicit(t *testing.T) { input := "# This test is a bit tricky. It should fail because the first use of\n" + "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + "# must be a table. The alternative would be quite weird. Namely, it wouldn't\n" + "# comply with the TOML spec: \"Each double-bracketed sub-table will belong to \n" + "# the most *recently* defined table element *above* it.\"\n" + "#\n" + "# This is in contrast to the *valid* test, table-array-implicit where\n" + "# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared\n" + "# later. (Although, `[albums]` could be.)\n" + "[[albums.songs]]\n" + "name = \"Glory Days\"\n" + "\n" + "[[albums]]\n" + "name = \"Born in the USA\"\n" testgenInvalid(t, input) } func TestInvalidTableArrayMalformedBracket(t *testing.T) { input := `[[albums] name = "Born to Run"` testgenInvalid(t, input) } func TestInvalidTableArrayMalformedEmpty(t *testing.T) { input := `[[]] name = "Born to Run"` testgenInvalid(t, input) } func TestInvalidTableEmpty(t *testing.T) { input := `[]` testgenInvalid(t, input) } func TestInvalidTableNestedBracketsClose(t *testing.T) { input := `[a]b] zyx = 42` testgenInvalid(t, input) } func TestInvalidTableNestedBracketsOpen(t *testing.T) { input := `[a[b] zyx = 42` testgenInvalid(t, input) } func TestInvalidTableWhitespace(t *testing.T) { input := `[invalid key]` testgenInvalid(t, input) } func TestInvalidTableWithPound(t *testing.T) { input := `[key#group] answer = 42` testgenInvalid(t, input) } func TestInvalidTextAfterArrayEntries(t *testing.T) { input := `array = [ "Is there life after an array separator?", No "Entry" ]` testgenInvalid(t, input) } func TestInvalidTextAfterInteger(t *testing.T) { input := `answer = 42 the ultimate answer?` testgenInvalid(t, input) } func TestInvalidTextAfterString(t *testing.T) { input := `string = "Is there life after strings?" No.` testgenInvalid(t, input) } func TestInvalidTextAfterTable(t *testing.T) { input := `[error] this shouldn't be here` testgenInvalid(t, input) } func TestInvalidTextBeforeArraySeparator(t *testing.T) { input := `array = [ "Is there life before an array separator?" No, "Entry" ]` testgenInvalid(t, input) } func TestInvalidTextInArray(t *testing.T) { input := `array = [ "Entry 1", I don't belong, "Entry 2", ]` testgenInvalid(t, input) } func TestValidArrayEmpty(t *testing.T) { input := `thevoid = [[[[[]]]]]` jsonRef := `{ "thevoid": { "type": "array", "value": [ {"type": "array", "value": [ {"type": "array", "value": [ {"type": "array", "value": [ {"type": "array", "value": []} ]} ]} ]} ]} }` testgenValid(t, input, jsonRef) } func TestValidArrayNospaces(t *testing.T) { input := `ints = [1,2,3]` jsonRef := `{ "ints": { "type": "array", "value": [ {"type": "integer", "value": "1"}, {"type": "integer", "value": "2"}, {"type": "integer", "value": "3"} ] } }` testgenValid(t, input, jsonRef) } func TestValidArraysHetergeneous(t *testing.T) { input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` jsonRef := `{ "mixed": { "type": "array", "value": [ {"type": "array", "value": [ {"type": "integer", "value": "1"}, {"type": "integer", "value": "2"} ]}, {"type": "array", "value": [ {"type": "string", "value": "a"}, {"type": "string", "value": "b"} ]}, {"type": "array", "value": [ {"type": "float", "value": "1.1"}, {"type": "float", "value": "2.1"} ]} ] } }` testgenValid(t, input, jsonRef) } func TestValidArraysNested(t *testing.T) { input := `nest = [["a"], ["b"]]` jsonRef := `{ "nest": { "type": "array", "value": [ {"type": "array", "value": [ {"type": "string", "value": "a"} ]}, {"type": "array", "value": [ {"type": "string", "value": "b"} ]} ] } }` testgenValid(t, input, jsonRef) } func TestValidArrays(t *testing.T) { input := `ints = [1, 2, 3] floats = [1.1, 2.1, 3.1] strings = ["a", "b", "c"] dates = [ 1987-07-05T17:45:00Z, 1979-05-27T07:32:00Z, 2006-06-01T11:00:00Z, ]` jsonRef := `{ "ints": { "type": "array", "value": [ {"type": "integer", "value": "1"}, {"type": "integer", "value": "2"}, {"type": "integer", "value": "3"} ] }, "floats": { "type": "array", "value": [ {"type": "float", "value": "1.1"}, {"type": "float", "value": "2.1"}, {"type": "float", "value": "3.1"} ] }, "strings": { "type": "array", "value": [ {"type": "string", "value": "a"}, {"type": "string", "value": "b"}, {"type": "string", "value": "c"} ] }, "dates": { "type": "array", "value": [ {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, {"type": "datetime", "value": "2006-06-01T11:00:00Z"} ] } }` testgenValid(t, input, jsonRef) } func TestValidBool(t *testing.T) { input := `t = true f = false` jsonRef := `{ "f": {"type": "bool", "value": "false"}, "t": {"type": "bool", "value": "true"} }` testgenValid(t, input, jsonRef) } func TestValidCommentsEverywhere(t *testing.T) { input := `# Top comment. # Top comment. # Top comment. # [no-extraneous-groups-please] [group] # Comment answer = 42 # Comment # no-extraneous-keys-please = 999 # Inbetween comment. more = [ # Comment # What about multiple # comments? # Can you handle it? # # Evil. # Evil. 42, 42, # Comments within arrays are fun. # What about multiple # comments? # Can you handle it? # # Evil. # Evil. # ] Did I fool you? ] # Hopefully not.` jsonRef := `{ "group": { "answer": {"type": "integer", "value": "42"}, "more": { "type": "array", "value": [ {"type": "integer", "value": "42"}, {"type": "integer", "value": "42"} ] } } }` testgenValid(t, input, jsonRef) } func TestValidDatetime(t *testing.T) { input := `bestdayever = 1987-07-05T17:45:00Z` jsonRef := `{ "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} }` testgenValid(t, input, jsonRef) } func TestValidEmpty(t *testing.T) { input := `` jsonRef := `{}` testgenValid(t, input, jsonRef) } func TestValidExample(t *testing.T) { input := `best-day-ever = 1987-07-05T17:45:00Z [numtheory] boring = false perfection = [6, 28, 496]` jsonRef := `{ "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, "numtheory": { "boring": {"type": "bool", "value": "false"}, "perfection": { "type": "array", "value": [ {"type": "integer", "value": "6"}, {"type": "integer", "value": "28"}, {"type": "integer", "value": "496"} ] } } }` testgenValid(t, input, jsonRef) } func TestValidFloat(t *testing.T) { input := `pi = 3.14 negpi = -3.14` jsonRef := `{ "pi": {"type": "float", "value": "3.14"}, "negpi": {"type": "float", "value": "-3.14"} }` testgenValid(t, input, jsonRef) } func TestValidImplicitAndExplicitAfter(t *testing.T) { input := `[a.b.c] answer = 42 [a] better = 43` jsonRef := `{ "a": { "better": {"type": "integer", "value": "43"}, "b": { "c": { "answer": {"type": "integer", "value": "42"} } } } }` testgenValid(t, input, jsonRef) } func TestValidImplicitAndExplicitBefore(t *testing.T) { input := `[a] better = 43 [a.b.c] answer = 42` jsonRef := `{ "a": { "better": {"type": "integer", "value": "43"}, "b": { "c": { "answer": {"type": "integer", "value": "42"} } } } }` testgenValid(t, input, jsonRef) } func TestValidImplicitGroups(t *testing.T) { input := `[a.b.c] answer = 42` jsonRef := `{ "a": { "b": { "c": { "answer": {"type": "integer", "value": "42"} } } } }` testgenValid(t, input, jsonRef) } func TestValidInteger(t *testing.T) { input := `answer = 42 neganswer = -42` jsonRef := `{ "answer": {"type": "integer", "value": "42"}, "neganswer": {"type": "integer", "value": "-42"} }` testgenValid(t, input, jsonRef) } func TestValidKeyEqualsNospace(t *testing.T) { input := `answer=42` jsonRef := `{ "answer": {"type": "integer", "value": "42"} }` testgenValid(t, input, jsonRef) } func TestValidKeySpace(t *testing.T) { input := `"a b" = 1` jsonRef := `{ "a b": {"type": "integer", "value": "1"} }` testgenValid(t, input, jsonRef) } func TestValidKeySpecialChars(t *testing.T) { input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" jsonRef := "{\n" + " \"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": {\n" + " \"type\": \"integer\", \"value\": \"1\"\n" + " }\n" + "}\n" testgenValid(t, input, jsonRef) } func TestValidLongFloat(t *testing.T) { input := `longpi = 3.141592653589793 neglongpi = -3.141592653589793` jsonRef := `{ "longpi": {"type": "float", "value": "3.141592653589793"}, "neglongpi": {"type": "float", "value": "-3.141592653589793"} }` testgenValid(t, input, jsonRef) } func TestValidLongInteger(t *testing.T) { input := `answer = 9223372036854775807 neganswer = -9223372036854775808` jsonRef := `{ "answer": {"type": "integer", "value": "9223372036854775807"}, "neganswer": {"type": "integer", "value": "-9223372036854775808"} }` testgenValid(t, input, jsonRef) } func TestValidMultilineString(t *testing.T) { input := `multiline_empty_one = """""" multiline_empty_two = """ """ multiline_empty_three = """\ """ multiline_empty_four = """\ \ \ """ equivalent_one = "The quick brown fox jumps over the lazy dog." equivalent_two = """ The quick brown \ fox jumps over \ the lazy dog.""" equivalent_three = """\ The quick brown \ fox jumps over \ the lazy dog.\ """` jsonRef := `{ "multiline_empty_one": { "type": "string", "value": "" }, "multiline_empty_two": { "type": "string", "value": "" }, "multiline_empty_three": { "type": "string", "value": "" }, "multiline_empty_four": { "type": "string", "value": "" }, "equivalent_one": { "type": "string", "value": "The quick brown fox jumps over the lazy dog." }, "equivalent_two": { "type": "string", "value": "The quick brown fox jumps over the lazy dog." }, "equivalent_three": { "type": "string", "value": "The quick brown fox jumps over the lazy dog." } }` testgenValid(t, input, jsonRef) } func TestValidRawMultilineString(t *testing.T) { input := `oneline = '''This string has a ' quote character.''' firstnl = ''' This string has a ' quote character.''' multiline = ''' This string has ' a quote character and more than one newline in it.'''` jsonRef := `{ "oneline": { "type": "string", "value": "This string has a ' quote character." }, "firstnl": { "type": "string", "value": "This string has a ' quote character." }, "multiline": { "type": "string", "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." } }` testgenValid(t, input, jsonRef) } func TestValidRawString(t *testing.T) { input := `backspace = 'This string has a \b backspace character.' tab = 'This string has a \t tab character.' newline = 'This string has a \n new line character.' formfeed = 'This string has a \f form feed character.' carriage = 'This string has a \r carriage return character.' slash = 'This string has a \/ slash character.' backslash = 'This string has a \\ backslash character.'` jsonRef := `{ "backspace": { "type": "string", "value": "This string has a \\b backspace character." }, "tab": { "type": "string", "value": "This string has a \\t tab character." }, "newline": { "type": "string", "value": "This string has a \\n new line character." }, "formfeed": { "type": "string", "value": "This string has a \\f form feed character." }, "carriage": { "type": "string", "value": "This string has a \\r carriage return character." }, "slash": { "type": "string", "value": "This string has a \\/ slash character." }, "backslash": { "type": "string", "value": "This string has a \\\\ backslash character." } }` testgenValid(t, input, jsonRef) } func TestValidStringEmpty(t *testing.T) { input := `answer = ""` jsonRef := `{ "answer": { "type": "string", "value": "" } }` testgenValid(t, input, jsonRef) } func TestValidStringEscapes(t *testing.T) { input := `backspace = "This string has a \b backspace character." tab = "This string has a \t tab character." newline = "This string has a \n new line character." formfeed = "This string has a \f form feed character." carriage = "This string has a \r carriage return character." quote = "This string has a \" quote character." backslash = "This string has a \\ backslash character." notunicode1 = "This string does not have a unicode \\u escape." notunicode2 = "This string does not have a unicode \u005Cu escape." notunicode3 = "This string does not have a unicode \\u0075 escape." notunicode4 = "This string does not have a unicode \\\u0075 escape."` jsonRef := `{ "backspace": { "type": "string", "value": "This string has a \u0008 backspace character." }, "tab": { "type": "string", "value": "This string has a \u0009 tab character." }, "newline": { "type": "string", "value": "This string has a \u000A new line character." }, "formfeed": { "type": "string", "value": "This string has a \u000C form feed character." }, "carriage": { "type": "string", "value": "This string has a \u000D carriage return character." }, "quote": { "type": "string", "value": "This string has a \u0022 quote character." }, "backslash": { "type": "string", "value": "This string has a \u005C backslash character." }, "notunicode1": { "type": "string", "value": "This string does not have a unicode \\u escape." }, "notunicode2": { "type": "string", "value": "This string does not have a unicode \u005Cu escape." }, "notunicode3": { "type": "string", "value": "This string does not have a unicode \\u0075 escape." }, "notunicode4": { "type": "string", "value": "This string does not have a unicode \\\u0075 escape." } }` testgenValid(t, input, jsonRef) } func TestValidStringSimple(t *testing.T) { input := `answer = "You are not drinking enough whisky."` jsonRef := `{ "answer": { "type": "string", "value": "You are not drinking enough whisky." } }` testgenValid(t, input, jsonRef) } func TestValidStringWithPound(t *testing.T) { input := `pound = "We see no # comments here." poundcomment = "But there are # some comments here." # Did I # mess you up?` jsonRef := `{ "pound": {"type": "string", "value": "We see no # comments here."}, "poundcomment": { "type": "string", "value": "But there are # some comments here." } }` testgenValid(t, input, jsonRef) } func TestValidTableArrayImplicit(t *testing.T) { input := `[[albums.songs]] name = "Glory Days"` jsonRef := `{ "albums": { "songs": [ {"name": {"type": "string", "value": "Glory Days"}} ] } }` testgenValid(t, input, jsonRef) } func TestValidTableArrayMany(t *testing.T) { input := `[[people]] first_name = "Bruce" last_name = "Springsteen" [[people]] first_name = "Eric" last_name = "Clapton" [[people]] first_name = "Bob" last_name = "Seger"` jsonRef := `{ "people": [ { "first_name": {"type": "string", "value": "Bruce"}, "last_name": {"type": "string", "value": "Springsteen"} }, { "first_name": {"type": "string", "value": "Eric"}, "last_name": {"type": "string", "value": "Clapton"} }, { "first_name": {"type": "string", "value": "Bob"}, "last_name": {"type": "string", "value": "Seger"} } ] }` testgenValid(t, input, jsonRef) } func TestValidTableArrayNest(t *testing.T) { input := `[[albums]] name = "Born to Run" [[albums.songs]] name = "Jungleland" [[albums.songs]] name = "Meeting Across the River" [[albums]] name = "Born in the USA" [[albums.songs]] name = "Glory Days" [[albums.songs]] name = "Dancing in the Dark"` jsonRef := `{ "albums": [ { "name": {"type": "string", "value": "Born to Run"}, "songs": [ {"name": {"type": "string", "value": "Jungleland"}}, {"name": {"type": "string", "value": "Meeting Across the River"}} ] }, { "name": {"type": "string", "value": "Born in the USA"}, "songs": [ {"name": {"type": "string", "value": "Glory Days"}}, {"name": {"type": "string", "value": "Dancing in the Dark"}} ] } ] }` testgenValid(t, input, jsonRef) } func TestValidTableArrayOne(t *testing.T) { input := `[[people]] first_name = "Bruce" last_name = "Springsteen"` jsonRef := `{ "people": [ { "first_name": {"type": "string", "value": "Bruce"}, "last_name": {"type": "string", "value": "Springsteen"} } ] }` testgenValid(t, input, jsonRef) } func TestValidTableEmpty(t *testing.T) { input := `[a]` jsonRef := `{ "a": {} }` testgenValid(t, input, jsonRef) } func TestValidTableSubEmpty(t *testing.T) { input := `[a] [a.b]` jsonRef := `{ "a": { "b": {} } }` testgenValid(t, input, jsonRef) } func TestValidTableWhitespace(t *testing.T) { input := `["valid key"]` jsonRef := `{ "valid key": {} }` testgenValid(t, input, jsonRef) } func TestValidTableWithPound(t *testing.T) { input := `["key#group"] answer = 42` jsonRef := `{ "key#group": { "answer": {"type": "integer", "value": "42"} } }` testgenValid(t, input, jsonRef) } func TestValidUnicodeEscape(t *testing.T) { input := `answer4 = "\u03B4" answer8 = "\U000003B4"` jsonRef := `{ "answer4": {"type": "string", "value": "\u03B4"}, "answer8": {"type": "string", "value": "\u03B4"} }` testgenValid(t, input, jsonRef) } func TestValidUnicodeLiteral(t *testing.T) { input := `answer = "δ"` jsonRef := `{ "answer": {"type": "string", "value": "δ"} }` testgenValid(t, input, jsonRef) } go-toml-1.9.5/tomlpub.go000066400000000000000000000026061416532417400151300ustar00rootroot00000000000000package 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 } go-toml-1.9.5/tomltree_create.go000066400000000000000000000105601416532417400166220ustar00rootroot00000000000000package 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 } go-toml-1.9.5/tomltree_create_test.go000066400000000000000000000145231416532417400176640ustar00rootroot00000000000000package toml import ( "reflect" "strconv" "testing" "time" ) type customString string type stringer struct{} func (s stringer) String() string { return "stringer" } func validate(t *testing.T, path string, object interface{}) { switch o := object.(type) { case *Tree: for key, tree := range o.values { validate(t, path+"."+key, tree) } case []*Tree: for index, tree := range o { validate(t, path+"."+strconv.Itoa(index), tree) } case *tomlValue: switch o.value.(type) { case int64, uint64, bool, string, float64, time.Time, []int64, []uint64, []bool, []string, []float64, []time.Time: default: t.Fatalf("tomlValue at key %s containing incorrect type %T", path, o.value) } default: t.Fatalf("value at key %s is of incorrect type %T", path, object) } t.Logf("validation ok %s as %T", path, object) } func validateTree(t *testing.T, tree *Tree) { validate(t, "", tree) } func TestTreeCreateToTree(t *testing.T) { data := map[string]interface{}{ "a_string": "bar", "an_int": 42, "time": time.Now(), "int8": int8(2), "int16": int16(2), "int32": int32(2), "uint8": uint8(2), "uint16": uint16(2), "uint32": uint32(2), "float32": float32(2), "a_bool": false, "stringer": stringer{}, "nested": map[string]interface{}{ "foo": "bar", }, "array": []string{"a", "b", "c"}, "array_uint": []uint{uint(1), uint(2)}, "array_table": []map[string]interface{}{{"sub_map": 52}}, "array_times": []time.Time{time.Now(), time.Now()}, "map_times": map[string]time.Time{"now": time.Now()}, "custom_string_map_key": map[customString]interface{}{customString("custom"): "custom"}, } tree, err := TreeFromMap(data) if err != nil { t.Fatal("unexpected error:", err) } validateTree(t, tree) } func TestTreeCreateToTreeInvalidLeafType(t *testing.T) { _, err := TreeFromMap(map[string]interface{}{"foo": t}) expected := "cannot convert type *testing.T to Tree" if err.Error() != expected { t.Fatalf("expected error %s, got %s", expected, err.Error()) } } func TestTreeCreateToTreeInvalidMapKeyType(t *testing.T) { _, err := TreeFromMap(map[string]interface{}{"foo": map[int]interface{}{2: 1}}) expected := "map key needs to be a string, not int (int)" if err.Error() != expected { t.Fatalf("expected error %s, got %s", expected, err.Error()) } } func TestTreeCreateToTreeInvalidArrayMemberType(t *testing.T) { _, err := TreeFromMap(map[string]interface{}{"foo": []*testing.T{t}}) expected := "cannot convert type *testing.T to Tree" if err.Error() != expected { t.Fatalf("expected error %s, got %s", expected, err.Error()) } } func TestTreeCreateToTreeInvalidTableGroupType(t *testing.T) { _, err := TreeFromMap(map[string]interface{}{"foo": []map[string]interface{}{{"hello": t}}}) expected := "cannot convert type *testing.T to Tree" if err.Error() != expected { t.Fatalf("expected error %s, got %s", expected, err.Error()) } } func TestRoundTripArrayOfTables(t *testing.T) { orig := "\n[[stuff]]\n name = \"foo\"\n things = [\"a\", \"b\"]\n" tree, err := Load(orig) if err != nil { t.Fatalf("unexpected error: %s", err) } m := tree.ToMap() tree, err = TreeFromMap(m) if err != nil { t.Fatalf("unexpected error: %s", err) } want := orig got := tree.String() if got != want { t.Errorf("want:\n%s\ngot:\n%s", want, got) } } func TestTomlSliceOfSlice(t *testing.T) { tree, err := Load(` hosts=[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ] `) m := tree.ToMap() tree, err = TreeFromMap(m) if err != nil { t.Error("should not error", err) } type Struct struct { Hosts [][]string } var actual Struct tree.Unmarshal(&actual) expected := Struct{Hosts: [][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}} if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) } } func TestTomlSliceOfSliceOfSlice(t *testing.T) { tree, err := Load(` hosts=[[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ]] `) m := tree.ToMap() tree, err = TreeFromMap(m) if err != nil { t.Error("should not error", err) } type Struct struct { Hosts [][][]string } var actual Struct tree.Unmarshal(&actual) expected := Struct{Hosts: [][][]string{[][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}}} if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) } } func TestTomlSliceOfSliceInt(t *testing.T) { tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `) m := tree.ToMap() tree, err = TreeFromMap(m) if err != nil { t.Error("should not error", err) } type Struct struct { Hosts [][]int } var actual Struct err = tree.Unmarshal(&actual) if err != nil { t.Error("should not error", err) } expected := Struct{Hosts: [][]int{[]int{1, 2, 3}, []int{4, 5, 6}}} if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) } } func TestTomlSliceOfSliceInt64(t *testing.T) { tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `) m := tree.ToMap() tree, err = TreeFromMap(m) if err != nil { t.Error("should not error", err) } type Struct struct { Hosts [][]int64 } var actual Struct err = tree.Unmarshal(&actual) if err != nil { t.Error("should not error", err) } expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}, []int64{4, 5, 6}}} if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) } } func TestTomlSliceOfSliceInt64FromMap(t *testing.T) { tree, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{int32(1), int8(2), 3}}}) if err != nil { t.Error("should not error", err) } type Struct struct { Hosts [][]int64 } var actual Struct err = tree.Unmarshal(&actual) if err != nil { t.Error("should not error", err) } expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}}} if !reflect.DeepEqual(actual, expected) { t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) } } func TestTomlSliceOfSliceError(t *testing.T) { // make Codecov happy _, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{1, 2, []struct{}{}}}}) expected := "cannot convert type []struct {} to Tree" if err.Error() != expected { t.Fatalf("unexpected error: %s", err) } } go-toml-1.9.5/tomltree_write.go000066400000000000000000000325751416532417400165230ustar00rootroot00000000000000package 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 } go-toml-1.9.5/tomltree_write_test.go000066400000000000000000000264771416532417400175660ustar00rootroot00000000000000package toml import ( "bytes" "errors" "fmt" "reflect" "strings" "testing" "time" ) type failingWriter struct { failAt int written int buffer bytes.Buffer } func (f *failingWriter) Write(p []byte) (n int, err error) { count := len(p) toWrite := f.failAt - (count + f.written) if toWrite < 0 { toWrite = 0 } if toWrite > count { f.written += count f.buffer.Write(p) return count, nil } f.buffer.Write(p[:toWrite]) f.written = f.failAt return toWrite, fmt.Errorf("failingWriter failed after writing %d bytes", f.written) } func assertErrorString(t *testing.T, expected string, err error) { expectedErr := errors.New(expected) if err == nil || err.Error() != expectedErr.Error() { t.Errorf("expecting error %s, but got %s instead", expected, err) } } func TestTreeWriteToEmptyTable(t *testing.T) { doc := `[[empty-tables]] [[empty-tables]]` toml, err := Load(doc) if err != nil { t.Fatal("Unexpected Load error:", err) } tomlString, err := toml.ToTomlString() if err != nil { t.Fatal("Unexpected ToTomlString error:", err) } expected := ` [[empty-tables]] [[empty-tables]] ` if tomlString != expected { t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString) } } func TestTreeWriteToTomlString(t *testing.T) { toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" } points = { x = 1, y = 2 }`) if err != nil { t.Fatal("Unexpected error:", err) } tomlString, _ := toml.ToTomlString() reparsedTree, err := Load(tomlString) assertTree(t, reparsedTree, err, map[string]interface{}{ "name": map[string]interface{}{ "first": "Tom", "last": "Preston-Werner", }, "points": map[string]interface{}{ "x": int64(1), "y": int64(2), }, }) } func TestTreeWriteToTomlStringSimple(t *testing.T) { tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n") if err != nil { t.Errorf("Test failed to parse: %v", err) return } result, err := tree.ToTomlString() if err != nil { t.Errorf("Unexpected error: %s", err) } expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n" if result != expected { t.Errorf("Expected got '%s', expected '%s'", result, expected) } } func TestTreeWriteToTomlStringKeysOrders(t *testing.T) { for i := 0; i < 100; i++ { tree, _ := Load(` foobar = true bar = "baz" foo = 1 [qux] foo = 1 bar = "baz2"`) stringRepr, _ := tree.ToTomlString() t.Log("Intermediate string representation:") t.Log(stringRepr) r := strings.NewReader(stringRepr) toml, err := LoadReader(r) if err != nil { t.Fatal("Unexpected error:", err) } assertTree(t, toml, err, map[string]interface{}{ "foobar": true, "bar": "baz", "foo": 1, "qux": map[string]interface{}{ "foo": 1, "bar": "baz2", }, }) } } func testMaps(t *testing.T, actual, expected map[string]interface{}) { if !reflect.DeepEqual(actual, expected) { t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual) } } func TestTreeWriteToMapSimple(t *testing.T) { tree, _ := Load("a = 42\nb = 17") expected := map[string]interface{}{ "a": int64(42), "b": int64(17), } testMaps(t, tree.ToMap(), expected) } func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) { tree := Tree{values: map[string]interface{}{"foo": int8(1)}} _, err := tree.ToTomlString() assertErrorString(t, "invalid value type at foo: int8", err) } func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) { tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} _, err := tree.ToTomlString() assertErrorString(t, "unsupported value type int8: 1", err) } func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) { tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} _, err := tree.ToTomlString() assertErrorString(t, "unsupported value type int8: 1", err) } func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) { toml, _ := Load(`a = 2`) writer := failingWriter{failAt: 0, written: 0} _, err := toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 0 bytes", err) } func TestTreeWriteToFailingWriterInTable(t *testing.T) { toml, _ := Load(` [b] a = 2`) writer := failingWriter{failAt: 2, written: 0} _, err := toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 2 bytes", err) writer = failingWriter{failAt: 13, written: 0} _, err = toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 13 bytes", err) } func TestTreeWriteToFailingWriterInArray(t *testing.T) { toml, _ := Load(` [[b]] a = 2`) writer := failingWriter{failAt: 2, written: 0} _, err := toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 2 bytes", err) writer = failingWriter{failAt: 15, written: 0} _, err = toml.WriteTo(&writer) assertErrorString(t, "failingWriter failed after writing 15 bytes", err) } func TestTreeWriteToMapExampleFile(t *testing.T) { tree, _ := LoadFile("example.toml") expected := map[string]interface{}{ "title": "TOML Example", "owner": map[string]interface{}{ "name": "Tom Preston-Werner", "organization": "GitHub", "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), }, "database": map[string]interface{}{ "server": "192.168.1.1", "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, "connection_max": int64(5000), "enabled": true, }, "servers": map[string]interface{}{ "alpha": map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, "beta": map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, }, "clients": map[string]interface{}{ "data": []interface{}{ []interface{}{"gamma", "delta"}, []interface{}{int64(1), int64(2)}, }, "score": 4e-08, }, } testMaps(t, tree.ToMap(), expected) } func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) { tree, _ := Load(` [[menu.main]] a = "menu 1" b = "menu 2" [[menu.main]] c = "menu 3" d = "menu 4"`) expected := map[string]interface{}{ "menu": map[string]interface{}{ "main": []interface{}{ map[string]interface{}{"a": "menu 1", "b": "menu 2"}, map[string]interface{}{"c": "menu 3", "d": "menu 4"}, }, }, } treeMap := tree.ToMap() testMaps(t, treeMap, expected) } func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) { tree, _ := Load(` [params] language_tabs = [ { key = "shell", name = "Shell" }, { key = "ruby", name = "Ruby" }, { key = "python", name = "Python" } ]`) expected := map[string]interface{}{ "params": map[string]interface{}{ "language_tabs": []interface{}{ map[string]interface{}{ "key": "shell", "name": "Shell", }, map[string]interface{}{ "key": "ruby", "name": "Ruby", }, map[string]interface{}{ "key": "python", "name": "Python", }, }, }, } treeMap := tree.ToMap() testMaps(t, treeMap, expected) } func TestTreeWriteToMapWithTableInMixedArray(t *testing.T) { tree, _ := Load(`a = [ "foo", [ "bar", {baz = "quux"}, ], [ {a = "b"}, {c = "d"}, ], ]`) expected := map[string]interface{}{ "a": []interface{}{ "foo", []interface{}{ "bar", map[string]interface{}{ "baz": "quux", }, }, []interface{}{ map[string]interface{}{ "a": "b", }, map[string]interface{}{ "c": "d", }, }, }, } treeMap := tree.ToMap() testMaps(t, treeMap, expected) } func TestTreeWriteToFloat(t *testing.T) { tree, err := Load(`a = 3.0`) if err != nil { t.Fatal(err) } str, err := tree.ToTomlString() if err != nil { t.Fatal(err) } expected := `a = 3.0` if strings.TrimSpace(str) != strings.TrimSpace(expected) { t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) } } func TestTreeWriteToSpecialFloat(t *testing.T) { expected := `a = +inf b = -inf c = nan` tree, err := Load(expected) if err != nil { t.Fatal(err) } str, err := tree.ToTomlString() if err != nil { t.Fatal(err) } if strings.TrimSpace(str) != strings.TrimSpace(expected) { t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) } } func TestOrderedEmptyTrees(t *testing.T) { type val struct { Key string `toml:"key"` } type structure struct { First val `toml:"first"` Empty []val `toml:"empty"` } input := structure{First: val{Key: "value"}} buf := new(bytes.Buffer) err := NewEncoder(buf).Order(OrderPreserve).Encode(input) if err != nil { t.Fatal("failed to encode input") } expected := ` [first] key = "value" ` if expected != buf.String() { t.Fatal("expected and encoded body aren't equal: ", expected, buf.String()) } } func TestOrderedNonIncreasedLine(t *testing.T) { type NiceMap map[string]string type Manifest struct { NiceMap `toml:"dependencies"` Build struct { BuildCommand string `toml:"build-command"` } `toml:"build"` } test := &Manifest{} test.Build.BuildCommand = "test" buf := new(bytes.Buffer) if err := NewEncoder(buf).Order(OrderPreserve).Encode(test); err != nil { panic(err) } expected := ` [dependencies] [build] build-command = "test" ` if expected != buf.String() { t.Fatal("expected and encoded body aren't equal: ", expected, buf.String()) } } func TestIssue290(t *testing.T) { tomlString := `[table] "127.0.0.1" = "value" "127.0.0.1:8028" = "value" "character encoding" = "value" "ʎǝʞ" = "value"` t1, err := Load(tomlString) if err != nil { t.Fatal("load err:", err) } s, err := t1.ToTomlString() if err != nil { t.Fatal("ToTomlString err:", err) } _, err = Load(s) if err != nil { t.Fatal("reload err:", err) } } func BenchmarkTreeToTomlString(b *testing.B) { toml, err := Load(sampleHard) if err != nil { b.Fatal("Unexpected error:", err) } for i := 0; i < b.N; i++ { _, err := toml.ToTomlString() if err != nil { b.Fatal(err) } } } var sampleHard = `# Test file for TOML # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate # This part you'll really hate [the] test_string = "You'll hate me after this - #" # " Annoying, isn't it? [the.hard] test_array = [ "] ", " # "] # ] There you go, parse this! test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] # You didn't think it'd as easy as chucking out the last #, did you? another_test_string = " Same thing, but with a string #" harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" # Things will get harder [the.hard."bit#"] "what?" = "You don't think some user won't do that?" multi_line_array = [ "]", # ] Oh yes I did ] # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test #[error] if you didn't catch this, your parser is broken #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this #array = [ # "This might most likely happen in multiline arrays", # Like here, # "or here, # and here" # ] End of array comment, forgot the # #number = 3.14 pi <--again forgot the # ` go-toml-1.9.5/tomltree_writepub.go000066400000000000000000000005351416532417400172210ustar00rootroot00000000000000package 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) }