pax_global_header00006660000000000000000000000064134617427620014526gustar00rootroot0000000000000052 comment=728039f679cbcd4f6a54e080d2219a4c4928c546 go-toml-1.4.0/000077500000000000000000000000001346174276200131065ustar00rootroot00000000000000go-toml-1.4.0/.circleci/000077500000000000000000000000001346174276200147415ustar00rootroot00000000000000go-toml-1.4.0/.circleci/config.yml000066400000000000000000000141561346174276200167400ustar00rootroot00000000000000version: 2.1 executors: golang: parameters: version: type: string docker: - image: circleci/golang:<< parameters.version >> commands: get_deps: description: "Get go dependencies" steps: - run: go get github.com/jstemmer/go-junit-report run_test: description: "Run unit tests for a go module" parameters: test_name: type: string module: type: string coverage: default: false type: boolean allow_fail: type: boolean default: false steps: - run: name: "Run tests for <>" command: | TEST_DIR="/tmp/test-results/<>" mkdir -p ${TEST_DIR} trap "go-junit-report ${TEST_DIR}/go-test-report.xml" EXIT go test <> -race -v \ <<# parameters.coverage >>-coverprofile=/tmp/workspace/coverage.txt -covermode=atomic<> \ | tee /tmp/test-results/go-test.out <<# parameters.allow_fail >>|| true<> jobs: go: parameters: version: type: string allow_fail: type: boolean default: false executor: name: golang version: "<>" working_directory: /go/src/github.com/pelletier/go-toml environment: GO111MODULE: "on" steps: - checkout - run: mkdir -p /tmp/workspace - run: go fmt ./... <<# parameters.allow_fail >>|| true<> - get_deps - run_test: test_name: "go-toml" module: "github.com/pelletier/go-toml" coverage: true allow_fail: <> - run_test: test_name: "tomljson" module: "github.com/pelletier/go-toml/cmd/tomljson" allow_fail: <> - run_test: test_name: "tomll" module: "github.com/pelletier/go-toml/cmd/tomll" allow_fail: <> - run_test: test_name: "query" module: "github.com/pelletier/go-toml/query" allow_fail: <> - store_test_results: path: /tmp/test-results codecov: docker: - image: "circleci/golang:1.12" steps: - attach_workspace: at: /tmp/workspace - run: name: "upload to codecov" working_directory: /tmp/workspace command: | curl https://codecov.io/bash > codecov.sh bash codecov.sh -v docker: docker: - image: "circleci/golang:1.12" steps: - checkout - setup_remote_docker: docker_layer_caching: true - run: docker build -t pelletier/go-toml:$CIRCLE_SHA1 . - run: name: "Publish docker image" command: | if [ "${CIRCLE_PR_REPONAME}" == "" ]; then IMAGE_NAME="pelletier/go-toml" IMAGE_SHA_TAG="${IMAGE_NAME}:$CIRCLE_SHA1" if [ "${CIRCLE_BRANCH}" = "master" ]; then docker login -u $DOCKER_USER -p $DOCKER_PASS docker tag ${IMAGE_SHA_TAG} ${IMAGE_NAME}:latest docker push ${IMAGE_NAME}:latest fi if [ "${CIRCLE_TAG}" != "" ]; then docker login -u $DOCKER_USER -p $DOCKER_PASS docker tag ${IMAGE_SHA_TAG} ${IMAGE_NAME}:${CIRCLE_TAG} docker push ${IMAGE_NAME}:${CIRCLE_TAG} fi else echo "not pushing docker image for forked repo" fi workflows: version: 2.1 build: jobs: - go: name: "go1_11" version: "1.11" - go: name: "go1_12" version: "1.12" post-steps: - run: go tool cover -html=/tmp/workspace/coverage.txt -o coverage.html - store_artifacts: path: /tmp/workspace/coverage.txt - store_artifacts: path: coverage.html - persist_to_workspace: root: /tmp/workspace paths: - coverage.txt - go: name: "gotip" version: "1.12" # use as base allow_fail: true pre-steps: - restore_cache: keys: - go-tip-source - run: name: "Compile go tip" command: | if [ ! -d "/tmp/go" ]; then git clone https://go.googlesource.com/go /tmp/go fi cd /tmp/go git checkout master git pull cd src ./make.bash echo 'export PATH="/tmp/go/bin:$PATH"' >> $BASH_ENV - run: go version - save_cache: key: go-tip-source paths: - "/tmp/go" - codecov: requires: - go1_11 - go1_12 - docker: requires: - codecov go-toml-1.4.0/.dockerignore000066400000000000000000000000461346174276200155620ustar00rootroot00000000000000cmd/tomll/tomll cmd/tomljson/tomljson go-toml-1.4.0/.github/000077500000000000000000000000001346174276200144465ustar00rootroot00000000000000go-toml-1.4.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001346174276200166315ustar00rootroot00000000000000go-toml-1.4.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000010401346174276200213160ustar00rootroot00000000000000--- 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 (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.4.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000010601346174276200223530ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. go-toml-1.4.0/.gitignore000066400000000000000000000001461346174276200150770ustar00rootroot00000000000000test_program/test_program_bin fuzz/ cmd/tomll/tomll cmd/tomljson/tomljson cmd/tomltestgen/tomltestgen go-toml-1.4.0/.travis.yml000066400000000000000000000011031346174276200152120ustar00rootroot00000000000000sudo: false language: go go: - 1.11.x - 1.12.x - tip matrix: allow_failures: - go: tip fast_finish: true env: - GO111MODULE=on script: - if [ -n "$(go fmt ./...)" ]; then exit 1; fi - go test github.com/pelletier/go-toml -race -coverprofile=coverage.txt -covermode=atomic - go test github.com/pelletier/go-toml/cmd/tomljson - go test github.com/pelletier/go-toml/cmd/tomll - go test github.com/pelletier/go-toml/query - ./benchmark.sh $TRAVIS_BRANCH https://github.com/$TRAVIS_REPO_SLUG.git after_success: - bash <(curl -s https://codecov.io/bash) go-toml-1.4.0/CONTRIBUTING.md000066400000000000000000000116001346174276200153350ustar00rootroot00000000000000## 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 [GoDoc][godoc]. 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 [godoc]: https://godoc.org/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.4.0/Dockerfile000066400000000000000000000004101346174276200150730ustar00rootroot00000000000000FROM 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 go-toml-1.4.0/LICENSE000066400000000000000000000021211346174276200141070ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 - 2017 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. go-toml-1.4.0/PULL_REQUEST_TEMPLATE.md000066400000000000000000000003131346174276200167040ustar00rootroot00000000000000**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.4.0/README.md000066400000000000000000000101021346174276200143570ustar00rootroot00000000000000# go-toml Go library for the [TOML](https://github.com/mojombo/toml) format. This library supports TOML version [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) [![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/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://travis-ci.org/pelletier/go-toml.svg?branch=master)](https://travis-ci.org/pelletier/go-toml) [![Windows Build status](https://ci.appveyor.com/api/projects/status/4aepwwjori266hkt/branch/master?svg=true)](https://ci.appveyor.com/project/pelletier/go-toml/branch/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) ## 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 * Mashaling 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.Println("Query result %d: %v", ii, item) } ``` ## Documentation The documentation and additional examples are available at [godoc.org](http://godoc.org/github.com/pelletier/go-toml). ## Tools Go-toml provides two handy command line tools: * `tomll`: Reads TOML files and lint 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 ``` ### Docker image Those tools are also availble 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). Read [LICENSE](LICENSE). go-toml-1.4.0/appveyor.yml000066400000000000000000000015311346174276200154760ustar00rootroot00000000000000version: "{build}" # Source Config clone_folder: c:\gopath\src\github.com\pelletier\go-toml # Build host environment: GOPATH: c:\gopath DEPTESTBYPASS501: 1 GOVERSION: 1.12 GO111MODULE: on init: - git config --global core.autocrlf input # Build install: # Install the specific Go version. - rmdir c:\go /s /q - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi - msiexec /i go%GOVERSION%.windows-amd64.msi /q - choco install bzr - set Path=c:\go\bin;c:\gopath\bin;C:\Program Files (x86)\Bazaar\;C:\Program Files\Mercurial\%Path% - go version - go env build: false deploy: false test_script: - go test github.com/pelletier/go-toml - go test github.com/pelletier/go-toml/cmd/tomljson - go test github.com/pelletier/go-toml/cmd/tomll - go test github.com/pelletier/go-toml/query go-toml-1.4.0/benchmark.json000066400000000000000000000071121346174276200157340ustar00rootroot00000000000000{ "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.4.0/benchmark.sh000077500000000000000000000015521346174276200154020ustar00rootroot00000000000000#!/bin/bash set -e 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 go install 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} popd >/dev/null echo "" echo "=== local" go test -bench=. -benchmem | tee ${local_benchmark} echo "" echo "=== diff" benchstat -delta-test=none ${ref_benchmark} ${local_benchmark}go-toml-1.4.0/benchmark.toml000066400000000000000000000121721346174276200157400ustar00rootroot00000000000000################################################################################ ## 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.4.0/benchmark.yml000066400000000000000000000035501346174276200155660ustar00rootroot00000000000000--- 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.4.0/benchmark_test.go000066400000000000000000000060111346174276200164240ustar00rootroot00000000000000package toml import ( "bytes" "encoding/json" "io/ioutil" "testing" "time" burntsushi "github.com/BurntSushi/toml" yaml "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 := 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.ResetTimer() for i := 0; i < b.N; i++ { target := benchmarkDoc{} err := 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.4.0/cmd/000077500000000000000000000000001346174276200136515ustar00rootroot00000000000000go-toml-1.4.0/cmd/tomljson/000077500000000000000000000000001346174276200155165ustar00rootroot00000000000000go-toml-1.4.0/cmd/tomljson/main.go000066400000000000000000000030651346174276200167750ustar00rootroot00000000000000// 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.4.0/cmd/tomljson/main_test.go000066400000000000000000000040641346174276200200340ustar00rootroot00000000000000package 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.4.0/cmd/tomll/000077500000000000000000000000001346174276200150005ustar00rootroot00000000000000go-toml-1.4.0/cmd/tomll/main.go000066400000000000000000000030331346174276200162520ustar00rootroot00000000000000// 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 ( "flag" "fmt" "io" "io/ioutil" "os" "github.com/pelletier/go-toml" ) func main() { 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.") } flag.Parse() // read from stdin and print to stdout if flag.NArg() == 0 { s, err := lintReader(os.Stdin) 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) if err != nil { io.WriteString(os.Stderr, err.Error()) os.Exit(-1) } ioutil.WriteFile(filename, []byte(s), 0644) } } } func lintFile(filename string) (string, error) { tree, err := toml.LoadFile(filename) if err != nil { return "", err } return tree.String(), nil } func lintReader(r io.Reader) (string, error) { tree, err := toml.LoadReader(r) if err != nil { return "", err } return tree.String(), nil } go-toml-1.4.0/cmd/tomltestgen/000077500000000000000000000000001346174276200162165ustar00rootroot00000000000000go-toml-1.4.0/cmd/tomltestgen/main.go000066400000000000000000000111151346174276200174700ustar00rootroot00000000000000// 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.4.0/doc.go000066400000000000000000000014251346174276200142040ustar00rootroot00000000000000// 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.4.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.4.0/doc_test.go000066400000000000000000000044671346174276200152540ustar00rootroot00000000000000// code examples for godoc package toml_test import ( "fmt" "log" 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 } go-toml-1.4.0/example-crlf.toml000066400000000000000000000012051346174276200163600ustar00rootroot00000000000000# 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 go-toml-1.4.0/example.toml000066400000000000000000000011501346174276200154330ustar00rootroot00000000000000# 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 go-toml-1.4.0/fuzz.go000066400000000000000000000007301346174276200144330ustar00rootroot00000000000000// +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.4.0/fuzz.sh000077500000000000000000000004431346174276200144440ustar00rootroot00000000000000#! /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.4.0/go.mod000066400000000000000000000002311346174276200142100ustar00rootroot00000000000000module github.com/pelletier/go-toml go 1.12 require ( github.com/BurntSushi/toml v0.3.1 github.com/davecgh/go-spew v1.1.1 gopkg.in/yaml.v2 v2.2.2 ) go-toml-1.4.0/go.sum000066400000000000000000000011311346174276200142350ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= go-toml-1.4.0/keysparsing.go000066400000000000000000000047231346174276200160020ustar00rootroot00000000000000// Parsing keys handling both bare and quoted keys. package toml import ( "errors" "fmt" "unicode" ) // 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 == '-' || unicode.IsNumber(r) } go-toml-1.4.0/keysparsing_test.go000066400000000000000000000041111346174276200170300ustar00rootroot00000000000000package 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.4.0/lexer.go000066400000000000000000000343011346174276200145550ustar00rootroot00000000000000// 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" "regexp" "strconv" "strings" ) var dateRegexp *regexp.Regexp // Define state functions type tomlLexStateFn func() tomlLexStateFn // Define lexer type tomlLexer struct { inputIdx int input []rune // Textual source currentTokenStart int currentTokenStop int tokens []token depth int 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 '[': 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 l.depth > 0 { return l.lexRvalue } 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 '[': l.depth++ return l.lexLeftBracket case ']': l.depth-- 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 l.depth == 0 { return l.lexVoid } return l.lexRvalue case '_': return l.errorf("cannot start number with underscore") } 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 } possibleDate := l.peekString(35) dateMatch := dateRegexp.FindString(possibleDate) if dateMatch != "" { l.fastForward(len(dateMatch)) return l.lexDate } if next == '+' || next == '-' || isDigit(next) { return l.lexNumber } if isAlphanumeric(next) { return l.lexKey } return l.errorf("no value can start with %c", next) } l.emit(tokenEOF) return nil } func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn { l.next() l.emit(tokenLeftCurlyBrace) return l.lexRvalue } func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn { l.next() l.emit(tokenRightCurlyBrace) return l.lexRvalue } func (l *tomlLexer) lexDate() tomlLexStateFn { l.emit(tokenDate) 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) 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 { growingString := "" 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()) } growingString += "\"" + str + "\"" l.next() continue } else if r == '\'' { l.next() str, err := l.lexLiteralStringAsString(`'`, false) if err != nil { return l.errorf(err.Error()) } growingString += "'" + str + "'" l.next() continue } else if r == '\n' { return l.errorf("keys cannot contain new lines") } else if isSpace(r) { break } else if r == '.' { // skip } else if !isValidBareChar(r) { return l.errorf("keys cannot contain %c character", r) } growingString += string(r) l.next() } l.emitWithValue(tokenKey, growingString) 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) return l.lexRvalue } func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) { growingString := "" 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 growingString, nil } next := l.peek() if next == eof { break } growingString += string(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) { growingString := "" if discardLeadingNewLine { if l.follow("\r\n") { l.skip() l.skip() } else if l.peek() == '\n' { l.skip() } } for { if l.follow(terminator) { return growingString, 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 '"': growingString += "\"" l.next() case 'n': growingString += "\n" l.next() case 'b': growingString += "\b" l.next() case 'f': growingString += "\f" l.next() case '/': growingString += "/" l.next() case 't': growingString += "\t" l.next() case 'r': growingString += "\r" l.next() case '\\': growingString += "\\" l.next() case 'u': l.next() code := "" for i := 0; i < 4; i++ { c := l.peek() if !isHexDigit(c) { return "", errors.New("unfinished unicode escape") } l.next() code = code + string(c) } intcode, err := strconv.ParseInt(code, 16, 32) if err != nil { return "", errors.New("invalid unicode escape: \\u" + code) } growingString += string(rune(intcode)) case 'U': l.next() code := "" for i := 0; i < 8; i++ { c := l.peek() if !isHexDigit(c) { return "", errors.New("unfinished unicode escape") } l.next() code = code + string(c) } intcode, err := strconv.ParseInt(code, 16, 64) if err != nil { return "", errors.New("invalid unicode escape: \\U" + code) } growingString += string(rune(intcode)) default: return "", errors.New("invalid escape sequence: \\" + string(l.peek())) } } else { r := l.peek() if 0x00 <= r && r <= 0x1F && !(acceptNewLines && (r == '\n' || r == '\r')) { return "", fmt.Errorf("unescaped control character %U", r) } l.next() growingString += string(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) 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() } } func init() { dateRegexp = regexp.MustCompile(`^\d{1,4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})`) } // 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.4.0/lexer_test.go000066400000000000000000000540251346174276200156210ustar00rootroot00000000000000package toml import ( "reflect" "testing" ) func testFlow(t *testing.T, input string, expectedFlow []token) { tokens := lexToml([]byte(input)) if !reflect.DeepEqual(tokens, expectedFlow) { t.Fatal("Different flows. Expected\n", expectedFlow, "\nGot:\n", tokens) } } 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, 15}, tokenRightBracket, "]"}, {Position{1, 16}, 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, 6}, 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 TestDateRegexp(t *testing.T) { if dateRegexp.FindString("1979-05-27T07:32:00Z") == "" { t.Error("basic lexing") } if dateRegexp.FindString("1979-05-27T00:32:00-07:00") == "" { t.Error("offset lexing") } if dateRegexp.FindString("1979-05-27T00:32:00.999999-07:00") == "" { t.Error("nano precision lexing") } } func TestKeyEqualDate(t *testing.T) { testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenDate, "1979-05-27T07:32:00Z"}, {Position{1, 27}, tokenEOF, ""}, }) testFlow(t, "foo = 1979-05-27T00:32:00-07:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenDate, "1979-05-27T00:32:00-07:00"}, {Position{1, 32}, tokenEOF, ""}, }) testFlow(t, "foo = 1979-05-27T00:32:00.999999-07:00", []token{ {Position{1, 1}, tokenKey, "foo"}, {Position{1, 5}, tokenEqual, "="}, {Position{1, 7}, tokenDate, "1979-05-27T00:32:00.999999-07:00"}, {Position{1, 39}, tokenEOF, ""}, }) } 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, "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 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 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 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.4.0/marshal.go000066400000000000000000000532701346174276200150730ustar00rootroot00000000000000package toml import ( "bytes" "errors" "fmt" "io" "reflect" "sort" "strconv" "strings" "time" ) const ( tagFieldName = "toml" tagFieldComment = "comment" tagCommented = "commented" tagMultiline = "multiline" tagDefault = "default" ) type tomlOpts struct { name string comment string commented bool multiline 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 defaultValue string } var annotationDefault = annotation{ tag: tagFieldName, comment: tagFieldComment, commented: tagCommented, multiline: tagMultiline, 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() // 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 mtype == timeType || isCustomMarshaler(mtype) default: return false } } // Check if the given marshal type maps to a Tree slice func isTreeSlice(mtype reflect.Type) bool { switch mtype.Kind() { case reflect.Slice: return !isOtherSlice(mtype) default: return false } } // Check if the given marshal type maps to a non-Tree slice func isOtherSlice(mtype reflect.Type) bool { switch mtype.Kind() { case reflect.Ptr: return isOtherSlice(mtype.Elem()) case reflect.Slice: return isPrimitive(mtype.Elem()) || isOtherSlice(mtype.Elem()) default: return false } } // Check if the given marshal type maps to a Tree func isTree(mtype reflect.Type) bool { switch mtype.Kind() { 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() } // Marshaler is the interface implemented by types that // can marshal themselves into valid TOML. type Marshaler interface { MarshalTOML() ([]byte, 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.Time time.Time{}, 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 } // 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, } } // 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 } // 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 } func (e *Encoder) marshal(v interface{}) ([]byte, error) { mtype := reflect.TypeOf(v) 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") } 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) } 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) 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: for i := 0; i < mtype.NumField(); i++ { mtypef, mvalf := mtype.Field(i), mval.Field(i) opts := tomlOptions(mtypef, e.annotation) if opts.include && (!opts.omitempty || !isZero(mvalf)) { val, err := e.valueToToml(mtypef.Type, mvalf) if err != nil { return nil, err } tval.SetWithOptions(opts.name, SetOptions{ Comment: opts.comment, Commented: opts.commented, Multiline: opts.multiline, }, 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) val, err := e.valueToToml(mtype.Elem(), mvalf) if err != nil { return nil, err } if e.quoteMapKeys { keyStr, err := tomlValueStringRepresentation(key.String(), "", e.arraysOneElementPerLine) if err != nil { return nil, err } tval.SetPath([]string{keyStr}, val) } else { tval.Set(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) { e.line++ if mtype.Kind() == reflect.Ptr { return e.valueToToml(mtype.Elem(), mval.Elem()) } switch { case isCustomMarshaler(mtype): return callCustomMarshaler(mval) case isTree(mtype): return e.valueToTree(mtype, mval) case isTreeSlice(mtype): return e.valueToTreeSlice(mtype, mval) case isOtherSlice(mtype): return e.valueToOtherSlice(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().(time.Time), nil default: return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) } } } // 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 := NewEncoder(&buf).Encode(t) return buf.Bytes(), err } // 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 } // 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 } func (d *Decoder) unmarshal(v interface{}) error { mtype := reflect.TypeOf(v) 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: default: return errors.New("only a pointer to struct or map can be unmarshaled from TOML") } sval, err := d.valueFromTree(elem, d.tval) if err != nil { return err } reflect.ValueOf(v).Elem().Set(sval) return nil } // Convert toml tree to marshal struct or map, using marshal type func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree) (reflect.Value, error) { if mtype.Kind() == reflect.Ptr { return d.unwrapPointer(mtype, tval) } var mval reflect.Value switch mtype.Kind() { case reflect.Struct: mval = reflect.New(mtype).Elem() for i := 0; i < mtype.NumField(); i++ { mtypef := mtype.Field(i) an := annotation{tag: d.tagName} opts := tomlOptions(mtypef, an) if opts.include { baseKey := opts.name keysToTry := []string{ baseKey, strings.ToLower(baseKey), strings.ToTitle(baseKey), strings.ToLower(string(baseKey[0])) + baseKey[1:], } found := false for _, key := range keysToTry { exists := tval.Has(key) if !exists { continue } val := tval.Get(key) mvalf, err := d.valueFromToml(mtypef.Type, val) if err != nil { return mval, formatError(err, tval.GetPosition(key)) } mval.Field(i).Set(mvalf) found = true break } if !found && opts.defaultValue != "" { mvalf := mval.Field(i) var val interface{} var err error switch mvalf.Kind() { case reflect.Bool: val, err = strconv.ParseBool(opts.defaultValue) if err != nil { return mval.Field(i), err } case reflect.Int: val, err = strconv.Atoi(opts.defaultValue) if err != nil { return mval.Field(i), err } case reflect.String: val = opts.defaultValue case reflect.Int64: val, err = strconv.ParseInt(opts.defaultValue, 10, 64) if err != nil { return mval.Field(i), err } case reflect.Float64: val, err = strconv.ParseFloat(opts.defaultValue, 64) if err != nil { return mval.Field(i), err } default: return mval.Field(i), fmt.Errorf("unsuported field type for default option") } mval.Field(i).Set(reflect.ValueOf(val)) } } } case reflect.Map: mval = reflect.MakeMap(mtype) for _, key := range tval.Keys() { // TODO: path splits key val := tval.GetPath([]string{key}) mvalf, err := d.valueFromToml(mtype.Elem(), val) if err != nil { return mval, formatError(err, tval.GetPosition(key)) } mval.SetMapIndex(reflect.ValueOf(key).Convert(mtype.Key()), mvalf) } } 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 := reflect.MakeSlice(mtype, len(tval), len(tval)) for i := 0; i < len(tval); i++ { val, err := d.valueFromTree(mtype.Elem(), tval[i]) 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) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { mval := reflect.MakeSlice(mtype, len(tval), len(tval)) for i := 0; i < len(tval); i++ { val, err := d.valueFromToml(mtype.Elem(), tval[i]) if err != nil { return mval, err } mval.Index(i).Set(val) } return mval, nil } // Convert toml value to marshal value, using marshal type func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}) (reflect.Value, error) { if mtype.Kind() == reflect.Ptr { return d.unwrapPointer(mtype, tval) } switch t := tval.(type) { case *Tree: if isTree(mtype) { return d.valueFromTree(mtype, t) } return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval) case []*Tree: if isTreeSlice(mtype) { return d.valueFromTreeSlice(mtype, t) } return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval) case []interface{}: if isOtherSlice(mtype) { return d.valueFromOtherSlice(mtype, t) } return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) default: switch mtype.Kind() { case reflect.Bool, reflect.Struct: val := reflect.ValueOf(tval) // if this passes for when mtype is reflect.Struct, tval is a time.Time 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) { 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(mtype).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) { return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) } if 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(uint64(val.Convert(mtype).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) { 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(mtype).Float()) { return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) } return val.Convert(mtype), nil 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{}) (reflect.Value, error) { val, err := d.valueFromToml(mtype.Elem(), tval) if err != nil { return reflect.ValueOf(nil), err } mval := reflect.New(mtype.Elem()) mval.Elem().Set(val) return mval, nil } 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)) defaultValue := vf.Tag.Get(tagDefault) result := tomlOpts{ name: vf.Name, comment: comment, commented: commented, multiline: multiline, 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], " ") } } 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.Map: fallthrough case reflect.Array: fallthrough case reflect.Slice: 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) } go-toml-1.4.0/marshal_OrderPreserve_Map_test.toml000066400000000000000000000003041346174276200221320ustar00rootroot00000000000000title = "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" go-toml-1.4.0/marshal_OrderPreserve_test.toml000066400000000000000000000011211346174276200213330ustar00rootroot00000000000000title = "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 int = 5000 string = "Bite me" date = 1979-05-27T07:32:00Z [[subdoclist]] name = "List.First" [[subdoclist]] name = "List.Second" go-toml-1.4.0/marshal_test.go000066400000000000000000001050041346174276200161230ustar00rootroot00000000000000package toml import ( "bytes" "encoding/json" "fmt" "io/ioutil" "reflect" "strings" "testing" "time" ) type basicMarshalTestStruct struct { String string `toml:"Zstring"` StringList []string `toml:"Ystrlist"` Sub basicMarshalTestSubStruct `toml:"Xsubdoc"` SubList []basicMarshalTestSubStruct `toml:"Wsublist"` } type basicMarshalTestSubStruct struct { String2 string } var basicTestData = basicMarshalTestStruct{ String: "Hello", StringList: []string{"Howdy", "Hey There"}, Sub: basicMarshalTestSubStruct{"One"}, SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, } var basicTestToml = []byte(`Ystrlist = ["Howdy","Hey There"] Zstring = "Hello" [[Wsublist]] String2 = "Two" [[Wsublist]] String2 = "Three" [Xsubdoc] String2 = "One" `) var basicTestTomlOrdered = []byte(`Zstring = "Hello" Ystrlist = ["Howdy","Hey There"] [Xsubdoc] String2 = "One" [[Wsublist]] String2 = "Two" [[Wsublist]] String2 = "Three" `) 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 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 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"` Float float32 `toml:"float"` 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), Float: 123.4, 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) } expected, _ := ioutil.ReadFile("marshal_test.toml") if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestDocMarshalOrdered(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Order(OrderPreserve).Encode(docData) if err != nil { t.Fatal(err) } expected, _ := ioutil.ReadFile("marshal_OrderPreserve_test.toml") 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 TestDocMarshalMaps(t *testing.T) { result, err := Marshal(mapTestDoc) if err != nil { t.Fatal(err) } expected, _ := ioutil.ReadFile("marshal_OrderPreserve_Map_test.toml") if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestDocMarshalOrderedMaps(t *testing.T) { var result bytes.Buffer err := NewEncoder(&result).Order(OrderPreserve).Encode(mapTestDoc) if err != nil { t.Fatal(err) } expected, _ := ioutil.ReadFile("marshal_OrderPreserve_Map_test.toml") 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 TestDocMarshalPointer(t *testing.T) { result, err := Marshal(&docData) if err != nil { t.Fatal(err) } expected, _ := ioutil.ReadFile("marshal_test.toml") if !bytes.Equal(result, expected) { t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } func TestDocUnmarshal(t *testing.T) { result := testDoc{} tomlData, _ := ioutil.ReadFile("marshal_test.toml") err := Unmarshal(tomlData, &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) { result := testDocSubs{} tree, _ := LoadFile("marshal_test.toml") subTree := tree.Get("subdoc").(*Tree) 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{ {"integer", 2, 0}, {"time", time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), 0}, {"stringlist", []string{"hello", "hi"}, 1}, {"timelist", []time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, {"objectlist", []tomlTypeCheckTest{}, 2}, {"object", tomlTypeCheckTest{}, 3}, } for _, test := range tests { expected := []bool{false, false, false, false} expected[test.typ] = true result := []bool{ isPrimitive(reflect.TypeOf(test.item)), isOtherSlice(reflect.TypeOf(test.item)), isTreeSlice(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 { FirsName string LastName string } func (c customMarshaler) MarshalTOML() ([]byte, error) { fullName := fmt.Sprintf("%s %s", c.FirsName, c.LastName) return []byte(fullName), nil } var customMarshalerData = customMarshaler{FirsName: "Sally", LastName: "Fields"} var customMarshalerToml = []byte(`Sally Fields`) var nestedCustomMarshalerData = customMarshalerParent{ Self: customMarshaler{FirsName: "Maiku", LastName: "Suteda"}, Friends: []customMarshaler{customMarshalerData}, } var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] me = "Maiku Suteda" `) 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) } } func TestNestedCustomMarshaler(t *testing.T) { result, err := Marshal(nestedCustomMarshalerData) if err != nil { t.Fatal(err) } expected := nestedCustomMarshalerToml if !bytes.Equal(result, expected) { t.Errorf("Bad nested custom marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) } } 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) } } 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) } } 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) } } 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() } } 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() } if err.Error() != "(1, 1): Can't convert 1z(string) to time.Duration. time: unknown unit z in duration 1z" { t.Fatalf("unexpected error: %s", err) } } 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 TestUnmarshalDefault(t *testing.T) { var doc struct { StringField string `default:"a"` BoolField bool `default:"true"` IntField int `default:"1"` Int64Field int64 `default:"2"` Float64Field float64 `default:"3.1"` } 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.IntField != 1 { t.Errorf("IntField should be 1, not %d", doc.IntField) } if doc.Int64Field != 2 { t.Errorf("Int64Field should be 2, not %d", doc.Int64Field) } if doc.Float64Field != 3.1 { t.Errorf("Float64Field should be 3.1, not %f", doc.Float64Field) } } 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 TestUnmarshalDefaultFailureUnsupported(t *testing.T) { var doc struct { Field struct{} `default:"blah"` } err := Unmarshal([]byte(``), &doc) if err == nil { t.Fatal("should error") } } go-toml-1.4.0/marshal_test.toml000066400000000000000000000011211346174276200164640ustar00rootroot00000000000000title = "TOML Marshal Testing" [basic] bool = true date = 1979-05-27T07:32:00Z float = 123.4 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.4.0/parser.go000066400000000000000000000253121346174276200147340ustar00rootroot00000000000000// TOML Parser. package toml import ( "errors" "fmt" "math" "reflect" "regexp" "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) } 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, ".")) } // 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 numberUnderscoreInvalidRegexp *regexp.Regexp var hexNumberUnderscoreInvalidRegexp *regexp.Regexp func numberContainsInvalidUnderscore(value string) error { if numberUnderscoreInvalidRegexp.MatchString(value) { return errors.New("invalid use of _ in number") } return nil } func hexNumberContainsInvalidUnderscore(value string) error { if hexNumberUnderscoreInvalidRegexp.MatchString(value) { return errors.New("invalid use of _ in hex number") } 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) var err error var val int64 if len(cleanedVal) >= 3 && cleanedVal[0] == '0' { switch cleanedVal[1] { case 'x': err = hexNumberContainsInvalidUnderscore(tok.val) if err != nil { p.raiseError(tok, "%s", err) } val, err = strconv.ParseInt(cleanedVal[2:], 16, 64) case 'o': err = numberContainsInvalidUnderscore(tok.val) if err != nil { p.raiseError(tok, "%s", err) } val, err = strconv.ParseInt(cleanedVal[2:], 8, 64) case 'b': err = numberContainsInvalidUnderscore(tok.val) if err != nil { p.raiseError(tok, "%s", err) } val, err = strconv.ParseInt(cleanedVal[2:], 2, 64) default: panic("invalid base") // the lexer should catch this first } } else { err = numberContainsInvalidUnderscore(tok.val) if err != nil { p.raiseError(tok, "%s", err) } val, err = strconv.ParseInt(cleanedVal, 10, 64) } if err != nil { p.raiseError(tok, "%s", err) } return val 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 tokenDate: val, err := time.ParseInLocation(time.RFC3339Nano, tok.val, 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) } p.raiseError(tok, "never reached") 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) value := p.parseRvalue() tree.Set(key.val, value) case tokenComma: if previous == nil { p.raiseError(follow, "inline table cannot start with a comma") } 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") } return tree } func (p *tomlParser) parseArray() interface{} { var array []interface{} arrayType := reflect.TypeOf(nil) 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 arrayType == nil { arrayType = reflect.TypeOf(val) } if reflect.TypeOf(val) != arrayType { p.raiseError(follow, "mixed types in array") } 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() } } // 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 } func init() { numberUnderscoreInvalidRegexp = regexp.MustCompile(`([^\d]_|_[^\d])|_$|^_`) hexNumberUnderscoreInvalidRegexp = regexp.MustCompile(`(^0x_)|([^\da-f]_|_[^\da-f])|_$|^_`) } go-toml-1.4.0/parser_test.go000066400000000000000000000562171346174276200160030ustar00rootroot00000000000000package toml import ( "fmt" "math" "reflect" "testing" "time" "github.com/davecgh/go-spew/spew" ) 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.Log("Asserting tree:\n", spew.Sdump(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 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 TestArrayMixedTypes(t *testing.T) { _, err := Load("a = [42, 16.0]") if err.Error() != "(1, 10): mixed types in array" { t.Error("Bad error message:", err.Error()) } _, err = Load("a = [42, \"hello\"]") if err.Error() != "(1, 11): mixed types in array" { t.Error("Bad error message:", err.Error()) } } 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 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 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): comma expected between fields in inline table" { t.Error("Bad error message:", err.Error()) } } func TestInlineTableCommaStart(t *testing.T) { _, err := Load("foo = {, hello = 53}") if err.Error() != "(1, 8): inline table cannot start with a comma" { t.Error("Bad error message:", err.Error()) } } func TestInlineTableDoubleComma(t *testing.T) { _, err := Load("foo = {hello = 53,, foo = 17}") if err.Error() != "(1, 19): need field between two commas in inline table" { 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}, }, }, }) } 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}, }, }, }) } 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, "", 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): cannot start number with underscore" { 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 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) } } go-toml-1.4.0/position.go000066400000000000000000000013501346174276200153000ustar00rootroot00000000000000// 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.4.0/position_test.go000066400000000000000000000007061346174276200163430ustar00rootroot00000000000000// 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.4.0/query/000077500000000000000000000000001346174276200142535ustar00rootroot00000000000000go-toml-1.4.0/query/doc.go000066400000000000000000000142151346174276200153520ustar00rootroot00000000000000// 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. // query.CompileAndExecute("$.foo[0:-1]", tree) // // Slice expressions may have an optional stride/step parameter: // // // select every other element // query.CompileAndExecute("$.foo[0:-1: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[0:]", tree) // query.CompileAndExecute("$.foo[:-1]", tree) // query.CompileAndExecute("$.foo[0:-1:]", tree) // query.CompileAndExecute("$.foo[::1]", tree) // query.CompileAndExecute("$.foo[0::1]", tree) // query.CompileAndExecute("$.foo[:-1:1]", tree) // query.CompileAndExecute("$.foo[0:-1: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.4.0/query/lexer.go000066400000000000000000000144431346174276200157270ustar00rootroot00000000000000// 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.4.0/query/lexer_test.go000066400000000000000000000114411346174276200167610ustar00rootroot00000000000000package 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.4.0/query/match.go000066400000000000000000000113241346174276200156770ustar00rootroot00000000000000package query import ( "fmt" "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.Get(f.Name) if item != nil { ctx.lastPosition = tree.GetPosition(f.Name) f.next.call(item, ctx) } } } else if tree, ok := node.(*toml.Tree); ok { item := tree.Get(f.Name) if item != nil { ctx.lastPosition = tree.GetPosition(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) { if arr, ok := node.([]interface{}); ok { if f.Idx < len(arr) && f.Idx >= 0 { if treesArray, ok := node.([]*toml.Tree); ok { if len(treesArray) > 0 { ctx.lastPosition = treesArray[0].Position() } } f.next.call(arr[f.Idx], ctx) } } } // filter by slicing type matchSliceFn struct { matchBase Start, End, Step int } func newMatchSliceFn(start, end, step int) *matchSliceFn { return &matchSliceFn{Start: start, End: end, Step: step} } func (f *matchSliceFn) call(node interface{}, ctx *queryContext) { if arr, ok := node.([]interface{}); ok { // adjust indexes for negative values, reverse ordering realStart, realEnd := f.Start, f.End if realStart < 0 { realStart = len(arr) + realStart } if realEnd < 0 { realEnd = len(arr) + realEnd } if realEnd < realStart { realEnd, realStart = realStart, realEnd // swap } // loop and gather for idx := realStart; idx < realEnd; idx += f.Step { if treesArray, ok := node.([]*toml.Tree); ok { if len(treesArray) > 0 { ctx.lastPosition = treesArray[0].Position() } } f.next.call(arr[idx], ctx) } } } // 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.Get(k) ctx.lastPosition = tree.GetPosition(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.Get(k) ctx.lastPosition = tree.GetPosition(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.Get(k) if fn(v) { ctx.lastPosition = castNode.GetPosition(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.4.0/query/match_test.go000066400000000000000000000067071346174276200167470ustar00rootroot00000000000000package query import ( "fmt" "github.com/pelletier/go-toml" "testing" ) // 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: result += fmt.Sprintf("{%d:%d:%d}", fn.Start, fn.End, fn.Step) 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(123, maxInt, 1), )) } func TestPathSliceStartEnd(t *testing.T) { assertPath(t, "$[123:456]", buildPath( newMatchSliceFn(123, 456, 1), )) } func TestPathSliceStartEndColon(t *testing.T) { assertPath(t, "$[123:456:]", buildPath( newMatchSliceFn(123, 456, 1), )) } func TestPathSliceStartStep(t *testing.T) { assertPath(t, "$[123::7]", buildPath( newMatchSliceFn(123, maxInt, 7), )) } func TestPathSliceEndStep(t *testing.T) { assertPath(t, "$[:456:7]", buildPath( newMatchSliceFn(0, 456, 7), )) } func TestPathSliceStep(t *testing.T) { assertPath(t, "$[::7]", buildPath( newMatchSliceFn(0, maxInt, 7), )) } func TestPathSliceAll(t *testing.T) { assertPath(t, "$[123:456:7]", buildPath( newMatchSliceFn(123, 456, 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.4.0/query/parser.go000066400000000000000000000137521346174276200161060ustar00rootroot00000000000000/* 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 start, end, step := 0, maxInt, 1 // parse optional start tok := p.getToken() if tok.typ == tokenInteger { start = tok.Int() tok = p.getToken() } if tok.typ != tokenColon { return p.parseError(tok, "expected ':'") } // parse optional end tok = p.getToken() if tok.typ == tokenInteger { end = tok.Int() tok = p.getToken() } if tok.typ == tokenRightBracket { p.query.appendPath(newMatchSliceFn(start, end, step)) return p.parseMatchExpr } if tok.typ != tokenColon { return p.parseError(tok, "expected ']' or ':'") } // parse optional step tok = p.getToken() if tok.typ == tokenInteger { step = tok.Int() if step < 0 { return p.parseError(tok, "step must be a positive value") } tok = p.getToken() } if tok.typ != tokenRightBracket { return p.parseError(tok, "expected ']'") } p.query.appendPath(newMatchSliceFn(start, end, 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.4.0/query/parser_test.go000066400000000000000000000236401346174276200171420ustar00rootroot00000000000000package 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 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 TestQueryIndex(t *testing.T) { assertQueryPositions(t, "[foo]\na = [1,2,3,4,5,6,7,8,9,0]", "$.foo.a[5]", []interface{}{ queryTestNode{ int64(6), toml.Position{2, 1}, }, }) } func TestQuerySliceRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [1,2,3,4,5,6,7,8,9,0]", "$.foo.a[0:5]", []interface{}{ 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}, }, queryTestNode{ int64(5), toml.Position{2, 1}, }, }) } func TestQuerySliceStep(t *testing.T) { assertQueryPositions(t, "[foo]\na = [1,2,3,4,5,6,7,8,9,0]", "$.foo.a[0:5:2]", []interface{}{ queryTestNode{ int64(1), toml.Position{2, 1}, }, queryTestNode{ int64(3), toml.Position{2, 1}, }, queryTestNode{ int64(5), toml.Position{2, 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{}{ // no float values in document }) 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)}, }, }, 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.4.0/query/query.go000066400000000000000000000100631346174276200157470ustar00rootroot00000000000000package 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.4.0/query/query_test.go000066400000000000000000000073031346174276200170110ustar00rootroot00000000000000package query import ( "fmt" "testing" "github.com/pelletier/go-toml" ) func assertArrayContainsInAnyOrder(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 _, o := range objects { found := false for _, a := range array { if a == o { found = true break } } if !found { t.Fatal(o, "not found in array", array) } } } 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" `) authors, err := CompileAndExecute("$.book.author", config) if err != nil { t.Fatal("unexpected error:", err) } names := authors.Values() if len(names) != 3 { t.Fatalf("query should return 3 names but returned %d", len(names)) } assertArrayContainsInAnyOrder(t, names, "Stephen King", "Ernest Hemmingway", "William Gibson") } func TestQueryReadmeExample(t *testing.T) { config, _ := toml.Load(` [postgres] user = "pelletier" password = "mypassword" `) query, err := Compile("$..[user,password]") if err != nil { t.Fatal("unexpected error:", err) } results := query.Execute(config) values := results.Values() if len(values) != 2 { t.Fatalf("query should return 2 values but returned %d", len(values)) } assertArrayContainsInAnyOrder(t, values, "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.4.0/query/tokens.go000066400000000000000000000025161346174276200161110ustar00rootroot00000000000000package query import ( "fmt" "github.com/pelletier/go-toml" "strconv" "unicode" ) // 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 unicode.IsLetter(r) || r == '_' } func isDigit(r rune) bool { return unicode.IsNumber(r) } func isHexDigit(r rune) bool { return isDigit(r) || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') } go-toml-1.4.0/token.go000066400000000000000000000037771346174276200145730ustar00rootroot00000000000000package toml import ( "fmt" "strconv" "unicode" ) // 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 tokenDate tokenKeyGroup tokenKeyGroupArray tokenComma tokenColon tokenDollar tokenStar tokenQuestion tokenDot tokenDotDot tokenEOL ) var tokenTypeNames = []string{ "Error", "EOF", "Comment", "Key", "String", "Integer", "True", "False", "Float", "Inf", "NaN", "=", "[", "]", "{", "}", "(", ")", "]]", "[[", "Date", "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) 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 unicode.IsLetter(r) || 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 unicode.IsNumber(r) } func isHexDigit(r rune) bool { return isDigit(r) || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') } go-toml-1.4.0/token_test.go000066400000000000000000000031151346174276200156140ustar00rootroot00000000000000package 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, "[["}, {tokenDate, "Date"}, {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.4.0/toml.go000066400000000000000000000241641346174276200144170ustar00rootroot00000000000000package 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 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 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 } } // 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, ".")) } // 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 } // 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 subtree.values[intermediateKey] = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})) } subtree = node[len(node)-1] } } var toInsert interface{} switch v := value.(type) { case *Tree: v.comment = opts.Comment toInsert = value case []*Tree: toInsert = value case *tomlValue: v.comment = opts.Comment toInsert = v default: toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline, 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 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 = errors.New(r.(string)) } }() 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.4.0/toml_test.go000066400000000000000000000072641346174276200154600ustar00rootroot00000000000000// Testing support for go-toml package toml import ( "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 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 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.4.0/toml_testgen_support_test.go000066400000000000000000000057641346174276200210100ustar00rootroot00000000000000// This is a support file for toml_testgen_test.go package toml import ( "bytes" "encoding/json" "fmt" "reflect" "testing" "time" "github.com/davecgh/go-spew/spew" ) 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%s", spew.Sdump(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.4.0/toml_testgen_test.go000066400000000000000000000537541346174276200172160ustar00rootroot00000000000000// Generated by tomltestgen for toml-test ref 39e37e6 on 2019-03-19T23:58:45-07:00 package toml import ( "testing" ) func TestInvalidArrayMixedTypesArraysAndInts(t *testing.T) { input := `arrays-and-ints = [1, ["Arrays are not integers."]]` testgenInvalid(t, input) } func TestInvalidArrayMixedTypesIntsAndFloats(t *testing.T) { input := `ints-and-floats = [1, 1.1]` testgenInvalid(t, input) } func TestInvalidArrayMixedTypesStringsAndInts(t *testing.T) { input := `strings-and-ints = ["hi", 42]` testgenInvalid(t, input) } 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.4.0/tomltree_create.go000066400000000000000000000077201346174276200166210ustar00rootroot00000000000000package 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 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.4.0/tomltree_create_test.go000066400000000000000000000065311346174276200176570ustar00rootroot00000000000000package toml import ( "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) } } go-toml-1.4.0/tomltree_write.go000066400000000000000000000245201346174276200165050ustar00rootroot00000000000000package toml import ( "bytes" "fmt" "io" "math" "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) 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() } // 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 tomlValueStringRepresentation(v interface{}, indent string, 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: // Ensure a round float does contain a decimal point. Otherwise feeding // the output back to the parser would convert to an integer. if math.Trunc(value) == value { return strings.ToLower(strconv.FormatFloat(value, 'f', 1, 32)), nil } return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil case string: if tv.multiline { return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil } return "\"" + encodeTomlString(value) + "\"", nil case []byte: b, _ := v.([]byte) return tomlValueStringRepresentation(string(b), indent, arraysOneElementPerLine) case bool: if value { return "true", nil } return "false", nil case time.Time: return value.Format(time.RFC3339), nil 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, indent, 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(value) stringBuffer.WriteString(`,`) stringBuffer.WriteString("\n") } stringBuffer.WriteString(indent + "]") 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) { // get lowest line number that is not 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) } func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder) (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 := k if keyspace != "" { combinedKey = keyspace + "." + combinedKey } var commented string if t.commented { commented = "# " } 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 } } writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n") bytesCount += int64(writtenBytesCount) if err != nil { return bytesCount, err } bytesCount, err = node.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord) if err != nil { return bytesCount, err } case []*Tree: for _, subTree := range node { writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n") bytesCount += int64(writtenBytesCount) if err != nil { return bytesCount, err } bytesCount, err = subTree.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord) 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]) } repr, err := tomlValueStringRepresentation(v, indent, 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 = "" } writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n") bytesCount += int64(writtenBytesCountComment) if errc != nil { return bytesCount, errc } } var commented string if v.commented { commented = "# " } writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n") bytesCount += int64(writtenBytesCount) if err != nil { return bytesCount, err } } } return bytesCount, nil } 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) { var buf bytes.Buffer _, err := t.WriteTo(&buf) if err != nil { return "", err } return buf.String(), 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] = node.value } } return result } go-toml-1.4.0/tomltree_write_test.go000066400000000000000000000225101346174276200175410ustar00rootroot00000000000000package 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)}, }, }, } 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 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 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 # `