From b4bb91fc138f59583d64f5a23d85801d62d54567 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sat, 30 Jan 2021 09:07:55 -0500 Subject: [PATCH] test --- Dockerfile | 11 - LICENSE | 21 - Makefile | 29 - benchmark.sh | 35 - benchmark/benchmark.json | 164 -- benchmark/benchmark.toml | 244 -- benchmark/benchmark.yml | 121 - benchmark/benchmark_test.go | 194 -- benchmark/go.mod | 11 - benchmark/go.sum | 8 - cmd/jsontoml/main.go | 82 - cmd/jsontoml/main_test.go | 92 - cmd/tomljson/main.go | 71 - cmd/tomljson/main_test.go | 90 - cmd/tomll/main.go | 65 - cmd/tomltestgen/main.go | 219 -- doc.go | 23 - doc_test.go | 170 -- example-crlf.toml | 30 - example.toml | 30 - fuzz.go | 31 - fuzz.sh | 15 - go.mod | 6 +- go.sum | 10 + keysparsing.go | 112 - keysparsing_test.go | 79 - lexer.go | 1031 -------- lexer_test.go | 1247 ---------- localtime.go | 281 --- localtime_test.go | 446 ---- marshal.go | 1293 ---------- marshal_OrderPreserve_test.toml | 39 - marshal_test.go | 4054 ------------------------------- marshal_test.toml | 39 - parser.go | 508 ---- parser_test.go | 1166 --------- position.go | 29 - position_test.go | 29 - query/README.md | 201 -- query/doc.go | 173 -- query/lexer.go | 357 --- query/lexer_test.go | 179 -- query/match.go | 311 --- query/match_test.go | 213 -- query/parser.go | 278 --- query/parser_test.go | 613 ----- query/query.go | 158 -- query/query_test.go | 151 -- query/tokens.go | 106 - token.go | 136 -- token_test.go | 69 - toml.go | 529 ---- toml_test.go | 261 -- toml_testgen_support_test.go | 119 - toml_testgen_test.go | 928 ------- tomlpub.go | 71 - tomltree_create.go | 155 -- tomltree_create_test.go | 243 -- tomltree_write.go | 535 ---- tomltree_write_test.go | 437 ---- 60 files changed, 13 insertions(+), 18335 deletions(-) delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 Makefile delete mode 100755 benchmark.sh delete mode 100644 benchmark/benchmark.json delete mode 100644 benchmark/benchmark.toml delete mode 100644 benchmark/benchmark.yml delete mode 100644 benchmark/benchmark_test.go delete mode 100644 benchmark/go.mod delete mode 100644 benchmark/go.sum delete mode 100644 cmd/jsontoml/main.go delete mode 100644 cmd/jsontoml/main_test.go delete mode 100644 cmd/tomljson/main.go delete mode 100644 cmd/tomljson/main_test.go delete mode 100644 cmd/tomll/main.go delete mode 100644 cmd/tomltestgen/main.go delete mode 100644 doc.go delete mode 100644 doc_test.go delete mode 100644 example-crlf.toml delete mode 100644 example.toml delete mode 100644 fuzz.go delete mode 100755 fuzz.sh delete mode 100644 keysparsing.go delete mode 100644 keysparsing_test.go delete mode 100644 lexer.go delete mode 100644 lexer_test.go delete mode 100644 localtime.go delete mode 100644 localtime_test.go delete mode 100644 marshal.go delete mode 100644 marshal_OrderPreserve_test.toml delete mode 100644 marshal_test.go delete mode 100644 marshal_test.toml delete mode 100644 parser.go delete mode 100644 parser_test.go delete mode 100644 position.go delete mode 100644 position_test.go delete mode 100644 query/README.md delete mode 100644 query/doc.go delete mode 100644 query/lexer.go delete mode 100644 query/lexer_test.go delete mode 100644 query/match.go delete mode 100644 query/match_test.go delete mode 100644 query/parser.go delete mode 100644 query/parser_test.go delete mode 100644 query/query.go delete mode 100644 query/query_test.go delete mode 100644 query/tokens.go delete mode 100644 token.go delete mode 100644 token_test.go delete mode 100644 toml.go delete mode 100644 toml_test.go delete mode 100644 toml_testgen_support_test.go delete mode 100644 toml_testgen_test.go delete mode 100644 tomlpub.go delete mode 100644 tomltree_create.go delete mode 100644 tomltree_create_test.go delete mode 100644 tomltree_write.go delete mode 100644 tomltree_write_test.go diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index fffdb01..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.12-alpine3.9 as builder -WORKDIR /go/src/github.com/pelletier/go-toml -COPY . . -ENV CGO_ENABLED=0 -ENV GOOS=linux -RUN go install ./... - -FROM scratch -COPY --from=builder /go/bin/tomll /usr/bin/tomll -COPY --from=builder /go/bin/tomljson /usr/bin/tomljson -COPY --from=builder /go/bin/jsontoml /usr/bin/jsontoml diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3a38ac2..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 - 2021 Thomas Pelletier, Eric Anderton - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index 9e4503a..0000000 --- a/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -export CGO_ENABLED=0 -go := go -go.goos ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f1) -go.goarch ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f2) - -out.tools := tomll tomljson jsontoml -out.dist := $(out.tools:=_$(go.goos)_$(go.goarch).tar.xz) -sources := $(wildcard **/*.go) - - -.PHONY: -tools: $(out.tools) - -$(out.tools): $(sources) - GOOS=$(go.goos) GOARCH=$(go.goarch) $(go) build ./cmd/$@ - -.PHONY: -dist: $(out.dist) - -$(out.dist):%_$(go.goos)_$(go.goarch).tar.xz: % - if [ "$(go.goos)" = "windows" ]; then \ - tar -cJf $@ $^.exe; \ - else \ - tar -cJf $@ $^; \ - fi - -.PHONY: -clean: - rm -rf $(out.tools) $(out.dist) diff --git a/benchmark.sh b/benchmark.sh deleted file mode 100755 index a69d304..0000000 --- a/benchmark.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -set -ex - -reference_ref=${1:-master} -reference_git=${2:-.} - -if ! `hash benchstat 2>/dev/null`; then - echo "Installing benchstat" - go get golang.org/x/perf/cmd/benchstat -fi - -tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX` -ref_tempdir="${tempdir}/ref" -ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt" -local_benchmark="`pwd`/benchmark-local.txt" - -echo "=== ${reference_ref} (${ref_tempdir})" -git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null -pushd ${ref_tempdir} >/dev/null -git checkout ${reference_ref} >/dev/null 2>/dev/null -go test -bench=. -benchmem | tee ${ref_benchmark} -cd benchmark -go test -bench=. -benchmem | tee -a ${ref_benchmark} -popd >/dev/null - -echo "" -echo "=== local" -go test -bench=. -benchmem | tee ${local_benchmark} -cd benchmark -go test -bench=. -benchmem | tee -a ${local_benchmark} - -echo "" -echo "=== diff" -benchstat -delta-test=none ${ref_benchmark} ${local_benchmark} diff --git a/benchmark/benchmark.json b/benchmark/benchmark.json deleted file mode 100644 index 86f99c6..0000000 --- a/benchmark/benchmark.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "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": {} - } - } - } -} diff --git a/benchmark/benchmark.toml b/benchmark/benchmark.toml deleted file mode 100644 index dfd77e0..0000000 --- a/benchmark/benchmark.toml +++ /dev/null @@ -1,244 +0,0 @@ -################################################################################ -## 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" diff --git a/benchmark/benchmark.yml b/benchmark/benchmark.yml deleted file mode 100644 index 0bd19f0..0000000 --- a/benchmark/benchmark.yml +++ /dev/null @@ -1,121 +0,0 @@ ---- -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: {} diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go deleted file mode 100644 index faf2da8..0000000 --- a/benchmark/benchmark_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package benchmark - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "testing" - "time" - - burntsushi "github.com/BurntSushi/toml" - "github.com/pelletier/go-toml" - "gopkg.in/yaml.v2" -) - -type benchmarkDoc struct { - Table struct { - Key string - Subtable struct { - Key string - } - Inline struct { - Name struct { - First string - Last string - } - Point struct { - X int64 - U int64 - } - } - } - String struct { - Basic struct { - Basic string - } - Multiline struct { - Key1 string - Key2 string - Key3 string - Continued struct { - Key1 string - Key2 string - Key3 string - } - } - Literal struct { - Winpath string - Winpath2 string - Quoted string - Regex string - Multiline struct { - Regex2 string - Lines string - } - } - } - Integer struct { - Key1 int64 - Key2 int64 - Key3 int64 - Key4 int64 - Underscores struct { - Key1 int64 - Key2 int64 - Key3 int64 - } - } - Float struct { - Fractional struct { - Key1 float64 - Key2 float64 - Key3 float64 - } - Exponent struct { - Key1 float64 - Key2 float64 - Key3 float64 - } - Both struct { - Key float64 - } - Underscores struct { - Key1 float64 - Key2 float64 - } - } - Boolean struct { - True bool - False bool - } - Datetime struct { - Key1 time.Time - Key2 time.Time - Key3 time.Time - } - Array struct { - Key1 []int64 - Key2 []string - Key3 [][]int64 - // TODO: Key4 not supported by go-toml's Unmarshal - Key5 []int64 - Key6 []int64 - } - Products []struct { - Name string - Sku int64 - Color string - } - Fruit []struct { - Name string - Physical struct { - Color string - Shape string - Variety []struct { - Name string - } - } - } -} - -func BenchmarkParseToml(b *testing.B) { - fileBytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := toml.LoadReader(bytes.NewReader(fileBytes)) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalToml(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := toml.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalBurntSushiToml(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.toml") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := burntsushi.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalJson(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.json") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := json.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkUnmarshalYaml(b *testing.B) { - bytes, err := ioutil.ReadFile("benchmark.yml") - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - target := benchmarkDoc{} - err := yaml.Unmarshal(bytes, &target) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/benchmark/go.mod b/benchmark/go.mod deleted file mode 100644 index 8271303..0000000 --- a/benchmark/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/pelletier/go-toml/benchmark - -go 1.12 - -require ( - github.com/BurntSushi/toml v0.3.1 - github.com/pelletier/go-toml v0.0.0 - gopkg.in/yaml.v2 v2.3.0 -) - -replace github.com/pelletier/go-toml => ../ diff --git a/benchmark/go.sum b/benchmark/go.sum deleted file mode 100644 index 6772ccc..0000000 --- a/benchmark/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cmd/jsontoml/main.go b/cmd/jsontoml/main.go deleted file mode 100644 index 0acc61e..0000000 --- a/cmd/jsontoml/main.go +++ /dev/null @@ -1,82 +0,0 @@ -// Jsontoml reads JSON and converts to TOML. -// -// Usage: -// cat file.toml | jsontoml > file.json -// jsontoml file1.toml > file.json -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/pelletier/go-toml" -) - -func main() { - flag.Usage = func() { - fmt.Fprintln(os.Stderr, "jsontoml can be used in two ways:") - fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Reading from a file name:") - fmt.Fprintln(os.Stderr, " tomljson file.toml") - } - flag.Parse() - os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr)) -} - -func processMain(files []string, defaultInput io.Reader, output io.Writer, errorOutput io.Writer) int { - // read from stdin and print to stdout - inputReader := defaultInput - - if len(files) > 0 { - file, err := os.Open(files[0]) - if err != nil { - printError(err, errorOutput) - return -1 - } - inputReader = file - defer file.Close() - } - s, err := reader(inputReader) - if err != nil { - printError(err, errorOutput) - return -1 - } - io.WriteString(output, s) - return 0 -} - -func printError(err error, output io.Writer) { - io.WriteString(output, err.Error()+"\n") -} - -func reader(r io.Reader) (string, error) { - jsonMap := make(map[string]interface{}) - jsonBytes, err := ioutil.ReadAll(r) - if err != nil { - return "", err - } - err = json.Unmarshal(jsonBytes, &jsonMap) - if err != nil { - return "", err - } - - tree, err := toml.TreeFromMap(jsonMap) - if err != nil { - return "", err - } - return mapToTOML(tree) -} - -func mapToTOML(t *toml.Tree) (string, error) { - tomlBytes, err := t.ToTomlString() - if err != nil { - return "", err - } - return string(tomlBytes[:]), nil -} diff --git a/cmd/jsontoml/main_test.go b/cmd/jsontoml/main_test.go deleted file mode 100644 index c591c66..0000000 --- a/cmd/jsontoml/main_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "bytes" - "io/ioutil" - "os" - "runtime" - "strings" - "testing" -) - -func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) { - output := buffer.String() - if output != expected { - t.Errorf("incorrect %s: \n%sexpected %s: \n%s", name, output, name, expected) - t.Log([]rune(output)) - t.Log([]rune(expected)) - } -} - -func expectProcessMainResults(t *testing.T, input string, args []string, exitCode int, expectedOutput string, expectedError string) { - inputReader := strings.NewReader(input) - - outputBuffer := new(bytes.Buffer) - errorBuffer := new(bytes.Buffer) - - returnCode := processMain(args, inputReader, outputBuffer, errorBuffer) - - expectBufferEquality(t, "output", outputBuffer, expectedOutput) - expectBufferEquality(t, "error", errorBuffer, expectedError) - - if returnCode != exitCode { - t.Error("incorrect return code:", returnCode, "expected", exitCode) - } -} - -func TestProcessMainReadFromStdin(t *testing.T) { - expectedOutput := ` -[mytoml] - a = 42.0 -` - input := `{ - "mytoml": { - "a": 42 - } -} -` - expectedError := `` - expectedExitCode := 0 - - expectProcessMainResults(t, input, []string{}, expectedExitCode, expectedOutput, expectedError) -} - -func TestProcessMainReadFromFile(t *testing.T) { - input := `{ - "mytoml": { - "a": 42 - } -} -` - tmpfile, err := ioutil.TempFile("", "example.json") - if err != nil { - t.Fatal(err) - } - if _, err := tmpfile.Write([]byte(input)); err != nil { - t.Fatal(err) - } - - defer os.Remove(tmpfile.Name()) - - expectedOutput := ` -[mytoml] - a = 42.0 -` - expectedError := `` - expectedExitCode := 0 - - expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError) -} - -func TestProcessMainReadFromMissingFile(t *testing.T) { - var expectedError string - if runtime.GOOS == "windows" { - expectedError = `open /this/file/does/not/exist: The system cannot find the path specified. -` - } else { - expectedError = `open /this/file/does/not/exist: no such file or directory -` - } - - expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError) -} diff --git a/cmd/tomljson/main.go b/cmd/tomljson/main.go deleted file mode 100644 index 322315b..0000000 --- a/cmd/tomljson/main.go +++ /dev/null @@ -1,71 +0,0 @@ -// 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 -} diff --git a/cmd/tomljson/main_test.go b/cmd/tomljson/main_test.go deleted file mode 100644 index d515ee0..0000000 --- a/cmd/tomljson/main_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package 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) -} diff --git a/cmd/tomll/main.go b/cmd/tomll/main.go deleted file mode 100644 index 93ab0c9..0000000 --- a/cmd/tomll/main.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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 -} diff --git a/cmd/tomltestgen/main.go b/cmd/tomltestgen/main.go deleted file mode 100644 index cd9bdd7..0000000 --- a/cmd/tomltestgen/main.go +++ /dev/null @@ -1,219 +0,0 @@ -// 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)) -} diff --git a/doc.go b/doc.go deleted file mode 100644 index a1406a3..0000000 --- a/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package toml is a TOML parser and manipulation library. -// -// This version supports the specification as described in -// https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md -// -// Marshaling -// -// Go-toml can marshal and unmarshal TOML documents from and to data -// structures. -// -// TOML document as a tree -// -// Go-toml can operate on a TOML document as a tree. Use one of the Load* -// functions to parse TOML data and obtain a Tree instance, then one of its -// methods to manipulate the tree. -// -// JSONPath-like queries -// -// The package github.com/pelletier/go-toml/query implements a system -// similar to JSONPath to quickly retrieve elements of a TOML document using a -// single expression. See the package documentation for more information. -// -package toml diff --git a/doc_test.go b/doc_test.go deleted file mode 100644 index 7aaddab..0000000 --- a/doc_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// code examples for godoc - -package toml_test - -import ( - "fmt" - "log" - "os" - - toml "github.com/pelletier/go-toml" -) - -func Example_tree() { - config, err := toml.LoadFile("config.toml") - - if err != nil { - fmt.Println("Error ", err.Error()) - } else { - // retrieve data directly - directUser := config.Get("postgres.user").(string) - directPassword := config.Get("postgres.password").(string) - fmt.Println("User is", directUser, " and password is", directPassword) - - // or using an intermediate object - configTree := config.Get("postgres").(*toml.Tree) - user := configTree.Get("user").(string) - password := configTree.Get("password").(string) - fmt.Println("User is", user, " and password is", password) - - // show where elements are in the file - fmt.Printf("User position: %v\n", configTree.GetPosition("user")) - fmt.Printf("Password position: %v\n", configTree.GetPosition("password")) - } -} - -func Example_unmarshal() { - type Employer struct { - Name string - Phone string - } - type Person struct { - Name string - Age int64 - Employer Employer - } - - document := []byte(` - name = "John" - age = 30 - [employer] - name = "Company Inc." - phone = "+1 234 567 89012" - `) - - person := Person{} - toml.Unmarshal(document, &person) - fmt.Println(person.Name, "is", person.Age, "and works at", person.Employer.Name) - // Output: - // John is 30 and works at Company Inc. -} - -func ExampleMarshal() { - type Postgres struct { - User string `toml:"user"` - Password string `toml:"password"` - Database string `toml:"db" commented:"true" comment:"not used anymore"` - } - type Config struct { - Postgres Postgres `toml:"postgres" comment:"Postgres configuration"` - } - - config := Config{Postgres{User: "pelletier", Password: "mypassword", Database: "old_database"}} - b, err := toml.Marshal(config) - if err != nil { - log.Fatal(err) - } - fmt.Println(string(b)) - // Output: - // # Postgres configuration - // [postgres] - // - // # not used anymore - // # db = "old_database" - // password = "mypassword" - // user = "pelletier" -} - -func ExampleUnmarshal() { - type Postgres struct { - User string - Password string - } - type Config struct { - Postgres Postgres - } - - doc := []byte(` - [postgres] - user = "pelletier" - password = "mypassword"`) - - config := Config{} - toml.Unmarshal(doc, &config) - fmt.Println("user=", config.Postgres.User) - // Output: - // user= pelletier -} - -func ExampleEncoder_anonymous() { - type Credentials struct { - User string `toml:"user"` - Password string `toml:"password"` - } - - type Protocol struct { - Name string `toml:"name"` - } - - type Config struct { - Version int `toml:"version"` - Credentials - Protocol `toml:"Protocol"` - } - config := Config{ - Version: 2, - Credentials: Credentials{ - User: "pelletier", - Password: "mypassword", - }, - Protocol: Protocol{ - Name: "tcp", - }, - } - fmt.Println("Default:") - fmt.Println("---------------") - - def := toml.NewEncoder(os.Stdout) - if err := def.Encode(config); err != nil { - log.Fatal(err) - } - - fmt.Println("---------------") - fmt.Println("With promotion:") - fmt.Println("---------------") - - prom := toml.NewEncoder(os.Stdout).PromoteAnonymous(true) - if err := prom.Encode(config); err != nil { - log.Fatal(err) - } - // Output: - // Default: - // --------------- - // password = "mypassword" - // user = "pelletier" - // version = 2 - // - // [Protocol] - // name = "tcp" - // --------------- - // With promotion: - // --------------- - // version = 2 - // - // [Credentials] - // password = "mypassword" - // user = "pelletier" - // - // [Protocol] - // name = "tcp" -} diff --git a/example-crlf.toml b/example-crlf.toml deleted file mode 100644 index 780d9c6..0000000 --- a/example-crlf.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it -score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported \ No newline at end of file diff --git a/example.toml b/example.toml deleted file mode 100644 index f45bf88..0000000 --- a/example.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it -score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported \ No newline at end of file diff --git a/fuzz.go b/fuzz.go deleted file mode 100644 index 14570c8..0000000 --- a/fuzz.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build gofuzz - -package toml - -func Fuzz(data []byte) int { - tree, err := LoadBytes(data) - if err != nil { - if tree != nil { - panic("tree must be nil if there is an error") - } - return 0 - } - - str, err := tree.ToTomlString() - if err != nil { - if str != "" { - panic(`str must be "" if there is an error`) - } - panic(err) - } - - tree, err = Load(str) - if err != nil { - if tree != nil { - panic("tree must be nil if there is an error") - } - return 0 - } - - return 1 -} diff --git a/fuzz.sh b/fuzz.sh deleted file mode 100755 index 3204b4c..0000000 --- a/fuzz.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/sh -set -eu - -go get github.com/dvyukov/go-fuzz/go-fuzz -go get github.com/dvyukov/go-fuzz/go-fuzz-build - -if [ ! -e toml-fuzz.zip ]; then - go-fuzz-build github.com/pelletier/go-toml -fi - -rm -fr fuzz -mkdir -p fuzz/corpus -cp *.toml fuzz/corpus - -go-fuzz -bin=toml-fuzz.zip -workdir=fuzz diff --git a/go.mod b/go.mod index e924cb9..3825359 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ -module github.com/pelletier/go-toml +module github.com/pelletier/go-toml/v2 -go 1.12 +go 1.14 -require github.com/davecgh/go-spew v1.1.1 +require github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index b5e2922..d021fdd 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/keysparsing.go b/keysparsing.go deleted file mode 100644 index e091500..0000000 --- a/keysparsing.go +++ /dev/null @@ -1,112 +0,0 @@ -// Parsing keys handling both bare and quoted keys. - -package toml - -import ( - "errors" - "fmt" -) - -// Convert the bare key group string to an array. -// The input supports double quotation and single quotation, -// but escape sequences are not supported. Lexers must unescape them beforehand. -func parseKey(key string) ([]string, error) { - runes := []rune(key) - var groups []string - - if len(key) == 0 { - return nil, errors.New("empty key") - } - - idx := 0 - for idx < len(runes) { - for ; idx < len(runes) && isSpace(runes[idx]); idx++ { - // skip leading whitespace - } - if idx >= len(runes) { - break - } - r := runes[idx] - if isValidBareChar(r) { - // parse bare key - startIdx := idx - endIdx := -1 - idx++ - for idx < len(runes) { - r = runes[idx] - if isValidBareChar(r) { - idx++ - } else if r == '.' { - endIdx = idx - break - } else if isSpace(r) { - endIdx = idx - for ; idx < len(runes) && isSpace(runes[idx]); idx++ { - // skip trailing whitespace - } - if idx < len(runes) && runes[idx] != '.' { - return nil, fmt.Errorf("invalid key character after whitespace: %c", runes[idx]) - } - break - } else { - return nil, fmt.Errorf("invalid bare key character: %c", r) - } - } - if endIdx == -1 { - endIdx = idx - } - groups = append(groups, string(runes[startIdx:endIdx])) - } else if r == '\'' { - // parse single quoted key - idx++ - startIdx := idx - for { - if idx >= len(runes) { - return nil, fmt.Errorf("unclosed single-quoted key") - } - r = runes[idx] - if r == '\'' { - groups = append(groups, string(runes[startIdx:idx])) - idx++ - break - } - idx++ - } - } else if r == '"' { - // parse double quoted key - idx++ - startIdx := idx - for { - if idx >= len(runes) { - return nil, fmt.Errorf("unclosed double-quoted key") - } - r = runes[idx] - if r == '"' { - groups = append(groups, string(runes[startIdx:idx])) - idx++ - break - } - idx++ - } - } else if r == '.' { - idx++ - if idx >= len(runes) { - return nil, fmt.Errorf("unexpected end of key") - } - r = runes[idx] - if !isValidBareChar(r) && r != '\'' && r != '"' && r != ' ' { - return nil, fmt.Errorf("expecting key part after dot") - } - } else { - return nil, fmt.Errorf("invalid key character: %c", r) - } - } - if len(groups) == 0 { - return nil, fmt.Errorf("empty key") - } - return groups, nil -} - -func isValidBareChar(r rune) bool { - return isAlphanumeric(r) || r == '-' || isDigit(r) -} diff --git a/keysparsing_test.go b/keysparsing_test.go deleted file mode 100644 index 3858259..0000000 --- a/keysparsing_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package 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{""}) -} diff --git a/lexer.go b/lexer.go deleted file mode 100644 index 313908e..0000000 --- a/lexer.go +++ /dev/null @@ -1,1031 +0,0 @@ -// TOML lexer. -// -// Written using the principles developed by Rob Pike in -// http://www.youtube.com/watch?v=HxaD_trXwRE - -package toml - -import ( - "bytes" - "errors" - "fmt" - "strconv" - "strings" -) - -// Define state functions -type tomlLexStateFn func() tomlLexStateFn - -// Define lexer -type tomlLexer struct { - inputIdx int - input []rune // Textual source - currentTokenStart int - currentTokenStop int - tokens []token - brackets []rune - line int - col int - endbufferLine int - endbufferCol int -} - -// Basic read operations on input - -func (l *tomlLexer) read() rune { - r := l.peek() - if r == '\n' { - l.endbufferLine++ - l.endbufferCol = 1 - } else { - l.endbufferCol++ - } - l.inputIdx++ - return r -} - -func (l *tomlLexer) next() rune { - r := l.read() - - if r != eof { - l.currentTokenStop++ - } - return r -} - -func (l *tomlLexer) ignore() { - l.currentTokenStart = l.currentTokenStop - l.line = l.endbufferLine - l.col = l.endbufferCol -} - -func (l *tomlLexer) skip() { - l.next() - l.ignore() -} - -func (l *tomlLexer) fastForward(n int) { - for i := 0; i < n; i++ { - l.next() - } -} - -func (l *tomlLexer) emitWithValue(t tokenType, value string) { - l.tokens = append(l.tokens, token{ - Position: Position{l.line, l.col}, - typ: t, - val: value, - }) - l.ignore() -} - -func (l *tomlLexer) emit(t tokenType) { - l.emitWithValue(t, string(l.input[l.currentTokenStart:l.currentTokenStop])) -} - -func (l *tomlLexer) peek() rune { - if l.inputIdx >= len(l.input) { - return eof - } - return l.input[l.inputIdx] -} - -func (l *tomlLexer) peekString(size int) string { - maxIdx := len(l.input) - upperIdx := l.inputIdx + size // FIXME: potential overflow - if upperIdx > maxIdx { - upperIdx = maxIdx - } - return string(l.input[l.inputIdx:upperIdx]) -} - -func (l *tomlLexer) follow(next string) bool { - return next == l.peekString(len(next)) -} - -// Error management - -func (l *tomlLexer) errorf(format string, args ...interface{}) tomlLexStateFn { - l.tokens = append(l.tokens, token{ - Position: Position{l.line, l.col}, - typ: tokenError, - val: fmt.Sprintf(format, args...), - }) - return nil -} - -// State functions - -func (l *tomlLexer) lexVoid() tomlLexStateFn { - for { - next := l.peek() - switch next { - case '}': // after '{' - return l.lexRightCurlyBrace - case '[': - return l.lexTableKey - case '#': - return l.lexComment(l.lexVoid) - case '=': - return l.lexEqual - case '\r': - fallthrough - case '\n': - l.skip() - continue - } - - if isSpace(next) { - l.skip() - } - - if isKeyStartChar(next) { - return l.lexKey - } - - if next == eof { - l.next() - break - } - } - - l.emit(tokenEOF) - return nil -} - -func (l *tomlLexer) lexRvalue() tomlLexStateFn { - for { - next := l.peek() - switch next { - case '.': - return l.errorf("cannot start float with a dot") - case '=': - return l.lexEqual - case '[': - return l.lexLeftBracket - case ']': - return l.lexRightBracket - case '{': - return l.lexLeftCurlyBrace - case '}': - return l.lexRightCurlyBrace - case '#': - return l.lexComment(l.lexRvalue) - case '"': - return l.lexString - case '\'': - return l.lexLiteralString - case ',': - return l.lexComma - case '\r': - fallthrough - case '\n': - l.skip() - if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '[' { - return l.lexRvalue - } - return l.lexVoid - } - - if l.follow("true") { - return l.lexTrue - } - - if l.follow("false") { - return l.lexFalse - } - - if l.follow("inf") { - return l.lexInf - } - - if l.follow("nan") { - return l.lexNan - } - - if isSpace(next) { - l.skip() - continue - } - - if next == eof { - l.next() - break - } - - if next == '+' || next == '-' { - return l.lexNumber - } - - if isDigit(next) { - return l.lexDateTimeOrNumber - } - - return l.errorf("no value can start with %c", next) - } - - l.emit(tokenEOF) - return nil -} - -func (l *tomlLexer) lexDateTimeOrNumber() tomlLexStateFn { - // Could be either a date/time, or a digit. - // The options for date/times are: - // YYYY-... => date or date-time - // HH:... => time - // Anything else should be a number. - - lookAhead := l.peekString(5) - if len(lookAhead) < 3 { - return l.lexNumber() - } - - for idx, r := range lookAhead { - if !isDigit(r) { - if idx == 2 && r == ':' { - return l.lexDateTimeOrTime() - } - if idx == 4 && r == '-' { - return l.lexDateTimeOrTime() - } - return l.lexNumber() - } - } - return l.lexNumber() -} - -func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn { - l.next() - l.emit(tokenLeftCurlyBrace) - l.brackets = append(l.brackets, '{') - return l.lexVoid -} - -func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn { - l.next() - l.emit(tokenRightCurlyBrace) - if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '{' { - return l.errorf("cannot have '}' here") - } - l.brackets = l.brackets[:len(l.brackets)-1] - return l.lexRvalue -} - -func (l *tomlLexer) lexDateTimeOrTime() tomlLexStateFn { - // Example matches: - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - // 07:32:00 - // 00:32:00.999999 - - // we already know those two are digits - l.next() - l.next() - - // Got 2 digits. At that point it could be either a time or a date(-time). - - r := l.next() - if r == ':' { - return l.lexTime() - } - - return l.lexDateTime() -} - -func (l *tomlLexer) lexDateTime() tomlLexStateFn { - // This state accepts an offset date-time, a local date-time, or a local date. - // - // v--- cursor - // 1979-05-27T07:32:00Z - // 1979-05-27T00:32:00-07:00 - // 1979-05-27T00:32:00.999999-07:00 - // 1979-05-27 07:32:00Z - // 1979-05-27 00:32:00-07:00 - // 1979-05-27 00:32:00.999999-07:00 - // 1979-05-27T07:32:00 - // 1979-05-27T00:32:00.999999 - // 1979-05-27 07:32:00 - // 1979-05-27 00:32:00.999999 - // 1979-05-27 - - // date - - // already checked by lexRvalue - l.next() // digit - l.next() // - - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid month digit in date: %c", r) - } - } - - r := l.next() - if r != '-' { - return l.errorf("expected - to separate month of a date, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid day digit in date: %c", r) - } - } - - l.emit(tokenLocalDate) - - r = l.peek() - - if r == eof { - - return l.lexRvalue - } - - if r != ' ' && r != 'T' { - return l.errorf("incorrect date/time separation character: %c", r) - } - - if r == ' ' { - lookAhead := l.peekString(3)[1:] - if len(lookAhead) < 2 { - return l.lexRvalue - } - for _, r := range lookAhead { - if !isDigit(r) { - return l.lexRvalue - } - } - } - - l.skip() // skip the T or ' ' - - // time - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid hour digit in time: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time hour/minute separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time minute/second separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid second digit in time: %c", r) - } - } - - r = l.peek() - if r == '.' { - l.next() - r := l.next() - if !isDigit(r) { - return l.errorf("expected at least one digit in time's fraction, not %c", r) - } - - for { - r := l.peek() - if !isDigit(r) { - break - } - l.next() - } - } - - l.emit(tokenLocalTime) - - return l.lexTimeOffset - -} - -func (l *tomlLexer) lexTimeOffset() tomlLexStateFn { - // potential offset - - // Z - // -07:00 - // +07:00 - // nothing - - r := l.peek() - - if r == 'Z' { - l.next() - l.emit(tokenTimeOffset) - } else if r == '+' || r == '-' { - l.next() - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid hour digit in time offset: %c", r) - } - } - - r = l.next() - if r != ':' { - return l.errorf("time offset hour/minute separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time offset: %c", r) - } - } - - l.emit(tokenTimeOffset) - } - - return l.lexRvalue -} - -func (l *tomlLexer) lexTime() tomlLexStateFn { - // v--- cursor - // 07:32:00 - // 00:32:00.999999 - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid minute digit in time: %c", r) - } - } - - r := l.next() - if r != ':' { - return l.errorf("time minute/second separator should be :, not %c", r) - } - - for i := 0; i < 2; i++ { - r := l.next() - if !isDigit(r) { - return l.errorf("invalid second digit in time: %c", r) - } - } - - r = l.peek() - if r == '.' { - l.next() - r := l.next() - if !isDigit(r) { - return l.errorf("expected at least one digit in time's fraction, not %c", r) - } - - for { - r := l.peek() - if !isDigit(r) { - break - } - l.next() - } - } - - l.emit(tokenLocalTime) - return l.lexRvalue - -} - -func (l *tomlLexer) lexTrue() tomlLexStateFn { - l.fastForward(4) - l.emit(tokenTrue) - return l.lexRvalue -} - -func (l *tomlLexer) lexFalse() tomlLexStateFn { - l.fastForward(5) - l.emit(tokenFalse) - return l.lexRvalue -} - -func (l *tomlLexer) lexInf() tomlLexStateFn { - l.fastForward(3) - l.emit(tokenInf) - return l.lexRvalue -} - -func (l *tomlLexer) lexNan() tomlLexStateFn { - l.fastForward(3) - l.emit(tokenNan) - return l.lexRvalue -} - -func (l *tomlLexer) lexEqual() tomlLexStateFn { - l.next() - l.emit(tokenEqual) - return l.lexRvalue -} - -func (l *tomlLexer) lexComma() tomlLexStateFn { - l.next() - l.emit(tokenComma) - if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '{' { - return l.lexVoid - } - return l.lexRvalue -} - -// Parse the key and emits its value without escape sequences. -// bare keys, basic string keys and literal string keys are supported. -func (l *tomlLexer) lexKey() tomlLexStateFn { - var sb strings.Builder - - for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() { - if r == '"' { - l.next() - str, err := l.lexStringAsString(`"`, false, true) - if err != nil { - return l.errorf(err.Error()) - } - sb.WriteString("\"") - sb.WriteString(str) - sb.WriteString("\"") - l.next() - continue - } else if r == '\'' { - l.next() - str, err := l.lexLiteralStringAsString(`'`, false) - if err != nil { - return l.errorf(err.Error()) - } - sb.WriteString("'") - sb.WriteString(str) - sb.WriteString("'") - l.next() - continue - } else if r == '\n' { - return l.errorf("keys cannot contain new lines") - } else if isSpace(r) { - var str strings.Builder - str.WriteString(" ") - - // skip trailing whitespace - l.next() - for r = l.peek(); isSpace(r); r = l.peek() { - str.WriteRune(r) - l.next() - } - // break loop if not a dot - if r != '.' { - break - } - str.WriteString(".") - // skip trailing whitespace after dot - l.next() - for r = l.peek(); isSpace(r); r = l.peek() { - str.WriteRune(r) - l.next() - } - sb.WriteString(str.String()) - continue - } else if r == '.' { - // skip - } else if !isValidBareChar(r) { - return l.errorf("keys cannot contain %c character", r) - } - sb.WriteRune(r) - l.next() - } - l.emitWithValue(tokenKey, sb.String()) - return l.lexVoid -} - -func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn { - return func() tomlLexStateFn { - for next := l.peek(); next != '\n' && next != eof; next = l.peek() { - if next == '\r' && l.follow("\r\n") { - break - } - l.next() - } - l.ignore() - return previousState - } -} - -func (l *tomlLexer) lexLeftBracket() tomlLexStateFn { - l.next() - l.emit(tokenLeftBracket) - l.brackets = append(l.brackets, '[') - return l.lexRvalue -} - -func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) { - var sb strings.Builder - - if discardLeadingNewLine { - if l.follow("\r\n") { - l.skip() - l.skip() - } else if l.peek() == '\n' { - l.skip() - } - } - - // find end of string - for { - if l.follow(terminator) { - return sb.String(), nil - } - - next := l.peek() - if next == eof { - break - } - sb.WriteRune(l.next()) - } - - return "", errors.New("unclosed string") -} - -func (l *tomlLexer) lexLiteralString() tomlLexStateFn { - l.skip() - - // handle special case for triple-quote - terminator := "'" - discardLeadingNewLine := false - if l.follow("''") { - l.skip() - l.skip() - terminator = "'''" - discardLeadingNewLine = true - } - - str, err := l.lexLiteralStringAsString(terminator, discardLeadingNewLine) - if err != nil { - return l.errorf(err.Error()) - } - - l.emitWithValue(tokenString, str) - l.fastForward(len(terminator)) - l.ignore() - return l.lexRvalue -} - -// Lex a string and return the results as a string. -// Terminator is the substring indicating the end of the token. -// The resulting string does not include the terminator. -func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) { - var sb strings.Builder - - if discardLeadingNewLine { - if l.follow("\r\n") { - l.skip() - l.skip() - } else if l.peek() == '\n' { - l.skip() - } - } - - for { - if l.follow(terminator) { - return sb.String(), nil - } - - if l.follow("\\") { - l.next() - switch l.peek() { - case '\r': - fallthrough - case '\n': - fallthrough - case '\t': - fallthrough - case ' ': - // skip all whitespace chars following backslash - for strings.ContainsRune("\r\n\t ", l.peek()) { - l.next() - } - case '"': - sb.WriteString("\"") - l.next() - case 'n': - sb.WriteString("\n") - l.next() - case 'b': - sb.WriteString("\b") - l.next() - case 'f': - sb.WriteString("\f") - l.next() - case '/': - sb.WriteString("/") - l.next() - case 't': - sb.WriteString("\t") - l.next() - case 'r': - sb.WriteString("\r") - l.next() - case '\\': - sb.WriteString("\\") - l.next() - case 'u': - l.next() - var code strings.Builder - for i := 0; i < 4; i++ { - c := l.peek() - if !isHexDigit(c) { - return "", errors.New("unfinished unicode escape") - } - l.next() - code.WriteRune(c) - } - intcode, err := strconv.ParseInt(code.String(), 16, 32) - if err != nil { - return "", errors.New("invalid unicode escape: \\u" + code.String()) - } - sb.WriteRune(rune(intcode)) - case 'U': - l.next() - var code strings.Builder - for i := 0; i < 8; i++ { - c := l.peek() - if !isHexDigit(c) { - return "", errors.New("unfinished unicode escape") - } - l.next() - code.WriteRune(c) - } - intcode, err := strconv.ParseInt(code.String(), 16, 64) - if err != nil { - return "", errors.New("invalid unicode escape: \\U" + code.String()) - } - sb.WriteRune(rune(intcode)) - default: - return "", errors.New("invalid escape sequence: \\" + string(l.peek())) - } - } else { - r := l.peek() - - if 0x00 <= r && r <= 0x1F && r != '\t' && !(acceptNewLines && (r == '\n' || r == '\r')) { - return "", fmt.Errorf("unescaped control character %U", r) - } - l.next() - sb.WriteRune(r) - } - - if l.peek() == eof { - break - } - } - - return "", errors.New("unclosed string") -} - -func (l *tomlLexer) lexString() tomlLexStateFn { - l.skip() - - // handle special case for triple-quote - terminator := `"` - discardLeadingNewLine := false - acceptNewLines := false - if l.follow(`""`) { - l.skip() - l.skip() - terminator = `"""` - discardLeadingNewLine = true - acceptNewLines = true - } - - str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines) - if err != nil { - return l.errorf(err.Error()) - } - - l.emitWithValue(tokenString, str) - l.fastForward(len(terminator)) - l.ignore() - return l.lexRvalue -} - -func (l *tomlLexer) lexTableKey() tomlLexStateFn { - l.next() - - if l.peek() == '[' { - // token '[[' signifies an array of tables - l.next() - l.emit(tokenDoubleLeftBracket) - return l.lexInsideTableArrayKey - } - // vanilla table key - l.emit(tokenLeftBracket) - return l.lexInsideTableKey -} - -// Parse the key till "]]", but only bare keys are supported -func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn { - for r := l.peek(); r != eof; r = l.peek() { - switch r { - case ']': - if l.currentTokenStop > l.currentTokenStart { - l.emit(tokenKeyGroupArray) - } - l.next() - if l.peek() != ']' { - break - } - l.next() - l.emit(tokenDoubleRightBracket) - return l.lexVoid - case '[': - return l.errorf("table array key cannot contain ']'") - default: - l.next() - } - } - return l.errorf("unclosed table array key") -} - -// Parse the key till "]" but only bare keys are supported -func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn { - for r := l.peek(); r != eof; r = l.peek() { - switch r { - case ']': - if l.currentTokenStop > l.currentTokenStart { - l.emit(tokenKeyGroup) - } - l.next() - l.emit(tokenRightBracket) - return l.lexVoid - case '[': - return l.errorf("table key cannot contain ']'") - default: - l.next() - } - } - return l.errorf("unclosed table key") -} - -func (l *tomlLexer) lexRightBracket() tomlLexStateFn { - l.next() - l.emit(tokenRightBracket) - if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '[' { - return l.errorf("cannot have ']' here") - } - l.brackets = l.brackets[:len(l.brackets)-1] - return l.lexRvalue -} - -type validRuneFn func(r rune) bool - -func isValidHexRune(r rune) bool { - return r >= 'a' && r <= 'f' || - r >= 'A' && r <= 'F' || - r >= '0' && r <= '9' || - r == '_' -} - -func isValidOctalRune(r rune) bool { - return r >= '0' && r <= '7' || r == '_' -} - -func isValidBinaryRune(r rune) bool { - return r == '0' || r == '1' || r == '_' -} - -func (l *tomlLexer) lexNumber() tomlLexStateFn { - r := l.peek() - - if r == '0' { - follow := l.peekString(2) - if len(follow) == 2 { - var isValidRune validRuneFn - switch follow[1] { - case 'x': - isValidRune = isValidHexRune - case 'o': - isValidRune = isValidOctalRune - case 'b': - isValidRune = isValidBinaryRune - default: - if follow[1] >= 'a' && follow[1] <= 'z' || follow[1] >= 'A' && follow[1] <= 'Z' { - return l.errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(follow[1])) - } - } - - if isValidRune != nil { - l.next() - l.next() - digitSeen := false - for { - next := l.peek() - if !isValidRune(next) { - break - } - digitSeen = true - l.next() - } - - if !digitSeen { - return l.errorf("number needs at least one digit") - } - - l.emit(tokenInteger) - - return l.lexRvalue - } - } - } - - if r == '+' || r == '-' { - l.next() - if l.follow("inf") { - return l.lexInf - } - if l.follow("nan") { - return l.lexNan - } - } - - pointSeen := false - expSeen := false - digitSeen := false - for { - next := l.peek() - if next == '.' { - if pointSeen { - return l.errorf("cannot have two dots in one float") - } - l.next() - if !isDigit(l.peek()) { - return l.errorf("float cannot end with a dot") - } - pointSeen = true - } else if next == 'e' || next == 'E' { - expSeen = true - l.next() - r := l.peek() - if r == '+' || r == '-' { - l.next() - } - } else if isDigit(next) { - digitSeen = true - l.next() - } else if next == '_' { - l.next() - } else { - break - } - if pointSeen && !digitSeen { - return l.errorf("cannot start float with a dot") - } - } - - if !digitSeen { - return l.errorf("no digit in that number") - } - if pointSeen || expSeen { - l.emit(tokenFloat) - } else { - l.emit(tokenInteger) - } - return l.lexRvalue -} - -func (l *tomlLexer) run() { - for state := l.lexVoid; state != nil; { - state = state() - } -} - -// Entry point -func lexToml(inputBytes []byte) []token { - runes := bytes.Runes(inputBytes) - l := &tomlLexer{ - input: runes, - tokens: make([]token, 0, 256), - line: 1, - col: 1, - endbufferLine: 1, - endbufferCol: 1, - } - l.run() - return l.tokens -} diff --git a/lexer_test.go b/lexer_test.go deleted file mode 100644 index 016e122..0000000 --- a/lexer_test.go +++ /dev/null @@ -1,1247 +0,0 @@ -package toml - -import ( - "bytes" - "fmt" - "reflect" - "testing" - "text/tabwriter" -) - -func testFlow(t *testing.T, input string, expectedFlow []token) { - tokens := lexToml([]byte(input)) - - if !reflect.DeepEqual(tokens, expectedFlow) { - diffFlowsColumnsFatal(t, expectedFlow, tokens) - } -} - -func diffFlowsColumnsFatal(t *testing.T, expectedFlow []token, actualFlow []token) { - max := len(expectedFlow) - if len(actualFlow) > max { - max = len(actualFlow) - } - - b := &bytes.Buffer{} - w := tabwriter.NewWriter(b, 0, 0, 1, ' ', tabwriter.Debug) - - fmt.Fprintln(w, "expected\tT\tP\tactual\tT\tP\tdiff") - - for i := 0; i < max; i++ { - expected := "" - expectedType := "" - expectedPos := "" - if i < len(expectedFlow) { - expected = fmt.Sprintf("%s", expectedFlow[i]) - expectedType = fmt.Sprintf("%s", expectedFlow[i].typ) - expectedPos = expectedFlow[i].Position.String() - } - actual := "" - actualType := "" - actualPos := "" - if i < len(actualFlow) { - actual = fmt.Sprintf("%s", actualFlow[i]) - actualType = fmt.Sprintf("%s", actualFlow[i].typ) - actualPos = actualFlow[i].Position.String() - } - different := "" - if i >= len(expectedFlow) { - different = "+" - } else if i >= len(actualFlow) { - different = "-" - } else if !reflect.DeepEqual(expectedFlow[i], actualFlow[i]) { - different = "x" - } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", expected, expectedType, expectedPos, actual, actualType, actualPos, different) - } - w.Flush() - t.Errorf("Different flows:\n%s", b.String()) -} - -func TestValidKeyGroup(t *testing.T) { - testFlow(t, "[hello world]", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, "hello world"}, - {Position{1, 13}, tokenRightBracket, "]"}, - {Position{1, 14}, tokenEOF, ""}, - }) -} - -func TestNestedQuotedUnicodeKeyGroup(t *testing.T) { - testFlow(t, `[ j . "ʞ" . l . 'ɯ' ]`, []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, ` j . "ʞ" . l . 'ɯ' `}, - {Position{1, 21}, tokenRightBracket, "]"}, - {Position{1, 22}, tokenEOF, ""}, - }) -} - -func TestNestedQuotedUnicodeKeyAssign(t *testing.T) { - testFlow(t, ` j . "ʞ" . l . 'ɯ' = 3`, []token{ - {Position{1, 2}, tokenKey, `j . "ʞ" . l . 'ɯ'`}, - {Position{1, 20}, tokenEqual, "="}, - {Position{1, 22}, tokenInteger, "3"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestUnclosedKeyGroup(t *testing.T) { - testFlow(t, "[hello world", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenError, "unclosed table key"}, - }) -} - -func TestComment(t *testing.T) { - testFlow(t, "# blahblah", []token{ - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestKeyGroupComment(t *testing.T) { - testFlow(t, "[hello world] # blahblah", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, "hello world"}, - {Position{1, 13}, tokenRightBracket, "]"}, - {Position{1, 25}, tokenEOF, ""}, - }) -} - -func TestMultipleKeyGroupsComment(t *testing.T) { - testFlow(t, "[hello world] # blahblah\n[test]", []token{ - {Position{1, 1}, tokenLeftBracket, "["}, - {Position{1, 2}, tokenKeyGroup, "hello world"}, - {Position{1, 13}, tokenRightBracket, "]"}, - {Position{2, 1}, tokenLeftBracket, "["}, - {Position{2, 2}, tokenKeyGroup, "test"}, - {Position{2, 6}, tokenRightBracket, "]"}, - {Position{2, 7}, tokenEOF, ""}, - }) -} - -func TestSimpleWindowsCRLF(t *testing.T) { - testFlow(t, "a=4\r\nb=2", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 2}, tokenEqual, "="}, - {Position{1, 3}, tokenInteger, "4"}, - {Position{2, 1}, tokenKey, "b"}, - {Position{2, 2}, tokenEqual, "="}, - {Position{2, 3}, tokenInteger, "2"}, - {Position{2, 4}, tokenEOF, ""}, - }) -} - -func TestBasicKey(t *testing.T) { - testFlow(t, "hello", []token{ - {Position{1, 1}, tokenKey, "hello"}, - {Position{1, 6}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithUnderscore(t *testing.T) { - testFlow(t, "hello_hello", []token{ - {Position{1, 1}, tokenKey, "hello_hello"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithDash(t *testing.T) { - testFlow(t, "hello-world", []token{ - {Position{1, 1}, tokenKey, "hello-world"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithUppercaseMix(t *testing.T) { - testFlow(t, "helloHELLOHello", []token{ - {Position{1, 1}, tokenKey, "helloHELLOHello"}, - {Position{1, 16}, tokenEOF, ""}, - }) -} - -func TestBasicKeyWithInternationalCharacters(t *testing.T) { - testFlow(t, "'héllÖ'", []token{ - {Position{1, 1}, tokenKey, "'héllÖ'"}, - {Position{1, 8}, tokenEOF, ""}, - }) -} - -func TestBasicKeyAndEqual(t *testing.T) { - testFlow(t, "hello =", []token{ - {Position{1, 1}, tokenKey, "hello"}, - {Position{1, 7}, tokenEqual, "="}, - {Position{1, 8}, tokenEOF, ""}, - }) -} - -func TestKeyWithSharpAndEqual(t *testing.T) { - testFlow(t, "key#name = 5", []token{ - {Position{1, 1}, tokenError, "keys cannot contain # character"}, - }) -} - -func TestKeyWithSymbolsAndEqual(t *testing.T) { - testFlow(t, "~!@$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{ - {Position{1, 1}, tokenError, "keys cannot contain ~ character"}, - }) -} - -func TestKeyEqualStringEscape(t *testing.T) { - testFlow(t, `foo = "hello\""`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello\""}, - {Position{1, 16}, tokenEOF, ""}, - }) -} - -func TestKeyEqualStringUnfinished(t *testing.T) { - testFlow(t, `foo = "bar`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unclosed string"}, - }) -} - -func TestKeyEqualString(t *testing.T) { - testFlow(t, `foo = "bar"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "bar"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestKeyEqualTrue(t *testing.T) { - testFlow(t, "foo = true", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenTrue, "true"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestKeyEqualFalse(t *testing.T) { - testFlow(t, "foo = false", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFalse, "false"}, - {Position{1, 12}, tokenEOF, ""}, - }) -} - -func TestArrayNestedString(t *testing.T) { - testFlow(t, `a = [ ["hello", "world"] ]`, []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 9}, tokenString, "hello"}, - {Position{1, 15}, tokenComma, ","}, - {Position{1, 18}, tokenString, "world"}, - {Position{1, 24}, tokenRightBracket, "]"}, - {Position{1, 26}, tokenRightBracket, "]"}, - {Position{1, 27}, tokenEOF, ""}, - }) -} - -func TestArrayNestedInts(t *testing.T) { - testFlow(t, "a = [ [42, 21], [10] ]", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenInteger, "42"}, - {Position{1, 10}, tokenComma, ","}, - {Position{1, 12}, tokenInteger, "21"}, - {Position{1, 14}, tokenRightBracket, "]"}, - {Position{1, 15}, tokenComma, ","}, - {Position{1, 17}, tokenLeftBracket, "["}, - {Position{1, 18}, tokenInteger, "10"}, - {Position{1, 20}, tokenRightBracket, "]"}, - {Position{1, 22}, tokenRightBracket, "]"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestArrayInts(t *testing.T) { - testFlow(t, "a = [ 42, 21, 10, ]", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 7}, tokenInteger, "42"}, - {Position{1, 9}, tokenComma, ","}, - {Position{1, 11}, tokenInteger, "21"}, - {Position{1, 13}, tokenComma, ","}, - {Position{1, 15}, tokenInteger, "10"}, - {Position{1, 17}, tokenComma, ","}, - {Position{1, 19}, tokenRightBracket, "]"}, - {Position{1, 20}, tokenEOF, ""}, - }) -} - -func TestMultilineArrayComments(t *testing.T) { - testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenLeftBracket, "["}, - {Position{1, 6}, tokenInteger, "1"}, - {Position{1, 7}, tokenComma, ","}, - {Position{2, 1}, tokenInteger, "2"}, - {Position{2, 2}, tokenComma, ","}, - {Position{3, 1}, tokenInteger, "3"}, - {Position{3, 2}, tokenComma, ","}, - {Position{4, 1}, tokenRightBracket, "]"}, - {Position{4, 2}, tokenEOF, ""}, - }) -} - -func TestNestedArraysComment(t *testing.T) { - toml := ` -someArray = [ -# does not work -["entry1"] -]` - testFlow(t, toml, []token{ - {Position{2, 1}, tokenKey, "someArray"}, - {Position{2, 11}, tokenEqual, "="}, - {Position{2, 13}, tokenLeftBracket, "["}, - {Position{4, 1}, tokenLeftBracket, "["}, - {Position{4, 3}, tokenString, "entry1"}, - {Position{4, 10}, tokenRightBracket, "]"}, - {Position{5, 1}, tokenRightBracket, "]"}, - {Position{5, 2}, tokenEOF, ""}, - }) -} - -func TestKeyEqualArrayBools(t *testing.T) { - testFlow(t, "foo = [true, false, true]", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenTrue, "true"}, - {Position{1, 12}, tokenComma, ","}, - {Position{1, 14}, tokenFalse, "false"}, - {Position{1, 19}, tokenComma, ","}, - {Position{1, 21}, tokenTrue, "true"}, - {Position{1, 25}, tokenRightBracket, "]"}, - {Position{1, 26}, tokenEOF, ""}, - }) -} - -func TestKeyEqualArrayBoolsWithComments(t *testing.T) { - testFlow(t, "foo = [true, false, true] # YEAH", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenTrue, "true"}, - {Position{1, 12}, tokenComma, ","}, - {Position{1, 14}, tokenFalse, "false"}, - {Position{1, 19}, tokenComma, ","}, - {Position{1, 21}, tokenTrue, "true"}, - {Position{1, 25}, tokenRightBracket, "]"}, - {Position{1, 33}, tokenEOF, ""}, - }) -} - -func TestKeyEqualDate(t *testing.T) { - t.Run("local date time", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T07:32:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenEOF, ""}, - }) - }) - - t.Run("local date time space", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 07:32:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenEOF, ""}, - }) - }) - - t.Run("local date time fraction", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00:32:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenEOF, ""}, - }) - }) - - t.Run("local date time fraction space", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time utc", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenTimeOffset, "Z"}, - {Position{1, 27}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time -07:00", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00:32:00-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenTimeOffset, "-07:00"}, - {Position{1, 32}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time fractions -07:00", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00:32:00.999999-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenTimeOffset, "-07:00"}, - {Position{1, 39}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated utc", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 07:32:00Z", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "07:32:00"}, - {Position{1, 26}, tokenTimeOffset, "Z"}, - {Position{1, 27}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenTimeOffset, "-07:00"}, - {Position{1, 32}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated fraction offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00.999999-07:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 33}, tokenTimeOffset, "-07:00"}, - {Position{1, 39}, tokenEOF, ""}, - }) - }) - - t.Run("local date", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 17}, tokenEOF, ""}, - }) - }) - - t.Run("local time", func(t *testing.T) { - testFlow(t, "foo = 07:32:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalTime, "07:32:00"}, - {Position{1, 15}, tokenEOF, ""}, - }) - }) - - t.Run("local time fraction", func(t *testing.T) { - testFlow(t, "foo = 00:32:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalTime, "00:32:00.999999"}, - {Position{1, 22}, tokenEOF, ""}, - }) - }) - - t.Run("local time invalid minute digit", func(t *testing.T) { - testFlow(t, "foo = 00:3x:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "invalid minute digit in time: x"}, - }) - }) - - t.Run("local time invalid minute/second digit", func(t *testing.T) { - testFlow(t, "foo = 00:30x00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "time minute/second separator should be :, not x"}, - }) - }) - - t.Run("local time invalid second digit", func(t *testing.T) { - testFlow(t, "foo = 00:30:x0.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "invalid second digit in time: x"}, - }) - }) - - t.Run("local time invalid second digit", func(t *testing.T) { - testFlow(t, "foo = 00:30:00.F", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "expected at least one digit in time's fraction, not F"}, - }) - }) - - t.Run("local date-time invalid minute digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:3x:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "invalid minute digit in time: x"}, - }) - }) - - t.Run("local date-time invalid hour digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T0x:30:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "invalid hour digit in time: x"}, - }) - }) - - t.Run("local date-time invalid hour digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27T00x30:00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "time hour/minute separator should be :, not x"}, - }) - }) - - t.Run("local date-time invalid minute/second digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:30x00.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "time minute/second separator should be :, not x"}, - }) - }) - - t.Run("local date-time invalid second digit", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:30:x0.999999", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "invalid second digit in time: x"}, - }) - }) - - t.Run("local date-time invalid fraction", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:30:00.F", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenError, "expected at least one digit in time's fraction, not F"}, - }) - }) - - t.Run("local date-time invalid month-date separator", func(t *testing.T) { - testFlow(t, "foo = 1979-05X27 00:30:00.F", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "expected - to separate month of a date, not X"}, - }) - }) - - t.Run("local date-time extra whitespace", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 ", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 19}, tokenEOF, ""}, - }) - }) - - t.Run("local date-time extra whitespace", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 ", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 22}, tokenEOF, ""}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-0x:00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenError, "invalid hour digit in time offset: x"}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-07x00", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenError, "time offset hour/minute separator should be :, not x"}, - }) - }) - - t.Run("offset date-time space separated offset", func(t *testing.T) { - testFlow(t, "foo = 1979-05-27 00:32:00-07:x0", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLocalDate, "1979-05-27"}, - {Position{1, 18}, tokenLocalTime, "00:32:00"}, - {Position{1, 26}, tokenError, "invalid minute digit in time offset: x"}, - }) - }) -} - -func TestFloatEndingWithDot(t *testing.T) { - testFlow(t, "foo = 42.", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "float cannot end with a dot"}, - }) -} - -func TestFloatWithTwoDots(t *testing.T) { - testFlow(t, "foo = 4.2.", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "cannot have two dots in one float"}, - }) -} - -func TestFloatWithExponent1(t *testing.T) { - testFlow(t, "a = 5e+22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "5e+22"}, - {Position{1, 10}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent2(t *testing.T) { - testFlow(t, "a = 5E+22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "5E+22"}, - {Position{1, 10}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent3(t *testing.T) { - testFlow(t, "a = -5e+22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "-5e+22"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent4(t *testing.T) { - testFlow(t, "a = -5e-22", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "-5e-22"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestFloatWithExponent5(t *testing.T) { - testFlow(t, "a = 6.626e-34", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenFloat, "6.626e-34"}, - {Position{1, 14}, tokenEOF, ""}, - }) -} - -func TestInvalidEsquapeSequence(t *testing.T) { - testFlow(t, `foo = "\x"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "invalid escape sequence: \\x"}, - }) -} - -func TestNestedArrays(t *testing.T) { - testFlow(t, "foo = [[[]]]", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenLeftBracket, "["}, - {Position{1, 9}, tokenLeftBracket, "["}, - {Position{1, 10}, tokenRightBracket, "]"}, - {Position{1, 11}, tokenRightBracket, "]"}, - {Position{1, 12}, tokenRightBracket, "]"}, - {Position{1, 13}, tokenEOF, ""}, - }) -} - -func TestKeyEqualNumber(t *testing.T) { - testFlow(t, "foo = 42", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "42"}, - {Position{1, 9}, tokenEOF, ""}, - }) - - testFlow(t, "foo = +42", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "+42"}, - {Position{1, 10}, tokenEOF, ""}, - }) - - testFlow(t, "foo = -42", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "-42"}, - {Position{1, 10}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 4.2", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFloat, "4.2"}, - {Position{1, 10}, tokenEOF, ""}, - }) - - testFlow(t, "foo = +4.2", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFloat, "+4.2"}, - {Position{1, 11}, tokenEOF, ""}, - }) - - testFlow(t, "foo = -4.2", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenFloat, "-4.2"}, - {Position{1, 11}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 1_000", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "1_000"}, - {Position{1, 12}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 5_349_221", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "5_349_221"}, - {Position{1, 16}, tokenEOF, ""}, - }) - - testFlow(t, "foo = 1_2_3_4_5", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "1_2_3_4_5"}, - {Position{1, 16}, tokenEOF, ""}, - }) - - testFlow(t, "flt8 = 9_224_617.445_991_228_313", []token{ - {Position{1, 1}, tokenKey, "flt8"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{1, 8}, tokenFloat, "9_224_617.445_991_228_313"}, - {Position{1, 33}, tokenEOF, ""}, - }) - - testFlow(t, "foo = +", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenError, "no digit in that number"}, - }) -} - -func TestMultiline(t *testing.T) { - testFlow(t, "foo = 42\nbar=21", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenInteger, "42"}, - {Position{2, 1}, tokenKey, "bar"}, - {Position{2, 4}, tokenEqual, "="}, - {Position{2, 5}, tokenInteger, "21"}, - {Position{2, 7}, tokenEOF, ""}, - }) -} - -func TestKeyEqualStringUnicodeEscape(t *testing.T) { - testFlow(t, `foo = "hello \u2665"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello ♥"}, - {Position{1, 21}, tokenEOF, ""}, - }) - testFlow(t, `foo = "hello \U000003B4"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello δ"}, - {Position{1, 25}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\uabcd"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\uabcd"}, - {Position{1, 15}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\uABCD"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\uABCD"}, - {Position{1, 15}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\U000bcdef"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\U000bcdef"}, - {Position{1, 19}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\U000BCDEF"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\U000BCDEF"}, - {Position{1, 19}, tokenEOF, ""}, - }) - testFlow(t, `foo = "\u2"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unfinished unicode escape"}, - }) - testFlow(t, `foo = "\U2"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unfinished unicode escape"}, - }) -} - -func TestKeyEqualStringNoEscape(t *testing.T) { - testFlow(t, "foo = \"hello \u0002\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unescaped control character U+0002"}, - }) - testFlow(t, "foo = \"hello \u001F\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unescaped control character U+001F"}, - }) -} - -func TestLiteralString(t *testing.T) { - testFlow(t, `foo = 'C:\Users\nodejs\templates'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `C:\Users\nodejs\templates`}, - {Position{1, 34}, tokenEOF, ""}, - }) - testFlow(t, `foo = '\\ServerX\admin$\system32\'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `\\ServerX\admin$\system32\`}, - {Position{1, 35}, tokenEOF, ""}, - }) - testFlow(t, `foo = 'Tom "Dubs" Preston-Werner'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `Tom "Dubs" Preston-Werner`}, - {Position{1, 34}, tokenEOF, ""}, - }) - testFlow(t, `foo = '<\i\c*\s*>'`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, `<\i\c*\s*>`}, - {Position{1, 19}, tokenEOF, ""}, - }) - testFlow(t, `foo = 'C:\Users\nodejs\unfinis`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenError, "unclosed string"}, - }) -} - -func TestMultilineLiteralString(t *testing.T) { - testFlow(t, `foo = '''hello 'literal' world'''`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, `hello 'literal' world`}, - {Position{1, 34}, tokenEOF, ""}, - }) - - testFlow(t, "foo = '''\nhello\n'literal'\nworld'''", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "hello\n'literal'\nworld"}, - {Position{4, 9}, tokenEOF, ""}, - }) - testFlow(t, "foo = '''\r\nhello\r\n'literal'\r\nworld'''", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "hello\r\n'literal'\r\nworld"}, - {Position{4, 9}, tokenEOF, ""}, - }) -} - -func TestMultilineString(t *testing.T) { - testFlow(t, `foo = """hello "literal" world"""`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, `hello "literal" world`}, - {Position{1, 34}, tokenEOF, ""}, - }) - - testFlow(t, "foo = \"\"\"\r\nhello\\\r\n\"literal\"\\\nworld\"\"\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "hello\"literal\"world"}, - {Position{4, 9}, tokenEOF, ""}, - }) - - testFlow(t, "foo = \"\"\"\\\n \\\n \\\n hello\\\nmultiline\\\nworld\"\"\"", []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, "hellomultilineworld"}, - {Position{6, 9}, tokenEOF, ""}, - }) - - testFlow(t, `foo = """hello world"""`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 10}, tokenString, "hello\tworld"}, - {Position{1, 24}, tokenEOF, ""}, - }) - - testFlow(t, "key2 = \"\"\"\nThe quick brown \\\n\n\n fox jumps over \\\n the lazy dog.\"\"\"", []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "The quick brown fox jumps over the lazy dog."}, - {Position{6, 21}, tokenEOF, ""}, - }) - - testFlow(t, "key2 = \"\"\"\\\n The quick brown \\\n fox jumps over \\\n the lazy dog.\\\n \"\"\"", []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{1, 11}, tokenString, "The quick brown fox jumps over the lazy dog."}, - {Position{5, 11}, tokenEOF, ""}, - }) - - testFlow(t, `key2 = "Roses are red\nViolets are blue"`, []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{1, 9}, tokenString, "Roses are red\nViolets are blue"}, - {Position{1, 41}, tokenEOF, ""}, - }) - - testFlow(t, "key2 = \"\"\"\nRoses are red\nViolets are blue\"\"\"", []token{ - {Position{1, 1}, tokenKey, "key2"}, - {Position{1, 6}, tokenEqual, "="}, - {Position{2, 1}, tokenString, "Roses are red\nViolets are blue"}, - {Position{3, 20}, tokenEOF, ""}, - }) -} - -func TestUnicodeString(t *testing.T) { - testFlow(t, `foo = "hello ♥ world"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello ♥ world"}, - {Position{1, 22}, tokenEOF, ""}, - }) -} - -func TestEscapeInString(t *testing.T) { - testFlow(t, `foo = "\b\f\/"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "\b\f/"}, - {Position{1, 15}, tokenEOF, ""}, - }) -} - -func TestTabInString(t *testing.T) { - testFlow(t, `foo = "hello world"`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 8}, tokenString, "hello\tworld"}, - {Position{1, 20}, tokenEOF, ""}, - }) -} - -func TestKeyGroupArray(t *testing.T) { - testFlow(t, "[[foo]]", []token{ - {Position{1, 1}, tokenDoubleLeftBracket, "[["}, - {Position{1, 3}, tokenKeyGroupArray, "foo"}, - {Position{1, 6}, tokenDoubleRightBracket, "]]"}, - {Position{1, 8}, tokenEOF, ""}, - }) -} - -func TestQuotedKey(t *testing.T) { - testFlow(t, "\"a b\" = 42", []token{ - {Position{1, 1}, tokenKey, "\"a b\""}, - {Position{1, 7}, tokenEqual, "="}, - {Position{1, 9}, tokenInteger, "42"}, - {Position{1, 11}, tokenEOF, ""}, - }) -} - -func TestQuotedKeyTab(t *testing.T) { - testFlow(t, "\"num\tber\" = 123", []token{ - {Position{1, 1}, tokenKey, "\"num\tber\""}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "123"}, - {Position{1, 16}, tokenEOF, ""}, - }) -} - -func TestKeyNewline(t *testing.T) { - testFlow(t, "a\n= 4", []token{ - {Position{1, 1}, tokenError, "keys cannot contain new lines"}, - }) -} - -func TestInvalidFloat(t *testing.T) { - testFlow(t, "a=7e1_", []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 2}, tokenEqual, "="}, - {Position{1, 3}, tokenFloat, "7e1_"}, - {Position{1, 7}, tokenEOF, ""}, - }) -} - -func TestLexUnknownRvalue(t *testing.T) { - testFlow(t, `a = !b`, []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenError, "no value can start with !"}, - }) - - testFlow(t, `a = \b`, []token{ - {Position{1, 1}, tokenKey, "a"}, - {Position{1, 3}, tokenEqual, "="}, - {Position{1, 5}, tokenError, `no value can start with \`}, - }) -} - -func TestLexInlineTableEmpty(t *testing.T) { - testFlow(t, `foo = {}`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 8}, tokenRightCurlyBrace, "}"}, - {Position{1, 9}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKey(t *testing.T) { - testFlow(t, `foo = { bar = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "bar"}, - {Position{1, 13}, tokenEqual, "="}, - {Position{1, 16}, tokenString, "baz"}, - {Position{1, 21}, tokenRightCurlyBrace, "}"}, - {Position{1, 22}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyDash(t *testing.T) { - testFlow(t, `foo = { -bar = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "-bar"}, - {Position{1, 14}, tokenEqual, "="}, - {Position{1, 17}, tokenString, "baz"}, - {Position{1, 22}, tokenRightCurlyBrace, "}"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyInArray(t *testing.T) { - testFlow(t, `foo = [{ -bar_ = "baz" }]`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftBracket, "["}, - {Position{1, 8}, tokenLeftCurlyBrace, "{"}, - {Position{1, 10}, tokenKey, "-bar_"}, - {Position{1, 16}, tokenEqual, "="}, - {Position{1, 19}, tokenString, "baz"}, - {Position{1, 24}, tokenRightCurlyBrace, "}"}, - {Position{1, 25}, tokenRightBracket, "]"}, - {Position{1, 26}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableError1(t *testing.T) { - testFlow(t, `foo = { 123 = 0 ]`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "123"}, - {Position{1, 13}, tokenEqual, "="}, - {Position{1, 15}, tokenInteger, "0"}, - {Position{1, 17}, tokenRightBracket, "]"}, - {Position{1, 18}, tokenError, "cannot have ']' here"}, - }) -} - -func TestLexInlineTableError2(t *testing.T) { - testFlow(t, `foo = { 123 = 0 }}`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "123"}, - {Position{1, 13}, tokenEqual, "="}, - {Position{1, 15}, tokenInteger, "0"}, - {Position{1, 17}, tokenRightCurlyBrace, "}"}, - {Position{1, 18}, tokenRightCurlyBrace, "}"}, - {Position{1, 19}, tokenError, "cannot have '}' here"}, - }) -} - -func TestLexInlineTableDottedKey1(t *testing.T) { - testFlow(t, `foo = { a = 0, 123.45abc = 0 }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "a"}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "0"}, - {Position{1, 14}, tokenComma, ","}, - {Position{1, 16}, tokenKey, "123.45abc"}, - {Position{1, 26}, tokenEqual, "="}, - {Position{1, 28}, tokenInteger, "0"}, - {Position{1, 30}, tokenRightCurlyBrace, "}"}, - {Position{1, 31}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableDottedKey2(t *testing.T) { - testFlow(t, `foo = { a = 0, '123'.'45abc' = 0 }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "a"}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "0"}, - {Position{1, 14}, tokenComma, ","}, - {Position{1, 16}, tokenKey, "'123'.'45abc'"}, - {Position{1, 30}, tokenEqual, "="}, - {Position{1, 32}, tokenInteger, "0"}, - {Position{1, 34}, tokenRightCurlyBrace, "}"}, - {Position{1, 35}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableDottedKey3(t *testing.T) { - testFlow(t, `foo = { a = 0, "123"."45ʎǝʞ" = 0 }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "a"}, - {Position{1, 11}, tokenEqual, "="}, - {Position{1, 13}, tokenInteger, "0"}, - {Position{1, 14}, tokenComma, ","}, - {Position{1, 16}, tokenKey, `"123"."45ʎǝʞ"`}, - {Position{1, 30}, tokenEqual, "="}, - {Position{1, 32}, tokenInteger, "0"}, - {Position{1, 34}, tokenRightCurlyBrace, "}"}, - {Position{1, 35}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyWithComma(t *testing.T) { - testFlow(t, `foo = { -bar1 = "baz", -bar_ = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "-bar1"}, - {Position{1, 15}, tokenEqual, "="}, - {Position{1, 18}, tokenString, "baz"}, - {Position{1, 22}, tokenComma, ","}, - {Position{1, 24}, tokenKey, "-bar_"}, - {Position{1, 30}, tokenEqual, "="}, - {Position{1, 33}, tokenString, "baz"}, - {Position{1, 38}, tokenRightCurlyBrace, "}"}, - {Position{1, 39}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableBareKeyUnderscore(t *testing.T) { - testFlow(t, `foo = { _bar = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "_bar"}, - {Position{1, 14}, tokenEqual, "="}, - {Position{1, 17}, tokenString, "baz"}, - {Position{1, 22}, tokenRightCurlyBrace, "}"}, - {Position{1, 23}, tokenEOF, ""}, - }) -} - -func TestLexInlineTableQuotedKey(t *testing.T) { - testFlow(t, `foo = { "bar" = "baz" }`, []token{ - {Position{1, 1}, tokenKey, "foo"}, - {Position{1, 5}, tokenEqual, "="}, - {Position{1, 7}, tokenLeftCurlyBrace, "{"}, - {Position{1, 9}, tokenKey, "\"bar\""}, - {Position{1, 15}, tokenEqual, "="}, - {Position{1, 18}, tokenString, "baz"}, - {Position{1, 23}, tokenRightCurlyBrace, "}"}, - {Position{1, 24}, tokenEOF, ""}, - }) -} - -func BenchmarkLexer(b *testing.B) { - sample := `title = "Hugo: A Fast and Flexible Website Generator" -baseurl = "http://gohugo.io/" -MetaDataFormat = "yaml" -pluralizeListTitles = false - -[params] - description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go" - author = "Steve Francia (spf13) and friends" - release = "0.22-DEV" - -[[menu.main]] - name = "Download Hugo" - pre = "" - url = "https://github.com/spf13/hugo/releases" - weight = -200 -` - b.ResetTimer() - for i := 0; i < b.N; i++ { - lexToml([]byte(sample)) - } -} diff --git a/localtime.go b/localtime.go deleted file mode 100644 index a2149e9..0000000 --- a/localtime.go +++ /dev/null @@ -1,281 +0,0 @@ -// Implementation of TOML's local date/time. -// Copied over from https://github.com/googleapis/google-cloud-go/blob/master/civil/civil.go -// to avoid pulling all the Google dependencies. -// -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package civil implements types for civil time, a time-zone-independent -// representation of time that follows the rules of the proleptic -// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second -// minutes. -// -// Because they lack location information, these types do not represent unique -// moments or intervals of time. Use time.Time for that purpose. -package toml - -import ( - "fmt" - "time" -) - -// A LocalDate represents a date (year, month, day). -// -// This type does not include location information, and therefore does not -// describe a unique 24-hour timespan. -type LocalDate struct { - Year int // Year (e.g., 2014). - Month time.Month // Month of the year (January = 1, ...). - Day int // Day of the month, starting at 1. -} - -// LocalDateOf returns the LocalDate in which a time occurs in that time's location. -func LocalDateOf(t time.Time) LocalDate { - var d LocalDate - d.Year, d.Month, d.Day = t.Date() - return d -} - -// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents. -func ParseLocalDate(s string) (LocalDate, error) { - t, err := time.Parse("2006-01-02", s) - if err != nil { - return LocalDate{}, err - } - return LocalDateOf(t), nil -} - -// String returns the date in RFC3339 full-date format. -func (d LocalDate) String() string { - return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) -} - -// IsValid reports whether the date is valid. -func (d LocalDate) IsValid() bool { - return LocalDateOf(d.In(time.UTC)) == d -} - -// In returns the time corresponding to time 00:00:00 of the date in the location. -// -// In is always consistent with time.LocalDate, even when time.LocalDate returns a time -// on a different day. For example, if loc is America/Indiana/Vincennes, then both -// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc) -// and -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc) -// return 23:00:00 on April 30, 1955. -// -// In panics if loc is nil. -func (d LocalDate) In(loc *time.Location) time.Time { - return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc) -} - -// AddDays returns the date that is n days in the future. -// n can also be negative to go into the past. -func (d LocalDate) AddDays(n int) LocalDate { - return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n)) -} - -// DaysSince returns the signed number of days between the date and s, not including the end day. -// This is the inverse operation to AddDays. -func (d LocalDate) DaysSince(s LocalDate) (days int) { - // We convert to Unix time so we do not have to worry about leap seconds: - // Unix time increases by exactly 86400 seconds per day. - deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() - return int(deltaUnix / 86400) -} - -// Before reports whether d1 occurs before d2. -func (d1 LocalDate) Before(d2 LocalDate) bool { - if d1.Year != d2.Year { - return d1.Year < d2.Year - } - if d1.Month != d2.Month { - return d1.Month < d2.Month - } - return d1.Day < d2.Day -} - -// After reports whether d1 occurs after d2. -func (d1 LocalDate) After(d2 LocalDate) bool { - return d2.Before(d1) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of d.String(). -func (d LocalDate) MarshalText() ([]byte, error) { - return []byte(d.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The date is expected to be a string in a format accepted by ParseLocalDate. -func (d *LocalDate) UnmarshalText(data []byte) error { - var err error - *d, err = ParseLocalDate(string(data)) - return err -} - -// A LocalTime represents a time with nanosecond precision. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -// -// This type exists to represent the TIME type in storage-based APIs like BigQuery. -// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type. -type LocalTime struct { - Hour int // The hour of the day in 24-hour format; range [0-23] - Minute int // The minute of the hour; range [0-59] - Second int // The second of the minute; range [0-59] - Nanosecond int // The nanosecond of the second; range [0-999999999] -} - -// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs -// in that time's location. It ignores the date. -func LocalTimeOf(t time.Time) LocalTime { - var tm LocalTime - tm.Hour, tm.Minute, tm.Second = t.Clock() - tm.Nanosecond = t.Nanosecond() - return tm -} - -// ParseLocalTime parses a string and returns the time value it represents. -// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After -// the HH:MM:SS part of the string, an optional fractional part may appear, -// consisting of a decimal point followed by one to nine decimal digits. -// (RFC3339 admits only one digit after the decimal point). -func ParseLocalTime(s string) (LocalTime, error) { - t, err := time.Parse("15:04:05.999999999", s) - if err != nil { - return LocalTime{}, err - } - return LocalTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalTime. If Nanoseconds -// is zero, no fractional part will be generated. Otherwise, the result will -// end with a fractional part consisting of a decimal point and nine digits. -func (t LocalTime) String() string { - s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) - if t.Nanosecond == 0 { - return s - } - return s + fmt.Sprintf(".%09d", t.Nanosecond) -} - -// IsValid reports whether the time is valid. -func (t LocalTime) IsValid() bool { - // Construct a non-zero time. - tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) - return LocalTimeOf(tm) == t -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of t.String(). -func (t LocalTime) MarshalText() ([]byte, error) { - return []byte(t.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The time is expected to be a string in a format accepted by ParseLocalTime. -func (t *LocalTime) UnmarshalText(data []byte) error { - var err error - *t, err = ParseLocalTime(string(data)) - return err -} - -// A LocalDateTime represents a date and time. -// -// This type does not include location information, and therefore does not -// describe a unique moment in time. -type LocalDateTime struct { - Date LocalDate - Time LocalTime -} - -// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub. - -// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location. -func LocalDateTimeOf(t time.Time) LocalDateTime { - return LocalDateTime{ - Date: LocalDateOf(t), - Time: LocalTimeOf(t), - } -} - -// ParseLocalDateTime parses a string and returns the LocalDateTime it represents. -// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits -// the time offset but includes an optional fractional time, as described in -// ParseLocalTime. Informally, the accepted format is -// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] -// where the 'T' may be a lower-case 't'. -func ParseLocalDateTime(s string) (LocalDateTime, error) { - t, err := time.Parse("2006-01-02T15:04:05.999999999", s) - if err != nil { - t, err = time.Parse("2006-01-02t15:04:05.999999999", s) - if err != nil { - return LocalDateTime{}, err - } - } - return LocalDateTimeOf(t), nil -} - -// String returns the date in the format described in ParseLocalDate. -func (dt LocalDateTime) String() string { - return dt.Date.String() + "T" + dt.Time.String() -} - -// IsValid reports whether the datetime is valid. -func (dt LocalDateTime) IsValid() bool { - return dt.Date.IsValid() && dt.Time.IsValid() -} - -// In returns the time corresponding to the LocalDateTime in the given location. -// -// If the time is missing or ambigous at the location, In returns the same -// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then -// both -// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc) -// and -// civil.LocalDateTime{ -// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}}, -// civil.LocalTime{Minute: 30}}.In(loc) -// return 23:30:00 on April 30, 1955. -// -// In panics if loc is nil. -func (dt LocalDateTime) In(loc *time.Location) time.Time { - return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day, dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc) -} - -// Before reports whether dt1 occurs before dt2. -func (dt1 LocalDateTime) Before(dt2 LocalDateTime) bool { - return dt1.In(time.UTC).Before(dt2.In(time.UTC)) -} - -// After reports whether dt1 occurs after dt2. -func (dt1 LocalDateTime) After(dt2 LocalDateTime) bool { - return dt2.Before(dt1) -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The output is the result of dt.String(). -func (dt LocalDateTime) MarshalText() ([]byte, error) { - return []byte(dt.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The datetime is expected to be a string in a format accepted by ParseLocalDateTime -func (dt *LocalDateTime) UnmarshalText(data []byte) error { - var err error - *dt, err = ParseLocalDateTime(string(data)) - return err -} diff --git a/localtime_test.go b/localtime_test.go deleted file mode 100644 index 4bbb5b0..0000000 --- a/localtime_test.go +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2016 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package toml - -import ( - "encoding/json" - "reflect" - "testing" - "time" -) - -func cmpEqual(x, y interface{}) bool { - return reflect.DeepEqual(x, y) -} - -func TestDates(t *testing.T) { - for _, test := range []struct { - date LocalDate - loc *time.Location - wantStr string - wantTime time.Time - }{ - { - date: LocalDate{2014, 7, 29}, - loc: time.Local, - wantStr: "2014-07-29", - wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local), - }, - { - date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)), - loc: time.UTC, - wantStr: "2014-08-20", - wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC), - }, - { - date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)), - loc: time.UTC, - wantStr: "0999-01-26", - wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC), - }, - } { - if got := test.date.String(); got != test.wantStr { - t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) - } - if got := test.date.In(test.loc); !got.Equal(test.wantTime) { - t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) - } - } -} - -func TestDateIsValid(t *testing.T) { - for _, test := range []struct { - date LocalDate - want bool - }{ - {LocalDate{2014, 7, 29}, true}, - {LocalDate{2000, 2, 29}, true}, - {LocalDate{10000, 12, 31}, true}, - {LocalDate{1, 1, 1}, true}, - {LocalDate{0, 1, 1}, true}, // year zero is OK - {LocalDate{-1, 1, 1}, true}, // negative year is OK - {LocalDate{1, 0, 1}, false}, - {LocalDate{1, 1, 0}, false}, - {LocalDate{2016, 1, 32}, false}, - {LocalDate{2016, 13, 1}, false}, - {LocalDate{1, -1, 1}, false}, - {LocalDate{1, 1, -1}, false}, - } { - got := test.date.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.date, got, test.want) - } - } -} - -func TestParseDate(t *testing.T) { - for _, test := range []struct { - str string - want LocalDate // if empty, expect an error - }{ - {"2016-01-02", LocalDate{2016, 1, 2}}, - {"2016-12-31", LocalDate{2016, 12, 31}}, - {"0003-02-04", LocalDate{3, 2, 4}}, - {"999-01-26", LocalDate{}}, - {"", LocalDate{}}, - {"2016-01-02x", LocalDate{}}, - } { - got, err := ParseLocalDate(test.str) - if got != test.want { - t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) - } - if err != nil && test.want != (LocalDate{}) { - t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) - } - } -} - -func TestDateArithmetic(t *testing.T) { - for _, test := range []struct { - desc string - start LocalDate - end LocalDate - days int - }{ - { - desc: "zero days noop", - start: LocalDate{2014, 5, 9}, - end: LocalDate{2014, 5, 9}, - days: 0, - }, - { - desc: "crossing a year boundary", - start: LocalDate{2014, 12, 31}, - end: LocalDate{2015, 1, 1}, - days: 1, - }, - { - desc: "negative number of days", - start: LocalDate{2015, 1, 1}, - end: LocalDate{2014, 12, 31}, - days: -1, - }, - { - desc: "full leap year", - start: LocalDate{2004, 1, 1}, - end: LocalDate{2005, 1, 1}, - days: 366, - }, - { - desc: "full non-leap year", - start: LocalDate{2001, 1, 1}, - end: LocalDate{2002, 1, 1}, - days: 365, - }, - { - desc: "crossing a leap second", - start: LocalDate{1972, 6, 30}, - end: LocalDate{1972, 7, 1}, - days: 1, - }, - { - desc: "dates before the unix epoch", - start: LocalDate{101, 1, 1}, - end: LocalDate{102, 1, 1}, - days: 365, - }, - } { - if got := test.start.AddDays(test.days); got != test.end { - t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) - } - if got := test.end.DaysSince(test.start); got != test.days { - t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) - } - } -} - -func TestDateBefore(t *testing.T) { - for _, test := range []struct { - d1, d2 LocalDate - want bool - }{ - {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true}, - {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, - {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true}, - {LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true}, - } { - if got := test.d1.Before(test.d2); got != test.want { - t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want) - } - } -} - -func TestDateAfter(t *testing.T) { - for _, test := range []struct { - d1, d2 LocalDate - want bool - }{ - {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false}, - {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, - {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false}, - } { - if got := test.d1.After(test.d2); got != test.want { - t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want) - } - } -} - -func TestTimeToString(t *testing.T) { - for _, test := range []struct { - str string - time LocalTime - roundTrip bool // ParseLocalTime(str).String() == str? - }{ - {"13:26:33", LocalTime{13, 26, 33, 0}, true}, - {"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true}, - {"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true}, - {"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false}, - {"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false}, - } { - gotTime, err := ParseLocalTime(test.str) - if err != nil { - t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) - continue - } - if gotTime != test.time { - t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) - } - if test.roundTrip { - gotStr := test.time.String() - if gotStr != test.str { - t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str) - } - } - } -} - -func TestTimeOf(t *testing.T) { - for _, test := range []struct { - time time.Time - want LocalTime - }{ - {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}}, - {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}}, - } { - if got := LocalTimeOf(test.time); got != test.want { - t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want) - } - } -} - -func TestTimeIsValid(t *testing.T) { - for _, test := range []struct { - time LocalTime - want bool - }{ - {LocalTime{0, 0, 0, 0}, true}, - {LocalTime{23, 0, 0, 0}, true}, - {LocalTime{23, 59, 59, 999999999}, true}, - {LocalTime{24, 59, 59, 999999999}, false}, - {LocalTime{23, 60, 59, 999999999}, false}, - {LocalTime{23, 59, 60, 999999999}, false}, - {LocalTime{23, 59, 59, 1000000000}, false}, - {LocalTime{-1, 0, 0, 0}, false}, - {LocalTime{0, -1, 0, 0}, false}, - {LocalTime{0, 0, -1, 0}, false}, - {LocalTime{0, 0, 0, -1}, false}, - } { - got := test.time.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.time, got, test.want) - } - } -} - -func TestDateTimeToString(t *testing.T) { - for _, test := range []struct { - str string - dateTime LocalDateTime - roundTrip bool // ParseLocalDateTime(str).String() == str? - }{ - {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true}, - {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true}, - {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false}, - } { - gotDateTime, err := ParseLocalDateTime(test.str) - if err != nil { - t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) - continue - } - if gotDateTime != test.dateTime { - t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) - } - if test.roundTrip { - gotStr := test.dateTime.String() - if gotStr != test.str { - t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str) - } - } - } -} - -func TestParseDateTimeErrors(t *testing.T) { - for _, str := range []string{ - "", - "2016-03-22", // just a date - "13:26:33", // just a time - "2016-03-22 13:26:33", // wrong separating character - "2016-03-22T13:26:33x", // extra at end - } { - if _, err := ParseLocalDateTime(str); err == nil { - t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str) - } - } -} - -func TestDateTimeOf(t *testing.T) { - for _, test := range []struct { - time time.Time - want LocalDateTime - }{ - {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), - LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}}, - {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), - LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}}, - } { - if got := LocalDateTimeOf(test.time); got != test.want { - t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) - } - } -} - -func TestDateTimeIsValid(t *testing.T) { - // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. - for _, test := range []struct { - dt LocalDateTime - want bool - }{ - {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true}, - {LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false}, - {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false}, - } { - got := test.dt.IsValid() - if got != test.want { - t.Errorf("%#v: got %t, want %t", test.dt, got, test.want) - } - } -} - -func TestDateTimeIn(t *testing.T) { - dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} - got := dt.In(time.UTC) - want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) - if !got.Equal(want) { - t.Errorf("got %v, want %v", got, want) - } -} - -func TestDateTimeBefore(t *testing.T) { - d1 := LocalDate{2016, 12, 31} - d2 := LocalDate{2017, 1, 1} - t1 := LocalTime{5, 6, 7, 8} - t2 := LocalTime{5, 6, 7, 9} - for _, test := range []struct { - dt1, dt2 LocalDateTime - want bool - }{ - {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true}, - {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true}, - {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false}, - {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, - } { - if got := test.dt1.Before(test.dt2); got != test.want { - t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) - } - } -} - -func TestDateTimeAfter(t *testing.T) { - d1 := LocalDate{2016, 12, 31} - d2 := LocalDate{2017, 1, 1} - t1 := LocalTime{5, 6, 7, 8} - t2 := LocalTime{5, 6, 7, 9} - for _, test := range []struct { - dt1, dt2 LocalDateTime - want bool - }{ - {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false}, - {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false}, - {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true}, - {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, - } { - if got := test.dt1.After(test.dt2); got != test.want { - t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) - } - } -} - -func TestMarshalJSON(t *testing.T) { - for _, test := range []struct { - value interface{} - want string - }{ - {LocalDate{1987, 4, 15}, `"1987-04-15"`}, - {LocalTime{18, 54, 2, 0}, `"18:54:02"`}, - {LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`}, - } { - bgot, err := json.Marshal(test.value) - if err != nil { - t.Fatal(err) - } - if got := string(bgot); got != test.want { - t.Errorf("%#v: got %s, want %s", test.value, got, test.want) - } - } -} - -func TestUnmarshalJSON(t *testing.T) { - var d LocalDate - var tm LocalTime - var dt LocalDateTime - for _, test := range []struct { - data string - ptr interface{} - want interface{} - }{ - {`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}}, - {`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}}, - {`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}}, - {`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}}, - } { - if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { - t.Fatalf("%s: %v", test.data, err) - } - if !cmpEqual(test.ptr, test.want) { - t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) - } - } - - for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`, - `19870415`, // a JSON number - `11987-04-15x`, // not a JSON string - - } { - if json.Unmarshal([]byte(bad), &d) == nil { - t.Errorf("%q, LocalDate: got nil, want error", bad) - } - if json.Unmarshal([]byte(bad), &tm) == nil { - t.Errorf("%q, LocalTime: got nil, want error", bad) - } - if json.Unmarshal([]byte(bad), &dt) == nil { - t.Errorf("%q, LocalDateTime: got nil, want error", bad) - } - } -} diff --git a/marshal.go b/marshal.go deleted file mode 100644 index 365543a..0000000 --- a/marshal.go +++ /dev/null @@ -1,1293 +0,0 @@ -package toml - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "io" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -const ( - tagFieldName = "toml" - tagFieldComment = "comment" - tagCommented = "commented" - tagMultiline = "multiline" - tagDefault = "default" -) - -type tomlOpts struct { - name string - nameFromTag bool - 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() -var unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() -var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() -var localDateType = reflect.TypeOf(LocalDate{}) -var localTimeType = reflect.TypeOf(LocalTime{}) -var localDateTimeType = reflect.TypeOf(LocalDateTime{}) -var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) - -// Check if the given marshal type maps to a Tree primitive -func isPrimitive(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isPrimitive(mtype.Elem()) - case reflect.Bool: - return true - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true - case reflect.Float32, reflect.Float64: - return true - case reflect.String: - return true - case reflect.Struct: - return isTimeType(mtype) - default: - return false - } -} - -func isTimeType(mtype reflect.Type) bool { - return mtype == timeType || mtype == localDateType || mtype == localDateTimeType || mtype == localTimeType -} - -// Check if the given marshal type maps to a Tree slice or array -func isTreeSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTreeSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isTree(mtype.Elem()) - default: - return false - } -} - -// Check if the given marshal type maps to a slice or array of a custom marshaler type -func isCustomMarshalerSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isCustomMarshalerSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isCustomMarshaler(mtype.Elem()) || isCustomMarshaler(reflect.New(mtype.Elem()).Type()) - default: - return false - } -} - -// Check if the given marshal type maps to a slice or array of a text marshaler type -func isTextMarshalerSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTextMarshalerSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return isTextMarshaler(mtype.Elem()) || isTextMarshaler(reflect.New(mtype.Elem()).Type()) - default: - return false - } -} - -// Check if the given marshal type maps to a non-Tree slice or array -func isOtherSequence(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isOtherSequence(mtype.Elem()) - case reflect.Slice, reflect.Array: - return !isTreeSequence(mtype) - default: - return false - } -} - -// Check if the given marshal type maps to a Tree -func isTree(mtype reflect.Type) bool { - switch mtype.Kind() { - case reflect.Ptr: - return isTree(mtype.Elem()) - case reflect.Map: - return true - case reflect.Struct: - return !isPrimitive(mtype) - default: - return false - } -} - -func isCustomMarshaler(mtype reflect.Type) bool { - return mtype.Implements(marshalerType) -} - -func callCustomMarshaler(mval reflect.Value) ([]byte, error) { - return mval.Interface().(Marshaler).MarshalTOML() -} - -func isTextMarshaler(mtype reflect.Type) bool { - return mtype.Implements(textMarshalerType) && !isTimeType(mtype) -} - -func callTextMarshaler(mval reflect.Value) ([]byte, error) { - return mval.Interface().(encoding.TextMarshaler).MarshalText() -} - -func isCustomUnmarshaler(mtype reflect.Type) bool { - return mtype.Implements(unmarshalerType) -} - -func callCustomUnmarshaler(mval reflect.Value, tval interface{}) error { - return mval.Interface().(Unmarshaler).UnmarshalTOML(tval) -} - -func isTextUnmarshaler(mtype reflect.Type) bool { - return mtype.Implements(textUnmarshalerType) -} - -func callTextUnmarshaler(mval reflect.Value, text []byte) error { - return mval.Interface().(encoding.TextUnmarshaler).UnmarshalText(text) -} - -// Marshaler is the interface implemented by types that -// can marshal themselves into valid TOML. -type Marshaler interface { - MarshalTOML() ([]byte, error) -} - -// Unmarshaler is the interface implemented by types that -// can unmarshal a TOML description of themselves. -type Unmarshaler interface { - UnmarshalTOML(interface{}) error -} - -/* -Marshal returns the TOML encoding of v. Behavior is similar to the Go json -encoder, except that there is no concept of a Marshaler interface or MarshalTOML -function for sub-structs, and currently only definite types can be marshaled -(i.e. no `interface{}`). - -The following struct annotations are supported: - - toml:"Field" Overrides the field's name to output. - omitempty When set, empty values and groups are not emitted. - comment:"comment" Emits a # comment on the same line. This supports new lines. - commented:"true" Emits the value as commented. - -Note that pointers are automatically assigned the "omitempty" option, as TOML -explicitly does not handle null values (saying instead the label should be -dropped). - -Tree structural types and corresponding marshal types: - - *Tree (*)struct, (*)map[string]interface{} - []*Tree (*)[](*)struct, (*)[](*)map[string]interface{} - []interface{} (as interface{}) (*)[]primitive, (*)[]([]interface{}) - interface{} (*)primitive - -Tree primitive types and corresponding marshal types: - - uint64 uint, uint8-uint64, pointers to same - int64 int, int8-uint64, pointers to same - float64 float32, float64, pointers to same - string string, pointers to same - bool bool, pointers to same - time.LocalTime time.LocalTime{}, pointers to same - -For additional flexibility, use the Encoder API. -*/ -func Marshal(v interface{}) ([]byte, error) { - return NewEncoder(nil).marshal(v) -} - -// Encoder writes TOML values to an output stream. -type Encoder struct { - w io.Writer - encOpts - annotation - line int - col int - order marshalOrder - promoteAnon bool - indentation string -} - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - w: w, - encOpts: encOptsDefaults, - annotation: annotationDefault, - line: 0, - col: 1, - order: OrderAlphabetical, - indentation: " ", - } -} - -// Encode writes the TOML encoding of v to the stream. -// -// See the documentation for Marshal for details. -func (e *Encoder) Encode(v interface{}) error { - b, err := e.marshal(v) - if err != nil { - return err - } - if _, err := e.w.Write(b); err != nil { - return err - } - return nil -} - -// QuoteMapKeys sets up the encoder to encode -// maps with string type keys with quoted TOML keys. -// -// This relieves the character limitations on map keys. -func (e *Encoder) QuoteMapKeys(v bool) *Encoder { - e.quoteMapKeys = v - return e -} - -// ArraysWithOneElementPerLine sets up the encoder to encode arrays -// with more than one element on multiple lines instead of one. -// -// For example: -// -// A = [1,2,3] -// -// Becomes -// -// A = [ -// 1, -// 2, -// 3, -// ] -func (e *Encoder) ArraysWithOneElementPerLine(v bool) *Encoder { - e.arraysOneElementPerLine = v - return e -} - -// Order allows to change in which order fields will be written to the output stream. -func (e *Encoder) Order(ord marshalOrder) *Encoder { - e.order = ord - return e -} - -// Indentation allows to change indentation when marshalling. -func (e *Encoder) Indentation(indent string) *Encoder { - e.indentation = indent - return e -} - -// SetTagName allows changing default tag "toml" -func (e *Encoder) SetTagName(v string) *Encoder { - e.tag = v - return e -} - -// SetTagComment allows changing default tag "comment" -func (e *Encoder) SetTagComment(v string) *Encoder { - e.comment = v - return e -} - -// SetTagCommented allows changing default tag "commented" -func (e *Encoder) SetTagCommented(v string) *Encoder { - e.commented = v - return e -} - -// SetTagMultiline allows changing default tag "multiline" -func (e *Encoder) SetTagMultiline(v string) *Encoder { - e.multiline = v - return e -} - -// PromoteAnonymous allows to change how anonymous struct fields are marshaled. -// Usually, they are marshaled as if the inner exported fields were fields in -// the outer struct. However, if an anonymous struct field is given a name in -// its TOML tag, it is treated like a regular struct field with that name. -// rather than being anonymous. -// -// In case anonymous promotion is enabled, all anonymous structs are promoted -// and treated like regular struct fields. -func (e *Encoder) PromoteAnonymous(promote bool) *Encoder { - e.promoteAnon = promote - return e -} - -func (e *Encoder) marshal(v interface{}) ([]byte, error) { - // Check if indentation is valid - for _, char := range e.indentation { - if !isSpace(char) { - return []byte{}, fmt.Errorf("invalid indentation: must only contains space or tab characters") - } - } - - mtype := reflect.TypeOf(v) - if mtype == nil { - return []byte{}, errors.New("nil cannot be marshaled to TOML") - } - - switch mtype.Kind() { - case reflect.Struct, reflect.Map: - case reflect.Ptr: - if mtype.Elem().Kind() != reflect.Struct { - return []byte{}, errors.New("Only pointer to struct can be marshaled to TOML") - } - if reflect.ValueOf(v).IsNil() { - return []byte{}, errors.New("nil pointer cannot be marshaled to TOML") - } - default: - return []byte{}, errors.New("Only a struct or map can be marshaled to TOML") - } - - sval := reflect.ValueOf(v) - if isCustomMarshaler(mtype) { - return callCustomMarshaler(sval) - } - if isTextMarshaler(mtype) { - return callTextMarshaler(sval) - } - t, err := e.valueToTree(mtype, sval) - if err != nil { - return []byte{}, err - } - - var buf bytes.Buffer - _, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order, e.indentation, false) - - return buf.Bytes(), err -} - -// Create next tree with a position based on Encoder.line -func (e *Encoder) nextTree() *Tree { - return newTreeWithPosition(Position{Line: e.line, Col: 1}) -} - -// Convert given marshal struct or map value to toml tree -func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) { - if mtype.Kind() == reflect.Ptr { - return e.valueToTree(mtype.Elem(), mval.Elem()) - } - tval := e.nextTree() - switch mtype.Kind() { - case reflect.Struct: - switch mval.Interface().(type) { - case Tree: - reflect.ValueOf(tval).Elem().Set(mval) - default: - for i := 0; i < mtype.NumField(); i++ { - mtypef, mvalf := mtype.Field(i), mval.Field(i) - opts := tomlOptions(mtypef, e.annotation) - if opts.include && ((mtypef.Type.Kind() != reflect.Interface && !opts.omitempty) || !isZero(mvalf)) { - val, err := e.valueToToml(mtypef.Type, mvalf) - if err != nil { - return nil, err - } - if tree, ok := val.(*Tree); ok && mtypef.Anonymous && !opts.nameFromTag && !e.promoteAnon { - e.appendTree(tval, tree) - } else { - val = e.wrapTomlValue(val, tval) - tval.SetPathWithOptions([]string{opts.name}, SetOptions{ - Comment: opts.comment, - Commented: opts.commented, - Multiline: opts.multiline, - }, val) - } - } - } - } - case reflect.Map: - keys := mval.MapKeys() - if e.order == OrderPreserve && len(keys) > 0 { - // Sorting []reflect.Value is not straight forward. - // - // OrderPreserve will support deterministic results when string is used - // as the key to maps. - typ := keys[0].Type() - kind := keys[0].Kind() - if kind == reflect.String { - ikeys := make([]string, len(keys)) - for i := range keys { - ikeys[i] = keys[i].Interface().(string) - } - sort.Strings(ikeys) - for i := range ikeys { - keys[i] = reflect.ValueOf(ikeys[i]).Convert(typ) - } - } - } - for _, key := range keys { - mvalf := mval.MapIndex(key) - if (mtype.Elem().Kind() == reflect.Ptr || mtype.Elem().Kind() == reflect.Interface) && mvalf.IsNil() { - continue - } - val, err := e.valueToToml(mtype.Elem(), mvalf) - if err != nil { - return nil, err - } - val = e.wrapTomlValue(val, tval) - if e.quoteMapKeys { - keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine) - if err != nil { - return nil, err - } - tval.SetPath([]string{keyStr}, val) - } else { - tval.SetPath([]string{key.String()}, val) - } - } - } - return tval, nil -} - -// Convert given marshal slice to slice of Toml trees -func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*Tree, error) { - tval := make([]*Tree, mval.Len(), mval.Len()) - for i := 0; i < mval.Len(); i++ { - val, err := e.valueToTree(mtype.Elem(), mval.Index(i)) - if err != nil { - return nil, err - } - tval[i] = val - } - return tval, nil -} - -// Convert given marshal slice to slice of toml values -func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - tval := make([]interface{}, mval.Len(), mval.Len()) - for i := 0; i < mval.Len(); i++ { - val, err := e.valueToToml(mtype.Elem(), mval.Index(i)) - if err != nil { - return nil, err - } - tval[i] = val - } - return tval, nil -} - -// Convert given marshal value to toml value -func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - if mtype.Kind() == reflect.Ptr { - switch { - case isCustomMarshaler(mtype): - return callCustomMarshaler(mval) - case isTextMarshaler(mtype): - b, err := callTextMarshaler(mval) - return string(b), err - default: - return e.valueToToml(mtype.Elem(), mval.Elem()) - } - } - if mtype.Kind() == reflect.Interface { - return e.valueToToml(mval.Elem().Type(), mval.Elem()) - } - switch { - case isCustomMarshaler(mtype): - return callCustomMarshaler(mval) - case isTextMarshaler(mtype): - b, err := callTextMarshaler(mval) - return string(b), err - case isTree(mtype): - return e.valueToTree(mtype, mval) - case isOtherSequence(mtype), isCustomMarshalerSequence(mtype), isTextMarshalerSequence(mtype): - return e.valueToOtherSlice(mtype, mval) - case isTreeSequence(mtype): - return e.valueToTreeSlice(mtype, mval) - default: - switch mtype.Kind() { - case reflect.Bool: - return mval.Bool(), nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if mtype.Kind() == reflect.Int64 && mtype == reflect.TypeOf(time.Duration(1)) { - return fmt.Sprint(mval), nil - } - return mval.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return mval.Uint(), nil - case reflect.Float32, reflect.Float64: - return mval.Float(), nil - case reflect.String: - return mval.String(), nil - case reflect.Struct: - return mval.Interface(), nil - default: - return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) - } - } -} - -func (e *Encoder) appendTree(t, o *Tree) error { - for key, value := range o.values { - if _, ok := t.values[key]; ok { - continue - } - if tomlValue, ok := value.(*tomlValue); ok { - tomlValue.position.Col = t.position.Col - } - t.values[key] = value - } - return nil -} - -// Create a toml value with the current line number as the position line -func (e *Encoder) wrapTomlValue(val interface{}, parent *Tree) interface{} { - _, isTree := val.(*Tree) - _, isTreeS := val.([]*Tree) - if isTree || isTreeS { - return val - } - - ret := &tomlValue{ - value: val, - position: Position{ - e.line, - parent.position.Col, - }, - } - e.line++ - return ret -} - -// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v. -// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for -// sub-structs, and only definite types can be unmarshaled. -func (t *Tree) Unmarshal(v interface{}) error { - d := Decoder{tval: t, tagName: tagFieldName} - return d.unmarshal(v) -} - -// Marshal returns the TOML encoding of Tree. -// See Marshal() documentation for types mapping table. -func (t *Tree) Marshal() ([]byte, error) { - var buf bytes.Buffer - _, err := t.WriteTo(&buf) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// Unmarshal parses the TOML-encoded data and stores the result in the value -// pointed to by v. Behavior is similar to the Go json encoder, except that there -// is no concept of an Unmarshaler interface or UnmarshalTOML function for -// sub-structs, and currently only definite types can be unmarshaled to (i.e. no -// `interface{}`). -// -// The following struct annotations are supported: -// -// toml:"Field" Overrides the field's name to map to. -// default:"foo" Provides a default value. -// -// For default values, only fields of the following types are supported: -// * string -// * bool -// * int -// * int64 -// * float64 -// -// See Marshal() documentation for types mapping table. -func Unmarshal(data []byte, v interface{}) error { - t, err := LoadReader(bytes.NewReader(data)) - if err != nil { - return err - } - return t.Unmarshal(v) -} - -// Decoder reads and decodes TOML values from an input stream. -type Decoder struct { - r io.Reader - tval *Tree - encOpts - tagName string - strict bool - visitor visitorState -} - -// NewDecoder returns a new decoder that reads from r. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{ - r: r, - encOpts: encOptsDefaults, - tagName: tagFieldName, - } -} - -// Decode reads a TOML-encoded value from it's input -// and unmarshals it in the value pointed at by v. -// -// See the documentation for Marshal for details. -func (d *Decoder) Decode(v interface{}) error { - var err error - d.tval, err = LoadReader(d.r) - if err != nil { - return err - } - return d.unmarshal(v) -} - -// SetTagName allows changing default tag "toml" -func (d *Decoder) SetTagName(v string) *Decoder { - d.tagName = v - return d -} - -// Strict allows changing to strict decoding. Any fields that are found in the -// input data and do not have a corresponding struct member cause an error. -func (d *Decoder) Strict(strict bool) *Decoder { - d.strict = strict - return d -} - -func (d *Decoder) unmarshal(v interface{}) error { - mtype := reflect.TypeOf(v) - if mtype == nil { - return errors.New("nil cannot be unmarshaled from TOML") - } - if mtype.Kind() != reflect.Ptr { - return errors.New("only a pointer to struct or map can be unmarshaled from TOML") - } - - elem := mtype.Elem() - - switch elem.Kind() { - case reflect.Struct, reflect.Map: - case reflect.Interface: - elem = mapStringInterfaceType - default: - return errors.New("only a pointer to struct or map can be unmarshaled from TOML") - } - - if reflect.ValueOf(v).IsNil() { - return errors.New("nil pointer cannot be unmarshaled from TOML") - } - - vv := reflect.ValueOf(v).Elem() - - if d.strict { - d.visitor = newVisitorState(d.tval) - } - - sval, err := d.valueFromTree(elem, d.tval, &vv) - if err != nil { - return err - } - if err := d.visitor.validate(); err != nil { - return err - } - reflect.ValueOf(v).Elem().Set(sval) - return nil -} - -// Convert toml tree to marshal struct or map, using marshal type. When mval1 -// is non-nil, merge fields into the given value instead of allocating a new one. -func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.Value) (reflect.Value, error) { - if mtype.Kind() == reflect.Ptr { - return d.unwrapPointer(mtype, tval, mval1) - } - - // Check if pointer to value implements the Unmarshaler interface. - if mvalPtr := reflect.New(mtype); isCustomUnmarshaler(mvalPtr.Type()) { - d.visitor.visitAll() - - if tval == nil { - return mvalPtr.Elem(), nil - } - - if err := callCustomUnmarshaler(mvalPtr, tval.ToMap()); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err) - } - return mvalPtr.Elem(), nil - } - - var mval reflect.Value - switch mtype.Kind() { - case reflect.Struct: - if mval1 != nil { - mval = *mval1 - } else { - mval = reflect.New(mtype).Elem() - } - - switch mval.Interface().(type) { - case Tree: - mval.Set(reflect.ValueOf(tval).Elem()) - default: - for i := 0; i < mtype.NumField(); i++ { - mtypef := mtype.Field(i) - an := annotation{tag: d.tagName} - opts := tomlOptions(mtypef, an) - if !opts.include { - continue - } - baseKey := opts.name - keysToTry := []string{ - baseKey, - strings.ToLower(baseKey), - strings.ToTitle(baseKey), - strings.ToLower(string(baseKey[0])) + baseKey[1:], - } - - found := false - if tval != nil { - for _, key := range keysToTry { - exists := tval.HasPath([]string{key}) - if !exists { - continue - } - - d.visitor.push(key) - val := tval.GetPath([]string{key}) - fval := mval.Field(i) - mvalf, err := d.valueFromToml(mtypef.Type, val, &fval) - if err != nil { - return mval, formatError(err, tval.GetPositionPath([]string{key})) - } - mval.Field(i).Set(mvalf) - found = true - d.visitor.pop() - break - } - } - - if !found && opts.defaultValue != "" { - mvalf := mval.Field(i) - var val interface{} - var err error - switch mvalf.Kind() { - case reflect.String: - val = opts.defaultValue - case reflect.Bool: - val, err = strconv.ParseBool(opts.defaultValue) - case reflect.Uint: - val, err = strconv.ParseUint(opts.defaultValue, 10, 0) - case reflect.Uint8: - val, err = strconv.ParseUint(opts.defaultValue, 10, 8) - case reflect.Uint16: - val, err = strconv.ParseUint(opts.defaultValue, 10, 16) - case reflect.Uint32: - val, err = strconv.ParseUint(opts.defaultValue, 10, 32) - case reflect.Uint64: - val, err = strconv.ParseUint(opts.defaultValue, 10, 64) - case reflect.Int: - val, err = strconv.ParseInt(opts.defaultValue, 10, 0) - case reflect.Int8: - val, err = strconv.ParseInt(opts.defaultValue, 10, 8) - case reflect.Int16: - val, err = strconv.ParseInt(opts.defaultValue, 10, 16) - case reflect.Int32: - val, err = strconv.ParseInt(opts.defaultValue, 10, 32) - case reflect.Int64: - // Check if the provided number has a non-numeric extension. - var hasExtension bool - if len(opts.defaultValue) > 0 { - lastChar := opts.defaultValue[len(opts.defaultValue)-1] - if lastChar < '0' || lastChar > '9' { - hasExtension = true - } - } - // If the value is a time.Duration with extension, parse as duration. - // If the value is an int64 or a time.Duration without extension, parse as number. - if hasExtension && mvalf.Type().String() == "time.Duration" { - val, err = time.ParseDuration(opts.defaultValue) - } else { - val, err = strconv.ParseInt(opts.defaultValue, 10, 64) - } - case reflect.Float32: - val, err = strconv.ParseFloat(opts.defaultValue, 32) - case reflect.Float64: - val, err = strconv.ParseFloat(opts.defaultValue, 64) - default: - return mvalf, fmt.Errorf("unsupported field type for default option") - } - - if err != nil { - return mvalf, err - } - mvalf.Set(reflect.ValueOf(val).Convert(mvalf.Type())) - } - - // save the old behavior above and try to check structs - if !found && opts.defaultValue == "" && mtypef.Type.Kind() == reflect.Struct { - tmpTval := tval - if !mtypef.Anonymous { - tmpTval = nil - } - fval := mval.Field(i) - v, err := d.valueFromTree(mtypef.Type, tmpTval, &fval) - if err != nil { - return v, err - } - mval.Field(i).Set(v) - } - } - } - case reflect.Map: - mval = reflect.MakeMap(mtype) - for _, key := range tval.Keys() { - d.visitor.push(key) - // TODO: path splits key - val := tval.GetPath([]string{key}) - mvalf, err := d.valueFromToml(mtype.Elem(), val, nil) - if err != nil { - return mval, formatError(err, tval.GetPositionPath([]string{key})) - } - mval.SetMapIndex(reflect.ValueOf(key).Convert(mtype.Key()), mvalf) - d.visitor.pop() - } - } - return mval, nil -} - -// Convert toml value to marshal struct/map slice, using marshal type -func (d *Decoder) valueFromTreeSlice(mtype reflect.Type, tval []*Tree) (reflect.Value, error) { - mval, err := makeSliceOrArray(mtype, len(tval)) - if err != nil { - return mval, err - } - - for i := 0; i < len(tval); i++ { - d.visitor.push(strconv.Itoa(i)) - val, err := d.valueFromTree(mtype.Elem(), tval[i], nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - d.visitor.pop() - } - return mval, nil -} - -// Convert toml value to marshal primitive slice, using marshal type -func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { - mval, err := makeSliceOrArray(mtype, len(tval)) - if err != nil { - return mval, err - } - - for i := 0; i < len(tval); i++ { - val, err := d.valueFromToml(mtype.Elem(), tval[i], nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - } - return mval, nil -} - -// Convert toml value to marshal primitive slice, using marshal type -func (d *Decoder) valueFromOtherSliceI(mtype reflect.Type, tval interface{}) (reflect.Value, error) { - val := reflect.ValueOf(tval) - length := val.Len() - - mval, err := makeSliceOrArray(mtype, length) - if err != nil { - return mval, err - } - - for i := 0; i < length; i++ { - val, err := d.valueFromToml(mtype.Elem(), val.Index(i).Interface(), nil) - if err != nil { - return mval, err - } - mval.Index(i).Set(val) - } - return mval, nil -} - -// Create a new slice or a new array with specified length -func makeSliceOrArray(mtype reflect.Type, tLength int) (reflect.Value, error) { - var mval reflect.Value - switch mtype.Kind() { - case reflect.Slice: - mval = reflect.MakeSlice(mtype, tLength, tLength) - case reflect.Array: - mval = reflect.New(reflect.ArrayOf(mtype.Len(), mtype.Elem())).Elem() - if tLength > mtype.Len() { - return mval, fmt.Errorf("unmarshal: TOML array length (%v) exceeds destination array length (%v)", tLength, mtype.Len()) - } - } - return mval, nil -} - -// Convert toml value to marshal value, using marshal type. When mval1 is non-nil -// and the given type is a struct value, merge fields into it. -func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) { - if mtype.Kind() == reflect.Ptr { - return d.unwrapPointer(mtype, tval, mval1) - } - - switch t := tval.(type) { - case *Tree: - var mval11 *reflect.Value - if mtype.Kind() == reflect.Struct { - mval11 = mval1 - } - - if isTree(mtype) { - return d.valueFromTree(mtype, t, mval11) - } - - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromTree(reflect.TypeOf(map[string]interface{}{}), t, nil) - } else { - return d.valueFromToml(mval1.Elem().Type(), t, nil) - } - } - - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval) - case []*Tree: - if isTreeSequence(mtype) { - return d.valueFromTreeSlice(mtype, t) - } - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromTreeSlice(reflect.TypeOf([]map[string]interface{}{}), t) - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval) - case []interface{}: - d.visitor.visit() - if isOtherSequence(mtype) { - return d.valueFromOtherSlice(mtype, t) - } - if mtype.Kind() == reflect.Interface { - if mval1 == nil || mval1.IsNil() { - return d.valueFromOtherSlice(reflect.TypeOf([]interface{}{}), t) - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) - default: - d.visitor.visit() - mvalPtr := reflect.New(mtype) - - // Check if pointer to value implements the Unmarshaler interface. - if isCustomUnmarshaler(mvalPtr.Type()) { - if err := callCustomUnmarshaler(mvalPtr, tval); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err) - } - return mvalPtr.Elem(), nil - } - - // Check if pointer to value implements the encoding.TextUnmarshaler. - if isTextUnmarshaler(mvalPtr.Type()) && !isTimeType(mtype) { - if err := d.unmarshalText(tval, mvalPtr); err != nil { - return reflect.ValueOf(nil), fmt.Errorf("unmarshal text: %v", err) - } - return mvalPtr.Elem(), nil - } - - switch mtype.Kind() { - case reflect.Bool, reflect.Struct: - val := reflect.ValueOf(tval) - - switch val.Type() { - case localDateType: - localDate := val.Interface().(LocalDate) - switch mtype { - case timeType: - return reflect.ValueOf(time.Date(localDate.Year, localDate.Month, localDate.Day, 0, 0, 0, 0, time.Local)), nil - } - case localDateTimeType: - localDateTime := val.Interface().(LocalDateTime) - switch mtype { - case timeType: - return reflect.ValueOf(time.Date( - localDateTime.Date.Year, - localDateTime.Date.Month, - localDateTime.Date.Day, - localDateTime.Time.Hour, - localDateTime.Time.Minute, - localDateTime.Time.Second, - localDateTime.Time.Nanosecond, - time.Local)), nil - } - } - - // if this passes for when mtype is reflect.Struct, tval is a time.LocalTime - if !val.Type().ConvertibleTo(mtype) { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.String: - val := reflect.ValueOf(tval) - // stupidly, int64 is convertible to string. So special case this. - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val := reflect.ValueOf(tval) - if mtype.Kind() == reflect.Int64 && mtype == reflect.TypeOf(time.Duration(1)) && val.Kind() == reflect.String { - d, err := time.ParseDuration(val.String()) - if err != nil { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v. %s", tval, tval, mtype.String(), err) - } - return reflect.ValueOf(d), nil - } - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Convert(reflect.TypeOf(int64(0))).Int()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - val := reflect.ValueOf(tval) - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - - if val.Convert(reflect.TypeOf(int(1))).Int() < 0 { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) is negative so does not fit in %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowUint(val.Convert(reflect.TypeOf(uint64(0))).Uint()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Float32, reflect.Float64: - val := reflect.ValueOf(tval) - if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) - } - if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Convert(reflect.TypeOf(float64(0))).Float()) { - return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) - } - - return val.Convert(mtype), nil - case reflect.Interface: - if mval1 == nil || mval1.IsNil() { - return reflect.ValueOf(tval), nil - } else { - ival := mval1.Elem() - return d.valueFromToml(mval1.Elem().Type(), t, &ival) - } - case reflect.Slice, reflect.Array: - if isOtherSequence(mtype) && isOtherSequence(reflect.TypeOf(t)) { - return d.valueFromOtherSliceI(mtype, t) - } - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) - default: - return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) - } - } -} - -func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) { - var melem *reflect.Value - - if mval1 != nil && !mval1.IsNil() && (mtype.Elem().Kind() == reflect.Struct || mtype.Elem().Kind() == reflect.Interface) { - elem := mval1.Elem() - melem = &elem - } - - val, err := d.valueFromToml(mtype.Elem(), tval, melem) - if err != nil { - return reflect.ValueOf(nil), err - } - mval := reflect.New(mtype.Elem()) - mval.Elem().Set(val) - return mval, nil -} - -func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error { - var buf bytes.Buffer - fmt.Fprint(&buf, tval) - return callTextUnmarshaler(mval, buf.Bytes()) -} - -func tomlOptions(vf reflect.StructField, an annotation) tomlOpts { - tag := vf.Tag.Get(an.tag) - parse := strings.Split(tag, ",") - var comment string - if c := vf.Tag.Get(an.comment); c != "" { - comment = c - } - commented, _ := strconv.ParseBool(vf.Tag.Get(an.commented)) - multiline, _ := strconv.ParseBool(vf.Tag.Get(an.multiline)) - defaultValue := vf.Tag.Get(tagDefault) - result := tomlOpts{ - name: vf.Name, - nameFromTag: false, - 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], " ") - result.nameFromTag = true - } - } - if vf.PkgPath != "" { - result.include = false - } - if len(parse) > 1 && strings.Trim(parse[1], " ") == "omitempty" { - result.omitempty = true - } - if vf.Type.Kind() == reflect.Ptr { - result.omitempty = true - } - return result -} - -func isZero(val reflect.Value) bool { - switch val.Type().Kind() { - case reflect.Slice, reflect.Array, reflect.Map: - return val.Len() == 0 - default: - return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) - } -} - -func formatError(err error, pos Position) error { - if err.Error()[0] == '(' { // Error already contains position information - return err - } - return fmt.Errorf("%s: %s", pos, err) -} - -// visitorState keeps track of which keys were unmarshaled. -type visitorState struct { - tree *Tree - path []string - keys map[string]struct{} - active bool -} - -func newVisitorState(tree *Tree) visitorState { - path, result := []string{}, map[string]struct{}{} - insertKeys(path, result, tree) - return visitorState{ - tree: tree, - path: path[:0], - keys: result, - active: true, - } -} - -func (s *visitorState) push(key string) { - if s.active { - s.path = append(s.path, key) - } -} - -func (s *visitorState) pop() { - if s.active { - s.path = s.path[:len(s.path)-1] - } -} - -func (s *visitorState) visit() { - if s.active { - delete(s.keys, strings.Join(s.path, ".")) - } -} - -func (s *visitorState) visitAll() { - if s.active { - for k := range s.keys { - if strings.HasPrefix(k, strings.Join(s.path, ".")) { - delete(s.keys, k) - } - } - } -} - -func (s *visitorState) validate() error { - if !s.active { - return nil - } - undecoded := make([]string, 0, len(s.keys)) - for key := range s.keys { - undecoded = append(undecoded, key) - } - sort.Strings(undecoded) - if len(undecoded) > 0 { - return fmt.Errorf("undecoded keys: %q", undecoded) - } - return nil -} - -func insertKeys(path []string, m map[string]struct{}, tree *Tree) { - for k, v := range tree.values { - switch node := v.(type) { - case []*Tree: - for i, item := range node { - insertKeys(append(path, k, strconv.Itoa(i)), m, item) - } - case *Tree: - insertKeys(append(path, k), m, node) - case *tomlValue: - m[strings.Join(append(path, k), ".")] = struct{}{} - } - } -} diff --git a/marshal_OrderPreserve_test.toml b/marshal_OrderPreserve_test.toml deleted file mode 100644 index 792b72e..0000000 --- a/marshal_OrderPreserve_test.toml +++ /dev/null @@ -1,39 +0,0 @@ -title = "TOML Marshal Testing" - -[basic_lists] - floats = [12.3,45.6,78.9] - bools = [true,false,true] - dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] - ints = [8001,8001,8002] - uints = [5002,5003] - strings = ["One","Two","Three"] - -[[subdocptrs]] - name = "Second" - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.second] - name = "Second" - - [subdoc.first] - name = "First" - -[basic] - uint = 5001 - bool = true - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - date = 1979-05-27T07:32:00Z - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" diff --git a/marshal_test.go b/marshal_test.go deleted file mode 100644 index f948de4..0000000 --- a/marshal_test.go +++ /dev/null @@ -1,4054 +0,0 @@ -package toml - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "os" - "reflect" - "strconv" - "strings" - "testing" - "time" -) - -type basicMarshalTestStruct struct { - String string `toml:"Zstring"` - StringList []string `toml:"Ystrlist"` - BasicMarshalTestSubAnonymousStruct - Sub basicMarshalTestSubStruct `toml:"Xsubdoc"` - SubList []basicMarshalTestSubStruct `toml:"Wsublist"` -} - -type basicMarshalTestSubStruct struct { - String2 string -} - -type BasicMarshalTestSubAnonymousStruct struct { - String3 string -} - -var basicTestData = basicMarshalTestStruct{ - String: "Hello", - StringList: []string{"Howdy", "Hey There"}, - BasicMarshalTestSubAnonymousStruct: BasicMarshalTestSubAnonymousStruct{"One"}, - Sub: basicMarshalTestSubStruct{"Two"}, - SubList: []basicMarshalTestSubStruct{{"Three"}, {"Four"}}, -} - -var basicTestToml = []byte(`String3 = "One" -Ystrlist = ["Howdy", "Hey There"] -Zstring = "Hello" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" - -[Xsubdoc] - String2 = "Two" -`) - -var basicTestTomlCustomIndentation = []byte(`String3 = "One" -Ystrlist = ["Howdy", "Hey There"] -Zstring = "Hello" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" - -[Xsubdoc] - String2 = "Two" -`) - -var basicTestTomlOrdered = []byte(`Zstring = "Hello" -Ystrlist = ["Howdy", "Hey There"] -String3 = "One" - -[Xsubdoc] - String2 = "Two" - -[[Wsublist]] - String2 = "Three" - -[[Wsublist]] - String2 = "Four" -`) - -var marshalTestToml = []byte(`title = "TOML Marshal Testing" - -[basic] - bool = true - date = 1979-05-27T07:32:00Z - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - uint = 5001 - -[basic_lists] - bools = [true, false, true] - dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] - floats = [12.3, 45.6, 78.9] - ints = [8001, 8001, 8002] - strings = ["One", "Two", "Three"] - uints = [5002, 5003] - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.first] - name = "First" - - [subdoc.second] - name = "Second" - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" - -[[subdocptrs]] - name = "Second" -`) - -var marshalOrderPreserveToml = []byte(`title = "TOML Marshal Testing" - -[basic_lists] - floats = [12.3, 45.6, 78.9] - bools = [true, false, true] - dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z] - ints = [8001, 8001, 8002] - uints = [5002, 5003] - strings = ["One", "Two", "Three"] - -[[subdocptrs]] - name = "Second" - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.second] - name = "Second" - - [subdoc.first] - name = "First" - -[basic] - uint = 5001 - bool = true - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - date = 1979-05-27T07:32:00Z - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" -`) - -var mashalOrderPreserveMapToml = []byte(`title = "TOML Marshal Testing" - -[basic_map] - one = "one" - two = "two" - -[long_map] - a7 = "1" - b3 = "2" - c8 = "3" - d4 = "4" - e6 = "5" - f5 = "6" - g10 = "7" - h1 = "8" - i2 = "9" - j9 = "10" -`) - -type Conf struct { - Name string - Age int - Inter interface{} -} - -type NestedStruct struct { - FirstName string - LastName string - Age int -} - -var doc = []byte(`Name = "rui" -Age = 18 - -[Inter] - FirstName = "wang" - LastName = "jl" - Age = 100`) - -func TestInterface(t *testing.T) { - var config Conf - config.Inter = &NestedStruct{} - err := Unmarshal(doc, &config) - expected := Conf{ - Name: "rui", - Age: 18, - Inter: &NestedStruct{ - FirstName: "wang", - LastName: "jl", - Age: 100, - }, - } - if err != nil || !reflect.DeepEqual(config, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, config) - } -} - -func TestBasicMarshal(t *testing.T) { - result, err := Marshal(basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestBasicMarshalCustomIndentation(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Indentation("\t").Encode(basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestTomlCustomIndentation - if !bytes.Equal(result.Bytes(), expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) - } -} - -func TestBasicMarshalWrongIndentation(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Indentation(" \n").Encode(basicTestData) - if err.Error() != "invalid indentation: must only contains space or tab characters" { - t.Error("expect err:invalid indentation: must only contains space or tab characters but got:", err) - } -} - -func TestBasicMarshalOrdered(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestTomlOrdered - if !bytes.Equal(result.Bytes(), expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) - } -} - -func TestBasicMarshalWithPointer(t *testing.T) { - result, err := Marshal(&basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestBasicMarshalOrderedWithPointer(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(&basicTestData) - if err != nil { - t.Fatal(err) - } - expected := basicTestTomlOrdered - if !bytes.Equal(result.Bytes(), expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes()) - } -} - -func TestBasicUnmarshal(t *testing.T) { - result := basicMarshalTestStruct{} - err := Unmarshal(basicTestToml, &result) - expected := basicTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } -} - -type quotedKeyMarshalTestStruct struct { - String string `toml:"Z.string-àéù"` - Float float64 `toml:"Yfloat-𝟘"` - Sub basicMarshalTestSubStruct `toml:"Xsubdoc-àéù"` - SubList []basicMarshalTestSubStruct `toml:"W.sublist-𝟘"` -} - -var quotedKeyMarshalTestData = quotedKeyMarshalTestStruct{ - String: "Hello", - Float: 3.5, - Sub: basicMarshalTestSubStruct{"One"}, - SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, -} - -var quotedKeyMarshalTestToml = []byte(`"Yfloat-𝟘" = 3.5 -"Z.string-àéù" = "Hello" - -[["W.sublist-𝟘"]] - String2 = "Two" - -[["W.sublist-𝟘"]] - String2 = "Three" - -["Xsubdoc-àéù"] - String2 = "One" -`) - -func TestBasicMarshalQuotedKey(t *testing.T) { - result, err := Marshal(quotedKeyMarshalTestData) - if err != nil { - t.Fatal(err) - } - expected := quotedKeyMarshalTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestBasicUnmarshalQuotedKey(t *testing.T) { - tree, err := LoadBytes(quotedKeyMarshalTestToml) - if err != nil { - t.Fatal(err) - } - - var q quotedKeyMarshalTestStruct - tree.Unmarshal(&q) - fmt.Println(q) - - if !reflect.DeepEqual(quotedKeyMarshalTestData, q) { - t.Errorf("Bad unmarshal: expected\n-----\n%v\n-----\ngot\n-----\n%v\n-----\n", quotedKeyMarshalTestData, q) - } -} - -type testDoc struct { - Title string `toml:"title"` - BasicLists testDocBasicLists `toml:"basic_lists"` - SubDocPtrs []*testSubDoc `toml:"subdocptrs"` - BasicMap map[string]string `toml:"basic_map"` - Subdocs testDocSubs `toml:"subdoc"` - Basics testDocBasics `toml:"basic"` - SubDocList []testSubDoc `toml:"subdoclist"` - err int `toml:"shouldntBeHere"` - unexported int `toml:"shouldntBeHere"` - Unexported2 int `toml:"-"` -} - -type testMapDoc struct { - Title string `toml:"title"` - BasicMap map[string]string `toml:"basic_map"` - LongMap map[string]string `toml:"long_map"` -} - -type testDocBasics struct { - Uint uint `toml:"uint"` - Bool bool `toml:"bool"` - Float32 float32 `toml:"float"` - Float64 float64 `toml:"float64"` - Int int `toml:"int"` - String *string `toml:"string"` - Date time.Time `toml:"date"` - unexported int `toml:"shouldntBeHere"` -} - -type testDocBasicLists struct { - Floats []*float32 `toml:"floats"` - Bools []bool `toml:"bools"` - Dates []time.Time `toml:"dates"` - Ints []int `toml:"ints"` - UInts []uint `toml:"uints"` - Strings []string `toml:"strings"` -} - -type testDocSubs struct { - Second *testSubDoc `toml:"second"` - First testSubDoc `toml:"first"` -} - -type testSubDoc struct { - Name string `toml:"name"` - unexported int `toml:"shouldntBeHere"` -} - -var biteMe = "Bite me" -var float1 float32 = 12.3 -var float2 float32 = 45.6 -var float3 float32 = 78.9 -var subdoc = testSubDoc{"Second", 0} - -var docData = testDoc{ - Title: "TOML Marshal Testing", - unexported: 0, - Unexported2: 0, - Basics: testDocBasics{ - Bool: true, - Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), - Float32: 123.4, - Float64: 123.456782132399, - Int: 5000, - Uint: 5001, - String: &biteMe, - unexported: 0, - }, - BasicLists: testDocBasicLists{ - Bools: []bool{true, false, true}, - Dates: []time.Time{ - time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), - time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC), - }, - Floats: []*float32{&float1, &float2, &float3}, - Ints: []int{8001, 8001, 8002}, - Strings: []string{"One", "Two", "Three"}, - UInts: []uint{5002, 5003}, - }, - BasicMap: map[string]string{ - "one": "one", - "two": "two", - }, - Subdocs: testDocSubs{ - First: testSubDoc{"First", 0}, - Second: &subdoc, - }, - SubDocList: []testSubDoc{ - {"List.First", 0}, - {"List.Second", 0}, - }, - SubDocPtrs: []*testSubDoc{&subdoc}, -} - -var mapTestDoc = testMapDoc{ - Title: "TOML Marshal Testing", - BasicMap: map[string]string{ - "one": "one", - "two": "two", - }, - LongMap: map[string]string{ - "h1": "8", - "i2": "9", - "b3": "2", - "d4": "4", - "f5": "6", - "e6": "5", - "a7": "1", - "c8": "3", - "j9": "10", - "g10": "7", - }, -} - -func TestDocMarshal(t *testing.T) { - result, err := Marshal(docData) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, marshalTestToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalTestToml, result) - } -} - -func TestDocMarshalOrdered(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(docData) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result.Bytes(), marshalOrderPreserveToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalOrderPreserveToml, result.Bytes()) - } -} - -func TestDocMarshalMaps(t *testing.T) { - result, err := Marshal(mapTestDoc) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, mashalOrderPreserveMapToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", mashalOrderPreserveMapToml, result) - } -} - -func TestDocMarshalOrderedMaps(t *testing.T) { - var result bytes.Buffer - err := NewEncoder(&result).Order(OrderPreserve).Encode(mapTestDoc) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result.Bytes(), mashalOrderPreserveMapToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", mashalOrderPreserveMapToml, result.Bytes()) - } -} - -func TestDocMarshalPointer(t *testing.T) { - result, err := Marshal(&docData) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(result, marshalTestToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", marshalTestToml, result) - } -} - -func TestDocUnmarshal(t *testing.T) { - result := testDoc{} - err := Unmarshal(marshalTestToml, &result) - expected := docData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - } -} - -func TestDocPartialUnmarshal(t *testing.T) { - file, err := ioutil.TempFile("", "test-*.toml") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - err = ioutil.WriteFile(file.Name(), marshalTestToml, 0) - if err != nil { - t.Fatal(err) - } - - tree, _ := LoadFile(file.Name()) - subTree := tree.Get("subdoc").(*Tree) - - result := testDocSubs{} - err = subTree.Unmarshal(&result) - expected := docData.Subdocs - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad partial unmartial: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - } -} - -type tomlTypeCheckTest struct { - name string - item interface{} - typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree -} - -func TestTypeChecks(t *testing.T) { - tests := []tomlTypeCheckTest{ - {"bool", true, 0}, - {"bool", false, 0}, - {"int", int(2), 0}, - {"int8", int8(2), 0}, - {"int16", int16(2), 0}, - {"int32", int32(2), 0}, - {"int64", int64(2), 0}, - {"uint", uint(2), 0}, - {"uint8", uint8(2), 0}, - {"uint16", uint16(2), 0}, - {"uint32", uint32(2), 0}, - {"uint64", uint64(2), 0}, - {"float32", float32(3.14), 0}, - {"float64", float64(3.14), 0}, - {"string", "lorem ipsum", 0}, - {"time", time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), 0}, - {"stringlist", []string{"hello", "hi"}, 1}, - {"stringlistptr", &[]string{"hello", "hi"}, 1}, - {"stringarray", [2]string{"hello", "hi"}, 1}, - {"stringarrayptr", &[2]string{"hello", "hi"}, 1}, - {"timelist", []time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"timelistptr", &[]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"timearray", [1]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"timearrayptr", &[1]time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, - {"objectlist", []tomlTypeCheckTest{}, 2}, - {"objectlistptr", &[]tomlTypeCheckTest{}, 2}, - {"objectarray", [2]tomlTypeCheckTest{{}, {}}, 2}, - {"objectlistptr", &[2]tomlTypeCheckTest{{}, {}}, 2}, - {"object", tomlTypeCheckTest{}, 3}, - {"objectptr", &tomlTypeCheckTest{}, 3}, - } - - for _, test := range tests { - expected := []bool{false, false, false, false} - expected[test.typ] = true - result := []bool{ - isPrimitive(reflect.TypeOf(test.item)), - isOtherSequence(reflect.TypeOf(test.item)), - isTreeSequence(reflect.TypeOf(test.item)), - isTree(reflect.TypeOf(test.item)), - } - if !reflect.DeepEqual(expected, result) { - t.Errorf("Bad type check on %q: expected %v, got %v", test.name, expected, result) - } - } -} - -type unexportedMarshalTestStruct struct { - String string `toml:"string"` - StringList []string `toml:"strlist"` - Sub basicMarshalTestSubStruct `toml:"subdoc"` - SubList []basicMarshalTestSubStruct `toml:"sublist"` - unexported int `toml:"shouldntBeHere"` - Unexported2 int `toml:"-"` -} - -var unexportedTestData = unexportedMarshalTestStruct{ - String: "Hello", - StringList: []string{"Howdy", "Hey There"}, - Sub: basicMarshalTestSubStruct{"One"}, - SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, - unexported: 0, - Unexported2: 0, -} - -var unexportedTestToml = []byte(`string = "Hello" -strlist = ["Howdy","Hey There"] -unexported = 1 -shouldntBeHere = 2 - -[subdoc] - String2 = "One" - -[[sublist]] - String2 = "Two" - -[[sublist]] - String2 = "Three" -`) - -func TestUnexportedUnmarshal(t *testing.T) { - result := unexportedMarshalTestStruct{} - err := Unmarshal(unexportedTestToml, &result) - expected := unexportedTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result) - } -} - -type errStruct struct { - Bool bool `toml:"bool"` - Date time.Time `toml:"date"` - Float float64 `toml:"float"` - Int int16 `toml:"int"` - String *string `toml:"string"` -} - -var errTomls = []string{ - "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", - "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", - "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", -} - -type mapErr struct { - Vals map[string]float64 -} - -type intErr struct { - Int1 int - Int2 int8 - Int3 int16 - Int4 int32 - Int5 int64 - UInt1 uint - UInt2 uint8 - UInt3 uint16 - UInt4 uint32 - UInt5 uint64 - Flt1 float32 - Flt2 float64 -} - -var intErrTomls = []string{ - "Int1 = []\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = []\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = []\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = []\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = []\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = []\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = []\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = []\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = []\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = []\nFlt1 = 1.0\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = []\nFlt2 = 2.0", - "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = []", -} - -func TestErrUnmarshal(t *testing.T) { - for ind, toml := range errTomls { - result := errStruct{} - err := Unmarshal([]byte(toml), &result) - if err == nil { - t.Errorf("Expected err from case %d\n", ind) - } - } - result2 := mapErr{} - err := Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2) - if err == nil { - t.Errorf("Expected err from map") - } - for ind, toml := range intErrTomls { - result3 := intErr{} - err := Unmarshal([]byte(toml), &result3) - if err == nil { - t.Errorf("Expected int err from case %d\n", ind) - } - } -} - -type emptyMarshalTestStruct struct { - Title string `toml:"title"` - Bool bool `toml:"bool"` - Int int `toml:"int"` - String string `toml:"string"` - StringList []string `toml:"stringlist"` - Ptr *basicMarshalTestStruct `toml:"ptr"` - Map map[string]string `toml:"map"` -} - -var emptyTestData = emptyMarshalTestStruct{ - Title: "Placeholder", - Bool: false, - Int: 0, - String: "", - StringList: []string{}, - Ptr: nil, - Map: map[string]string{}, -} - -var emptyTestToml = []byte(`bool = false -int = 0 -string = "" -stringlist = [] -title = "Placeholder" - -[map] -`) - -type emptyMarshalTestStruct2 struct { - Title string `toml:"title"` - Bool bool `toml:"bool,omitempty"` - Int int `toml:"int, omitempty"` - String string `toml:"string,omitempty "` - StringList []string `toml:"stringlist,omitempty"` - Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` - Map map[string]string `toml:"map,omitempty"` -} - -var emptyTestData2 = emptyMarshalTestStruct2{ - Title: "Placeholder", - Bool: false, - Int: 0, - String: "", - StringList: []string{}, - Ptr: nil, - Map: map[string]string{}, -} - -var emptyTestToml2 = []byte(`title = "Placeholder" -`) - -func TestEmptyMarshal(t *testing.T) { - result, err := Marshal(emptyTestData) - if err != nil { - t.Fatal(err) - } - expected := emptyTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad empty marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestEmptyMarshalOmit(t *testing.T) { - result, err := Marshal(emptyTestData2) - if err != nil { - t.Fatal(err) - } - expected := emptyTestToml2 - if !bytes.Equal(result, expected) { - t.Errorf("Bad empty omit marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestEmptyUnmarshal(t *testing.T) { - result := emptyMarshalTestStruct{} - err := Unmarshal(emptyTestToml, &result) - expected := emptyTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result) - } -} - -func TestEmptyUnmarshalOmit(t *testing.T) { - result := emptyMarshalTestStruct2{} - err := Unmarshal(emptyTestToml, &result) - expected := emptyTestData2 - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result) - } -} - -type pointerMarshalTestStruct struct { - Str *string - List *[]string - ListPtr *[]*string - Map *map[string]string - MapPtr *map[string]*string - EmptyStr *string - EmptyList *[]string - EmptyMap *map[string]string - DblPtr *[]*[]*string -} - -var pointerStr = "Hello" -var pointerList = []string{"Hello back"} -var pointerListPtr = []*string{&pointerStr} -var pointerMap = map[string]string{"response": "Goodbye"} -var pointerMapPtr = map[string]*string{"alternate": &pointerStr} -var pointerTestData = pointerMarshalTestStruct{ - Str: &pointerStr, - List: &pointerList, - ListPtr: &pointerListPtr, - Map: &pointerMap, - MapPtr: &pointerMapPtr, - EmptyStr: nil, - EmptyList: nil, - EmptyMap: nil, -} - -var pointerTestToml = []byte(`List = ["Hello back"] -ListPtr = ["Hello"] -Str = "Hello" - -[Map] - response = "Goodbye" - -[MapPtr] - alternate = "Hello" -`) - -func TestPointerMarshal(t *testing.T) { - result, err := Marshal(pointerTestData) - if err != nil { - t.Fatal(err) - } - expected := pointerTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad pointer marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestPointerUnmarshal(t *testing.T) { - result := pointerMarshalTestStruct{} - err := Unmarshal(pointerTestToml, &result) - expected := pointerTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result) - } -} - -func TestUnmarshalTypeMismatch(t *testing.T) { - result := pointerMarshalTestStruct{} - err := Unmarshal([]byte("List = 123"), &result) - if !strings.HasPrefix(err.Error(), "(1, 1): Can't convert 123(int64) to []string(slice)") { - t.Errorf("Type mismatch must be reported: got %v", err.Error()) - } -} - -type nestedMarshalTestStruct struct { - String [][]string - //Struct [][]basicMarshalTestSubStruct - StringPtr *[]*[]*string - // StructPtr *[]*[]*basicMarshalTestSubStruct -} - -var str1 = "Three" -var str2 = "Four" -var strPtr = []*string{&str1, &str2} -var strPtr2 = []*[]*string{&strPtr} - -var nestedTestData = nestedMarshalTestStruct{ - String: [][]string{{"Five", "Six"}, {"One", "Two"}}, - StringPtr: &strPtr2, -} - -var nestedTestToml = []byte(`String = [["Five", "Six"], ["One", "Two"]] -StringPtr = [["Three", "Four"]] -`) - -func TestNestedMarshal(t *testing.T) { - result, err := Marshal(nestedTestData) - if err != nil { - t.Fatal(err) - } - expected := nestedTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad nested marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestNestedUnmarshal(t *testing.T) { - result := nestedMarshalTestStruct{} - err := Unmarshal(nestedTestToml, &result) - expected := nestedTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result) - } -} - -type customMarshalerParent struct { - Self customMarshaler `toml:"me"` - Friends []customMarshaler `toml:"friends"` -} - -type customMarshaler struct { - FirstName string - LastName string -} - -func (c customMarshaler) MarshalTOML() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", c.FirstName, c.LastName) - return []byte(fullName), nil -} - -var customMarshalerData = customMarshaler{FirstName: "Sally", LastName: "Fields"} -var customMarshalerToml = []byte(`Sally Fields`) -var nestedCustomMarshalerData = customMarshalerParent{ - Self: customMarshaler{FirstName: "Maiku", LastName: "Suteda"}, - Friends: []customMarshaler{customMarshalerData}, -} -var nestedCustomMarshalerToml = []byte(`friends = ["Sally Fields"] -me = "Maiku Suteda" -`) -var nestedCustomMarshalerTomlForUnmarshal = []byte(`[friends] -FirstName = "Sally" -LastName = "Fields"`) - -func TestCustomMarshaler(t *testing.T) { - result, err := Marshal(customMarshalerData) - if err != nil { - t.Fatal(err) - } - expected := customMarshalerToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad custom marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type IntOrString string - -func (x *IntOrString) MarshalTOML() ([]byte, error) { - s := *(*string)(x) - _, err := strconv.Atoi(s) - if err != nil { - return []byte(fmt.Sprintf(`"%s"`, s)), nil - } - return []byte(s), nil -} - -func TestNestedCustomMarshaler(t *testing.T) { - num := IntOrString("100") - str := IntOrString("hello") - var parent = struct { - IntField *IntOrString `toml:"int"` - StringField *IntOrString `toml:"string"` - }{ - &num, - &str, - } - - result, err := Marshal(parent) - if err != nil { - t.Fatal(err) - } - expected := `int = 100 -string = "hello" -` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type textMarshaler struct { - FirstName string - LastName string -} - -func (m textMarshaler) MarshalText() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) - return []byte(fullName), nil -} - -func TestTextMarshaler(t *testing.T) { - m := textMarshaler{FirstName: "Sally", LastName: "Fields"} - - result, err := Marshal(m) - if err != nil { - t.Fatal(err) - } - expected := `Sally Fields` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestUnmarshalTextMarshaler(t *testing.T) { - var nested = struct { - Friends textMarshaler `toml:"friends"` - }{} - - var expected = struct { - Friends textMarshaler `toml:"friends"` - }{ - Friends: textMarshaler{FirstName: "Sally", LastName: "Fields"}, - } - - err := Unmarshal(nestedCustomMarshalerTomlForUnmarshal, &nested) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(nested, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, nested) - } -} - -func TestNestedTextMarshaler(t *testing.T) { - var parent = struct { - Self textMarshaler `toml:"me"` - Friends []textMarshaler `toml:"friends"` - Stranger *textMarshaler `toml:"stranger"` - }{ - Self: textMarshaler{FirstName: "Maiku", LastName: "Suteda"}, - Friends: []textMarshaler{textMarshaler{FirstName: "Sally", LastName: "Fields"}}, - Stranger: &textMarshaler{FirstName: "Earl", LastName: "Henson"}, - } - - result, err := Marshal(parent) - if err != nil { - t.Fatal(err) - } - expected := `friends = ["Sally Fields"] -me = "Maiku Suteda" -stranger = "Earl Henson" -` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type precedentMarshaler struct { - FirstName string - LastName string -} - -func (m precedentMarshaler) MarshalText() ([]byte, error) { - return []byte("shadowed"), nil -} - -func (m precedentMarshaler) MarshalTOML() ([]byte, error) { - fullName := fmt.Sprintf("%s %s", m.FirstName, m.LastName) - return []byte(fullName), nil -} - -func TestPrecedentMarshaler(t *testing.T) { - m := textMarshaler{FirstName: "Sally", LastName: "Fields"} - - result, err := Marshal(m) - if err != nil { - t.Fatal(err) - } - expected := `Sally Fields` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type customPointerMarshaler struct { - FirstName string - LastName string -} - -func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) { - return []byte(`"hidden"`), nil -} - -type textPointerMarshaler struct { - FirstName string - LastName string -} - -func (m *textPointerMarshaler) MarshalText() ([]byte, error) { - return []byte("hidden"), nil -} - -func TestPointerMarshaler(t *testing.T) { - var parent = struct { - Self customPointerMarshaler `toml:"me"` - Stranger *customPointerMarshaler `toml:"stranger"` - Friend textPointerMarshaler `toml:"friend"` - Fiend *textPointerMarshaler `toml:"fiend"` - }{ - Self: customPointerMarshaler{FirstName: "Maiku", LastName: "Suteda"}, - Stranger: &customPointerMarshaler{FirstName: "Earl", LastName: "Henson"}, - Friend: textPointerMarshaler{FirstName: "Sally", LastName: "Fields"}, - Fiend: &textPointerMarshaler{FirstName: "Casper", LastName: "Snider"}, - } - - result, err := Marshal(parent) - if err != nil { - t.Fatal(err) - } - expected := `fiend = "hidden" -stranger = "hidden" - -[friend] - FirstName = "Sally" - LastName = "Fields" - -[me] - FirstName = "Maiku" - LastName = "Suteda" -` - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestPointerCustomMarshalerSequence(t *testing.T) { - var customPointerMarshalerSlice *[]*customPointerMarshaler - var customPointerMarshalerArray *[2]*customPointerMarshaler - - if !isCustomMarshalerSequence(reflect.TypeOf(customPointerMarshalerSlice)) { - t.Errorf("error: should be a sequence of custom marshaler interfaces") - } - if !isCustomMarshalerSequence(reflect.TypeOf(customPointerMarshalerArray)) { - t.Errorf("error: should be a sequence of custom marshaler interfaces") - } -} - -func TestPointerTextMarshalerSequence(t *testing.T) { - var textPointerMarshalerSlice *[]*textPointerMarshaler - var textPointerMarshalerArray *[2]*textPointerMarshaler - - if !isTextMarshalerSequence(reflect.TypeOf(textPointerMarshalerSlice)) { - t.Errorf("error: should be a sequence of text marshaler interfaces") - } - if !isTextMarshalerSequence(reflect.TypeOf(textPointerMarshalerArray)) { - t.Errorf("error: should be a sequence of text marshaler interfaces") - } -} - -var commentTestToml = []byte(` -# it's a comment on type -[postgres] - # isCommented = "dvalue" - noComment = "cvalue" - - # A comment on AttrB with a - # break line - password = "bvalue" - - # A comment on AttrA - user = "avalue" - - [[postgres.My]] - - # a comment on my on typeC - My = "Foo" - - [[postgres.My]] - - # a comment on my on typeC - My = "Baar" -`) - -func TestMarshalComment(t *testing.T) { - type TypeC struct { - My string `comment:"a comment on my on typeC"` - } - type TypeB struct { - AttrA string `toml:"user" comment:"A comment on AttrA"` - AttrB string `toml:"password" comment:"A comment on AttrB with a\n break line"` - AttrC string `toml:"noComment"` - AttrD string `toml:"isCommented" commented:"true"` - My []TypeC - } - type TypeA struct { - TypeB TypeB `toml:"postgres" comment:"it's a comment on type"` - } - - ta := []TypeC{{My: "Foo"}, {My: "Baar"}} - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue", AttrC: "cvalue", AttrD: "dvalue", My: ta}} - result, err := Marshal(config) - if err != nil { - t.Fatal(err) - } - expected := commentTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestMarshalMultilineCommented(t *testing.T) { - expectedToml := []byte(`# MultilineArray = [ - # 100, - # 200, - # 300, -# ] -# MultilineNestedArray = [ - # [ - # "a", - # "b", - # "c", -# ], - # [ - # "d", - # "e", - # "f", -# ], -# ] -# MultilineString = """ -# I -# am -# Allen""" -NonCommented = "Not commented line" -`) - type StructWithMultiline struct { - NonCommented string - MultilineString string `commented:"true" multiline:"true"` - MultilineArray []int `commented:"true"` - MultilineNestedArray [][]string `commented:"true"` - } - - var buf bytes.Buffer - enc := NewEncoder(&buf) - if err := enc.ArraysWithOneElementPerLine(true).Encode(StructWithMultiline{ - NonCommented: "Not commented line", - MultilineString: "I\nam\nAllen", - MultilineArray: []int{100, 200, 300}, - MultilineNestedArray: [][]string{ - {"a", "b", "c"}, - {"d", "e", "f"}, - }, - }); err == nil { - result := buf.Bytes() - if !bytes.Equal(result, expectedToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result) - } - } else { - t.Fatal(err) - } -} - -func TestMarshalNonPrimitiveTypeCommented(t *testing.T) { - expectedToml := []byte(` -# [CommentedMapField] - - # [CommentedMapField.CommentedMapField1] - # SingleLineString = "This line should be commented out" - - # [CommentedMapField.CommentedMapField2] - # SingleLineString = "This line should be commented out" - -# [CommentedStructField] - - # [CommentedStructField.CommentedStructField] - # MultilineArray = [ - # 1, - # 2, - # ] - # MultilineNestedArray = [ - # [ - # 10, - # 20, - # ], - # [ - # 100, - # 200, - # ], - # ] - # MultilineString = """ -# This line -# should be -# commented out""" - - # [CommentedStructField.NotCommentedStructField] - # MultilineArray = [ - # 1, - # 2, - # ] - # MultilineNestedArray = [ - # [ - # 10, - # 20, - # ], - # [ - # 100, - # 200, - # ], - # ] - # MultilineString = """ -# This line -# should be -# commented out""" - -[NotCommentedStructField] - - # [NotCommentedStructField.CommentedStructField] - # MultilineArray = [ - # 1, - # 2, - # ] - # MultilineNestedArray = [ - # [ - # 10, - # 20, - # ], - # [ - # 100, - # 200, - # ], - # ] - # MultilineString = """ -# This line -# should be -# commented out""" - - [NotCommentedStructField.NotCommentedStructField] - MultilineArray = [ - 3, - 4, - ] - MultilineNestedArray = [ - [ - 30, - 40, - ], - [ - 300, - 400, - ], - ] - MultilineString = """ -This line -should NOT be -commented out""" -`) - type InnerStruct struct { - MultilineString string `multiline:"true"` - MultilineArray []int - MultilineNestedArray [][]int - } - type MiddleStruct struct { - NotCommentedStructField InnerStruct - CommentedStructField InnerStruct `commented:"true"` - } - type OuterStruct struct { - CommentedStructField MiddleStruct `commented:"true"` - NotCommentedStructField MiddleStruct - CommentedMapField map[string]struct{ SingleLineString string } `commented:"true"` - } - - commentedTestStruct := OuterStruct{ - CommentedStructField: MiddleStruct{ - NotCommentedStructField: InnerStruct{ - MultilineString: "This line\nshould be\ncommented out", - MultilineArray: []int{1, 2}, - MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, - }, - CommentedStructField: InnerStruct{ - MultilineString: "This line\nshould be\ncommented out", - MultilineArray: []int{1, 2}, - MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, - }, - }, - NotCommentedStructField: MiddleStruct{ - NotCommentedStructField: InnerStruct{ - MultilineString: "This line\nshould NOT be\ncommented out", - MultilineArray: []int{3, 4}, - MultilineNestedArray: [][]int{{30, 40}, {300, 400}}, - }, - CommentedStructField: InnerStruct{ - MultilineString: "This line\nshould be\ncommented out", - MultilineArray: []int{1, 2}, - MultilineNestedArray: [][]int{{10, 20}, {100, 200}}, - }, - }, - CommentedMapField: map[string]struct{ SingleLineString string }{ - "CommentedMapField1": { - SingleLineString: "This line should be commented out", - }, - "CommentedMapField2": { - SingleLineString: "This line should be commented out", - }, - }, - } - - var buf bytes.Buffer - enc := NewEncoder(&buf) - if err := enc.ArraysWithOneElementPerLine(true).Encode(commentedTestStruct); err == nil { - result := buf.Bytes() - if !bytes.Equal(result, expectedToml) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result) - } - } else { - t.Fatal(err) - } -} - -type mapsTestStruct struct { - Simple map[string]string - Paths map[string]string - Other map[string]float64 - X struct { - Y struct { - Z map[string]bool - } - } -} - -var mapsTestData = mapsTestStruct{ - Simple: map[string]string{ - "one plus one": "two", - "next": "three", - }, - Paths: map[string]string{ - "/this/is/a/path": "/this/is/also/a/path", - "/heloo.txt": "/tmp/lololo.txt", - }, - Other: map[string]float64{ - "testing": 3.9999, - }, - X: struct{ Y struct{ Z map[string]bool } }{ - Y: struct{ Z map[string]bool }{ - Z: map[string]bool{ - "is.Nested": true, - }, - }, - }, -} -var mapsTestToml = []byte(` -[Other] - "testing" = 3.9999 - -[Paths] - "/heloo.txt" = "/tmp/lololo.txt" - "/this/is/a/path" = "/this/is/also/a/path" - -[Simple] - "next" = "three" - "one plus one" = "two" - -[X] - - [X.Y] - - [X.Y.Z] - "is.Nested" = true -`) - -func TestEncodeQuotedMapKeys(t *testing.T) { - var buf bytes.Buffer - if err := NewEncoder(&buf).QuoteMapKeys(true).Encode(mapsTestData); err != nil { - t.Fatal(err) - } - result := buf.Bytes() - expected := mapsTestToml - if !bytes.Equal(result, expected) { - t.Errorf("Bad maps marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestDecodeQuotedMapKeys(t *testing.T) { - result := mapsTestStruct{} - err := NewDecoder(bytes.NewBuffer(mapsTestToml)).Decode(&result) - expected := mapsTestData - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad maps unmarshal: expected %v, got %v", expected, result) - } -} - -type structArrayNoTag struct { - A struct { - B []int64 - C []int64 - } -} - -func TestMarshalArray(t *testing.T) { - expected := []byte(` -[A] - B = [1, 2, 3] - C = [1] -`) - - m := structArrayNoTag{ - A: struct { - B []int64 - C []int64 - }{ - B: []int64{1, 2, 3}, - C: []int64{1}, - }, - } - - b, err := Marshal(m) - - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(b, expected) { - t.Errorf("Bad arrays marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) - } -} - -func TestMarshalArrayOnePerLine(t *testing.T) { - expected := []byte(` -[A] - B = [ - 1, - 2, - 3, - ] - C = [1] -`) - - m := structArrayNoTag{ - A: struct { - B []int64 - C []int64 - }{ - B: []int64{1, 2, 3}, - C: []int64{1}, - }, - } - - var buf bytes.Buffer - encoder := NewEncoder(&buf).ArraysWithOneElementPerLine(true) - err := encoder.Encode(m) - - if err != nil { - t.Fatal(err) - } - - b := buf.Bytes() - - if !bytes.Equal(b, expected) { - t.Errorf("Bad arrays marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, b) - } -} - -var customTagTestToml = []byte(` -[postgres] - password = "bvalue" - user = "avalue" - - [[postgres.My]] - My = "Foo" - - [[postgres.My]] - My = "Baar" -`) - -func TestMarshalCustomTag(t *testing.T) { - type TypeC struct { - My string - } - type TypeB struct { - AttrA string `file:"user"` - AttrB string `file:"password"` - My []TypeC - } - type TypeA struct { - TypeB TypeB `file:"postgres"` - } - - ta := []TypeC{{My: "Foo"}, {My: "Baar"}} - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue", My: ta}} - var buf bytes.Buffer - err := NewEncoder(&buf).SetTagName("file").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -var customCommentTagTestToml = []byte(` -# db connection -[postgres] - - # db pass - password = "bvalue" - - # db user - user = "avalue" -`) - -func TestMarshalCustomComment(t *testing.T) { - type TypeB struct { - AttrA string `toml:"user" descr:"db user"` - AttrB string `toml:"password" descr:"db pass"` - } - type TypeA struct { - TypeB TypeB `toml:"postgres" descr:"db connection"` - } - - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue"}} - var buf bytes.Buffer - err := NewEncoder(&buf).SetTagComment("descr").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customCommentTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -var customCommentedTagTestToml = []byte(` -[postgres] - # password = "bvalue" - # user = "avalue" -`) - -func TestMarshalCustomCommented(t *testing.T) { - type TypeB struct { - AttrA string `toml:"user" disable:"true"` - AttrB string `toml:"password" disable:"true"` - } - type TypeA struct { - TypeB TypeB `toml:"postgres"` - } - - config := TypeA{TypeB{AttrA: "avalue", AttrB: "bvalue"}} - var buf bytes.Buffer - err := NewEncoder(&buf).SetTagCommented("disable").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customCommentedTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestMarshalDirectMultilineString(t *testing.T) { - tree := newTree() - tree.SetWithOptions("mykey", SetOptions{ - Multiline: true, - }, "my\x11multiline\nstring\ba\tb\fc\rd\"e\\!") - result, err := tree.Marshal() - if err != nil { - t.Fatal("marshal should not error:", err) - } - expected := []byte("mykey = \"\"\"\nmy\\u0011multiline\nstring\\ba\tb\\fc\rd\"e\\!\"\"\"\n") - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) { - type Test struct { - Field1 string `toml:"Fie ld1"` - Field2 string - } - - type TestCase struct { - desc string - input []byte - expected Test - } - - testCases := []TestCase{ - { - desc: "multiline string with tab", - input: []byte("Field2 = \"\"\"\nhello\tworld\"\"\""), - expected: Test{ - Field2: "hello\tworld", - }, - }, - { - desc: "quoted key with tab", - input: []byte("\"Fie\tld1\" = \"key with tab\""), - expected: Test{ - Field1: "key with tab", - }, - }, - { - desc: "basic string tab", - input: []byte("Field2 = \"hello\tworld\""), - expected: Test{ - Field2: "hello\tworld", - }, - }, - } - - for i := range testCases { - result := Test{} - err := Unmarshal(testCases[i].input, &result) - if err != nil { - t.Errorf("%s test error:%v", testCases[i].desc, err) - continue - } - - if !reflect.DeepEqual(result, testCases[i].expected) { - t.Errorf("%s test error: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n", - testCases[i].desc, testCases[i].expected, result) - } - } -} - -var customMultilineTagTestToml = []byte(`int_slice = [ - 1, - 2, - 3, -] -`) - -func TestMarshalCustomMultiline(t *testing.T) { - type TypeA struct { - AttrA []int `toml:"int_slice" mltln:"true"` - } - - config := TypeA{AttrA: []int{1, 2, 3}} - var buf bytes.Buffer - err := NewEncoder(&buf).ArraysWithOneElementPerLine(true).SetTagMultiline("mltln").Encode(config) - if err != nil { - t.Fatal(err) - } - expected := customMultilineTagTestToml - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -func TestMultilineWithAdjacentQuotationMarks(t *testing.T) { - type testStruct struct { - Str string `multiline:"true"` - } - type testCase struct { - expected []byte - data testStruct - } - - testCases := []testCase{ - { - expected: []byte(`Str = """ -hello\"""" -`), - data: testStruct{ - Str: "hello\"", - }, - }, - { - expected: []byte(`Str = """ -""\"""\"""\"""" -`), - data: testStruct{ - Str: "\"\"\"\"\"\"\"\"\"", - }, - }, - } - for i := range testCases { - result, err := Marshal(testCases[i].data) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(result, testCases[i].expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", - testCases[i].expected, result) - } else { - var data testStruct - if err = Unmarshal(result, &data); err != nil { - t.Fatal(err) - } - if data.Str != testCases[i].data.Str { - t.Errorf("Round trip test fail: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", - testCases[i].data.Str, data.Str) - } - } - } -} - -func TestMarshalEmbedTree(t *testing.T) { - expected := []byte(`OuterField1 = "Out" -OuterField2 = 1024 - -[TreeField] - InnerField1 = "In" - InnerField2 = 2048 - - [TreeField.EmbedStruct] - EmbedField = "Embed" -`) - type InnerStruct struct { - InnerField1 string - InnerField2 int - EmbedStruct struct { - EmbedField string - } - } - - type OuterStruct struct { - OuterField1 string - OuterField2 int - TreeField *Tree - } - - tree, err := Load(` -InnerField1 = "In" -InnerField2 = 2048 - -[EmbedStruct] - EmbedField = "Embed" -`) - if err != nil { - t.Fatal(err) - } - - out := OuterStruct{ - "Out", - 1024, - tree, - } - actual, _ := Marshal(out) - - if !bytes.Equal(actual, expected) { - t.Errorf("Bad marshal: expected %s, got %s", expected, actual) - } -} - -var testDocBasicToml = []byte(` -[document] - bool_val = true - date_val = 1979-05-27T07:32:00Z - float_val = 123.4 - int_val = 5000 - string_val = "Bite me" - uint_val = 5001 -`) - -type testDocCustomTag struct { - Doc testDocBasicsCustomTag `file:"document"` -} -type testDocBasicsCustomTag struct { - Bool bool `file:"bool_val"` - Date time.Time `file:"date_val"` - Float float32 `file:"float_val"` - Int int `file:"int_val"` - Uint uint `file:"uint_val"` - String *string `file:"string_val"` - unexported int `file:"shouldntBeHere"` -} - -var testDocCustomTagData = testDocCustomTag{ - Doc: testDocBasicsCustomTag{ - Bool: true, - Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), - Float: 123.4, - Int: 5000, - Uint: 5001, - String: &biteMe, - unexported: 0, - }, -} - -func TestUnmarshalCustomTag(t *testing.T) { - buf := bytes.NewBuffer(testDocBasicToml) - - result := testDocCustomTag{} - err := NewDecoder(buf).SetTagName("file").Decode(&result) - if err != nil { - t.Fatal(err) - } - expected := testDocCustomTagData - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - - } -} - -func TestUnmarshalMap(t *testing.T) { - testToml := []byte(` - a = 1 - b = 2 - c = 3 - `) - var result map[string]int - err := Unmarshal(testToml, &result) - if err != nil { - t.Errorf("Received unexpected error: %s", err) - return - } - - expected := map[string]int{ - "a": 1, - "b": 2, - "c": 3, - } - - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } -} - -func TestUnmarshalMapWithTypedKey(t *testing.T) { - testToml := []byte(` - a = 1 - b = 2 - c = 3 - `) - - type letter string - var result map[letter]int - err := Unmarshal(testToml, &result) - if err != nil { - t.Errorf("Received unexpected error: %s", err) - return - } - - expected := map[letter]int{ - "a": 1, - "b": 2, - "c": 3, - } - - if !reflect.DeepEqual(result, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) - } -} - -func TestUnmarshalNonPointer(t *testing.T) { - a := 1 - err := Unmarshal([]byte{}, a) - if err == nil { - t.Fatal("unmarshal should err when given a non pointer") - } -} - -func TestUnmarshalInvalidPointerKind(t *testing.T) { - a := 1 - err := Unmarshal([]byte{}, &a) - if err == nil { - t.Fatal("unmarshal should err when given an invalid pointer type") - } -} - -func TestMarshalSlice(t *testing.T) { - m := make([]int, 1) - m[0] = 1 - - var buf bytes.Buffer - err := NewEncoder(&buf).Encode(&m) - if err == nil { - t.Error("expected error, got nil") - return - } - if err.Error() != "Only pointer to struct can be marshaled to TOML" { - t.Fail() - } -} - -func TestMarshalSlicePointer(t *testing.T) { - m := make([]int, 1) - m[0] = 1 - - var buf bytes.Buffer - err := NewEncoder(&buf).Encode(m) - if err == nil { - t.Error("expected error, got nil") - return - } - if err.Error() != "Only a struct or map can be marshaled to TOML" { - t.Fail() - } -} - -func TestMarshalNestedArrayInlineTables(t *testing.T) { - type table struct { - Value1 int `toml:"ZValue1"` - Value2 int `toml:"YValue2"` - Value3 int `toml:"XValue3"` - } - - type nestedTable struct { - Table table - } - - nestedArray := struct { - Simple [][]table - SimplePointer *[]*[]table - Nested [][]nestedTable - NestedPointer *[]*[]nestedTable - }{ - Simple: [][]table{{{Value1: 1}, {Value1: 10}}}, - SimplePointer: &[]*[]table{{{Value2: 2}}}, - Nested: [][]nestedTable{{{Table: table{Value3: 3}}}}, - NestedPointer: &[]*[]nestedTable{{{Table: table{Value3: -3}}}}, - } - - expectedPreserve := `Simple = [[{ ZValue1 = 1, YValue2 = 0, XValue3 = 0 }, { ZValue1 = 10, YValue2 = 0, XValue3 = 0 }]] -SimplePointer = [[{ ZValue1 = 0, YValue2 = 2, XValue3 = 0 }]] -Nested = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = 3 } }]] -NestedPointer = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = -3 } }]] -` - - expectedAlphabetical := `Nested = [[{ Table = { XValue3 = 3, YValue2 = 0, ZValue1 = 0 } }]] -NestedPointer = [[{ Table = { XValue3 = -3, YValue2 = 0, ZValue1 = 0 } }]] -Simple = [[{ XValue3 = 0, YValue2 = 0, ZValue1 = 1 }, { XValue3 = 0, YValue2 = 0, ZValue1 = 10 }]] -SimplePointer = [[{ XValue3 = 0, YValue2 = 2, ZValue1 = 0 }]] -` - - var bufPreserve bytes.Buffer - if err := NewEncoder(&bufPreserve).Order(OrderPreserve).Encode(nestedArray); err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(bufPreserve.Bytes(), []byte(expectedPreserve)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedPreserve, bufPreserve.String()) - } - - var bufAlphabetical bytes.Buffer - if err := NewEncoder(&bufAlphabetical).Order(OrderAlphabetical).Encode(nestedArray); err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(bufAlphabetical.Bytes(), []byte(expectedAlphabetical)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedAlphabetical, bufAlphabetical.String()) - } -} - -type testDuration struct { - Nanosec time.Duration `toml:"nanosec"` - Microsec1 time.Duration `toml:"microsec1"` - Microsec2 *time.Duration `toml:"microsec2"` - Millisec time.Duration `toml:"millisec"` - Sec time.Duration `toml:"sec"` - Min time.Duration `toml:"min"` - Hour time.Duration `toml:"hour"` - Mixed time.Duration `toml:"mixed"` - AString string `toml:"a_string"` -} - -var testDurationToml = []byte(` -nanosec = "1ns" -microsec1 = "1us" -microsec2 = "1µs" -millisec = "1ms" -sec = "1s" -min = "1m" -hour = "1h" -mixed = "1h1m1s1ms1µs1ns" -a_string = "15s" -`) - -func TestUnmarshalDuration(t *testing.T) { - buf := bytes.NewBuffer(testDurationToml) - - result := testDuration{} - err := NewDecoder(buf).Decode(&result) - if err != nil { - t.Fatal(err) - } - ms := time.Duration(1) * time.Microsecond - expected := testDuration{ - Nanosec: 1, - Microsec1: time.Microsecond, - Microsec2: &ms, - Millisec: time.Millisecond, - Sec: time.Second, - Min: time.Minute, - Hour: time.Hour, - Mixed: time.Hour + - time.Minute + - time.Second + - time.Millisecond + - time.Microsecond + - time.Nanosecond, - AString: "15s", - } - if !reflect.DeepEqual(result, expected) { - resStr, _ := json.MarshalIndent(result, "", " ") - expStr, _ := json.MarshalIndent(expected, "", " ") - t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) - - } -} - -var testDurationToml2 = []byte(`a_string = "15s" -hour = "1h0m0s" -microsec1 = "1µs" -microsec2 = "1µs" -millisec = "1ms" -min = "1m0s" -mixed = "1h1m1.001001001s" -nanosec = "1ns" -sec = "1s" -`) - -func TestMarshalDuration(t *testing.T) { - ms := time.Duration(1) * time.Microsecond - data := testDuration{ - Nanosec: 1, - Microsec1: time.Microsecond, - Microsec2: &ms, - Millisec: time.Millisecond, - Sec: time.Second, - Min: time.Minute, - Hour: time.Hour, - Mixed: time.Hour + - time.Minute + - time.Second + - time.Millisecond + - time.Microsecond + - time.Nanosecond, - AString: "15s", - } - - var buf bytes.Buffer - err := NewEncoder(&buf).Encode(data) - if err != nil { - t.Fatal(err) - } - expected := testDurationToml2 - result := buf.Bytes() - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } -} - -type testBadDuration struct { - Val time.Duration `toml:"val"` -} - -var testBadDurationToml = []byte(`val = "1z"`) - -func TestUnmarshalBadDuration(t *testing.T) { - buf := bytes.NewBuffer(testBadDurationToml) - - result := testBadDuration{} - err := NewDecoder(buf).Decode(&result) - if err == nil { - t.Fatal("expected bad duration error") - } -} - -var testCamelCaseKeyToml = []byte(`fooBar = 10`) - -func TestUnmarshalCamelCaseKey(t *testing.T) { - var x struct { - FooBar int - B int - } - - if err := Unmarshal(testCamelCaseKeyToml, &x); err != nil { - t.Fatal(err) - } - - if x.FooBar != 10 { - t.Fatal("Did not set camelCase'd key") - } -} - -func TestUnmarshalNegativeUint(t *testing.T) { - type check struct{ U uint } - - tree, _ := Load("u = -1") - err := tree.Unmarshal(&check{}) - if err.Error() != "(1, 1): -1(int64) is negative so does not fit in uint" { - t.Error("expect err:(1, 1): -1(int64) is negative so does not fit in uint but got:", err) - } -} - -func TestUnmarshalCheckConversionFloatInt(t *testing.T) { - type conversionCheck struct { - U uint - I int - F float64 - } - - treeU, _ := Load("u = 1e300") - treeI, _ := Load("i = 1e300") - treeF, _ := Load("f = 9223372036854775806") - - errU := treeU.Unmarshal(&conversionCheck{}) - errI := treeI.Unmarshal(&conversionCheck{}) - errF := treeF.Unmarshal(&conversionCheck{}) - - if errU.Error() != "(1, 1): Can't convert 1e+300(float64) to uint" { - t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to uint but got:", errU) - } - if errI.Error() != "(1, 1): Can't convert 1e+300(float64) to int" { - t.Error("expect err:(1, 1): Can't convert 1e+300(float64) to int but got:", errI) - } - if errF.Error() != "(1, 1): Can't convert 9223372036854775806(int64) to float64" { - t.Error("expect err:(1, 1): Can't convert 9223372036854775806(int64) to float64 but got:", errF) - } -} - -func TestUnmarshalOverflow(t *testing.T) { - type overflow struct { - U8 uint8 - I8 int8 - F32 float32 - } - - treeU8, _ := Load("u8 = 300") - treeI8, _ := Load("i8 = 300") - treeF32, _ := Load("f32 = 1e300") - - errU8 := treeU8.Unmarshal(&overflow{}) - errI8 := treeI8.Unmarshal(&overflow{}) - errF32 := treeF32.Unmarshal(&overflow{}) - - if errU8.Error() != "(1, 1): 300(int64) would overflow uint8" { - t.Error("expect err:(1, 1): 300(int64) would overflow uint8 but got:", errU8) - } - if errI8.Error() != "(1, 1): 300(int64) would overflow int8" { - t.Error("expect err:(1, 1): 300(int64) would overflow int8 but got:", errI8) - } - if errF32.Error() != "(1, 1): 1e+300(float64) would overflow float32" { - t.Error("expect err:(1, 1): 1e+300(float64) would overflow float32 but got:", errF32) - } -} - -func TestUnmarshalDefault(t *testing.T) { - type EmbeddedStruct struct { - StringField string `default:"c"` - } - - type aliasUint uint - - var doc struct { - StringField string `default:"a"` - BoolField bool `default:"true"` - UintField uint `default:"1"` - Uint8Field uint8 `default:"8"` - Uint16Field uint16 `default:"16"` - Uint32Field uint32 `default:"32"` - Uint64Field uint64 `default:"64"` - IntField int `default:"-1"` - Int8Field int8 `default:"-8"` - Int16Field int16 `default:"-16"` - Int32Field int32 `default:"-32"` - Int64Field int64 `default:"-64"` - Float32Field float32 `default:"32.1"` - Float64Field float64 `default:"64.1"` - DurationField time.Duration `default:"120ms"` - DurationField2 time.Duration `default:"120000000"` - NonEmbeddedStruct struct { - StringField string `default:"b"` - } - EmbeddedStruct - AliasUintField aliasUint `default:"1000"` - } - - err := Unmarshal([]byte(``), &doc) - if err != nil { - t.Fatal(err) - } - if doc.BoolField != true { - t.Errorf("BoolField should be true, not %t", doc.BoolField) - } - if doc.StringField != "a" { - t.Errorf("StringField should be \"a\", not %s", doc.StringField) - } - if doc.UintField != 1 { - t.Errorf("UintField should be 1, not %d", doc.UintField) - } - if doc.Uint8Field != 8 { - t.Errorf("Uint8Field should be 8, not %d", doc.Uint8Field) - } - if doc.Uint16Field != 16 { - t.Errorf("Uint16Field should be 16, not %d", doc.Uint16Field) - } - if doc.Uint32Field != 32 { - t.Errorf("Uint32Field should be 32, not %d", doc.Uint32Field) - } - if doc.Uint64Field != 64 { - t.Errorf("Uint64Field should be 64, not %d", doc.Uint64Field) - } - if doc.IntField != -1 { - t.Errorf("IntField should be -1, not %d", doc.IntField) - } - if doc.Int8Field != -8 { - t.Errorf("Int8Field should be -8, not %d", doc.Int8Field) - } - if doc.Int16Field != -16 { - t.Errorf("Int16Field should be -16, not %d", doc.Int16Field) - } - if doc.Int32Field != -32 { - t.Errorf("Int32Field should be -32, not %d", doc.Int32Field) - } - if doc.Int64Field != -64 { - t.Errorf("Int64Field should be -64, not %d", doc.Int64Field) - } - if doc.Float32Field != 32.1 { - t.Errorf("Float32Field should be 32.1, not %f", doc.Float32Field) - } - if doc.Float64Field != 64.1 { - t.Errorf("Float64Field should be 64.1, not %f", doc.Float64Field) - } - if doc.DurationField != 120*time.Millisecond { - t.Errorf("DurationField should be 120ms, not %s", doc.DurationField.String()) - } - if doc.DurationField2 != 120*time.Millisecond { - t.Errorf("DurationField2 should be 120000000, not %d", doc.DurationField2) - } - if doc.NonEmbeddedStruct.StringField != "b" { - t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField) - } - if doc.EmbeddedStruct.StringField != "c" { - t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField) - } - if doc.AliasUintField != 1000 { - t.Errorf("AliasUintField should be 1000, not %d", doc.AliasUintField) - } -} - -func TestUnmarshalDefaultFailureBool(t *testing.T) { - var doc struct { - Field bool `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureInt(t *testing.T) { - var doc struct { - Field int `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureInt64(t *testing.T) { - var doc struct { - Field int64 `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureFloat64(t *testing.T) { - var doc struct { - Field float64 `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureDuration(t *testing.T) { - var doc struct { - Field time.Duration `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestUnmarshalDefaultFailureUnsupported(t *testing.T) { - var doc struct { - Field struct{} `default:"blah"` - } - - err := Unmarshal([]byte(``), &doc) - if err == nil { - t.Fatal("should error") - } -} - -func TestMarshalNestedAnonymousStructs(t *testing.T) { - type Embedded struct { - Value string `toml:"value"` - Top struct { - Value string `toml:"value"` - } `toml:"top"` - } - - type Named struct { - Value string `toml:"value"` - } - - var doc struct { - Embedded - Named `toml:"named"` - Anonymous struct { - Value string `toml:"value"` - } `toml:"anonymous"` - } - - expected := `value = "" - -[anonymous] - value = "" - -[named] - value = "" - -[top] - value = "" -` - - result, err := Marshal(doc) - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, string(result)) - } -} - -func TestEncoderPromoteNestedAnonymousStructs(t *testing.T) { - type Embedded struct { - Value string `toml:"value"` - } - - var doc struct { - Embedded - } - - expected := ` -[Embedded] - value = "" -` - var buf bytes.Buffer - if err := NewEncoder(&buf).PromoteAnonymous(true).Encode(doc); err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(buf.Bytes(), []byte(expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, buf.String()) - } -} - -func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) { - type Embedded struct { - Value string `toml:"value"` - Top struct { - Value string `toml:"value"` - } `toml:"top"` - } - - var doc struct { - Value string `toml:"value"` - Embedded - } - doc.Embedded.Value = "shadowed" - doc.Value = "shadows" - - expected := `value = "shadows" - -[top] - value = "" -` - - result, err := Marshal(doc) - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if !bytes.Equal(result, []byte(expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, string(result)) - } -} - -func TestUnmarshalNestedAnonymousStructs(t *testing.T) { - type Nested struct { - Value string `toml:"nested_field"` - } - type Deep struct { - Nested - } - type Document struct { - Deep - Value string `toml:"own_field"` - } - - var doc Document - - err := Unmarshal([]byte(`nested_field = "nested value"`+"\n"+`own_field = "own value"`), &doc) - if err != nil { - t.Fatal("should not error") - } - if doc.Value != "own value" || doc.Nested.Value != "nested value" { - t.Fatal("unexpected values") - } -} - -func TestUnmarshalNestedAnonymousStructs_Controversial(t *testing.T) { - type Nested struct { - Value string `toml:"nested"` - } - type Deep struct { - Nested - } - type Document struct { - Deep - Value string `toml:"own"` - } - - var doc Document - - err := Unmarshal([]byte(`nested = "nested value"`+"\n"+`own = "own value"`), &doc) - if err == nil { - t.Fatal("should error") - } -} - -type unexportedFieldPreservationTest struct { - Exported string `toml:"exported"` - unexported string - Nested1 unexportedFieldPreservationTestNested `toml:"nested1"` - Nested2 *unexportedFieldPreservationTestNested `toml:"nested2"` - Nested3 *unexportedFieldPreservationTestNested `toml:"nested3"` - Slice1 []unexportedFieldPreservationTestNested `toml:"slice1"` - Slice2 []*unexportedFieldPreservationTestNested `toml:"slice2"` -} - -type unexportedFieldPreservationTestNested struct { - Exported1 string `toml:"exported1"` - unexported1 string -} - -func TestUnmarshalPreservesUnexportedFields(t *testing.T) { - toml := ` - exported = "visible" - unexported = "ignored" - - [nested1] - exported1 = "visible1" - unexported1 = "ignored1" - - [nested2] - exported1 = "visible2" - unexported1 = "ignored2" - - [nested3] - exported1 = "visible3" - unexported1 = "ignored3" - - [[slice1]] - exported1 = "visible3" - - [[slice1]] - exported1 = "visible4" - - [[slice2]] - exported1 = "visible5" - ` - - t.Run("unexported field should not be set from toml", func(t *testing.T) { - var actual unexportedFieldPreservationTest - err := Unmarshal([]byte(toml), &actual) - - if err != nil { - t.Fatal("did not expect an error") - } - - expect := unexportedFieldPreservationTest{ - Exported: "visible", - unexported: "", - Nested1: unexportedFieldPreservationTestNested{"visible1", ""}, - Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, - Nested3: &unexportedFieldPreservationTestNested{"visible3", ""}, - Slice1: []unexportedFieldPreservationTestNested{ - {Exported1: "visible3"}, - {Exported1: "visible4"}, - }, - Slice2: []*unexportedFieldPreservationTestNested{ - {Exported1: "visible5"}, - }, - } - - if !reflect.DeepEqual(actual, expect) { - t.Fatalf("%+v did not equal %+v", actual, expect) - } - }) - - t.Run("unexported field should be preserved", func(t *testing.T) { - actual := unexportedFieldPreservationTest{ - Exported: "foo", - unexported: "bar", - Nested1: unexportedFieldPreservationTestNested{"baz", "bax"}, - Nested2: nil, - Nested3: &unexportedFieldPreservationTestNested{"baz", "bax"}, - } - err := Unmarshal([]byte(toml), &actual) - - if err != nil { - t.Fatal("did not expect an error") - } - - expect := unexportedFieldPreservationTest{ - Exported: "visible", - unexported: "bar", - Nested1: unexportedFieldPreservationTestNested{"visible1", "bax"}, - Nested2: &unexportedFieldPreservationTestNested{"visible2", ""}, - Nested3: &unexportedFieldPreservationTestNested{"visible3", "bax"}, - Slice1: []unexportedFieldPreservationTestNested{ - {Exported1: "visible3"}, - {Exported1: "visible4"}, - }, - Slice2: []*unexportedFieldPreservationTestNested{ - {Exported1: "visible5"}, - }, - } - - if !reflect.DeepEqual(actual, expect) { - t.Fatalf("%+v did not equal %+v", actual, expect) - } - }) -} - -func TestTreeMarshal(t *testing.T) { - cases := [][]byte{ - basicTestToml, - marshalTestToml, - emptyTestToml, - pointerTestToml, - } - for _, expected := range cases { - t.Run("", func(t *testing.T) { - tree, err := LoadBytes(expected) - if err != nil { - t.Fatal(err) - } - result, err := tree.Marshal() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) - } - }) - } -} - -func TestMarshalArrays(t *testing.T) { - cases := []struct { - Data interface{} - Expected string - }{ - { - Data: struct { - XY [2]int - }{ - XY: [2]int{1, 2}, - }, - Expected: `XY = [1, 2] -`, - }, - { - Data: struct { - XY [1][2]int - }{ - XY: [1][2]int{{1, 2}}, - }, - Expected: `XY = [[1, 2]] -`, - }, - { - Data: struct { - XY [1][]int - }{ - XY: [1][]int{{1, 2}}, - }, - Expected: `XY = [[1, 2]] -`, - }, - { - Data: struct { - XY [][2]int - }{ - XY: [][2]int{{1, 2}}, - }, - Expected: `XY = [[1, 2]] -`, - }, - } - for _, tc := range cases { - t.Run("", func(t *testing.T) { - result, err := Marshal(tc.Data) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(result, []byte(tc.Expected)) { - t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", []byte(tc.Expected), result) - } - }) - } -} - -func TestUnmarshalLocalDate(t *testing.T) { - t.Run("ToLocalDate", func(t *testing.T) { - type dateStruct struct { - Date LocalDate - } - - toml := `date = 1979-05-27` - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year != 1979 { - t.Errorf("expected year 1979, got %d", obj.Date.Year) - } - if obj.Date.Month != 5 { - t.Errorf("expected month 5, got %d", obj.Date.Month) - } - if obj.Date.Day != 27 { - t.Errorf("expected day 27, got %d", obj.Date.Day) - } - }) - - t.Run("ToLocalDate", func(t *testing.T) { - type dateStruct struct { - Date time.Time - } - - toml := `date = 1979-05-27` - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year() != 1979 { - t.Errorf("expected year 1979, got %d", obj.Date.Year()) - } - if obj.Date.Month() != 5 { - t.Errorf("expected month 5, got %d", obj.Date.Month()) - } - if obj.Date.Day() != 27 { - t.Errorf("expected day 27, got %d", obj.Date.Day()) - } - }) -} - -func TestMarshalLocalDate(t *testing.T) { - type dateStruct struct { - Date LocalDate - } - - obj := dateStruct{Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }} - - b, err := Marshal(obj) - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := string(b) - expected := `Date = 1979-05-27 -` - - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } -} - -func TestUnmarshalLocalDateTime(t *testing.T) { - examples := []struct { - name string - in string - out LocalDateTime - }{ - { - name: "normal", - in: "1979-05-27T07:32:00", - out: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }}, - { - name: "with nanoseconds", - in: "1979-05-27T00:32:00.999999", - out: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }, - } - - for i, example := range examples { - toml := fmt.Sprintf(`date = %s`, example.in) - - t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Date LocalDateTime - } - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date != example.out { - t.Errorf("expected '%s', got '%s'", example.out, obj.Date) - } - }) - - t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Date time.Time - } - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Date.Year() != example.out.Date.Year { - t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) - } - if obj.Date.Month() != example.out.Date.Month { - t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) - } - if obj.Date.Day() != example.out.Date.Day { - t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) - } - if obj.Date.Hour() != example.out.Time.Hour { - t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) - } - if obj.Date.Minute() != example.out.Time.Minute { - t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) - } - if obj.Date.Second() != example.out.Time.Second { - t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) - } - if obj.Date.Nanosecond() != example.out.Time.Nanosecond { - t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) - } - }) - } -} - -func TestMarshalLocalDateTime(t *testing.T) { - type dateStruct struct { - DateTime LocalDateTime - } - - examples := []struct { - name string - in LocalDateTime - out string - }{ - { - name: "normal", - out: "DateTime = 1979-05-27T07:32:00\n", - in: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }}, - { - name: "with nanoseconds", - out: "DateTime = 1979-05-27T00:32:00.999999000\n", - in: LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }, - } - - for i, example := range examples { - t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { - obj := dateStruct{ - DateTime: example.in, - } - b, err := Marshal(obj) - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := string(b) - - if got != example.out { - t.Errorf("expected '%s', got '%s'", example.out, got) - } - }) - } -} - -func TestUnmarshalLocalTime(t *testing.T) { - examples := []struct { - name string - in string - out LocalTime - }{ - { - name: "normal", - in: "07:32:00", - out: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }, - { - name: "with nanoseconds", - in: "00:32:00.999999", - out: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - } - - for i, example := range examples { - toml := fmt.Sprintf(`Time = %s`, example.in) - - t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { - type dateStruct struct { - Time LocalTime - } - - var obj dateStruct - - err := Unmarshal([]byte(toml), &obj) - - if err != nil { - t.Fatal(err) - } - - if obj.Time != example.out { - t.Errorf("expected '%s', got '%s'", example.out, obj.Time) - } - }) - } -} - -func TestMarshalLocalTime(t *testing.T) { - type timeStruct struct { - Time LocalTime - } - - examples := []struct { - name string - in LocalTime - out string - }{ - { - name: "normal", - out: "Time = 07:32:00\n", - in: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }}, - { - name: "with nanoseconds", - out: "Time = 00:32:00.999999000\n", - in: LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - } - - for i, example := range examples { - t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { - obj := timeStruct{ - Time: example.in, - } - b, err := Marshal(obj) - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := string(b) - - if got != example.out { - t.Errorf("expected '%s', got '%s'", example.out, got) - } - }) - } -} - -// test case for issue #339 -func TestUnmarshalSameInnerField(t *testing.T) { - type InterStruct2 struct { - Test string - Name string - Age int - } - type Inter2 struct { - Name string - Age int - InterStruct2 InterStruct2 - } - type Server struct { - Name string `toml:"name"` - Inter2 Inter2 `toml:"inter2"` - } - - var server Server - - if err := Unmarshal([]byte(`name = "123" -[inter2] -name = "inter2" -age = 222`), &server); err == nil { - expected := Server{ - Name: "123", - Inter2: Inter2{ - Name: "inter2", - Age: 222, - }, - } - if !reflect.DeepEqual(server, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, server) - } - } else { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestMarshalInterface(t *testing.T) { - type InnerStruct struct { - InnerField string - } - - type OuterStruct struct { - PrimitiveField interface{} - ArrayField interface{} - StructArrayField interface{} - MapField map[string]interface{} - StructField interface{} - PointerField interface{} - NilField interface{} - InterfacePointerField *interface{} - } - - expected := []byte(`ArrayField = [1, 2, 3] -InterfacePointerField = "hello world" -PrimitiveField = "string" - -[MapField] - key1 = "value1" - key2 = false - - [MapField.key3] - InnerField = "value3" - -[PointerField] - InnerField = "yyy" - -[[StructArrayField]] - InnerField = "s1" - -[[StructArrayField]] - InnerField = "s2" - -[StructField] - InnerField = "xxx" -`) - - var h interface{} = "hello world" - if result, err := Marshal(OuterStruct{ - "string", - []int{1, 2, 3}, - []InnerStruct{{"s1"}, {"s2"}}, - map[string]interface{}{ - "key1": "value1", - "key2": false, - "key3": InnerStruct{"value3"}, - "nil value": nil, - }, - InnerStruct{ - "xxx", - }, - &InnerStruct{ - "yyy", - }, - nil, - &h, - }); err == nil { - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalToNilInterface(t *testing.T) { - toml := []byte(` -PrimitiveField = "Hello" -ArrayField = [1,2,3] -InterfacePointerField = "World" - -[StructField] -Field1 = 123 -Field2 = "Field2" - -[MapField] -MapField1 = [4,5,6] -MapField2 = {A = "A"} -MapField3 = false - -[[StructArrayField]] -Name = "Allen" -Age = 20 - -[[StructArrayField]] -Name = "Jack" -Age = 23 -`) - - type OuterStruct struct { - PrimitiveField interface{} - ArrayField interface{} - StructArrayField interface{} - MapField map[string]interface{} - StructField interface{} - NilField interface{} - InterfacePointerField *interface{} - } - - var s interface{} = "World" - expected := OuterStruct{ - PrimitiveField: "Hello", - ArrayField: []interface{}{int64(1), int64(2), int64(3)}, - StructField: map[string]interface{}{ - "Field1": int64(123), - "Field2": "Field2", - }, - MapField: map[string]interface{}{ - "MapField1": []interface{}{int64(4), int64(5), int64(6)}, - "MapField2": map[string]interface{}{ - "A": "A", - }, - "MapField3": false, - }, - NilField: nil, - InterfacePointerField: &s, - StructArrayField: []map[string]interface{}{ - { - "Name": "Allen", - "Age": int64(20), - }, - { - "Name": "Jack", - "Age": int64(23), - }, - }, - } - actual := OuterStruct{} - if err := Unmarshal(toml, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalToNonNilInterface(t *testing.T) { - toml := []byte(` -PrimitiveField = "Allen" -ArrayField = [1,2,3] - -[StructField] -InnerField = "After1" - -[PointerField] -InnerField = "After2" - -[InterfacePointerField] -InnerField = "After" - -[MapField] -MapField1 = [4,5,6] -MapField2 = {A = "A"} -MapField3 = false - -[[StructArrayField]] -InnerField = "After3" - -[[StructArrayField]] -InnerField = "After4" -`) - type InnerStruct struct { - InnerField interface{} - } - - type OuterStruct struct { - PrimitiveField interface{} - ArrayField interface{} - StructArrayField interface{} - MapField map[string]interface{} - StructField interface{} - PointerField interface{} - NilField interface{} - InterfacePointerField *interface{} - } - - var s interface{} = InnerStruct{"After"} - expected := OuterStruct{ - PrimitiveField: "Allen", - ArrayField: []int{1, 2, 3}, - StructField: InnerStruct{InnerField: "After1"}, - MapField: map[string]interface{}{ - "MapField1": []interface{}{int64(4), int64(5), int64(6)}, - "MapField2": map[string]interface{}{ - "A": "A", - }, - "MapField3": false, - }, - PointerField: &InnerStruct{InnerField: "After2"}, - NilField: nil, - InterfacePointerField: &s, - StructArrayField: []InnerStruct{ - {InnerField: "After3"}, - {InnerField: "After4"}, - }, - } - actual := OuterStruct{ - PrimitiveField: "aaa", - ArrayField: []int{100, 200, 300, 400}, - StructField: InnerStruct{InnerField: "Before1"}, - MapField: map[string]interface{}{ - "MapField1": []int{4, 5, 6}, - "MapField2": map[string]string{ - "B": "BBB", - }, - "MapField3": true, - }, - PointerField: &InnerStruct{InnerField: "Before2"}, - NilField: nil, - InterfacePointerField: &s, - StructArrayField: []InnerStruct{ - {InnerField: "Before3"}, - {InnerField: "Before4"}, - }, - } - if err := Unmarshal(toml, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalEmbedTree(t *testing.T) { - toml := []byte(` -OuterField1 = "Out" -OuterField2 = 1024 - -[TreeField] -InnerField1 = "In" -InnerField2 = 2048 - - [TreeField.EmbedStruct] - EmbedField = "Embed" - -`) - type InnerStruct struct { - InnerField1 string - InnerField2 int - EmbedStruct struct { - EmbedField string - } - } - - type OuterStruct struct { - OuterField1 string - OuterField2 int - TreeField *Tree - } - - out := OuterStruct{} - actual := InnerStruct{} - expected := InnerStruct{ - "In", - 2048, - struct { - EmbedField string - }{ - EmbedField: "Embed", - }, - } - if err := Unmarshal(toml, &out); err != nil { - t.Fatal(err) - } - if err := out.TreeField.Unmarshal(&actual); err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } -} - -func TestMarshalNil(t *testing.T) { - if _, err := Marshal(nil); err == nil { - t.Errorf("Expected err from nil marshal") - } - if _, err := Marshal((*struct{})(nil)); err == nil { - t.Errorf("Expected err from nil marshal") - } -} - -func TestUnmarshalNil(t *testing.T) { - if err := Unmarshal([]byte(`whatever = "whatever"`), nil); err == nil { - t.Errorf("Expected err from nil marshal") - } - if err := Unmarshal([]byte(`whatever = "whatever"`), (*struct{})(nil)); err == nil { - t.Errorf("Expected err from nil marshal") - } -} - -var sliceTomlDemo = []byte(`str_slice = ["Howdy","Hey There"] -str_slice_ptr= ["Howdy","Hey There"] -int_slice=[1,2] -int_slice_ptr=[1,2] -[[struct_slice]] -String2="1" -[[struct_slice]] -String2="2" -[[struct_slice_ptr]] -String2="1" -[[struct_slice_ptr]] -String2="2" -`) - -type sliceStruct struct { - Slice []string ` toml:"str_slice" ` - SlicePtr *[]string ` toml:"str_slice_ptr" ` - IntSlice []int ` toml:"int_slice" ` - IntSlicePtr *[]int ` toml:"int_slice_ptr" ` - StructSlice []basicMarshalTestSubStruct ` toml:"struct_slice" ` - StructSlicePtr *[]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` -} - -type arrayStruct struct { - Slice [4]string ` toml:"str_slice" ` - SlicePtr *[4]string ` toml:"str_slice_ptr" ` - IntSlice [4]int ` toml:"int_slice" ` - IntSlicePtr *[4]int ` toml:"int_slice_ptr" ` - StructSlice [4]basicMarshalTestSubStruct ` toml:"struct_slice" ` - StructSlicePtr *[4]basicMarshalTestSubStruct ` toml:"struct_slice_ptr" ` -} - -type arrayTooSmallStruct struct { - Slice [1]string ` toml:"str_slice" ` - StructSlice [1]basicMarshalTestSubStruct ` toml:"struct_slice" ` -} - -func TestUnmarshalSlice(t *testing.T) { - tree, _ := LoadBytes(sliceTomlDemo) - tree, _ = TreeFromMap(tree.ToMap()) - - var actual sliceStruct - err := tree.Unmarshal(&actual) - if err != nil { - t.Error("shound not err", err) - } - expected := sliceStruct{ - Slice: []string{"Howdy", "Hey There"}, - SlicePtr: &[]string{"Howdy", "Hey There"}, - IntSlice: []int{1, 2}, - IntSlicePtr: &[]int{1, 2}, - StructSlice: []basicMarshalTestSubStruct{{"1"}, {"2"}}, - StructSlicePtr: &[]basicMarshalTestSubStruct{{"1"}, {"2"}}, - } - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual) - } - -} - -func TestUnmarshalSliceFail(t *testing.T) { - tree, _ := TreeFromMap(map[string]interface{}{ - "str_slice": []int{1, 2}, - }) - - var actual sliceStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(0, 0): Can't convert 1(int64) to string" { - t.Error("expect err:(0, 0): Can't convert 1(int64) to string but got ", err) - } -} - -func TestUnmarshalSliceFail2(t *testing.T) { - tree, _ := Load(`str_slice=[1,2]`) - - var actual sliceStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(1, 1): Can't convert 1(int64) to string" { - t.Error("expect err:(1, 1): Can't convert 1(int64) to string but got ", err) - } - -} - -func TestMarshalMixedTypeArray(t *testing.T) { - type InnerStruct struct { - IntField int - StrField string - } - - type TestStruct struct { - ArrayField []interface{} - } - - expected := []byte(`ArrayField = [3.14, 100, true, "hello world", { IntField = 100, StrField = "inner1" }, [{ IntField = 200, StrField = "inner2" }, { IntField = 300, StrField = "inner3" }]] -`) - - if result, err := Marshal(TestStruct{ - ArrayField: []interface{}{ - 3.14, - 100, - true, - "hello world", - InnerStruct{ - IntField: 100, - StrField: "inner1", - }, - []InnerStruct{ - {IntField: 200, StrField: "inner2"}, - {IntField: 300, StrField: "inner3"}, - }, - }, - }); err == nil { - if !bytes.Equal(result, expected) { - t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalMixedTypeArray(t *testing.T) { - type TestStruct struct { - ArrayField []interface{} - } - - toml := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] -`) - - actual := TestStruct{} - expected := TestStruct{ - ArrayField: []interface{}{ - 3.14, - int64(100), - true, - "hello world", - map[string]interface{}{ - "Field": "inner1", - }, - []map[string]interface{}{ - {"Field": "inner2"}, - {"Field": "inner3"}, - }, - }, - } - - if err := Unmarshal(toml, &actual); err == nil { - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual) - } - } else { - t.Fatal(err) - } -} - -func TestUnmarshalArray(t *testing.T) { - var tree *Tree - var err error - - tree, _ = LoadBytes(sliceTomlDemo) - var actual1 arrayStruct - err = tree.Unmarshal(&actual1) - if err != nil { - t.Error("shound not err", err) - } - - tree, _ = TreeFromMap(tree.ToMap()) - var actual2 arrayStruct - err = tree.Unmarshal(&actual2) - if err != nil { - t.Error("shound not err", err) - } - - expected := arrayStruct{ - Slice: [4]string{"Howdy", "Hey There"}, - SlicePtr: &[4]string{"Howdy", "Hey There"}, - IntSlice: [4]int{1, 2}, - IntSlicePtr: &[4]int{1, 2}, - StructSlice: [4]basicMarshalTestSubStruct{{"1"}, {"2"}}, - StructSlicePtr: &[4]basicMarshalTestSubStruct{{"1"}, {"2"}}, - } - if !reflect.DeepEqual(actual1, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual1) - } - if !reflect.DeepEqual(actual2, expected) { - t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual2) - } -} - -func TestUnmarshalArrayFail(t *testing.T) { - tree, _ := TreeFromMap(map[string]interface{}{ - "str_slice": []string{"Howdy", "Hey There"}, - }) - - var actual arrayTooSmallStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(0, 0): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } -} - -func TestUnmarshalArrayFail2(t *testing.T) { - tree, _ := Load(`str_slice=["Howdy","Hey There"]`) - - var actual arrayTooSmallStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(1, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } -} - -func TestUnmarshalArrayFail3(t *testing.T) { - tree, _ := Load(`[[struct_slice]] -String2="1" -[[struct_slice]] -String2="2"`) - - var actual arrayTooSmallStruct - err := tree.Unmarshal(&actual) - if err.Error() != "(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1)" { - t.Error("expect err:(3, 1): unmarshal: TOML array length (2) exceeds destination array length (1) but got ", err) - } -} - -func TestDecoderStrict(t *testing.T) { - input := ` -[decoded] - key = "" - -[undecoded] - key = "" - - [undecoded.inner] - key = "" - - [[undecoded.array]] - key = "" - - [[undecoded.array]] - key = "" - -` - var doc struct { - Decoded struct { - Key string - } - } - - expected := `undecoded keys: ["undecoded.array.0.key" "undecoded.array.1.key" "undecoded.inner.key" "undecoded.key"]` - - err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - if err == nil { - t.Error("expected error, got none") - } else if err.Error() != expected { - t.Errorf("expect err: %s, got: %s", expected, err.Error()) - } - - if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&doc); err != nil { - t.Errorf("unexpected err: %s", err) - } - - var m map[string]interface{} - if err := NewDecoder(bytes.NewReader([]byte(input))).Decode(&m); err != nil { - t.Errorf("unexpected err: %s", err) - } -} - -func TestDecoderStrictValid(t *testing.T) { - input := ` -[decoded] - key = "" -` - var doc struct { - Decoded struct { - Key string - } - } - - err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - if err != nil { - t.Fatal("unexpected error:", err) - } -} - -type docUnmarshalTOML struct { - Decoded struct { - Key string - } -} - -func (d *docUnmarshalTOML) UnmarshalTOML(i interface{}) error { - if iMap, ok := i.(map[string]interface{}); !ok { - return fmt.Errorf("type assertion error: wants %T, have %T", map[string]interface{}{}, i) - } else if key, ok := iMap["key"]; !ok { - return fmt.Errorf("key '%s' not in map", "key") - } else if keyString, ok := key.(string); !ok { - return fmt.Errorf("type assertion error: wants %T, have %T", "", key) - } else { - d.Decoded.Key = keyString - } - return nil -} - -func TestDecoderStrictCustomUnmarshal(t *testing.T) { - input := `key = "ok"` - var doc docUnmarshalTOML - err := NewDecoder(bytes.NewReader([]byte(input))).Strict(true).Decode(&doc) - if err != nil { - t.Fatal("unexpected error:", err) - } - if doc.Decoded.Key != "ok" { - t.Errorf("Bad unmarshal: expected ok, got %v", doc.Decoded.Key) - } -} - -type parent struct { - Doc docUnmarshalTOML - DocPointer *docUnmarshalTOML -} - -func TestCustomUnmarshal(t *testing.T) { - input := ` -[Doc] - key = "ok1" -[DocPointer] - key = "ok2" -` - - var d parent - if err := Unmarshal([]byte(input), &d); err != nil { - t.Fatalf("unexpected err: %s", err.Error()) - } - if d.Doc.Decoded.Key != "ok1" { - t.Errorf("Bad unmarshal: expected ok, got %v", d.Doc.Decoded.Key) - } - if d.DocPointer.Decoded.Key != "ok2" { - t.Errorf("Bad unmarshal: expected ok, got %v", d.DocPointer.Decoded.Key) - } -} - -func TestCustomUnmarshalError(t *testing.T) { - input := ` -[Doc] - key = 1 -[DocPointer] - key = "ok2" -` - - expected := "(2, 1): unmarshal toml: type assertion error: wants string, have int64" - - var d parent - err := Unmarshal([]byte(input), &d) - if err == nil { - t.Error("expected error, got none") - } else if err.Error() != expected { - t.Errorf("expect err: %s, got: %s", expected, err.Error()) - } -} - -type intWrapper struct { - Value int -} - -func (w *intWrapper) UnmarshalText(text []byte) error { - var err error - if w.Value, err = strconv.Atoi(string(text)); err == nil { - return nil - } - if b, err := strconv.ParseBool(string(text)); err == nil { - if b { - w.Value = 1 - } - return nil - } - if f, err := strconv.ParseFloat(string(text), 32); err == nil { - w.Value = int(f) - return nil - } - return fmt.Errorf("unsupported: %s", text) -} - -func TestTextUnmarshal(t *testing.T) { - var doc struct { - UnixTime intWrapper - Version *intWrapper - - Bool intWrapper - Int intWrapper - Float intWrapper - } - - input := ` -UnixTime = "12" -Version = "42" -Bool = true -Int = 21 -Float = 2.0 -` - - if err := Unmarshal([]byte(input), &doc); err != nil { - t.Fatalf("unexpected err: %s", err.Error()) - } - if doc.UnixTime.Value != 12 { - t.Fatalf("expected UnixTime: 12 got: %d", doc.UnixTime.Value) - } - if doc.Version.Value != 42 { - t.Fatalf("expected Version: 42 got: %d", doc.Version.Value) - } - if doc.Bool.Value != 1 { - t.Fatalf("expected Bool: 1 got: %d", doc.Bool.Value) - } - if doc.Int.Value != 21 { - t.Fatalf("expected Int: 21 got: %d", doc.Int.Value) - } - if doc.Float.Value != 2 { - t.Fatalf("expected Float: 2 got: %d", doc.Float.Value) - } -} - -func TestTextUnmarshalError(t *testing.T) { - var doc struct { - Failer intWrapper - } - - input := `Failer = "hello"` - if err := Unmarshal([]byte(input), &doc); err == nil { - t.Fatalf("expected err, got none") - } -} - -// issue406 -func TestPreserveNotEmptyField(t *testing.T) { - toml := []byte(`Field1 = "ccc"`) - type Inner struct { - InnerField1 string - InnerField2 int - } - type TestStruct struct { - Field1 string - Field2 int - Field3 Inner - } - - actual := TestStruct{ - "aaa", - 100, - Inner{ - "bbb", - 200, - }, - } - - expected := TestStruct{ - "ccc", - 100, - Inner{ - "bbb", - 200, - }, - } - - err := Unmarshal(toml, &actual) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } -} - -// github issue 432 -func TestUnmarshalEmptyInterface(t *testing.T) { - doc := []byte(`User = "pelletier"`) - - var v interface{} - - err := Unmarshal(doc, &v) - if err != nil { - t.Fatal(err) - } - - x, ok := v.(map[string]interface{}) - if !ok { - t.Fatal(err) - } - - if x["User"] != "pelletier" { - t.Fatalf("expected User=pelletier, but got %v", x) - } -} - -func TestUnmarshalEmptyInterfaceDeep(t *testing.T) { - doc := []byte(` -User = "pelletier" -Age = 99 - -[foo] -bar = 42 -`) - - var v interface{} - - err := Unmarshal(doc, &v) - if err != nil { - t.Fatal(err) - } - - x, ok := v.(map[string]interface{}) - if !ok { - t.Fatal(err) - } - - expected := map[string]interface{}{ - "User": "pelletier", - "Age": 99, - "foo": map[string]interface{}{ - "bar": 42, - }, - } - - reflect.DeepEqual(x, expected) -} - -type Config struct { - Key string `toml:"key"` - Obj Custom `toml:"obj"` -} - -type Custom struct { - v string -} - -func (c *Custom) UnmarshalTOML(v interface{}) error { - c.v = "called" - return nil -} - -func TestGithubIssue431(t *testing.T) { - doc := `key = "value"` - tree, err := LoadBytes([]byte(doc)) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - var c Config - if err := tree.Unmarshal(&c); err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if c.Key != "value" { - t.Errorf("expected c.Key='value', not '%s'", c.Key) - } - - if c.Obj.v == "called" { - t.Errorf("UnmarshalTOML should not have been called") - } -} - -type durationString struct { - time.Duration -} - -func (d *durationString) UnmarshalTOML(v interface{}) error { - d.Duration = 10 * time.Second - return nil -} - -type config437Error struct { -} - -func (e *config437Error) UnmarshalTOML(v interface{}) error { - return errors.New("expected") -} - -type config437 struct { - HTTP struct { - PingTimeout durationString `toml:"PingTimeout"` - ErrorField config437Error - } `toml:"HTTP"` -} - -func TestGithubIssue437(t *testing.T) { - src := ` -[HTTP] -PingTimeout = "32m" -` - cfg := &config437{} - cfg.HTTP.PingTimeout = durationString{time.Second} - - r := strings.NewReader(src) - err := NewDecoder(r).Decode(cfg) - if err != nil { - t.Fatalf("unexpected errors %s", err) - } - expected := durationString{10 * time.Second} - if cfg.HTTP.PingTimeout != expected { - t.Fatalf("expected '%s', got '%s'", expected, cfg.HTTP.PingTimeout) - } -} - -func TestLeafUnmarshalerError(t *testing.T) { - src := ` -[HTTP] -ErrorField = "foo" -` - cfg := &config437{} - - r := strings.NewReader(src) - err := NewDecoder(r).Decode(cfg) - if err == nil { - t.Fatalf("error was expected") - } -} diff --git a/marshal_test.toml b/marshal_test.toml deleted file mode 100644 index ba5e110..0000000 --- a/marshal_test.toml +++ /dev/null @@ -1,39 +0,0 @@ -title = "TOML Marshal Testing" - -[basic] - bool = true - date = 1979-05-27T07:32:00Z - float = 123.4 - float64 = 123.456782132399 - int = 5000 - string = "Bite me" - uint = 5001 - -[basic_lists] - bools = [true,false,true] - dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] - floats = [12.3,45.6,78.9] - ints = [8001,8001,8002] - strings = ["One","Two","Three"] - uints = [5002,5003] - -[basic_map] - one = "one" - two = "two" - -[subdoc] - - [subdoc.first] - name = "First" - - [subdoc.second] - name = "Second" - -[[subdoclist]] - name = "List.First" - -[[subdoclist]] - name = "List.Second" - -[[subdocptrs]] - name = "Second" diff --git a/parser.go b/parser.go deleted file mode 100644 index f5e1a44..0000000 --- a/parser.go +++ /dev/null @@ -1,508 +0,0 @@ -// TOML Parser. - -package toml - -import ( - "errors" - "fmt" - "math" - "reflect" - "strconv" - "strings" - "time" -) - -type tomlParser struct { - flowIdx int - flow []token - tree *Tree - currentTable []string - seenTableKeys []string -} - -type tomlParserStateFn func() tomlParserStateFn - -// Formats and panics an error message based on a token -func (p *tomlParser) raiseError(tok *token, msg string, args ...interface{}) { - panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) -} - -func (p *tomlParser) run() { - for state := p.parseStart; state != nil; { - state = state() - } -} - -func (p *tomlParser) peek() *token { - if p.flowIdx >= len(p.flow) { - return nil - } - return &p.flow[p.flowIdx] -} - -func (p *tomlParser) assume(typ tokenType) { - tok := p.getToken() - if tok == nil { - p.raiseError(tok, "was expecting token %s, but token stream is empty", tok) - } - if tok.typ != typ { - p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok) - } -} - -func (p *tomlParser) getToken() *token { - tok := p.peek() - if tok == nil { - return nil - } - p.flowIdx++ - return tok -} - -func (p *tomlParser) parseStart() tomlParserStateFn { - tok := p.peek() - - // end of stream, parsing is finished - if tok == nil { - return nil - } - - switch tok.typ { - case tokenDoubleLeftBracket: - return p.parseGroupArray - case tokenLeftBracket: - return p.parseGroup - case tokenKey: - return p.parseAssign - case tokenEOF: - return nil - case tokenError: - p.raiseError(tok, "parsing error: %s", tok.String()) - default: - p.raiseError(tok, "unexpected token %s", tok.typ) - } - return nil -} - -func (p *tomlParser) parseGroupArray() tomlParserStateFn { - startToken := p.getToken() // discard the [[ - key := p.getToken() - if key.typ != tokenKeyGroupArray { - p.raiseError(key, "unexpected token %s, was expecting a table array key", key) - } - - // get or create table array element at the indicated part in the path - keys, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid table array key: %s", err) - } - p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries - destTree := p.tree.GetPath(keys) - var array []*Tree - if destTree == nil { - array = make([]*Tree, 0) - } else if target, ok := destTree.([]*Tree); ok && target != nil { - array = destTree.([]*Tree) - } else { - p.raiseError(key, "key %s is already assigned and not of type table array", key) - } - p.currentTable = keys - - // add a new tree to the end of the table array - newTree := newTree() - newTree.position = startToken.Position - array = append(array, newTree) - p.tree.SetPath(p.currentTable, array) - - // remove all keys that were children of this table array - prefix := key.val + "." - found := false - for ii := 0; ii < len(p.seenTableKeys); { - tableKey := p.seenTableKeys[ii] - if strings.HasPrefix(tableKey, prefix) { - p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...) - } else { - found = (tableKey == key.val) - ii++ - } - } - - // keep this key name from use by other kinds of assignments - if !found { - p.seenTableKeys = append(p.seenTableKeys, key.val) - } - - // move to next parser state - p.assume(tokenDoubleRightBracket) - return p.parseStart -} - -func (p *tomlParser) parseGroup() tomlParserStateFn { - startToken := p.getToken() // discard the [ - key := p.getToken() - if key.typ != tokenKeyGroup { - p.raiseError(key, "unexpected token %s, was expecting a table key", key) - } - for _, item := range p.seenTableKeys { - if item == key.val { - p.raiseError(key, "duplicated tables") - } - } - - p.seenTableKeys = append(p.seenTableKeys, key.val) - keys, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid table array key: %s", err) - } - if err := p.tree.createSubTree(keys, startToken.Position); err != nil { - p.raiseError(key, "%s", err) - } - destTree := p.tree.GetPath(keys) - if target, ok := destTree.(*Tree); ok && target != nil && target.inline { - p.raiseError(key, "could not re-define exist inline table or its sub-table : %s", - strings.Join(keys, ".")) - } - p.assume(tokenRightBracket) - p.currentTable = keys - return p.parseStart -} - -func (p *tomlParser) parseAssign() tomlParserStateFn { - key := p.getToken() - p.assume(tokenEqual) - - parsedKey, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid key: %s", err.Error()) - } - - value := p.parseRvalue() - var tableKey []string - if len(p.currentTable) > 0 { - tableKey = p.currentTable - } else { - tableKey = []string{} - } - - prefixKey := parsedKey[0 : len(parsedKey)-1] - tableKey = append(tableKey, prefixKey...) - - // find the table to assign, looking out for arrays of tables - var targetNode *Tree - switch node := p.tree.GetPath(tableKey).(type) { - case []*Tree: - targetNode = node[len(node)-1] - case *Tree: - targetNode = node - case nil: - // create intermediate - if err := p.tree.createSubTree(tableKey, key.Position); err != nil { - p.raiseError(key, "could not create intermediate group: %s", err) - } - targetNode = p.tree.GetPath(tableKey).(*Tree) - default: - p.raiseError(key, "Unknown table type for path: %s", - strings.Join(tableKey, ".")) - } - - if targetNode.inline { - p.raiseError(key, "could not add key or sub-table to exist inline table or its sub-table : %s", - strings.Join(tableKey, ".")) - } - - // assign value to the found table - keyVal := parsedKey[len(parsedKey)-1] - localKey := []string{keyVal} - finalKey := append(tableKey, keyVal) - if targetNode.GetPath(localKey) != nil { - p.raiseError(key, "The following key was defined twice: %s", - strings.Join(finalKey, ".")) - } - var toInsert interface{} - - switch value.(type) { - case *Tree, []*Tree: - toInsert = value - default: - toInsert = &tomlValue{value: value, position: key.Position} - } - targetNode.values[keyVal] = toInsert - return p.parseStart -} - -var errInvalidUnderscore = errors.New("invalid use of _ in number") - -func numberContainsInvalidUnderscore(value string) error { - // For large numbers, you may use underscores between digits to enhance - // readability. Each underscore must be surrounded by at least one digit on - // each side. - - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscore - } - } - hasBefore = isDigit(r) - } - return nil -} - -var errInvalidUnderscoreHex = errors.New("invalid use of _ in hex number") - -func hexNumberContainsInvalidUnderscore(value string) error { - hasBefore := false - for idx, r := range value { - if r == '_' { - if !hasBefore || idx+1 >= len(value) { - // can't end with an underscore - return errInvalidUnderscoreHex - } - } - hasBefore = isHexDigit(r) - } - return nil -} - -func cleanupNumberToken(value string) string { - cleanedVal := strings.Replace(value, "_", "", -1) - return cleanedVal -} - -func (p *tomlParser) parseRvalue() interface{} { - tok := p.getToken() - if tok == nil || tok.typ == tokenEOF { - p.raiseError(tok, "expecting a value") - } - - switch tok.typ { - case tokenString: - return tok.val - case tokenTrue: - return true - case tokenFalse: - return false - case tokenInf: - if tok.val[0] == '-' { - return math.Inf(-1) - } - return math.Inf(1) - case tokenNan: - return math.NaN() - case tokenInteger: - cleanedVal := cleanupNumberToken(tok.val) - 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 tokenLocalTime: - val, err := ParseLocalTime(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLocalDate: - // a local date may be followed by: - // * nothing: this is a local date - // * a local time: this is a local date-time - - next := p.peek() - if next == nil || next.typ != tokenLocalTime { - val, err := ParseLocalDate(tok.val) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - } - - localDate := tok - localTime := p.getToken() - - next = p.peek() - if next == nil || next.typ != tokenTimeOffset { - v := localDate.val + "T" + localTime.val - val, err := ParseLocalDateTime(v) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - } - - offset := p.getToken() - - layout := time.RFC3339Nano - v := localDate.val + "T" + localTime.val + offset.val - val, err := time.ParseInLocation(layout, v, time.UTC) - if err != nil { - p.raiseError(tok, "%s", err) - } - return val - case tokenLeftBracket: - return p.parseArray() - case tokenLeftCurlyBrace: - return p.parseInlineTable() - case tokenEqual: - p.raiseError(tok, "cannot have multiple equals for the same key") - case tokenError: - p.raiseError(tok, "%s", tok) - default: - panic(fmt.Errorf("unhandled token: %v", tok)) - } - - return nil -} - -func tokenIsComma(t *token) bool { - return t != nil && t.typ == tokenComma -} - -func (p *tomlParser) parseInlineTable() *Tree { - tree := newTree() - var previous *token -Loop: - for { - follow := p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated inline table") - } - switch follow.typ { - case tokenRightCurlyBrace: - p.getToken() - break Loop - case tokenKey, tokenInteger, tokenString: - if !tokenIsComma(previous) && previous != nil { - p.raiseError(follow, "comma expected between fields in inline table") - } - key := p.getToken() - p.assume(tokenEqual) - - parsedKey, err := parseKey(key.val) - if err != nil { - p.raiseError(key, "invalid key: %s", err) - } - - value := p.parseRvalue() - tree.SetPath(parsedKey, value) - case tokenComma: - if tokenIsComma(previous) { - p.raiseError(follow, "need field between two commas in inline table") - } - p.getToken() - default: - p.raiseError(follow, "unexpected token type in inline table: %s", follow.String()) - } - previous = follow - } - if tokenIsComma(previous) { - p.raiseError(previous, "trailing comma at the end of inline table") - } - tree.inline = true - return tree -} - -func (p *tomlParser) parseArray() interface{} { - var array []interface{} - arrayType := reflect.TypeOf(newTree()) - for { - follow := p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated array") - } - if follow.typ == tokenRightBracket { - p.getToken() - break - } - val := p.parseRvalue() - if reflect.TypeOf(val) != arrayType { - arrayType = nil - } - array = append(array, val) - follow = p.peek() - if follow == nil || follow.typ == tokenEOF { - p.raiseError(follow, "unterminated array") - } - if follow.typ != tokenRightBracket && follow.typ != tokenComma { - p.raiseError(follow, "missing comma") - } - if follow.typ == tokenComma { - p.getToken() - } - } - - // if the array is a mixed-type array or its length is 0, - // don't convert it to a table array - if len(array) <= 0 { - arrayType = nil - } - // An array of Trees is actually an array of inline - // tables, which is a shorthand for a table array. If the - // array was not converted from []interface{} to []*Tree, - // the two notations would not be equivalent. - if arrayType == reflect.TypeOf(newTree()) { - tomlArray := make([]*Tree, len(array)) - for i, v := range array { - tomlArray[i] = v.(*Tree) - } - return tomlArray - } - return array -} - -func parseToml(flow []token) *Tree { - result := newTree() - result.position = Position{1, 1} - parser := &tomlParser{ - flowIdx: 0, - flow: flow, - tree: result, - currentTable: make([]string, 0), - seenTableKeys: make([]string, 0), - } - parser.run() - return result -} diff --git a/parser_test.go b/parser_test.go deleted file mode 100644 index 60fa375..0000000 --- a/parser_test.go +++ /dev/null @@ -1,1166 +0,0 @@ -package 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 TestLocalDateTime(t *testing.T) { - tree, err := Load("a = 1979-05-27T07:32:00") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }, - }) -} - -func TestLocalDateTimeNano(t *testing.T) { - tree, err := Load("a = 1979-05-27T07:32:00.999999") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalDateTime{ - Date: LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - Time: LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }, - }) -} - -func TestLocalDate(t *testing.T) { - tree, err := Load("a = 1979-05-27") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalDate{ - Year: 1979, - Month: 5, - Day: 27, - }, - }) -} - -func TestLocalDateError(t *testing.T) { - _, err := Load("a = 2020-09-31") - if err == nil { - t.Fatalf("should error") - } -} - -func TestLocalTimeError(t *testing.T) { - _, err := Load("a = 07:99:00") - if err == nil { - t.Fatalf("should error") - } -} - -func TestLocalDateTimeError(t *testing.T) { - _, err := Load("a = 2020-09-31T07:99:00") - if err == nil { - t.Fatalf("should error") - } -} - -func TestDateTimeOffsetError(t *testing.T) { - _, err := Load("a = 2020-09-31T07:99:00Z") - if err == nil { - t.Fatalf("should error") - } -} - -func TestLocalTime(t *testing.T) { - tree, err := Load("a = 07:32:00") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalTime{ - Hour: 7, - Minute: 32, - Second: 0, - Nanosecond: 0, - }, - }) -} - -func TestLocalTimeNano(t *testing.T) { - tree, err := Load("a = 00:32:00.999999") - assertTree(t, tree, err, map[string]interface{}{ - "a": LocalTime{ - Hour: 0, - Minute: 32, - Second: 0, - Nanosecond: 999999000, - }, - }) -} - -func TestSimpleString(t *testing.T) { - tree, err := Load("a = \"hello world\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "hello world", - }) -} - -func TestSpaceKey(t *testing.T) { - tree, err := Load("\"a b\" = \"hello world\"") - assertTree(t, tree, err, map[string]interface{}{ - "a b": "hello world", - }) -} - -func TestDoubleQuotedKey(t *testing.T) { - tree, err := Load(` - "key" = "a" - "\t" = "b" - "\U0001F914" = "c" - "\u2764" = "d" - `) - assertTree(t, tree, err, map[string]interface{}{ - "key": "a", - "\t": "b", - "\U0001F914": "c", - "\u2764": "d", - }) -} - -func TestSingleQuotedKey(t *testing.T) { - tree, err := Load(` - 'key' = "a" - '\t' = "b" - '\U0001F914' = "c" - '\u2764' = "d" - `) - assertTree(t, tree, err, map[string]interface{}{ - `key`: "a", - `\t`: "b", - `\U0001F914`: "c", - `\u2764`: "d", - }) -} - -func TestStringEscapables(t *testing.T) { - tree, err := Load("a = \"a \\n b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \n b", - }) - - tree, err = Load("a = \"a \\t b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \t b", - }) - - tree, err = Load("a = \"a \\r b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \r b", - }) - - tree, err = Load("a = \"a \\\\ b\"") - assertTree(t, tree, err, map[string]interface{}{ - "a": "a \\ b", - }) -} - -func TestEmptyQuotedString(t *testing.T) { - tree, err := Load(`[""] -"" = 1`) - assertTree(t, tree, err, map[string]interface{}{ - "": map[string]interface{}{ - "": int64(1), - }, - }) -} - -func TestBools(t *testing.T) { - tree, err := Load("a = true\nb = false") - assertTree(t, tree, err, map[string]interface{}{ - "a": true, - "b": false, - }) -} - -func TestNestedKeys(t *testing.T) { - tree, err := Load("[a.b.c]\nd = 42") - assertTree(t, tree, err, map[string]interface{}{ - "a": map[string]interface{}{ - "b": map[string]interface{}{ - "c": map[string]interface{}{ - "d": int64(42), - }, - }, - }, - }) -} - -func TestNestedQuotedUnicodeKeys(t *testing.T) { - tree, err := Load("[ j . \"ʞ\" . l ]\nd = 42") - assertTree(t, tree, err, map[string]interface{}{ - "j": map[string]interface{}{ - "ʞ": map[string]interface{}{ - "l": map[string]interface{}{ - "d": int64(42), - }, - }, - }, - }) - - tree, err = Load("[ g . h . i ]\nd = 42") - assertTree(t, tree, err, map[string]interface{}{ - "g": map[string]interface{}{ - "h": map[string]interface{}{ - "i": map[string]interface{}{ - "d": int64(42), - }, - }, - }, - }) - - tree, err = Load("[ d.e.f ]\nk = 42") - assertTree(t, tree, err, map[string]interface{}{ - "d": map[string]interface{}{ - "e": map[string]interface{}{ - "f": map[string]interface{}{ - "k": int64(42), - }, - }, - }, - }) -} - -func TestArrayOne(t *testing.T) { - tree, err := Load("a = [1]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1)}, - }) -} - -func TestArrayZero(t *testing.T) { - tree, err := Load("a = []") - assertTree(t, tree, err, map[string]interface{}{ - "a": []interface{}{}, - }) -} - -func TestArraySimple(t *testing.T) { - tree, err := Load("a = [42, 21, 10]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(42), int64(21), int64(10)}, - }) - - tree, _ = Load("a = [42, 21, 10,]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(42), int64(21), int64(10)}, - }) -} - -func TestArrayMultiline(t *testing.T) { - tree, err := Load("a = [42,\n21, 10,]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(42), int64(21), int64(10)}, - }) -} - -func TestArrayNested(t *testing.T) { - tree, err := Load("a = [[42, 21], [10]]") - assertTree(t, tree, err, map[string]interface{}{ - "a": [][]int64{{int64(42), int64(21)}, {int64(10)}}, - }) -} - -func TestNestedArrayComment(t *testing.T) { - tree, err := Load(` -someArray = [ -# does not work -["entry1"] -]`) - assertTree(t, tree, err, map[string]interface{}{ - "someArray": [][]string{{"entry1"}}, - }) -} - -func TestNestedEmptyArrays(t *testing.T) { - tree, err := Load("a = [[[]]]") - assertTree(t, tree, err, map[string]interface{}{ - "a": [][][]interface{}{{{}}}, - }) -} - -func TestArrayNestedStrings(t *testing.T) { - tree, err := Load("data = [ [\"gamma\", \"delta\"], [\"Foo\"] ]") - assertTree(t, tree, err, map[string]interface{}{ - "data": [][]string{{"gamma", "delta"}, {"Foo"}}, - }) -} - -func TestParseUnknownRvalue(t *testing.T) { - _, err := Load("a = !bssss") - if err == nil { - t.Error("Expecting a parse error") - } - - _, err = Load("a = /b") - if err == nil { - t.Error("Expecting a parse error") - } -} - -func TestMissingValue(t *testing.T) { - _, err := Load("a = ") - if err.Error() != "(1, 5): expecting a value" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestUnterminatedArray(t *testing.T) { - _, err := Load("a = [1,") - if err.Error() != "(1, 8): unterminated array" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = [1") - if err.Error() != "(1, 7): unterminated array" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = [1 2") - if err.Error() != "(1, 8): missing comma" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestNewlinesInArrays(t *testing.T) { - tree, err := Load("a = [1,\n2,\n3]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1), int64(2), int64(3)}, - }) -} - -func TestArrayWithExtraComma(t *testing.T) { - tree, err := Load("a = [1,\n2,\n3,\n]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1), int64(2), int64(3)}, - }) -} - -func TestArrayWithExtraCommaComment(t *testing.T) { - tree, err := Load("a = [1, # wow\n2, # such items\n3, # so array\n]") - assertTree(t, tree, err, map[string]interface{}{ - "a": []int64{int64(1), int64(2), int64(3)}, - }) -} - -func TestSimpleInlineGroup(t *testing.T) { - tree, err := Load("key = {a = 42}") - assertTree(t, tree, err, map[string]interface{}{ - "key": map[string]interface{}{ - "a": int64(42), - }, - }) -} - -func TestDoubleInlineGroup(t *testing.T) { - tree, err := Load("key = {a = 42, b = \"foo\"}") - assertTree(t, tree, err, map[string]interface{}{ - "key": map[string]interface{}{ - "a": int64(42), - "b": "foo", - }, - }) -} - -func TestNestedInlineGroup(t *testing.T) { - tree, err := Load("out = {block0 = {x = 99, y = 100}, block1 = {p = \"999\", q = \"1000\"}}") - assertTree(t, tree, err, map[string]interface{}{ - "out": map[string]interface{}{ - "block0": map[string]interface{}{ - "x": int64(99), - "y": int64(100), - }, - "block1": map[string]interface{}{ - "p": "999", - "q": "1000", - }, - }, - }) -} - -func TestArrayInNestedInlineGroup(t *testing.T) { - tree, err := Load(`image = {name = "xxx", palette = {id = 100, colors = ["red", "blue", "green"]}}`) - assertTree(t, tree, err, map[string]interface{}{ - "image": map[string]interface{}{ - "name": "xxx", - "palette": map[string]interface{}{ - "id": int64(100), - "colors": []string{ - "red", - "blue", - "green", - }, - }, - }, - }) -} - -func TestExampleInlineGroup(t *testing.T) { - tree, err := Load(`name = { first = "Tom", last = "Preston-Werner" } -point = { x = 1, y = 2 }`) - assertTree(t, tree, err, map[string]interface{}{ - "name": map[string]interface{}{ - "first": "Tom", - "last": "Preston-Werner", - }, - "point": map[string]interface{}{ - "x": int64(1), - "y": int64(2), - }, - }) -} - -func TestInlineGroupBareKeysUnderscore(t *testing.T) { - tree, err := Load(`foo = { _bar = "buz" }`) - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "_bar": "buz", - }, - }) -} - -func TestInlineGroupBareKeysDash(t *testing.T) { - tree, err := Load(`foo = { -bar = "buz" }`) - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "-bar": "buz", - }, - }) -} - -func TestInlineGroupKeyQuoted(t *testing.T) { - tree, err := Load(`foo = { "bar" = "buz" }`) - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "buz", - }, - }) -} - -func TestExampleInlineGroupInArray(t *testing.T) { - tree, err := Load(`points = [{ x = 1, y = 2 }]`) - assertTree(t, tree, err, map[string]interface{}{ - "points": []map[string]interface{}{ - { - "x": int64(1), - "y": int64(2), - }, - }, - }) -} - -func TestInlineTableUnterminated(t *testing.T) { - _, err := Load("foo = {") - if err.Error() != "(1, 8): unterminated inline table" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableCommaExpected(t *testing.T) { - _, err := Load("foo = {hello = 53 test = foo}") - if err.Error() != "(1, 19): unexpected token type in inline table: no value can start with t" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableCommaStart(t *testing.T) { - _, err := Load("foo = {, hello = 53}") - if err.Error() != "(1, 8): unexpected token type in inline table: keys cannot contain , character" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableDoubleComma(t *testing.T) { - _, err := Load("foo = {hello = 53,, foo = 17}") - if err.Error() != "(1, 19): unexpected token type in inline table: keys cannot contain , character" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInlineTableTrailingComma(t *testing.T) { - _, err := Load("foo = {hello = 53, foo = 17,}") - if err.Error() != "(1, 28): trailing comma at the end of inline table" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestAddKeyToInlineTable(t *testing.T) { - _, err := Load("type = { name = \"Nail\" }\ntype.edible = false") - if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : type" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestAddSubTableToInlineTable(t *testing.T) { - _, err := Load("a = { b = \"c\" }\na.d.e = \"f\"") - if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : a.d" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestAddKeyToSubTableOfInlineTable(t *testing.T) { - _, err := Load("a = { b = { c = \"d\" } }\na.b.e = \"f\"") - if err.Error() != "(2, 1): could not add key or sub-table to exist inline table or its sub-table : a.b" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestReDefineInlineTable(t *testing.T) { - _, err := Load("a = { b = \"c\" }\n[a]\n d = \"e\"") - if err.Error() != "(2, 2): could not re-define exist inline table or its sub-table : a" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDuplicateGroups(t *testing.T) { - _, err := Load("[foo]\na=2\n[foo]b=3") - if err.Error() != "(3, 2): duplicated tables" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDuplicateKeys(t *testing.T) { - _, err := Load("foo = 2\nfoo = 3") - if err.Error() != "(2, 1): The following key was defined twice: foo" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestEmptyIntermediateTable(t *testing.T) { - _, err := Load("[foo..bar]") - if err.Error() != "(1, 2): invalid table array key: expecting key part after dot" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestImplicitDeclarationBefore(t *testing.T) { - tree, err := Load("[a.b.c]\nanswer = 42\n[a]\nbetter = 43") - assertTree(t, tree, err, map[string]interface{}{ - "a": map[string]interface{}{ - "b": map[string]interface{}{ - "c": map[string]interface{}{ - "answer": int64(42), - }, - }, - "better": int64(43), - }, - }) -} - -func TestFloatsWithoutLeadingZeros(t *testing.T) { - _, err := Load("a = .42") - if err.Error() != "(1, 5): cannot start float with a dot" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = -.42") - if err.Error() != "(1, 5): cannot start float with a dot" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestMissingFile(t *testing.T) { - _, err := LoadFile("foo.toml") - if err.Error() != "open foo.toml: no such file or directory" && - err.Error() != "open foo.toml: The system cannot find the file specified." { - t.Error("Bad error message:", err.Error()) - } -} - -func TestParseFile(t *testing.T) { - tree, err := LoadFile("example.toml") - - assertTree(t, tree, err, map[string]interface{}{ - "title": "TOML Example", - "owner": map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }, - "database": map[string]interface{}{ - "server": "192.168.1.1", - "ports": []int64{8001, 8001, 8002}, - "connection_max": 5000, - "enabled": true, - }, - "servers": map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, - "clients": map[string]interface{}{ - "data": []interface{}{ - []string{"gamma", "delta"}, - []int64{1, 2}, - }, - "score": 4e-08, - }, - }) -} - -func TestParseFileCRLF(t *testing.T) { - tree, err := LoadFile("example-crlf.toml") - - assertTree(t, tree, err, map[string]interface{}{ - "title": "TOML Example", - "owner": map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }, - "database": map[string]interface{}{ - "server": "192.168.1.1", - "ports": []int64{8001, 8001, 8002}, - "connection_max": 5000, - "enabled": true, - }, - "servers": map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, - "clients": map[string]interface{}{ - "data": []interface{}{ - []string{"gamma", "delta"}, - []int64{1, 2}, - }, - "score": 4e-08, - }, - }) -} - -func TestParseKeyGroupArray(t *testing.T) { - tree, err := Load("[[foo.bar]] a = 42\n[[foo.bar]] a = 69") - assertTree(t, tree, err, map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": []map[string]interface{}{ - {"a": int64(42)}, - {"a": int64(69)}, - }, - }, - }) -} - -func TestParseKeyGroupArrayUnfinished(t *testing.T) { - _, err := Load("[[foo.bar]\na = 42") - if err.Error() != "(1, 10): was expecting token [[, but got unclosed table array key instead" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("[[foo.[bar]\na = 42") - if err.Error() != "(1, 3): unexpected token table array key cannot contain ']', was expecting a table array key" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestParseKeyGroupArrayQueryExample(t *testing.T) { - tree, err := Load(` - [[book]] - title = "The Stand" - author = "Stephen King" - [[book]] - title = "For Whom the Bell Tolls" - author = "Ernest Hemmingway" - [[book]] - title = "Neuromancer" - author = "William Gibson" - `) - - assertTree(t, tree, err, map[string]interface{}{ - "book": []map[string]interface{}{ - {"title": "The Stand", "author": "Stephen King"}, - {"title": "For Whom the Bell Tolls", "author": "Ernest Hemmingway"}, - {"title": "Neuromancer", "author": "William Gibson"}, - }, - }) -} - -func TestParseKeyGroupArraySpec(t *testing.T) { - tree, err := Load("[[fruit]]\n name=\"apple\"\n [fruit.physical]\n color=\"red\"\n shape=\"round\"\n [[fruit]]\n name=\"banana\"") - assertTree(t, tree, err, map[string]interface{}{ - "fruit": []map[string]interface{}{ - {"name": "apple", "physical": map[string]interface{}{"color": "red", "shape": "round"}}, - {"name": "banana"}, - }, - }) -} - -func TestTomlValueStringRepresentation(t *testing.T) { - for idx, item := range []struct { - Value interface{} - Expect string - }{ - {int64(12345), "12345"}, - {uint64(50), "50"}, - {float64(123.45), "123.45"}, - {true, "true"}, - {"hello world", "\"hello world\""}, - {"\b\t\n\f\r\"\\", "\"\\b\\t\\n\\f\\r\\\"\\\\\""}, - {"\x05", "\"\\u0005\""}, - {time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), "1979-05-27T07:32:00Z"}, - {[]interface{}{"gamma", "delta"}, "[\"gamma\", \"delta\"]"}, - {nil, ""}, - } { - result, err := tomlValueStringRepresentation(item.Value, "", "", OrderAlphabetical, false) - if err != nil { - t.Errorf("Test %d - unexpected error: %s", idx, err) - } - if result != item.Expect { - t.Errorf("Test %d - got '%s', expected '%s'", idx, result, item.Expect) - } - } -} - -func TestToStringMapStringString(t *testing.T) { - tree, err := TreeFromMap(map[string]interface{}{"m": map[string]interface{}{"v": "abc"}}) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - want := "\n[m]\n v = \"abc\"\n" - got := tree.String() - - if got != want { - t.Errorf("want:\n%q\ngot:\n%q", want, got) - } -} - -func assertPosition(t *testing.T, text string, ref map[string]Position) { - tree, err := Load(text) - if err != nil { - t.Errorf("Error loading document text: `%v`", text) - t.Errorf("Error: %v", err) - } - for path, pos := range ref { - testPos := tree.GetPosition(path) - if testPos.Invalid() { - t.Errorf("Failed to query tree path or path has invalid position: %s", path) - } else if pos != testPos { - t.Errorf("Expected position %v, got %v instead", pos, testPos) - } - } -} - -func TestDocumentPositions(t *testing.T) { - assertPosition(t, - "[foo]\nbar=42\nbaz=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 1}, - "foo.bar": {2, 1}, - "foo.baz": {3, 1}, - }) -} - -func TestDocumentPositionsWithSpaces(t *testing.T) { - assertPosition(t, - " [foo]\n bar=42\n baz=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 3}, - "foo.bar": {2, 3}, - "foo.baz": {3, 3}, - }) -} - -func TestDocumentPositionsWithGroupArray(t *testing.T) { - assertPosition(t, - "[[foo]]\nbar=42\nbaz=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 1}, - "foo.bar": {2, 1}, - "foo.baz": {3, 1}, - }) -} - -func TestNestedTreePosition(t *testing.T) { - assertPosition(t, - "[foo.bar]\na=42\nb=69", - map[string]Position{ - "": {1, 1}, - "foo": {1, 1}, - "foo.bar": {1, 1}, - "foo.bar.a": {2, 1}, - "foo.bar.b": {3, 1}, - }) -} - -func TestInvalidGroupArray(t *testing.T) { - _, err := Load("[table#key]\nanswer = 42") - if err == nil { - t.Error("Should error") - } - - _, err = Load("[foo.[bar]\na = 42") - if err.Error() != "(1, 2): unexpected token table key cannot contain ']', was expecting a table key" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDoubleEqual(t *testing.T) { - _, err := Load("foo= = 2") - if err.Error() != "(1, 6): cannot have multiple equals for the same key" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestGroupArrayReassign(t *testing.T) { - _, err := Load("[hello]\n[[hello]]") - if err.Error() != "(2, 3): key \"hello\" is already assigned and not of type table array" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestInvalidFloatParsing(t *testing.T) { - _, err := Load("a=1e_2") - if err.Error() != "(1, 3): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a=1e2_") - if err.Error() != "(1, 3): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a=1__2") - if err.Error() != "(1, 3): invalid use of _ in number" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a=_1_2") - if err.Error() != "(1, 3): no value can start with _" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestMapKeyIsNum(t *testing.T) { - _, err := Load("table={2018=1,2019=2}") - if err != nil { - t.Error("should be passed") - } - _, err = Load(`table={"2018"=1,"2019"=2}`) - if err != nil { - t.Error("should be passed") - } -} - -func TestInvalidKeyInlineTable(t *testing.T) { - _, err := Load("table={invalid..key = 1}") - if err.Error() != "(1, 8): invalid key: expecting key part after dot" { - t.Error("Bad error message:", err.Error()) - } -} - -func TestDottedKeys(t *testing.T) { - tree, err := Load(` -name = "Orange" -physical.color = "orange" -physical.shape = "round" -site."google.com" = true`) - - assertTree(t, tree, err, map[string]interface{}{ - "name": "Orange", - "physical": map[string]interface{}{ - "color": "orange", - "shape": "round", - }, - "site": map[string]interface{}{ - "google.com": true, - }, - }) -} - -func TestInvalidDottedKeyEmptyGroup(t *testing.T) { - _, err := Load(`a..b = true`) - if err == nil { - t.Fatal("should return an error") - } - if err.Error() != "(1, 1): invalid key: expecting key part after dot" { - t.Fatalf("invalid error message: %s", err) - } -} - -func TestAccidentalNewlines(t *testing.T) { - expected := "The quick brown fox jumps over the lazy dog." - tree, err := Load(`str1 = "The quick brown fox jumps over the lazy dog." - -str2 = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -str3 = """\ - The quick brown \` + " " + ` - fox jumps over \` + " " + ` - the lazy dog.\` + " " + ` - """`) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := tree.Get("str1") - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } - - got = tree.Get("str2") - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } - - got = tree.Get("str3") - if got != expected { - t.Errorf("expected '%s', got '%s'", expected, got) - } -} diff --git a/position.go b/position.go deleted file mode 100644 index c17bff8..0000000 --- a/position.go +++ /dev/null @@ -1,29 +0,0 @@ -// Position support for go-toml - -package toml - -import ( - "fmt" -) - -// Position of a document element within a TOML document. -// -// Line and Col are both 1-indexed positions for the element's line number and -// column number, respectively. Values of zero or less will cause Invalid(), -// to return true. -type Position struct { - Line int // line within the document - Col int // column within the line -} - -// String representation of the position. -// Displays 1-indexed line and column numbers. -func (p Position) String() string { - return fmt.Sprintf("(%d, %d)", p.Line, p.Col) -} - -// Invalid returns whether or not the position is valid (i.e. with negative or -// null values) -func (p Position) Invalid() bool { - return p.Line <= 0 || p.Col <= 0 -} diff --git a/position_test.go b/position_test.go deleted file mode 100644 index 63ad1af..0000000 --- a/position_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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) - } - } -} diff --git a/query/README.md b/query/README.md deleted file mode 100644 index 75b3759..0000000 --- a/query/README.md +++ /dev/null @@ -1,201 +0,0 @@ -# Query package - -## Overview - -Package query performs JSONPath-like queries on a TOML document. - -The query path implementation is based loosely on the JSONPath specification: -http://goessner.net/articles/JsonPath/. - -The idea behind a query path is to allow quick access to any element, or set -of elements within TOML document, with a single expression. - -```go -result, err := query.CompileAndExecute("$.foo.bar.baz", tree) -``` - -This is roughly equivalent to: - -```go -next := tree.Get("foo") -if next != nil { - next = next.Get("bar") - if next != nil { - next = next.Get("baz") - } -} -result := next -``` - -err is nil if any parsing exception occurs. - -If no node in the tree matches the query, result will simply contain an empty list of -items. - -As illustrated above, the query path is much more efficient, especially since -the structure of the TOML file can vary. Rather than making assumptions about -a document's structure, a query allows the programmer to make structured -requests into the document, and get zero or more values as a result. - -## Query syntax - -The syntax of a query begins with a root token, followed by any number -sub-expressions: - -``` -$ - Root of the TOML tree. This must always come first. -.name - Selects child of this node, where 'name' is a TOML key - name. -['name'] - Selects child of this node, where 'name' is a string - containing a TOML key name. -[index] - Selcts child array element at 'index'. -..expr - Recursively selects all children, filtered by an a union, - index, or slice expression. -..* - Recursive selection of all nodes at this point in the - tree. -.* - Selects all children of the current node. -[expr,expr] - Union operator - a logical 'or' grouping of two or more - sub-expressions: index, key name, or filter. -[start:end:step] - Slice operator - selects array elements from start to - end-1, at the given step. All three arguments are - optional. -[?(filter)] - Named filter expression - the function 'filter' is - used to filter children at this node. -``` - -## Query Indexes And Slices - -Index expressions perform no bounds checking, and will contribute no -values to the result set if the provided index or index range is invalid. -Negative indexes represent values from the end of the array, counting backwards. - -```go -// select the last index of the array named 'foo' -query.CompileAndExecute("$.foo[-1]", tree) -``` - -Slice expressions are supported, by using ':' to separate a start/end index pair. - -```go -// select up to the first five elements in the array -query.CompileAndExecute("$.foo[0:5]", tree) -``` - -Slice expressions also allow negative indexes for the start and stop -arguments. - -```go -// select all array elements except the last one. -query.CompileAndExecute("$.foo[0:-1]", tree) -``` - -Slice expressions may have an optional stride/step parameter: - -```go -// select every other element -query.CompileAndExecute("$.foo[0::2]", tree) -``` - -Slice start and end parameters are also optional: - -```go -// these are all equivalent and select all the values in the array -query.CompileAndExecute("$.foo[:]", tree) -query.CompileAndExecute("$.foo[::]", tree) -query.CompileAndExecute("$.foo[::1]", tree) -query.CompileAndExecute("$.foo[0:]", tree) -query.CompileAndExecute("$.foo[0::]", tree) -query.CompileAndExecute("$.foo[0::1]", tree) -``` - -## Query Filters - -Query filters are used within a Union [,] or single Filter [] expression. -A filter only allows nodes that qualify through to the next expression, -and/or into the result set. - -```go -// returns children of foo that are permitted by the 'bar' filter. -query.CompileAndExecute("$.foo[?(bar)]", tree) -``` - -There are several filters provided with the library: - -``` -tree - Allows nodes of type Tree. -int - Allows nodes of type int64. -float - Allows nodes of type float64. -string - Allows nodes of type string. -time - Allows nodes of type time.Time. -bool - Allows nodes of type bool. -``` - -## Query Results - -An executed query returns a Result object. This contains the nodes -in the TOML tree that qualify the query expression. Position information -is also available for each value in the set. - -```go -// display the results of a query -results := query.CompileAndExecute("$.foo.bar.baz", tree) -for idx, value := results.Values() { - fmt.Println("%v: %v", results.Positions()[idx], value) -} -``` - -## Compiled Queries - -Queries may be executed directly on a Tree object, or compiled ahead -of time and executed discretely. The former is more convenient, but has the -penalty of having to recompile the query expression each time. - -```go -// basic query -results := query.CompileAndExecute("$.foo.bar.baz", tree) - -// compiled query -query, err := toml.Compile("$.foo.bar.baz") -results := query.Execute(tree) - -// run the compiled query again on a different tree -moreResults := query.Execute(anotherTree) -``` - -## User Defined Query Filters - -Filter expressions may also be user defined by using the SetFilter() -function on the Query object. The function must return true/false, which -signifies if the passed node is kept or discarded, respectively. - -```go -// create a query that references a user-defined filter -query, _ := query.Compile("$[?(bazOnly)]") - -// define the filter, and assign it to the query -query.SetFilter("bazOnly", func(node interface{}) bool{ - if tree, ok := node.(*Tree); ok { - return tree.Has("baz") - } - return false // reject all other node types -}) - -// run the query -query.Execute(tree) -``` diff --git a/query/doc.go b/query/doc.go deleted file mode 100644 index d0efb21..0000000 --- a/query/doc.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package query performs JSONPath-like queries on a TOML document. -// -// The query path implementation is based loosely on the JSONPath specification: -// http://goessner.net/articles/JsonPath/. -// -// The idea behind a query path is to allow quick access to any element, or set -// of elements within TOML document, with a single expression. -// -// result, err := query.CompileAndExecute("$.foo.bar.baz", tree) -// -// This is roughly equivalent to: -// -// next := tree.Get("foo") -// if next != nil { -// next = next.Get("bar") -// if next != nil { -// next = next.Get("baz") -// } -// } -// result := next -// -// err is nil if any parsing exception occurs. -// -// If no node in the tree matches the query, result will simply contain an empty list of -// items. -// -// As illustrated above, the query path is much more efficient, especially since -// the structure of the TOML file can vary. Rather than making assumptions about -// a document's structure, a query allows the programmer to make structured -// requests into the document, and get zero or more values as a result. -// -// Query syntax -// -// The syntax of a query begins with a root token, followed by any number -// sub-expressions: -// -// $ -// Root of the TOML tree. This must always come first. -// .name -// Selects child of this node, where 'name' is a TOML key -// name. -// ['name'] -// Selects child of this node, where 'name' is a string -// containing a TOML key name. -// [index] -// Selcts child array element at 'index'. -// ..expr -// Recursively selects all children, filtered by an a union, -// index, or slice expression. -// ..* -// Recursive selection of all nodes at this point in the -// tree. -// .* -// Selects all children of the current node. -// [expr,expr] -// Union operator - a logical 'or' grouping of two or more -// sub-expressions: index, key name, or filter. -// [start:end:step] -// Slice operator - selects array elements from start to -// end-1, at the given step. All three arguments are -// optional. -// [?(filter)] -// Named filter expression - the function 'filter' is -// used to filter children at this node. -// -// Query Indexes And Slices -// -// Index expressions perform no bounds checking, and will contribute no -// values to the result set if the provided index or index range is invalid. -// Negative indexes represent values from the end of the array, counting backwards. -// -// // select the last index of the array named 'foo' -// query.CompileAndExecute("$.foo[-1]", tree) -// -// Slice expressions are supported, by using ':' to separate a start/end index pair. -// -// // select up to the first five elements in the array -// query.CompileAndExecute("$.foo[0:5]", tree) -// -// Slice expressions also allow negative indexes for the start and stop -// arguments. -// -// // select all array elements except the last one. -// query.CompileAndExecute("$.foo[0:-1]", tree) -// -// Slice expressions may have an optional stride/step parameter: -// -// // select every other element -// query.CompileAndExecute("$.foo[0::2]", tree) -// -// Slice start and end parameters are also optional: -// -// // these are all equivalent and select all the values in the array -// query.CompileAndExecute("$.foo[:]", tree) -// query.CompileAndExecute("$.foo[::]", tree) -// query.CompileAndExecute("$.foo[::1]", tree) -// query.CompileAndExecute("$.foo[0:]", tree) -// query.CompileAndExecute("$.foo[0::]", tree) -// query.CompileAndExecute("$.foo[0::1]", tree) -// -// Query Filters -// -// Query filters are used within a Union [,] or single Filter [] expression. -// A filter only allows nodes that qualify through to the next expression, -// and/or into the result set. -// -// // returns children of foo that are permitted by the 'bar' filter. -// query.CompileAndExecute("$.foo[?(bar)]", tree) -// -// There are several filters provided with the library: -// -// tree -// Allows nodes of type Tree. -// int -// Allows nodes of type int64. -// float -// Allows nodes of type float64. -// string -// Allows nodes of type string. -// time -// Allows nodes of type time.Time. -// bool -// Allows nodes of type bool. -// -// Query Results -// -// An executed query returns a Result object. This contains the nodes -// in the TOML tree that qualify the query expression. Position information -// is also available for each value in the set. -// -// // display the results of a query -// results := query.CompileAndExecute("$.foo.bar.baz", tree) -// for idx, value := results.Values() { -// fmt.Println("%v: %v", results.Positions()[idx], value) -// } -// -// Compiled Queries -// -// Queries may be executed directly on a Tree object, or compiled ahead -// of time and executed discretely. The former is more convenient, but has the -// penalty of having to recompile the query expression each time. -// -// // basic query -// results := query.CompileAndExecute("$.foo.bar.baz", tree) -// -// // compiled query -// query, err := toml.Compile("$.foo.bar.baz") -// results := query.Execute(tree) -// -// // run the compiled query again on a different tree -// moreResults := query.Execute(anotherTree) -// -// User Defined Query Filters -// -// Filter expressions may also be user defined by using the SetFilter() -// function on the Query object. The function must return true/false, which -// signifies if the passed node is kept or discarded, respectively. -// -// // create a query that references a user-defined filter -// query, _ := query.Compile("$[?(bazOnly)]") -// -// // define the filter, and assign it to the query -// query.SetFilter("bazOnly", func(node interface{}) bool{ -// if tree, ok := node.(*Tree); ok { -// return tree.Has("baz") -// } -// return false // reject all other node types -// }) -// -// // run the query -// query.Execute(tree) -// -package query diff --git a/query/lexer.go b/query/lexer.go deleted file mode 100644 index 2dc3194..0000000 --- a/query/lexer.go +++ /dev/null @@ -1,357 +0,0 @@ -// 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 -} diff --git a/query/lexer_test.go b/query/lexer_test.go deleted file mode 100644 index 8ce0501..0000000 --- a/query/lexer_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package 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'"}, - }) -} diff --git a/query/match.go b/query/match.go deleted file mode 100644 index 37b43da..0000000 --- a/query/match.go +++ /dev/null @@ -1,311 +0,0 @@ -package query - -import ( - "fmt" - "reflect" - - "github.com/pelletier/go-toml" -) - -// base match -type matchBase struct { - next pathFn -} - -func (f *matchBase) setNext(next pathFn) { - f.next = next -} - -// terminating functor - gathers results -type terminatingFn struct { - // empty -} - -func newTerminatingFn() *terminatingFn { - return &terminatingFn{} -} - -func (f *terminatingFn) setNext(next pathFn) { - // do nothing -} - -func (f *terminatingFn) call(node interface{}, ctx *queryContext) { - ctx.result.appendResult(node, ctx.lastPosition) -} - -// match single key -type matchKeyFn struct { - matchBase - Name string -} - -func newMatchKeyFn(name string) *matchKeyFn { - return &matchKeyFn{Name: name} -} - -func (f *matchKeyFn) call(node interface{}, ctx *queryContext) { - if array, ok := node.([]*toml.Tree); ok { - for _, tree := range array { - item := tree.GetPath([]string{f.Name}) - if item != nil { - ctx.lastPosition = tree.GetPositionPath([]string{f.Name}) - f.next.call(item, ctx) - } - } - } else if tree, ok := node.(*toml.Tree); ok { - item := tree.GetPath([]string{f.Name}) - if item != nil { - ctx.lastPosition = tree.GetPositionPath([]string{f.Name}) - f.next.call(item, ctx) - } - } -} - -// match single index -type matchIndexFn struct { - matchBase - Idx int -} - -func newMatchIndexFn(idx int) *matchIndexFn { - return &matchIndexFn{Idx: idx} -} - -func (f *matchIndexFn) call(node interface{}, ctx *queryContext) { - v := reflect.ValueOf(node) - if v.Kind() == reflect.Slice { - if v.Len() == 0 { - return - } - - // Manage negative values - idx := f.Idx - if idx < 0 { - idx += v.Len() - } - if 0 <= idx && idx < v.Len() { - callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) - } - } -} - -func callNextIndexSlice(next pathFn, node interface{}, ctx *queryContext, value interface{}) { - if treesArray, ok := node.([]*toml.Tree); ok { - ctx.lastPosition = treesArray[0].Position() - } - next.call(value, ctx) -} - -// filter by slicing -type matchSliceFn struct { - matchBase - Start, End, Step *int -} - -func newMatchSliceFn() *matchSliceFn { - return &matchSliceFn{} -} - -func (f *matchSliceFn) setStart(start int) *matchSliceFn { - f.Start = &start - return f -} - -func (f *matchSliceFn) setEnd(end int) *matchSliceFn { - f.End = &end - return f -} - -func (f *matchSliceFn) setStep(step int) *matchSliceFn { - f.Step = &step - return f -} - -func (f *matchSliceFn) call(node interface{}, ctx *queryContext) { - v := reflect.ValueOf(node) - if v.Kind() == reflect.Slice { - if v.Len() == 0 { - return - } - - var start, end, step int - - // Initialize step - if f.Step != nil { - step = *f.Step - } else { - step = 1 - } - - // Initialize start - if f.Start != nil { - start = *f.Start - // Manage negative values - if start < 0 { - start += v.Len() - } - // Manage out of range values - start = max(start, 0) - start = min(start, v.Len()-1) - } else if step > 0 { - start = 0 - } else { - start = v.Len() - 1 - } - - // Initialize end - if f.End != nil { - end = *f.End - // Manage negative values - if end < 0 { - end += v.Len() - } - // Manage out of range values - end = max(end, -1) - end = min(end, v.Len()) - } else if step > 0 { - end = v.Len() - } else { - end = -1 - } - - // Loop on values - if step > 0 { - for idx := start; idx < end; idx += step { - callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) - } - } else { - for idx := start; idx > end; idx += step { - callNextIndexSlice(f.next, node, ctx, v.Index(idx).Interface()) - } - } - } -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -// match anything -type matchAnyFn struct { - matchBase -} - -func newMatchAnyFn() *matchAnyFn { - return &matchAnyFn{} -} - -func (f *matchAnyFn) call(node interface{}, ctx *queryContext) { - if tree, ok := node.(*toml.Tree); ok { - for _, k := range tree.Keys() { - v := tree.GetPath([]string{k}) - ctx.lastPosition = tree.GetPositionPath([]string{k}) - f.next.call(v, ctx) - } - } -} - -// filter through union -type matchUnionFn struct { - Union []pathFn -} - -func (f *matchUnionFn) setNext(next pathFn) { - for _, fn := range f.Union { - fn.setNext(next) - } -} - -func (f *matchUnionFn) call(node interface{}, ctx *queryContext) { - for _, fn := range f.Union { - fn.call(node, ctx) - } -} - -// match every single last node in the tree -type matchRecursiveFn struct { - matchBase -} - -func newMatchRecursiveFn() *matchRecursiveFn { - return &matchRecursiveFn{} -} - -func (f *matchRecursiveFn) call(node interface{}, ctx *queryContext) { - originalPosition := ctx.lastPosition - if tree, ok := node.(*toml.Tree); ok { - var visit func(tree *toml.Tree) - visit = func(tree *toml.Tree) { - for _, k := range tree.Keys() { - v := tree.GetPath([]string{k}) - ctx.lastPosition = tree.GetPositionPath([]string{k}) - f.next.call(v, ctx) - switch node := v.(type) { - case *toml.Tree: - visit(node) - case []*toml.Tree: - for _, subtree := range node { - visit(subtree) - } - } - } - } - ctx.lastPosition = originalPosition - f.next.call(tree, ctx) - visit(tree) - } -} - -// match based on an externally provided functional filter -type matchFilterFn struct { - matchBase - Pos toml.Position - Name string -} - -func newMatchFilterFn(name string, pos toml.Position) *matchFilterFn { - return &matchFilterFn{Name: name, Pos: pos} -} - -func (f *matchFilterFn) call(node interface{}, ctx *queryContext) { - fn, ok := (*ctx.filters)[f.Name] - if !ok { - panic(fmt.Sprintf("%s: query context does not have filter '%s'", - f.Pos.String(), f.Name)) - } - switch castNode := node.(type) { - case *toml.Tree: - for _, k := range castNode.Keys() { - v := castNode.GetPath([]string{k}) - if fn(v) { - ctx.lastPosition = castNode.GetPositionPath([]string{k}) - f.next.call(v, ctx) - } - } - case []*toml.Tree: - for _, v := range castNode { - if fn(v) { - if len(castNode) > 0 { - ctx.lastPosition = castNode[0].Position() - } - f.next.call(v, ctx) - } - } - case []interface{}: - for _, v := range castNode { - if fn(v) { - f.next.call(v, ctx) - } - } - } -} diff --git a/query/match_test.go b/query/match_test.go deleted file mode 100644 index 47472c1..0000000 --- a/query/match_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package query - -import ( - "fmt" - "strconv" - "testing" - - "github.com/pelletier/go-toml" -) - -// dump path tree to a string -func pathString(root pathFn) string { - result := fmt.Sprintf("%T:", root) - switch fn := root.(type) { - case *terminatingFn: - result += "{}" - case *matchKeyFn: - result += fmt.Sprintf("{%s}", fn.Name) - result += pathString(fn.next) - case *matchIndexFn: - result += fmt.Sprintf("{%d}", fn.Idx) - result += pathString(fn.next) - case *matchSliceFn: - startString, endString, stepString := "nil", "nil", "nil" - if fn.Start != nil { - startString = strconv.Itoa(*fn.Start) - } - if fn.End != nil { - endString = strconv.Itoa(*fn.End) - } - if fn.Step != nil { - stepString = strconv.Itoa(*fn.Step) - } - result += fmt.Sprintf("{%s:%s:%s}", startString, endString, stepString) - result += pathString(fn.next) - case *matchAnyFn: - result += "{}" - result += pathString(fn.next) - case *matchUnionFn: - result += "{[" - for _, v := range fn.Union { - result += pathString(v) + ", " - } - result += "]}" - case *matchRecursiveFn: - result += "{}" - result += pathString(fn.next) - case *matchFilterFn: - result += fmt.Sprintf("{%s}", fn.Name) - result += pathString(fn.next) - } - return result -} - -func assertPathMatch(t *testing.T, path, ref *Query) bool { - pathStr := pathString(path.root) - refStr := pathString(ref.root) - if pathStr != refStr { - t.Errorf("paths do not match") - t.Log("test:", pathStr) - t.Log("ref: ", refStr) - return false - } - return true -} - -func assertPath(t *testing.T, query string, ref *Query) { - path, _ := parseQuery(lexQuery(query)) - assertPathMatch(t, path, ref) -} - -func buildPath(parts ...pathFn) *Query { - query := newQuery() - for _, v := range parts { - query.appendPath(v) - } - return query -} - -func TestPathRoot(t *testing.T) { - assertPath(t, - "$", - buildPath( - // empty - )) -} - -func TestPathKey(t *testing.T) { - assertPath(t, - "$.foo", - buildPath( - newMatchKeyFn("foo"), - )) -} - -func TestPathBracketKey(t *testing.T) { - assertPath(t, - "$[foo]", - buildPath( - newMatchKeyFn("foo"), - )) -} - -func TestPathBracketStringKey(t *testing.T) { - assertPath(t, - "$['foo']", - buildPath( - newMatchKeyFn("foo"), - )) -} - -func TestPathIndex(t *testing.T) { - assertPath(t, - "$[123]", - buildPath( - newMatchIndexFn(123), - )) -} - -func TestPathSliceStart(t *testing.T) { - assertPath(t, - "$[123:]", - buildPath( - newMatchSliceFn().setStart(123), - )) -} - -func TestPathSliceStartEnd(t *testing.T) { - assertPath(t, - "$[123:456]", - buildPath( - newMatchSliceFn().setStart(123).setEnd(456), - )) -} - -func TestPathSliceStartEndColon(t *testing.T) { - assertPath(t, - "$[123:456:]", - buildPath( - newMatchSliceFn().setStart(123).setEnd(456), - )) -} - -func TestPathSliceStartStep(t *testing.T) { - assertPath(t, - "$[123::7]", - buildPath( - newMatchSliceFn().setStart(123).setStep(7), - )) -} - -func TestPathSliceEndStep(t *testing.T) { - assertPath(t, - "$[:456:7]", - buildPath( - newMatchSliceFn().setEnd(456).setStep(7), - )) -} - -func TestPathSliceStep(t *testing.T) { - assertPath(t, - "$[::7]", - buildPath( - newMatchSliceFn().setStep(7), - )) -} - -func TestPathSliceAll(t *testing.T) { - assertPath(t, - "$[123:456:7]", - buildPath( - newMatchSliceFn().setStart(123).setEnd(456).setStep(7), - )) -} - -func TestPathAny(t *testing.T) { - assertPath(t, - "$.*", - buildPath( - newMatchAnyFn(), - )) -} - -func TestPathUnion(t *testing.T) { - assertPath(t, - "$[foo, bar, baz]", - buildPath( - &matchUnionFn{[]pathFn{ - newMatchKeyFn("foo"), - newMatchKeyFn("bar"), - newMatchKeyFn("baz"), - }}, - )) -} - -func TestPathRecurse(t *testing.T) { - assertPath(t, - "$..*", - buildPath( - newMatchRecursiveFn(), - )) -} - -func TestPathFilterExpr(t *testing.T) { - assertPath(t, - "$[?('foo'),?(bar)]", - buildPath( - &matchUnionFn{[]pathFn{ - newMatchFilterFn("foo", toml.Position{}), - newMatchFilterFn("bar", toml.Position{}), - }}, - )) -} diff --git a/query/parser.go b/query/parser.go deleted file mode 100644 index be27d35..0000000 --- a/query/parser.go +++ /dev/null @@ -1,278 +0,0 @@ -/* - Based on the "jsonpath" spec/concept. - - http://goessner.net/articles/JsonPath/ - https://code.google.com/p/json-path/ -*/ - -package query - -import ( - "fmt" -) - -const maxInt = int(^uint(0) >> 1) - -type queryParser struct { - flow chan token - tokensBuffer []token - query *Query - union []pathFn - err error -} - -type queryParserStateFn func() queryParserStateFn - -// Formats and panics an error message based on a token -func (p *queryParser) parseError(tok *token, msg string, args ...interface{}) queryParserStateFn { - p.err = fmt.Errorf(tok.Position.String()+": "+msg, args...) - return nil // trigger parse to end -} - -func (p *queryParser) run() { - for state := p.parseStart; state != nil; { - state = state() - } -} - -func (p *queryParser) backup(tok *token) { - p.tokensBuffer = append(p.tokensBuffer, *tok) -} - -func (p *queryParser) peek() *token { - if len(p.tokensBuffer) != 0 { - return &(p.tokensBuffer[0]) - } - - tok, ok := <-p.flow - if !ok { - return nil - } - p.backup(&tok) - return &tok -} - -func (p *queryParser) lookahead(types ...tokenType) bool { - result := true - buffer := []token{} - - for _, typ := range types { - tok := p.getToken() - if tok == nil { - result = false - break - } - buffer = append(buffer, *tok) - if tok.typ != typ { - result = false - break - } - } - // add the tokens back to the buffer, and return - p.tokensBuffer = append(p.tokensBuffer, buffer...) - return result -} - -func (p *queryParser) getToken() *token { - if len(p.tokensBuffer) != 0 { - tok := p.tokensBuffer[0] - p.tokensBuffer = p.tokensBuffer[1:] - return &tok - } - tok, ok := <-p.flow - if !ok { - return nil - } - return &tok -} - -func (p *queryParser) parseStart() queryParserStateFn { - tok := p.getToken() - - if tok == nil || tok.typ == tokenEOF { - return nil - } - - if tok.typ != tokenDollar { - return p.parseError(tok, "Expected '$' at start of expression") - } - - return p.parseMatchExpr -} - -// handle '.' prefix, '[]', and '..' -func (p *queryParser) parseMatchExpr() queryParserStateFn { - tok := p.getToken() - switch tok.typ { - case tokenDotDot: - p.query.appendPath(&matchRecursiveFn{}) - // nested parse for '..' - tok := p.getToken() - switch tok.typ { - case tokenKey: - p.query.appendPath(newMatchKeyFn(tok.val)) - return p.parseMatchExpr - case tokenLeftBracket: - return p.parseBracketExpr - case tokenStar: - // do nothing - the recursive predicate is enough - return p.parseMatchExpr - } - - case tokenDot: - // nested parse for '.' - tok := p.getToken() - switch tok.typ { - case tokenKey: - p.query.appendPath(newMatchKeyFn(tok.val)) - return p.parseMatchExpr - case tokenStar: - p.query.appendPath(&matchAnyFn{}) - return p.parseMatchExpr - } - - case tokenLeftBracket: - return p.parseBracketExpr - - case tokenEOF: - return nil // allow EOF at this stage - } - return p.parseError(tok, "expected match expression") -} - -func (p *queryParser) parseBracketExpr() queryParserStateFn { - if p.lookahead(tokenInteger, tokenColon) { - return p.parseSliceExpr - } - if p.peek().typ == tokenColon { - return p.parseSliceExpr - } - return p.parseUnionExpr -} - -func (p *queryParser) parseUnionExpr() queryParserStateFn { - var tok *token - - // this state can be traversed after some sub-expressions - // so be careful when setting up state in the parser - if p.union == nil { - p.union = []pathFn{} - } - -loop: // labeled loop for easy breaking - for { - if len(p.union) > 0 { - // parse delimiter or terminator - tok = p.getToken() - switch tok.typ { - case tokenComma: - // do nothing - case tokenRightBracket: - break loop - default: - return p.parseError(tok, "expected ',' or ']', not '%s'", tok.val) - } - } - - // parse sub expression - tok = p.getToken() - switch tok.typ { - case tokenInteger: - p.union = append(p.union, newMatchIndexFn(tok.Int())) - case tokenKey: - p.union = append(p.union, newMatchKeyFn(tok.val)) - case tokenString: - p.union = append(p.union, newMatchKeyFn(tok.val)) - case tokenQuestion: - return p.parseFilterExpr - default: - return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union)) - } - } - - // if there is only one sub-expression, use that instead - if len(p.union) == 1 { - p.query.appendPath(p.union[0]) - } else { - p.query.appendPath(&matchUnionFn{p.union}) - } - - p.union = nil // clear out state - return p.parseMatchExpr -} - -func (p *queryParser) parseSliceExpr() queryParserStateFn { - // init slice to grab all elements - var start, end, step *int = nil, nil, nil - - // parse optional start - tok := p.getToken() - if tok.typ == tokenInteger { - v := tok.Int() - start = &v - tok = p.getToken() - } - if tok.typ != tokenColon { - return p.parseError(tok, "expected ':'") - } - - // parse optional end - tok = p.getToken() - if tok.typ == tokenInteger { - v := tok.Int() - end = &v - tok = p.getToken() - } - if tok.typ == tokenRightBracket { - p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step}) - return p.parseMatchExpr - } - if tok.typ != tokenColon { - return p.parseError(tok, "expected ']' or ':'") - } - - // parse optional step - tok = p.getToken() - if tok.typ == tokenInteger { - v := tok.Int() - if v == 0 { - return p.parseError(tok, "step cannot be zero") - } - step = &v - tok = p.getToken() - } - if tok.typ != tokenRightBracket { - return p.parseError(tok, "expected ']'") - } - - p.query.appendPath(&matchSliceFn{Start: start, End: end, Step: step}) - return p.parseMatchExpr -} - -func (p *queryParser) parseFilterExpr() queryParserStateFn { - tok := p.getToken() - if tok.typ != tokenLeftParen { - return p.parseError(tok, "expected left-parenthesis for filter expression") - } - tok = p.getToken() - if tok.typ != tokenKey && tok.typ != tokenString { - return p.parseError(tok, "expected key or string for filter function name") - } - name := tok.val - tok = p.getToken() - if tok.typ != tokenRightParen { - return p.parseError(tok, "expected right-parenthesis for filter expression") - } - p.union = append(p.union, newMatchFilterFn(name, tok.Position)) - return p.parseUnionExpr -} - -func parseQuery(flow chan token) (*Query, error) { - parser := &queryParser{ - flow: flow, - tokensBuffer: []token{}, - query: newQuery(), - } - parser.run() - return parser.query, parser.err -} diff --git a/query/parser_test.go b/query/parser_test.go deleted file mode 100644 index 91d3f70..0000000 --- a/query/parser_test.go +++ /dev/null @@ -1,613 +0,0 @@ -package query - -import ( - "fmt" - "io/ioutil" - "sort" - "strings" - "testing" - "time" - - "github.com/pelletier/go-toml" -) - -type queryTestNode struct { - value interface{} - position toml.Position -} - -func valueString(root interface{}) string { - result := "" //fmt.Sprintf("%T:", root) - switch node := root.(type) { - case *Result: - items := []string{} - for i, v := range node.Values() { - items = append(items, fmt.Sprintf("%s:%s", - node.Positions()[i].String(), valueString(v))) - } - sort.Strings(items) - result = "[" + strings.Join(items, ", ") + "]" - case queryTestNode: - result = fmt.Sprintf("%s:%s", - node.position.String(), valueString(node.value)) - case []interface{}: - items := []string{} - for _, v := range node { - items = append(items, valueString(v)) - } - sort.Strings(items) - result = "[" + strings.Join(items, ", ") + "]" - case *toml.Tree: - // workaround for unreliable map key ordering - items := []string{} - for _, k := range node.Keys() { - v := node.GetPath([]string{k}) - items = append(items, k+":"+valueString(v)) - } - sort.Strings(items) - result = "{" + strings.Join(items, ", ") + "}" - case map[string]interface{}: - // workaround for unreliable map key ordering - items := []string{} - for k, v := range node { - items = append(items, k+":"+valueString(v)) - } - sort.Strings(items) - result = "{" + strings.Join(items, ", ") + "}" - case int64: - result += fmt.Sprintf("%d", node) - case string: - result += "'" + node + "'" - case float64: - result += fmt.Sprintf("%f", node) - case bool: - result += fmt.Sprintf("%t", node) - case time.Time: - result += fmt.Sprintf("'%v'", node) - } - return result -} - -func assertValue(t *testing.T, result, ref interface{}) { - pathStr := valueString(result) - refStr := valueString(ref) - if pathStr != refStr { - t.Errorf("values do not match") - t.Log("test:", pathStr) - t.Log("ref: ", refStr) - } -} - -func assertParseError(t *testing.T, query string, errString string) { - _, err := Compile(query) - if err == nil { - t.Error("error should be non-nil") - return - } - if err.Error() != errString { - t.Errorf("error does not match") - t.Log("test:", err.Error()) - t.Log("ref: ", errString) - } -} - -func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) { - tree, err := toml.Load(tomlDoc) - if err != nil { - t.Errorf("Non-nil toml parse error: %v", err) - return - } - q, err := Compile(query) - if err != nil { - t.Error(err) - return - } - results := q.Execute(tree) - assertValue(t, results, ref) -} - -func TestQueryRoot(t *testing.T) { - assertQueryPositions(t, - "a = 42", - "$", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "a": int64(42), - }, toml.Position{1, 1}, - }, - }) -} - -func TestQueryKey(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = 42", - "$.foo.a", - []interface{}{ - queryTestNode{ - int64(42), toml.Position{2, 1}, - }, - }) -} - -func TestQueryKeyString(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = 42", - "$.foo['a']", - []interface{}{ - queryTestNode{ - int64(42), toml.Position{2, 1}, - }, - }) -} - -func TestQueryKeyUnicodeString(t *testing.T) { - assertQueryPositions(t, - "['f𝟘.o']\na = 42", - "$['f𝟘.o']['a']", - []interface{}{ - queryTestNode{ - int64(42), toml.Position{2, 1}, - }, - }) -} - -func TestQueryIndexError1(t *testing.T) { - assertParseError(t, "$.foo.a[5", "(1, 10): expected ',' or ']', not ''") -} - -func TestQueryIndexError2(t *testing.T) { - assertParseError(t, "$.foo.a[]", "(1, 9): expected union sub expression, not ']', 0") -} - -func TestQueryIndex(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[5]", - []interface{}{ - queryTestNode{int64(5), toml.Position{2, 1}}, - }) -} - -func TestQueryIndexNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[-2]", - []interface{}{ - queryTestNode{int64(8), toml.Position{2, 1}}, - }) -} - -func TestQueryIndexWrong(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[99]", - []interface{}{}) -} - -func TestQueryIndexEmpty(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = []", - "$.foo.a[5]", - []interface{}{}) -} - -func TestQueryIndexTree(t *testing.T) { - assertQueryPositions(t, - "[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\nb = 3", - "$.foo[1].b", - []interface{}{ - queryTestNode{int64(3), toml.Position{4, 1}}, - }) -} - -func TestQuerySliceError1(t *testing.T) { - assertParseError(t, "$.foo.a[3:?]", "(1, 11): expected ']' or ':'") -} - -func TestQuerySliceError2(t *testing.T) { - assertParseError(t, "$.foo.a[:::]", "(1, 11): expected ']'") -} - -func TestQuerySliceError3(t *testing.T) { - assertParseError(t, "$.foo.a[::0]", "(1, 11): step cannot be zero") -} - -func TestQuerySliceRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[:5]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(3), toml.Position{2, 1}}, - queryTestNode{int64(4), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStep(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[0:5:2]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(4), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStartNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[-3:]", - []interface{}{ - queryTestNode{int64(7), toml.Position{2, 1}}, - queryTestNode{int64(8), toml.Position{2, 1}}, - queryTestNode{int64(9), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceEndNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[:-6]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(3), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStepNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[::-2]", - []interface{}{ - queryTestNode{int64(9), toml.Position{2, 1}}, - queryTestNode{int64(7), toml.Position{2, 1}}, - queryTestNode{int64(5), toml.Position{2, 1}}, - queryTestNode{int64(3), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStartOverRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[-99:3]", - []interface{}{ - queryTestNode{int64(0), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceStartOverRangeNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[99:7:-1]", - []interface{}{ - queryTestNode{int64(9), toml.Position{2, 1}}, - queryTestNode{int64(8), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceEndOverRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[7:99]", - []interface{}{ - queryTestNode{int64(7), toml.Position{2, 1}}, - queryTestNode{int64(8), toml.Position{2, 1}}, - queryTestNode{int64(9), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceEndOverRangeNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[2:-99:-1]", - []interface{}{ - queryTestNode{int64(2), toml.Position{2, 1}}, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(0), toml.Position{2, 1}}, - }) -} - -func TestQuerySliceWrongRange(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[5:3]", - []interface{}{}) -} - -func TestQuerySliceWrongRangeNegative(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", - "$.foo.a[3:5:-1]", - []interface{}{}) -} - -func TestQuerySliceEmpty(t *testing.T) { - assertQueryPositions(t, - "[foo]\na = []", - "$.foo.a[5:]", - []interface{}{}) -} - -func TestQuerySliceTree(t *testing.T) { - assertQueryPositions(t, - "[[foo]]\na='nok'\n[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\na='ok'\nb = 3", - "$.foo[1:].a", - []interface{}{ - queryTestNode{ - []interface{}{ - int64(0), int64(1), int64(2), int64(3), int64(4), - int64(5), int64(6), int64(7), int64(8), int64(9)}, - toml.Position{4, 1}}, - queryTestNode{"ok", toml.Position{6, 1}}, - }) -} - -func TestQueryAny(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4", - "$.foo.*", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - }) -} -func TestQueryUnionSimple(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", - "$.*[bar,foo]", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, toml.Position{7, 1}, - }, - }) -} - -func TestQueryRecursionAll(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", - "$..*", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, - }, - "baz": map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, - }, - "gorf": map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, - }, - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "bar": map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{int64(1), toml.Position{2, 1}}, - queryTestNode{int64(2), toml.Position{3, 1}}, - queryTestNode{ - map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, - }, toml.Position{4, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - queryTestNode{int64(3), toml.Position{5, 1}}, - queryTestNode{int64(4), toml.Position{6, 1}}, - queryTestNode{ - map[string]interface{}{ - "foo": map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, - }, toml.Position{7, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, toml.Position{7, 1}, - }, - queryTestNode{int64(5), toml.Position{8, 1}}, - queryTestNode{int64(6), toml.Position{9, 1}}, - }) -} - -func TestQueryRecursionUnionSimple(t *testing.T) { - assertQueryPositions(t, - "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", - "$..['foo','bar']", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "bar": map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(3), - "b": int64(4), - }, toml.Position{4, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(1), - "b": int64(2), - }, toml.Position{1, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "a": int64(5), - "b": int64(6), - }, toml.Position{7, 1}, - }, - }) -} - -func TestQueryFilterFn(t *testing.T) { - buff, err := ioutil.ReadFile("../example.toml") - if err != nil { - t.Error(err) - return - } - - assertQueryPositions(t, string(buff), - "$..[?(int)]", - []interface{}{ - queryTestNode{int64(8001), toml.Position{13, 1}}, - queryTestNode{int64(8001), toml.Position{13, 1}}, - queryTestNode{int64(8002), toml.Position{13, 1}}, - queryTestNode{int64(5000), toml.Position{14, 1}}, - }) - - assertQueryPositions(t, string(buff), - "$..[?(string)]", - []interface{}{ - queryTestNode{"TOML Example", toml.Position{3, 1}}, - queryTestNode{"Tom Preston-Werner", toml.Position{6, 1}}, - queryTestNode{"GitHub", toml.Position{7, 1}}, - queryTestNode{"GitHub Cofounder & CEO\nLikes tater tots and beer.", toml.Position{8, 1}}, - queryTestNode{"192.168.1.1", toml.Position{12, 1}}, - queryTestNode{"10.0.0.1", toml.Position{21, 3}}, - queryTestNode{"eqdc10", toml.Position{22, 3}}, - queryTestNode{"10.0.0.2", toml.Position{25, 3}}, - queryTestNode{"eqdc10", toml.Position{26, 3}}, - }) - - assertQueryPositions(t, string(buff), - "$..[?(float)]", - []interface{}{ - queryTestNode{4e-08, toml.Position{30, 1}}, - }) - - tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - assertQueryPositions(t, string(buff), - "$..[?(tree)]", - []interface{}{ - queryTestNode{ - map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": tv, - }, toml.Position{5, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "server": "192.168.1.1", - "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, - "connection_max": int64(5000), - "enabled": true, - }, toml.Position{11, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, toml.Position{17, 1}, - }, - queryTestNode{ - map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, toml.Position{20, 3}, - }, - queryTestNode{ - map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, toml.Position{24, 3}, - }, - queryTestNode{ - map[string]interface{}{ - "data": []interface{}{ - []interface{}{"gamma", "delta"}, - []interface{}{int64(1), int64(2)}, - }, - "score": 4e-08, - }, toml.Position{28, 1}, - }, - }) - - assertQueryPositions(t, string(buff), - "$..[?(time)]", - []interface{}{ - queryTestNode{tv, toml.Position{9, 1}}, - }) - - assertQueryPositions(t, string(buff), - "$..[?(bool)]", - []interface{}{ - queryTestNode{true, toml.Position{15, 1}}, - }) -} diff --git a/query/query.go b/query/query.go deleted file mode 100644 index 1c6cd80..0000000 --- a/query/query.go +++ /dev/null @@ -1,158 +0,0 @@ -package 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 - }, -} diff --git a/query/query_test.go b/query/query_test.go deleted file mode 100644 index 87d1351..0000000 --- a/query/query_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package query - -import ( - "fmt" - "testing" - - "github.com/pelletier/go-toml" -) - -func assertArrayContainsInOrder(t *testing.T, array []interface{}, objects ...interface{}) { - if len(array) != len(objects) { - t.Fatalf("array contains %d objects but %d are expected", len(array), len(objects)) - } - - for i := 0; i < len(array); i++ { - if array[i] != objects[i] { - t.Fatalf("wanted '%s', have '%s'", objects[i], array[i]) - } - } -} - -func checkQuery(t *testing.T, tree *toml.Tree, query string, objects ...interface{}) { - results, err := CompileAndExecute(query, tree) - if err != nil { - t.Fatal("unexpected error:", err) - } - assertArrayContainsInOrder(t, results.Values(), objects...) -} - -func TestQueryExample(t *testing.T) { - config, _ := toml.Load(` - [[book]] - title = "The Stand" - author = "Stephen King" - [[book]] - title = "For Whom the Bell Tolls" - author = "Ernest Hemmingway" - [[book]] - title = "Neuromancer" - author = "William Gibson" - `) - - checkQuery(t, config, "$.book.author", "Stephen King", "Ernest Hemmingway", "William Gibson") - - checkQuery(t, config, "$.book[0].author", "Stephen King") - checkQuery(t, config, "$.book[-1].author", "William Gibson") - checkQuery(t, config, "$.book[1:].author", "Ernest Hemmingway", "William Gibson") - checkQuery(t, config, "$.book[-1:].author", "William Gibson") - checkQuery(t, config, "$.book[::2].author", "Stephen King", "William Gibson") - checkQuery(t, config, "$.book[::-1].author", "William Gibson", "Ernest Hemmingway", "Stephen King") - checkQuery(t, config, "$.book[:].author", "Stephen King", "Ernest Hemmingway", "William Gibson") - checkQuery(t, config, "$.book[::].author", "Stephen King", "Ernest Hemmingway", "William Gibson") -} - -func TestQueryReadmeExample(t *testing.T) { - config, _ := toml.Load(` -[postgres] -user = "pelletier" -password = "mypassword" -`) - - checkQuery(t, config, "$..[user,password]", "pelletier", "mypassword") -} - -func TestQueryPathNotPresent(t *testing.T) { - config, _ := toml.Load(`a = "hello"`) - query, err := Compile("$.foo.bar") - if err != nil { - t.Fatal("unexpected error:", err) - } - results := query.Execute(config) - if err != nil { - t.Fatalf("err should be nil. got %s instead", err) - } - if len(results.items) != 0 { - t.Fatalf("no items should be matched. %d matched instead", len(results.items)) - } -} - -func ExampleNodeFilterFn_filterExample() { - tree, _ := toml.Load(` - [struct_one] - foo = "foo" - bar = "bar" - - [struct_two] - baz = "baz" - gorf = "gorf" - `) - - // create a query that references a user-defined-filter - query, _ := Compile("$[?(bazOnly)]") - - // define the filter, and assign it to the query - query.SetFilter("bazOnly", func(node interface{}) bool { - if tree, ok := node.(*toml.Tree); ok { - return tree.Has("baz") - } - return false // reject all other node types - }) - - // results contain only the 'struct_two' Tree - query.Execute(tree) -} - -func ExampleQuery_queryExample() { - config, _ := toml.Load(` - [[book]] - title = "The Stand" - author = "Stephen King" - [[book]] - title = "For Whom the Bell Tolls" - author = "Ernest Hemmingway" - [[book]] - title = "Neuromancer" - author = "William Gibson" - `) - - // find and print all the authors in the document - query, _ := Compile("$.book.author") - authors := query.Execute(config) - for _, name := range authors.Values() { - fmt.Println(name) - } -} - -func TestTomlQuery(t *testing.T) { - tree, err := toml.Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") - if err != nil { - t.Error(err) - return - } - query, err := Compile("$.foo.bar") - if err != nil { - t.Error(err) - return - } - result := query.Execute(tree) - values := result.Values() - if len(values) != 1 { - t.Errorf("Expected resultset of 1, got %d instead: %v", len(values), values) - } - - if tt, ok := values[0].(*toml.Tree); !ok { - t.Errorf("Expected type of Tree: %T", values[0]) - } else if tt.Get("a") != int64(1) { - t.Errorf("Expected 'a' with a value 1: %v", tt.Get("a")) - } else if tt.Get("b") != int64(2) { - t.Errorf("Expected 'b' with a value 2: %v", tt.Get("b")) - } -} diff --git a/query/tokens.go b/query/tokens.go deleted file mode 100644 index 098c856..0000000 --- a/query/tokens.go +++ /dev/null @@ -1,106 +0,0 @@ -package query - -import ( - "fmt" - "strconv" - - "github.com/pelletier/go-toml" -) - -// Define tokens -type tokenType int - -const ( - eof = -(iota + 1) -) - -const ( - tokenError tokenType = iota - tokenEOF - tokenKey - tokenString - tokenInteger - tokenFloat - tokenLeftBracket - tokenRightBracket - tokenLeftParen - tokenRightParen - tokenComma - tokenColon - tokenDollar - tokenStar - tokenQuestion - tokenDot - tokenDotDot -) - -var tokenTypeNames = []string{ - "Error", - "EOF", - "Key", - "String", - "Integer", - "Float", - "[", - "]", - "(", - ")", - ",", - ":", - "$", - "*", - "?", - ".", - "..", -} - -type token struct { - toml.Position - typ tokenType - val string -} - -func (tt tokenType) String() string { - idx := int(tt) - if idx < len(tokenTypeNames) { - return tokenTypeNames[idx] - } - return "Unknown" -} - -func (t token) Int() int { - if result, err := strconv.Atoi(t.val); err != nil { - panic(err) - } else { - return result - } -} - -func (t token) String() string { - switch t.typ { - case tokenEOF: - return "EOF" - case tokenError: - return t.val - } - - return fmt.Sprintf("%q", t.val) -} - -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -func isAlphanumeric(r rune) bool { - return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' -} - -func isDigit(r rune) bool { - return '0' <= r && r <= '9' -} - -func isHexDigit(r rune) bool { - return isDigit(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} diff --git a/token.go b/token.go deleted file mode 100644 index b437fdd..0000000 --- a/token.go +++ /dev/null @@ -1,136 +0,0 @@ -package toml - -import "fmt" - -// Define tokens -type tokenType int - -const ( - eof = -(iota + 1) -) - -const ( - tokenError tokenType = iota - tokenEOF - tokenComment - tokenKey - tokenString - tokenInteger - tokenTrue - tokenFalse - tokenFloat - tokenInf - tokenNan - tokenEqual - tokenLeftBracket - tokenRightBracket - tokenLeftCurlyBrace - tokenRightCurlyBrace - tokenLeftParen - tokenRightParen - tokenDoubleLeftBracket - tokenDoubleRightBracket - tokenLocalDate - tokenLocalTime - tokenTimeOffset - tokenKeyGroup - tokenKeyGroupArray - tokenComma - tokenColon - tokenDollar - tokenStar - tokenQuestion - tokenDot - tokenDotDot - tokenEOL -) - -var tokenTypeNames = []string{ - "Error", - "EOF", - "Comment", - "Key", - "String", - "Integer", - "True", - "False", - "Float", - "Inf", - "NaN", - "=", - "[", - "]", - "{", - "}", - "(", - ")", - "]]", - "[[", - "LocalDate", - "LocalTime", - "TimeOffset", - "KeyGroup", - "KeyGroupArray", - ",", - ":", - "$", - "*", - "?", - ".", - "..", - "EOL", -} - -type token struct { - Position - typ tokenType - val string -} - -func (tt tokenType) String() string { - idx := int(tt) - if idx < len(tokenTypeNames) { - return tokenTypeNames[idx] - } - return "Unknown" -} - -func (t token) String() string { - switch t.typ { - case tokenEOF: - return "EOF" - case tokenError: - return t.val - } - - return fmt.Sprintf("%q", t.val) -} - -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -func isAlphanumeric(r rune) bool { - return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' -} - -func isKeyChar(r rune) bool { - // Keys start with the first character that isn't whitespace or [ and end - // with the last non-whitespace character before the equals sign. Keys - // cannot contain a # character." - return !(r == '\r' || r == '\n' || r == eof || r == '=') -} - -func isKeyStartChar(r rune) bool { - return !(isSpace(r) || r == '\r' || r == '\n' || r == eof || r == '[') -} - -func isDigit(r rune) bool { - return '0' <= r && r <= '9' -} - -func isHexDigit(r rune) bool { - return isDigit(r) || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} diff --git a/token_test.go b/token_test.go deleted file mode 100644 index 4508225..0000000 --- a/token_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package toml - -import "testing" - -func TestTokenStringer(t *testing.T) { - var tests = []struct { - tt tokenType - expect string - }{ - {tokenError, "Error"}, - {tokenEOF, "EOF"}, - {tokenComment, "Comment"}, - {tokenKey, "Key"}, - {tokenString, "String"}, - {tokenInteger, "Integer"}, - {tokenTrue, "True"}, - {tokenFalse, "False"}, - {tokenFloat, "Float"}, - {tokenEqual, "="}, - {tokenLeftBracket, "["}, - {tokenRightBracket, "]"}, - {tokenLeftCurlyBrace, "{"}, - {tokenRightCurlyBrace, "}"}, - {tokenLeftParen, "("}, - {tokenRightParen, ")"}, - {tokenDoubleLeftBracket, "]]"}, - {tokenDoubleRightBracket, "[["}, - {tokenLocalDate, "LocalDate"}, - {tokenLocalTime, "LocalTime"}, - {tokenTimeOffset, "TimeOffset"}, - {tokenKeyGroup, "KeyGroup"}, - {tokenKeyGroupArray, "KeyGroupArray"}, - {tokenComma, ","}, - {tokenColon, ":"}, - {tokenDollar, "$"}, - {tokenStar, "*"}, - {tokenQuestion, "?"}, - {tokenDot, "."}, - {tokenDotDot, ".."}, - {tokenEOL, "EOL"}, - {tokenEOL + 1, "Unknown"}, - } - - for i, test := range tests { - got := test.tt.String() - if got != test.expect { - t.Errorf("[%d] invalid string of token type; got %q, expected %q", i, got, test.expect) - } - } -} - -func TestTokenString(t *testing.T) { - var tests = []struct { - tok token - expect string - }{ - {token{Position{1, 1}, tokenEOF, ""}, "EOF"}, - {token{Position{1, 1}, tokenError, "Δt"}, "Δt"}, - {token{Position{1, 1}, tokenString, "bar"}, `"bar"`}, - {token{Position{1, 1}, tokenString, "123456789012345"}, `"123456789012345"`}, - } - - for i, test := range tests { - got := test.tok.String() - if got != test.expect { - t.Errorf("[%d] invalid of string token; got %q, expected %q", i, got, test.expect) - } - } -} diff --git a/toml.go b/toml.go deleted file mode 100644 index cbb89a9..0000000 --- a/toml.go +++ /dev/null @@ -1,529 +0,0 @@ -package toml - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "runtime" - "strings" -) - -type tomlValue struct { - value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list - comment string - commented bool - multiline bool - position Position -} - -// Tree is the result of the parsing of a TOML file. -type Tree struct { - values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree - comment string - commented bool - inline bool - position Position -} - -func newTree() *Tree { - return newTreeWithPosition(Position{}) -} - -func newTreeWithPosition(pos Position) *Tree { - return &Tree{ - values: make(map[string]interface{}), - position: pos, - } -} - -// TreeFromMap initializes a new Tree object using the given map. -func TreeFromMap(m map[string]interface{}) (*Tree, error) { - result, err := toTree(m) - if err != nil { - return nil, err - } - return result.(*Tree), nil -} - -// Position returns the position of the tree. -func (t *Tree) Position() Position { - return t.position -} - -// Has returns a boolean indicating if the given key exists. -func (t *Tree) Has(key string) bool { - if key == "" { - return false - } - return t.HasPath(strings.Split(key, ".")) -} - -// HasPath returns true if the given path of keys exists, false otherwise. -func (t *Tree) HasPath(keys []string) bool { - return t.GetPath(keys) != nil -} - -// Keys returns the keys of the toplevel tree (does not recurse). -func (t *Tree) Keys() []string { - keys := make([]string, len(t.values)) - i := 0 - for k := range t.values { - keys[i] = k - i++ - } - return keys -} - -// Get the value at key in the Tree. -// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. -// If you need to retrieve non-bare keys, use GetPath. -// Returns nil if the path does not exist in the tree. -// If keys is of length zero, the current tree is returned. -func (t *Tree) Get(key string) interface{} { - if key == "" { - return t - } - return t.GetPath(strings.Split(key, ".")) -} - -// GetPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetPath(keys []string) interface{} { - if len(keys) == 0 { - return t - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return nil - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return nil - } - subtree = node[len(node)-1] - default: - return nil // cannot navigate through other node types - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - return node.value - default: - return node - } -} - -// GetArray returns the value at key in the Tree. -// It returns []string, []int64, etc type if key has homogeneous lists -// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. -// Returns nil if the path does not exist in the tree. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetArray(key string) interface{} { - if key == "" { - return t - } - return t.GetArrayPath(strings.Split(key, ".")) -} - -// GetArrayPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetArrayPath(keys []string) interface{} { - if len(keys) == 0 { - return t - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return nil - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return nil - } - subtree = node[len(node)-1] - default: - return nil // cannot navigate through other node types - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - switch n := node.value.(type) { - case []interface{}: - return getArray(n) - default: - return node.value - } - default: - return node - } -} - -// if homogeneous array, then return slice type object over []interface{} -func getArray(n []interface{}) interface{} { - var s []string - var i64 []int64 - var f64 []float64 - var bl []bool - for _, value := range n { - switch v := value.(type) { - case string: - s = append(s, v) - case int64: - i64 = append(i64, v) - case float64: - f64 = append(f64, v) - case bool: - bl = append(bl, v) - default: - return n - } - } - if len(s) == len(n) { - return s - } else if len(i64) == len(n) { - return i64 - } else if len(f64) == len(n) { - return f64 - } else if len(bl) == len(n) { - return bl - } - return n -} - -// GetPosition returns the position of the given key. -func (t *Tree) GetPosition(key string) Position { - if key == "" { - return t.position - } - return t.GetPositionPath(strings.Split(key, ".")) -} - -// SetPositionPath sets the position of element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree position is set. -func (t *Tree) SetPositionPath(keys []string, pos Position) { - if len(keys) == 0 { - t.position = pos - return - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return - } - subtree = node[len(node)-1] - default: - return - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - node.position = pos - return - case *Tree: - node.position = pos - return - case []*Tree: - // go to most recent element - if len(node) == 0 { - return - } - node[len(node)-1].position = pos - return - } -} - -// GetPositionPath returns the element in the tree indicated by 'keys'. -// If keys is of length zero, the current tree is returned. -func (t *Tree) GetPositionPath(keys []string) Position { - if len(keys) == 0 { - return t.position - } - subtree := t - for _, intermediateKey := range keys[:len(keys)-1] { - value, exists := subtree.values[intermediateKey] - if !exists { - return Position{0, 0} - } - switch node := value.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - return Position{0, 0} - } - subtree = node[len(node)-1] - default: - return Position{0, 0} - } - } - // branch based on final node type - switch node := subtree.values[keys[len(keys)-1]].(type) { - case *tomlValue: - return node.position - case *Tree: - return node.position - case []*Tree: - // go to most recent element - if len(node) == 0 { - return Position{0, 0} - } - return node[len(node)-1].position - default: - return Position{0, 0} - } -} - -// GetDefault works like Get but with a default value -func (t *Tree) GetDefault(key string, def interface{}) interface{} { - val := t.Get(key) - if val == nil { - return def - } - return val -} - -// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. -// The default values within the struct are valid default options. -type SetOptions struct { - Comment string - Commented bool - Multiline bool -} - -// SetWithOptions is the same as Set, but allows you to provide formatting -// instructions to the key, that will be used by Marshal(). -func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { - t.SetPathWithOptions(strings.Split(key, "."), opts, value) -} - -// SetPathWithOptions is the same as SetPath, but allows you to provide -// formatting instructions to the key, that will be reused by Marshal(). -func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { - subtree := t - for i, intermediateKey := range keys[:len(keys)-1] { - nextTree, exists := subtree.values[intermediateKey] - if !exists { - nextTree = newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) - subtree.values[intermediateKey] = nextTree // add new element here - } - switch node := nextTree.(type) { - case *Tree: - subtree = node - case []*Tree: - // go to most recent element - if len(node) == 0 { - // create element if it does not exist - node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})) - subtree.values[intermediateKey] = node - } - subtree = node[len(node)-1] - } - } - - var toInsert interface{} - - switch v := value.(type) { - case *Tree: - v.comment = opts.Comment - v.commented = opts.Commented - toInsert = value - case []*Tree: - for i := range v { - v[i].commented = opts.Commented - } - toInsert = value - case *tomlValue: - v.comment = opts.Comment - v.commented = opts.Commented - v.multiline = opts.Multiline - 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 - tree.inline = subtree.inline - subtree.values[intermediateKey] = tree - nextTree = tree - } - - switch node := nextTree.(type) { - case []*Tree: - subtree = node[len(node)-1] - case *Tree: - subtree = node - default: - return fmt.Errorf("unknown type for path %s (%s): %T (%#v)", - strings.Join(keys, "."), intermediateKey, nextTree, nextTree) - } - } - return nil -} - -// LoadBytes creates a Tree from a []byte. -func LoadBytes(b []byte) (tree *Tree, err error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - err = 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) -} diff --git a/toml_test.go b/toml_test.go deleted file mode 100644 index 0c7b6d3..0000000 --- a/toml_test.go +++ /dev/null @@ -1,261 +0,0 @@ -// Testing support for go-toml - -package toml - -import ( - "reflect" - "testing" -) - -func TestTomlHas(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if !tree.Has("test.key") { - t.Errorf("Has - expected test.key to exists") - } - - if tree.Has("") { - t.Errorf("Should return false if the key is not provided") - } -} - -func TestTomlGet(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if tree.Get("") != tree { - t.Errorf("Get should return the tree itself when given an empty path") - } - - if tree.Get("test.key") != "value" { - t.Errorf("Get should return the value") - } - if tree.Get(`\`) != nil { - t.Errorf("should return nil when the key is malformed") - } -} - -func TestTomlGetArray(t *testing.T) { - tree, _ := Load(` - [test] - key = ["one", "two"] - key2 = [true, false, false] - key3 = [1.5,2.5] - `) - - if tree.GetArray("") != tree { - t.Errorf("GetArray should return the tree itself when given an empty path") - } - - expect := []string{"one", "two"} - actual := tree.GetArray("test.key").([]string) - if !reflect.DeepEqual(actual, expect) { - t.Errorf("GetArray should return the []string value") - } - - expect2 := []bool{true, false, false} - actual2 := tree.GetArray("test.key2").([]bool) - if !reflect.DeepEqual(actual2, expect2) { - t.Errorf("GetArray should return the []bool value") - } - - expect3 := []float64{1.5, 2.5} - actual3 := tree.GetArray("test.key3").([]float64) - if !reflect.DeepEqual(actual3, expect3) { - t.Errorf("GetArray should return the []float64 value") - } - - if tree.GetArray(`\`) != nil { - t.Errorf("should return nil when the key is malformed") - } -} - -func TestTomlGetDefault(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if tree.GetDefault("", "hello") != tree { - t.Error("GetDefault should return the tree itself when given an empty path") - } - - if tree.GetDefault("test.key", "hello") != "value" { - t.Error("Get should return the value") - } - - if tree.GetDefault("whatever", "hello") != "hello" { - t.Error("GetDefault should return the default value if the key does not exist") - } -} - -func TestTomlHasPath(t *testing.T) { - tree, _ := Load(` - [test] - key = "value" - `) - - if !tree.HasPath([]string{"test", "key"}) { - t.Errorf("HasPath - expected test.key to exists") - } -} - -func TestTomlDelete(t *testing.T) { - tree, _ := Load(` - key = "value" - `) - err := tree.Delete("key") - if err != nil { - t.Errorf("Delete - unexpected error while deleting key: %s", err.Error()) - } - - if tree.Get("key") != nil { - t.Errorf("Delete should have removed key but did not.") - } - -} - -func TestTomlDeleteUnparsableKey(t *testing.T) { - tree, _ := Load(` - key = "value" - `) - err := tree.Delete(".") - if err == nil { - t.Errorf("Delete should error") - } -} - -func TestTomlDeleteNestedKey(t *testing.T) { - tree, _ := Load(` - [foo] - [foo.bar] - key = "value" - `) - err := tree.Delete("foo.bar.key") - if err != nil { - t.Errorf("Error while deleting nested key: %s", err.Error()) - } - - if tree.Get("key") != nil { - t.Errorf("Delete should have removed nested key but did not.") - } - -} - -func TestTomlDeleteNonexistentNestedKey(t *testing.T) { - tree, _ := Load(` - [foo] - [foo.bar] - key = "value" - `) - err := tree.Delete("foo.not.there.key") - if err == nil { - t.Errorf("Delete should have thrown an error trying to delete key in nonexistent tree") - } -} - -func TestTomlGetPath(t *testing.T) { - node := newTree() - //TODO: set other node data - - for idx, item := range []struct { - Path []string - Expected *Tree - }{ - { // empty path test - []string{}, - node, - }, - } { - result := node.GetPath(item.Path) - if result != item.Expected { - t.Errorf("GetPath[%d] %v - expected %v, got %v instead.", idx, item.Path, item.Expected, result) - } - } - - tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") - if tree.GetPath([]string{"whatever"}) != nil { - t.Error("GetPath should return nil when the key does not exist") - } -} - -func TestTomlGetArrayPath(t *testing.T) { - for idx, item := range []struct { - Name string - Path []string - Make func() (tree *Tree, expected interface{}) - }{ - { - Name: "empty", - Path: []string{}, - Make: func() (tree *Tree, expected interface{}) { - tree = newTree() - expected = tree - return - }, - }, - { - Name: "int64", - Path: []string{"a"}, - Make: func() (tree *Tree, expected interface{}) { - var err error - tree, err = Load(`a = [1,2,3]`) - if err != nil { - panic(err) - } - expected = []int64{1, 2, 3} - return - }, - }, - } { - t.Run(item.Name, func(t *testing.T) { - tree, expected := item.Make() - result := tree.GetArrayPath(item.Path) - if !reflect.DeepEqual(result, expected) { - t.Errorf("GetArrayPath[%d] %v - expected %#v, got %#v instead.", idx, item.Path, expected, result) - } - }) - } - - tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6") - if tree.GetArrayPath([]string{"whatever"}) != nil { - t.Error("GetArrayPath should return nil when the key does not exist") - } - -} - -func TestTomlFromMap(t *testing.T) { - simpleMap := map[string]interface{}{"hello": 42} - tree, err := TreeFromMap(simpleMap) - if err != nil { - t.Fatal("unexpected error:", err) - } - if tree.Get("hello") != int64(42) { - t.Fatal("hello should be 42, not", tree.Get("hello")) - } -} - -func TestLoadBytesBOM(t *testing.T) { - payloads := [][]byte{ - []byte("\xFE\xFFhello=1"), - []byte("\xFF\xFEhello=1"), - []byte("\xEF\xBB\xBFhello=1"), - []byte("\x00\x00\xFE\xFFhello=1"), - []byte("\xFF\xFE\x00\x00hello=1"), - } - for _, data := range payloads { - tree, err := LoadBytes(data) - if err != nil { - t.Fatal("unexpected error:", err, "for:", data) - } - v := tree.Get("hello") - if v != int64(1) { - t.Fatal("hello should be 1, not", v) - } - } -} diff --git a/toml_testgen_support_test.go b/toml_testgen_support_test.go deleted file mode 100644 index eef9b9f..0000000 --- a/toml_testgen_support_test.go +++ /dev/null @@ -1,119 +0,0 @@ -// 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, - } -} diff --git a/toml_testgen_test.go b/toml_testgen_test.go deleted file mode 100644 index 2306926..0000000 --- a/toml_testgen_test.go +++ /dev/null @@ -1,928 +0,0 @@ -// Generated by tomltestgen for toml-test ref 39e37e6 on 2019-03-19T23:58:45-07:00 -package toml - -import ( - "testing" -) - -func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { - input := `no-leads = 1987-7-05T17:45:00Z` - testgenInvalid(t, input) -} - -func TestInvalidDatetimeMalformedNoSecs(t *testing.T) { - input := `no-secs = 1987-07-05T17:45Z` - testgenInvalid(t, input) -} - -func TestInvalidDatetimeMalformedNoT(t *testing.T) { - input := `no-t = 1987-07-0517:45:00Z` - testgenInvalid(t, input) -} - -func TestInvalidDatetimeMalformedWithMilli(t *testing.T) { - input := `with-milli = 1987-07-5T17:45:00.12Z` - testgenInvalid(t, input) -} - -func TestInvalidDuplicateKeyTable(t *testing.T) { - input := `[fruit] -type = "apple" - -[fruit.type] -apple = "yes"` - testgenInvalid(t, input) -} - -func TestInvalidDuplicateKeys(t *testing.T) { - input := `dupe = false -dupe = true` - testgenInvalid(t, input) -} - -func TestInvalidDuplicateTables(t *testing.T) { - input := `[a] -[a]` - testgenInvalid(t, input) -} - -func TestInvalidEmptyImplicitTable(t *testing.T) { - input := `[naughty..naughty]` - testgenInvalid(t, input) -} - -func TestInvalidEmptyTable(t *testing.T) { - input := `[]` - testgenInvalid(t, input) -} - -func TestInvalidFloatNoLeadingZero(t *testing.T) { - input := `answer = .12345 -neganswer = -.12345` - testgenInvalid(t, input) -} - -func TestInvalidFloatNoTrailingDigits(t *testing.T) { - input := `answer = 1. -neganswer = -1.` - testgenInvalid(t, input) -} - -func TestInvalidKeyEmpty(t *testing.T) { - input := ` = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyHash(t *testing.T) { - input := `a# = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyNewline(t *testing.T) { - input := `a -= 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyOpenBracket(t *testing.T) { - input := `[abc = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeySingleOpenBracket(t *testing.T) { - input := `[` - testgenInvalid(t, input) -} - -func TestInvalidKeySpace(t *testing.T) { - input := `a b = 1` - testgenInvalid(t, input) -} - -func TestInvalidKeyStartBracket(t *testing.T) { - input := `[a] -[xyz = 5 -[b]` - testgenInvalid(t, input) -} - -func TestInvalidKeyTwoEquals(t *testing.T) { - input := `key= = 1` - testgenInvalid(t, input) -} - -func TestInvalidStringBadByteEscape(t *testing.T) { - input := `naughty = "\xAg"` - testgenInvalid(t, input) -} - -func TestInvalidStringBadEscape(t *testing.T) { - input := `invalid-escape = "This string has a bad \a escape character."` - testgenInvalid(t, input) -} - -func TestInvalidStringByteEscapes(t *testing.T) { - input := `answer = "\x33"` - testgenInvalid(t, input) -} - -func TestInvalidStringNoClose(t *testing.T) { - input := `no-ending-quote = "One time, at band camp` - testgenInvalid(t, input) -} - -func TestInvalidTableArrayImplicit(t *testing.T) { - input := "# This test is a bit tricky. It should fail because the first use of\n" + - "# `[[albums.songs]]` without first declaring `albums` implies that `albums`\n" + - "# must be a table. The alternative would be quite weird. Namely, it wouldn't\n" + - "# comply with the TOML spec: \"Each double-bracketed sub-table will belong to \n" + - "# the most *recently* defined table element *above* it.\"\n" + - "#\n" + - "# This is in contrast to the *valid* test, table-array-implicit where\n" + - "# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared\n" + - "# later. (Although, `[albums]` could be.)\n" + - "[[albums.songs]]\n" + - "name = \"Glory Days\"\n" + - "\n" + - "[[albums]]\n" + - "name = \"Born in the USA\"\n" - testgenInvalid(t, input) -} - -func TestInvalidTableArrayMalformedBracket(t *testing.T) { - input := `[[albums] -name = "Born to Run"` - testgenInvalid(t, input) -} - -func TestInvalidTableArrayMalformedEmpty(t *testing.T) { - input := `[[]] -name = "Born to Run"` - testgenInvalid(t, input) -} - -func TestInvalidTableEmpty(t *testing.T) { - input := `[]` - testgenInvalid(t, input) -} - -func TestInvalidTableNestedBracketsClose(t *testing.T) { - input := `[a]b] -zyx = 42` - testgenInvalid(t, input) -} - -func TestInvalidTableNestedBracketsOpen(t *testing.T) { - input := `[a[b] -zyx = 42` - testgenInvalid(t, input) -} - -func TestInvalidTableWhitespace(t *testing.T) { - input := `[invalid key]` - testgenInvalid(t, input) -} - -func TestInvalidTableWithPound(t *testing.T) { - input := `[key#group] -answer = 42` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterArrayEntries(t *testing.T) { - input := `array = [ - "Is there life after an array separator?", No - "Entry" -]` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterInteger(t *testing.T) { - input := `answer = 42 the ultimate answer?` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterString(t *testing.T) { - input := `string = "Is there life after strings?" No.` - testgenInvalid(t, input) -} - -func TestInvalidTextAfterTable(t *testing.T) { - input := `[error] this shouldn't be here` - testgenInvalid(t, input) -} - -func TestInvalidTextBeforeArraySeparator(t *testing.T) { - input := `array = [ - "Is there life before an array separator?" No, - "Entry" -]` - testgenInvalid(t, input) -} - -func TestInvalidTextInArray(t *testing.T) { - input := `array = [ - "Entry 1", - I don't belong, - "Entry 2", -]` - testgenInvalid(t, input) -} - -func TestValidArrayEmpty(t *testing.T) { - input := `thevoid = [[[[[]]]]]` - jsonRef := `{ - "thevoid": { "type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": []} - ]} - ]} - ]} - ]} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArrayNospaces(t *testing.T) { - input := `ints = [1,2,3]` - jsonRef := `{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArraysHetergeneous(t *testing.T) { - input := `mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]` - jsonRef := `{ - "mixed": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"} - ]}, - {"type": "array", "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"} - ]} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArraysNested(t *testing.T) { - input := `nest = [["a"], ["b"]]` - jsonRef := `{ - "nest": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "string", "value": "a"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "b"} - ]} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidArrays(t *testing.T) { - input := `ints = [1, 2, 3] -floats = [1.1, 2.1, 3.1] -strings = ["a", "b", "c"] -dates = [ - 1987-07-05T17:45:00Z, - 1979-05-27T07:32:00Z, - 2006-06-01T11:00:00Z, -]` - jsonRef := `{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - }, - "floats": { - "type": "array", - "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"}, - {"type": "float", "value": "3.1"} - ] - }, - "strings": { - "type": "array", - "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"}, - {"type": "string", "value": "c"} - ] - }, - "dates": { - "type": "array", - "value": [ - {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, - {"type": "datetime", "value": "2006-06-01T11:00:00Z"} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidBool(t *testing.T) { - input := `t = true -f = false` - jsonRef := `{ - "f": {"type": "bool", "value": "false"}, - "t": {"type": "bool", "value": "true"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidCommentsEverywhere(t *testing.T) { - input := `# Top comment. - # Top comment. -# Top comment. - -# [no-extraneous-groups-please] - -[group] # Comment -answer = 42 # Comment -# no-extraneous-keys-please = 999 -# Inbetween comment. -more = [ # Comment - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. - 42, 42, # Comments within arrays are fun. - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. -# ] Did I fool you? -] # Hopefully not.` - jsonRef := `{ - "group": { - "answer": {"type": "integer", "value": "42"}, - "more": { - "type": "array", - "value": [ - {"type": "integer", "value": "42"}, - {"type": "integer", "value": "42"} - ] - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidDatetime(t *testing.T) { - input := `bestdayever = 1987-07-05T17:45:00Z` - jsonRef := `{ - "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidEmpty(t *testing.T) { - input := `` - jsonRef := `{}` - testgenValid(t, input, jsonRef) -} - -func TestValidExample(t *testing.T) { - input := `best-day-ever = 1987-07-05T17:45:00Z - -[numtheory] -boring = false -perfection = [6, 28, 496]` - jsonRef := `{ - "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - "numtheory": { - "boring": {"type": "bool", "value": "false"}, - "perfection": { - "type": "array", - "value": [ - {"type": "integer", "value": "6"}, - {"type": "integer", "value": "28"}, - {"type": "integer", "value": "496"} - ] - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidFloat(t *testing.T) { - input := `pi = 3.14 -negpi = -3.14` - jsonRef := `{ - "pi": {"type": "float", "value": "3.14"}, - "negpi": {"type": "float", "value": "-3.14"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidImplicitAndExplicitAfter(t *testing.T) { - input := `[a.b.c] -answer = 42 - -[a] -better = 43` - jsonRef := `{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidImplicitAndExplicitBefore(t *testing.T) { - input := `[a] -better = 43 - -[a.b.c] -answer = 42` - jsonRef := `{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidImplicitGroups(t *testing.T) { - input := `[a.b.c] -answer = 42` - jsonRef := `{ - "a": { - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidInteger(t *testing.T) { - input := `answer = 42 -neganswer = -42` - jsonRef := `{ - "answer": {"type": "integer", "value": "42"}, - "neganswer": {"type": "integer", "value": "-42"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidKeyEqualsNospace(t *testing.T) { - input := `answer=42` - jsonRef := `{ - "answer": {"type": "integer", "value": "42"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidKeySpace(t *testing.T) { - input := `"a b" = 1` - jsonRef := `{ - "a b": {"type": "integer", "value": "1"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidKeySpecialChars(t *testing.T) { - input := "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\" = 1\n" - jsonRef := "{\n" + - " \"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": {\n" + - " \"type\": \"integer\", \"value\": \"1\"\n" + - " }\n" + - "}\n" - testgenValid(t, input, jsonRef) -} - -func TestValidLongFloat(t *testing.T) { - input := `longpi = 3.141592653589793 -neglongpi = -3.141592653589793` - jsonRef := `{ - "longpi": {"type": "float", "value": "3.141592653589793"}, - "neglongpi": {"type": "float", "value": "-3.141592653589793"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidLongInteger(t *testing.T) { - input := `answer = 9223372036854775807 -neganswer = -9223372036854775808` - jsonRef := `{ - "answer": {"type": "integer", "value": "9223372036854775807"}, - "neganswer": {"type": "integer", "value": "-9223372036854775808"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidMultilineString(t *testing.T) { - input := `multiline_empty_one = """""" -multiline_empty_two = """ -""" -multiline_empty_three = """\ - """ -multiline_empty_four = """\ - \ - \ - """ - -equivalent_one = "The quick brown fox jumps over the lazy dog." -equivalent_two = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -equivalent_three = """\ - The quick brown \ - fox jumps over \ - the lazy dog.\ - """` - jsonRef := `{ - "multiline_empty_one": { - "type": "string", - "value": "" - }, - "multiline_empty_two": { - "type": "string", - "value": "" - }, - "multiline_empty_three": { - "type": "string", - "value": "" - }, - "multiline_empty_four": { - "type": "string", - "value": "" - }, - "equivalent_one": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_two": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_three": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidRawMultilineString(t *testing.T) { - input := `oneline = '''This string has a ' quote character.''' -firstnl = ''' -This string has a ' quote character.''' -multiline = ''' -This string -has ' a quote character -and more than -one newline -in it.'''` - jsonRef := `{ - "oneline": { - "type": "string", - "value": "This string has a ' quote character." - }, - "firstnl": { - "type": "string", - "value": "This string has a ' quote character." - }, - "multiline": { - "type": "string", - "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidRawString(t *testing.T) { - input := `backspace = 'This string has a \b backspace character.' -tab = 'This string has a \t tab character.' -newline = 'This string has a \n new line character.' -formfeed = 'This string has a \f form feed character.' -carriage = 'This string has a \r carriage return character.' -slash = 'This string has a \/ slash character.' -backslash = 'This string has a \\ backslash character.'` - jsonRef := `{ - "backspace": { - "type": "string", - "value": "This string has a \\b backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \\t tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \\n new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \\f form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \\r carriage return character." - }, - "slash": { - "type": "string", - "value": "This string has a \\/ slash character." - }, - "backslash": { - "type": "string", - "value": "This string has a \\\\ backslash character." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringEmpty(t *testing.T) { - input := `answer = ""` - jsonRef := `{ - "answer": { - "type": "string", - "value": "" - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringEscapes(t *testing.T) { - input := `backspace = "This string has a \b backspace character." -tab = "This string has a \t tab character." -newline = "This string has a \n new line character." -formfeed = "This string has a \f form feed character." -carriage = "This string has a \r carriage return character." -quote = "This string has a \" quote character." -backslash = "This string has a \\ backslash character." -notunicode1 = "This string does not have a unicode \\u escape." -notunicode2 = "This string does not have a unicode \u005Cu escape." -notunicode3 = "This string does not have a unicode \\u0075 escape." -notunicode4 = "This string does not have a unicode \\\u0075 escape."` - jsonRef := `{ - "backspace": { - "type": "string", - "value": "This string has a \u0008 backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \u0009 tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \u000A new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \u000C form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \u000D carriage return character." - }, - "quote": { - "type": "string", - "value": "This string has a \u0022 quote character." - }, - "backslash": { - "type": "string", - "value": "This string has a \u005C backslash character." - }, - "notunicode1": { - "type": "string", - "value": "This string does not have a unicode \\u escape." - }, - "notunicode2": { - "type": "string", - "value": "This string does not have a unicode \u005Cu escape." - }, - "notunicode3": { - "type": "string", - "value": "This string does not have a unicode \\u0075 escape." - }, - "notunicode4": { - "type": "string", - "value": "This string does not have a unicode \\\u0075 escape." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringSimple(t *testing.T) { - input := `answer = "You are not drinking enough whisky."` - jsonRef := `{ - "answer": { - "type": "string", - "value": "You are not drinking enough whisky." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidStringWithPound(t *testing.T) { - input := `pound = "We see no # comments here." -poundcomment = "But there are # some comments here." # Did I # mess you up?` - jsonRef := `{ - "pound": {"type": "string", "value": "We see no # comments here."}, - "poundcomment": { - "type": "string", - "value": "But there are # some comments here." - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayImplicit(t *testing.T) { - input := `[[albums.songs]] -name = "Glory Days"` - jsonRef := `{ - "albums": { - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}} - ] - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayMany(t *testing.T) { - input := `[[people]] -first_name = "Bruce" -last_name = "Springsteen" - -[[people]] -first_name = "Eric" -last_name = "Clapton" - -[[people]] -first_name = "Bob" -last_name = "Seger"` - jsonRef := `{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - }, - { - "first_name": {"type": "string", "value": "Eric"}, - "last_name": {"type": "string", "value": "Clapton"} - }, - { - "first_name": {"type": "string", "value": "Bob"}, - "last_name": {"type": "string", "value": "Seger"} - } - ] -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayNest(t *testing.T) { - input := `[[albums]] -name = "Born to Run" - - [[albums.songs]] - name = "Jungleland" - - [[albums.songs]] - name = "Meeting Across the River" - -[[albums]] -name = "Born in the USA" - - [[albums.songs]] - name = "Glory Days" - - [[albums.songs]] - name = "Dancing in the Dark"` - jsonRef := `{ - "albums": [ - { - "name": {"type": "string", "value": "Born to Run"}, - "songs": [ - {"name": {"type": "string", "value": "Jungleland"}}, - {"name": {"type": "string", "value": "Meeting Across the River"}} - ] - }, - { - "name": {"type": "string", "value": "Born in the USA"}, - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}}, - {"name": {"type": "string", "value": "Dancing in the Dark"}} - ] - } - ] -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableArrayOne(t *testing.T) { - input := `[[people]] -first_name = "Bruce" -last_name = "Springsteen"` - jsonRef := `{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - } - ] -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableEmpty(t *testing.T) { - input := `[a]` - jsonRef := `{ - "a": {} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableSubEmpty(t *testing.T) { - input := `[a] -[a.b]` - jsonRef := `{ - "a": { "b": {} } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableWhitespace(t *testing.T) { - input := `["valid key"]` - jsonRef := `{ - "valid key": {} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidTableWithPound(t *testing.T) { - input := `["key#group"] -answer = 42` - jsonRef := `{ - "key#group": { - "answer": {"type": "integer", "value": "42"} - } -}` - testgenValid(t, input, jsonRef) -} - -func TestValidUnicodeEscape(t *testing.T) { - input := `answer4 = "\u03B4" -answer8 = "\U000003B4"` - jsonRef := `{ - "answer4": {"type": "string", "value": "\u03B4"}, - "answer8": {"type": "string", "value": "\u03B4"} -}` - testgenValid(t, input, jsonRef) -} - -func TestValidUnicodeLiteral(t *testing.T) { - input := `answer = "δ"` - jsonRef := `{ - "answer": {"type": "string", "value": "δ"} -}` - testgenValid(t, input, jsonRef) -} diff --git a/tomlpub.go b/tomlpub.go deleted file mode 100644 index 4136b46..0000000 --- a/tomlpub.go +++ /dev/null @@ -1,71 +0,0 @@ -package toml - -// PubTOMLValue wrapping tomlValue in order to access all properties from outside. -type PubTOMLValue = tomlValue - -func (ptv *PubTOMLValue) Value() interface{} { - return ptv.value -} -func (ptv *PubTOMLValue) Comment() string { - return ptv.comment -} -func (ptv *PubTOMLValue) Commented() bool { - return ptv.commented -} -func (ptv *PubTOMLValue) Multiline() bool { - return ptv.multiline -} -func (ptv *PubTOMLValue) Position() Position { - return ptv.position -} - -func (ptv *PubTOMLValue) SetValue(v interface{}) { - ptv.value = v -} -func (ptv *PubTOMLValue) SetComment(s string) { - ptv.comment = s -} -func (ptv *PubTOMLValue) SetCommented(c bool) { - ptv.commented = c -} -func (ptv *PubTOMLValue) SetMultiline(m bool) { - ptv.multiline = m -} -func (ptv *PubTOMLValue) SetPosition(p Position) { - ptv.position = p -} - -// PubTree wrapping Tree in order to access all properties from outside. -type PubTree = Tree - -func (pt *PubTree) Values() map[string]interface{} { - return pt.values -} - -func (pt *PubTree) Comment() string { - return pt.comment -} - -func (pt *PubTree) Commented() bool { - return pt.commented -} - -func (pt *PubTree) Inline() bool { - return pt.inline -} - -func (pt *PubTree) SetValues(v map[string]interface{}) { - pt.values = v -} - -func (pt *PubTree) SetComment(c string) { - pt.comment = c -} - -func (pt *PubTree) SetCommented(c bool) { - pt.commented = c -} - -func (pt *PubTree) SetInline(i bool) { - pt.inline = i -} diff --git a/tomltree_create.go b/tomltree_create.go deleted file mode 100644 index 8035350..0000000 --- a/tomltree_create.go +++ /dev/null @@ -1,155 +0,0 @@ -package toml - -import ( - "fmt" - "reflect" - "time" -) - -var kindToType = [reflect.String + 1]reflect.Type{ - reflect.Bool: reflect.TypeOf(true), - reflect.String: reflect.TypeOf(""), - reflect.Float32: reflect.TypeOf(float64(1)), - reflect.Float64: reflect.TypeOf(float64(1)), - reflect.Int: reflect.TypeOf(int64(1)), - reflect.Int8: reflect.TypeOf(int64(1)), - reflect.Int16: reflect.TypeOf(int64(1)), - reflect.Int32: reflect.TypeOf(int64(1)), - reflect.Int64: reflect.TypeOf(int64(1)), - reflect.Uint: reflect.TypeOf(uint64(1)), - reflect.Uint8: reflect.TypeOf(uint64(1)), - reflect.Uint16: reflect.TypeOf(uint64(1)), - reflect.Uint32: reflect.TypeOf(uint64(1)), - reflect.Uint64: reflect.TypeOf(uint64(1)), -} - -// typeFor returns a reflect.Type for a reflect.Kind, or nil if none is found. -// supported values: -// string, bool, int64, uint64, float64, time.Time, int, int8, int16, int32, uint, uint8, uint16, uint32, float32 -func typeFor(k reflect.Kind) reflect.Type { - if k > 0 && int(k) < len(kindToType) { - return kindToType[k] - } - return nil -} - -func simpleValueCoercion(object interface{}) (interface{}, error) { - switch original := object.(type) { - case string, bool, int64, uint64, float64, time.Time: - return original, nil - case int: - return int64(original), nil - case int8: - return int64(original), nil - case int16: - return int64(original), nil - case int32: - return int64(original), nil - case uint: - return uint64(original), nil - case uint8: - return uint64(original), nil - case uint16: - return uint64(original), nil - case uint32: - return uint64(original), nil - case float32: - return float64(original), nil - case fmt.Stringer: - return original.String(), nil - case []interface{}: - value := reflect.ValueOf(original) - length := value.Len() - arrayValue := reflect.MakeSlice(value.Type(), 0, length) - for i := 0; i < length; i++ { - val := value.Index(i).Interface() - simpleValue, err := simpleValueCoercion(val) - if err != nil { - return nil, err - } - arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) - } - return arrayValue.Interface(), nil - default: - return nil, fmt.Errorf("cannot convert type %T to Tree", object) - } -} - -func sliceToTree(object interface{}) (interface{}, error) { - // arrays are a bit tricky, since they can represent either a - // collection of simple values, which is represented by one - // *tomlValue, or an array of tables, which is represented by an - // array of *Tree. - - // holding the assumption that this function is called from toTree only when value.Kind() is Array or Slice - value := reflect.ValueOf(object) - insideType := value.Type().Elem() - length := value.Len() - if length > 0 { - insideType = reflect.ValueOf(value.Index(0).Interface()).Type() - } - if insideType.Kind() == reflect.Map { - // this is considered as an array of tables - tablesArray := make([]*Tree, 0, length) - for i := 0; i < length; i++ { - table := value.Index(i) - tree, err := toTree(table.Interface()) - if err != nil { - return nil, err - } - tablesArray = append(tablesArray, tree.(*Tree)) - } - return tablesArray, nil - } - - sliceType := typeFor(insideType.Kind()) - if sliceType == nil { - sliceType = insideType - } - - arrayValue := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, length) - - for i := 0; i < length; i++ { - val := value.Index(i).Interface() - simpleValue, err := simpleValueCoercion(val) - if err != nil { - return nil, err - } - arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) - } - return &tomlValue{value: arrayValue.Interface(), position: Position{}}, nil -} - -func toTree(object interface{}) (interface{}, error) { - value := reflect.ValueOf(object) - - if value.Kind() == reflect.Map { - values := map[string]interface{}{} - keys := value.MapKeys() - for _, key := range keys { - if key.Kind() != reflect.String { - if _, ok := key.Interface().(string); !ok { - return nil, fmt.Errorf("map key needs to be a string, not %T (%v)", key.Interface(), key.Kind()) - } - } - - v := value.MapIndex(key) - newValue, err := toTree(v.Interface()) - if err != nil { - return nil, err - } - values[key.String()] = newValue - } - return &Tree{values: values, position: Position{}}, nil - } - - if value.Kind() == reflect.Array || value.Kind() == reflect.Slice { - return sliceToTree(object) - } - - simpleValue, err := simpleValueCoercion(object) - if err != nil { - return nil, err - } - return &tomlValue{value: simpleValue, position: Position{}}, nil -} diff --git a/tomltree_create_test.go b/tomltree_create_test.go deleted file mode 100644 index 9ea129f..0000000 --- a/tomltree_create_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package toml - -import ( - "reflect" - "strconv" - "testing" - "time" -) - -type customString string - -type stringer struct{} - -func (s stringer) String() string { - return "stringer" -} - -func validate(t *testing.T, path string, object interface{}) { - switch o := object.(type) { - case *Tree: - for key, tree := range o.values { - validate(t, path+"."+key, tree) - } - case []*Tree: - for index, tree := range o { - validate(t, path+"."+strconv.Itoa(index), tree) - } - case *tomlValue: - switch o.value.(type) { - case int64, uint64, bool, string, float64, time.Time, - []int64, []uint64, []bool, []string, []float64, []time.Time: - default: - t.Fatalf("tomlValue at key %s containing incorrect type %T", path, o.value) - } - default: - t.Fatalf("value at key %s is of incorrect type %T", path, object) - } - t.Logf("validation ok %s as %T", path, object) -} - -func validateTree(t *testing.T, tree *Tree) { - validate(t, "", tree) -} - -func TestTreeCreateToTree(t *testing.T) { - data := map[string]interface{}{ - "a_string": "bar", - "an_int": 42, - "time": time.Now(), - "int8": int8(2), - "int16": int16(2), - "int32": int32(2), - "uint8": uint8(2), - "uint16": uint16(2), - "uint32": uint32(2), - "float32": float32(2), - "a_bool": false, - "stringer": stringer{}, - "nested": map[string]interface{}{ - "foo": "bar", - }, - "array": []string{"a", "b", "c"}, - "array_uint": []uint{uint(1), uint(2)}, - "array_table": []map[string]interface{}{{"sub_map": 52}}, - "array_times": []time.Time{time.Now(), time.Now()}, - "map_times": map[string]time.Time{"now": time.Now()}, - "custom_string_map_key": map[customString]interface{}{customString("custom"): "custom"}, - } - tree, err := TreeFromMap(data) - if err != nil { - t.Fatal("unexpected error:", err) - } - validateTree(t, tree) -} - -func TestTreeCreateToTreeInvalidLeafType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": t}) - expected := "cannot convert type *testing.T to Tree" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestTreeCreateToTreeInvalidMapKeyType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": map[int]interface{}{2: 1}}) - expected := "map key needs to be a string, not int (int)" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestTreeCreateToTreeInvalidArrayMemberType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": []*testing.T{t}}) - expected := "cannot convert type *testing.T to Tree" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestTreeCreateToTreeInvalidTableGroupType(t *testing.T) { - _, err := TreeFromMap(map[string]interface{}{"foo": []map[string]interface{}{{"hello": t}}}) - expected := "cannot convert type *testing.T to Tree" - if err.Error() != expected { - t.Fatalf("expected error %s, got %s", expected, err.Error()) - } -} - -func TestRoundTripArrayOfTables(t *testing.T) { - orig := "\n[[stuff]]\n name = \"foo\"\n things = [\"a\", \"b\"]\n" - tree, err := Load(orig) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - m := tree.ToMap() - - tree, err = TreeFromMap(m) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - want := orig - got := tree.String() - - if got != want { - t.Errorf("want:\n%s\ngot:\n%s", want, got) - } -} - -func TestTomlSliceOfSlice(t *testing.T) { - tree, err := Load(` hosts=[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]string - } - var actual Struct - tree.Unmarshal(&actual) - - expected := Struct{Hosts: [][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } -} - -func TestTomlSliceOfSliceOfSlice(t *testing.T) { - tree, err := Load(` hosts=[[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ]] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][][]string - } - var actual Struct - tree.Unmarshal(&actual) - - expected := Struct{Hosts: [][][]string{[][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } -} - -func TestTomlSliceOfSliceInt(t *testing.T) { - tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]int - } - var actual Struct - err = tree.Unmarshal(&actual) - if err != nil { - t.Error("should not error", err) - } - - expected := Struct{Hosts: [][]int{[]int{1, 2, 3}, []int{4, 5, 6}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } - -} -func TestTomlSliceOfSliceInt64(t *testing.T) { - tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `) - m := tree.ToMap() - tree, err = TreeFromMap(m) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]int64 - } - var actual Struct - err = tree.Unmarshal(&actual) - if err != nil { - t.Error("should not error", err) - } - - expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}, []int64{4, 5, 6}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } - -} - -func TestTomlSliceOfSliceInt64FromMap(t *testing.T) { - tree, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{int32(1), int8(2), 3}}}) - if err != nil { - t.Error("should not error", err) - } - type Struct struct { - Hosts [][]int64 - } - var actual Struct - err = tree.Unmarshal(&actual) - if err != nil { - t.Error("should not error", err) - } - - expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}}} - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual) - } - -} -func TestTomlSliceOfSliceError(t *testing.T) { // make Codecov happy - _, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{1, 2, []struct{}{}}}}) - expected := "cannot convert type []struct {} to Tree" - if err.Error() != expected { - t.Fatalf("unexpected error: %s", err) - } -} diff --git a/tomltree_write.go b/tomltree_write.go deleted file mode 100644 index 0586122..0000000 --- a/tomltree_write.go +++ /dev/null @@ -1,535 +0,0 @@ -package toml - -import ( - "bytes" - "fmt" - "io" - "math" - "math/big" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -type valueComplexity int - -const ( - valueSimple valueComplexity = iota + 1 - valueComplex -) - -type sortNode struct { - key string - complexity valueComplexity -} - -// Encodes a string to a TOML-compliant multi-line string value -// This function is a clone of the existing encodeTomlString function, except that whitespace characters -// are preserved. Quotation marks and backslashes are also not escaped. -func encodeMultilineTomlString(value string, commented string) string { - var b bytes.Buffer - adjacentQuoteCount := 0 - - b.WriteString(commented) - for i, rr := range value { - if rr != '"' { - adjacentQuoteCount = 0 - } else { - adjacentQuoteCount++ - } - switch rr { - case '\b': - b.WriteString(`\b`) - case '\t': - b.WriteString("\t") - case '\n': - b.WriteString("\n" + commented) - case '\f': - b.WriteString(`\f`) - case '\r': - b.WriteString("\r") - case '"': - if adjacentQuoteCount >= 3 || i == len(value)-1 { - adjacentQuoteCount = 0 - b.WriteString(`\"`) - } else { - b.WriteString(`"`) - } - case '\\': - b.WriteString(`\`) - default: - intRr := uint16(rr) - if intRr < 0x001F { - b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) - } else { - b.WriteRune(rr) - } - } - } - return b.String() -} - -// Encodes a string to a TOML-compliant string value -func encodeTomlString(value string) string { - var b bytes.Buffer - - for _, rr := range value { - switch rr { - case '\b': - b.WriteString(`\b`) - case '\t': - b.WriteString(`\t`) - case '\n': - b.WriteString(`\n`) - case '\f': - b.WriteString(`\f`) - case '\r': - b.WriteString(`\r`) - case '"': - b.WriteString(`\"`) - case '\\': - b.WriteString(`\\`) - default: - intRr := uint16(rr) - if intRr < 0x001F { - b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) - } else { - b.WriteRune(rr) - } - } - } - return b.String() -} - -func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) { - var orderedVals []sortNode - switch ord { - case OrderPreserve: - orderedVals = sortByLines(t) - default: - orderedVals = sortAlphabetical(t) - } - - var values []string - for _, node := range orderedVals { - k := node.key - v := t.values[k] - - repr, err := tomlValueStringRepresentation(v, "", "", ord, false) - if err != nil { - return "", err - } - values = append(values, quoteKeyIfNeeded(k)+" = "+repr) - } - return "{ " + strings.Join(values, ", ") + " }", nil -} - -func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) { - // this interface check is added to dereference the change made in the writeTo function. - // That change was made to allow this function to see formatting options. - tv, ok := v.(*tomlValue) - if ok { - v = tv.value - } else { - tv = &tomlValue{} - } - - switch value := v.(type) { - case uint64: - return strconv.FormatUint(value, 10), nil - case int64: - return strconv.FormatInt(value, 10), nil - case float64: - // Default bit length is full 64 - bits := 64 - // Float panics if nan is used - if !math.IsNaN(value) { - // if 32 bit accuracy is enough to exactly show, use 32 - _, acc := big.NewFloat(value).Float32() - if acc == big.Exact { - bits = 32 - } - } - if math.Trunc(value) == value { - return strings.ToLower(strconv.FormatFloat(value, 'f', 1, bits)), nil - } - return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil - case string: - if tv.multiline { - return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil - } - return "\"" + encodeTomlString(value) + "\"", nil - case []byte: - b, _ := v.([]byte) - return string(b), nil - case bool: - if value { - return "true", nil - } - return "false", nil - case time.Time: - return value.Format(time.RFC3339), nil - case LocalDate: - return value.String(), nil - case LocalDateTime: - return value.String(), nil - case LocalTime: - return value.String(), nil - case *Tree: - return tomlTreeStringRepresentation(value, ord) - case nil: - return "", nil - } - - rv := reflect.ValueOf(v) - - if rv.Kind() == reflect.Slice { - var values []string - for i := 0; i < rv.Len(); i++ { - item := rv.Index(i).Interface() - itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine) - if err != nil { - return "", err - } - values = append(values, itemRepr) - } - if arraysOneElementPerLine && len(values) > 1 { - stringBuffer := bytes.Buffer{} - valueIndent := indent + ` ` // TODO: move that to a shared encoder state - - stringBuffer.WriteString("[\n") - - for _, value := range values { - stringBuffer.WriteString(valueIndent) - stringBuffer.WriteString(commented + value) - stringBuffer.WriteString(`,`) - stringBuffer.WriteString("\n") - } - - stringBuffer.WriteString(indent + commented + "]") - - return stringBuffer.String(), nil - } - return "[" + strings.Join(values, ", ") + "]", nil - } - return "", fmt.Errorf("unsupported value type %T: %v", v, v) -} - -func getTreeArrayLine(trees []*Tree) (line int) { - // 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, " ", false) -} - -func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder, indentString string, parentCommented bool) (int64, error) { - var orderedVals []sortNode - - switch ord { - case OrderPreserve: - orderedVals = sortByLines(t) - default: - orderedVals = sortAlphabetical(t) - } - - for _, node := range orderedVals { - switch node.complexity { - case valueComplex: - k := node.key - v := t.values[k] - - combinedKey := quoteKeyIfNeeded(k) - if keyspace != "" { - combinedKey = keyspace + "." + combinedKey - } - - switch node := v.(type) { - // node has to be of those two types given how keys are sorted above - case *Tree: - tv, ok := t.values[k].(*Tree) - if !ok { - return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) - } - if tv.comment != "" { - comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1) - start := "# " - if strings.HasPrefix(comment, "#") { - start = "" - } - writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment) - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - - var commented string - if parentCommented || t.commented || tv.commented { - commented = "# " - } - writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || tv.commented) - if err != nil { - return bytesCount, err - } - case []*Tree: - for _, subTree := range node { - var commented string - if parentCommented || t.commented || subTree.commented { - commented = "# " - } - writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - - bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || subTree.commented) - if err != nil { - return bytesCount, err - } - } - } - default: // Simple - k := node.key - v, ok := t.values[k].(*tomlValue) - if !ok { - return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) - } - - var commented string - if parentCommented || t.commented || v.commented { - commented = "# " - } - repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine) - if err != nil { - return bytesCount, err - } - - if v.comment != "" { - comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1) - start := "# " - if strings.HasPrefix(comment, "#") { - start = "" - } - writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n") - bytesCount += int64(writtenBytesCountComment) - if errc != nil { - return bytesCount, errc - } - } - - quotedKey := quoteKeyIfNeeded(k) - writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n") - bytesCount += int64(writtenBytesCount) - if err != nil { - return bytesCount, err - } - } - } - - return bytesCount, nil -} - -// quote a key if it does not fit the bare key format (A-Za-z0-9_-) -// quoted keys use the same rules as strings -func quoteKeyIfNeeded(k string) string { - // when encoding a map with the 'quoteMapKeys' option enabled, the tree will contain - // keys that have already been quoted. - // not an ideal situation, but good enough of a stop gap. - if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' { - return k - } - isBare := true - for _, r := range k { - if !isValidBareChar(r) { - isBare = false - break - } - } - if isBare { - return k - } - return quoteKey(k) -} - -func quoteKey(k string) string { - return "\"" + encodeTomlString(k) + "\"" -} - -func writeStrings(w io.Writer, s ...string) (int, error) { - var n int - for i := range s { - b, err := io.WriteString(w, s[i]) - n += b - if err != nil { - return n, err - } - } - return n, nil -} - -// WriteTo encode the Tree as Toml and writes it to the writer w. -// Returns the number of bytes written in case of success, or an error if anything happened. -func (t *Tree) WriteTo(w io.Writer) (int64, error) { - return t.writeTo(w, "", "", 0, false) -} - -// ToTomlString generates a human-readable representation of the current tree. -// Output spans multiple lines, and is suitable for ingest by a TOML parser. -// If the conversion cannot be performed, ToString returns a non-nil error. -func (t *Tree) ToTomlString() (string, error) { - b, err := t.Marshal() - if err != nil { - return "", err - } - return string(b), nil -} - -// String generates a human-readable representation of the current tree. -// Alias of ToString. Present to implement the fmt.Stringer interface. -func (t *Tree) String() string { - result, _ := t.ToTomlString() - return result -} - -// ToMap recursively generates a representation of the tree using Go built-in structures. -// The following types are used: -// -// * bool -// * float64 -// * int64 -// * string -// * uint64 -// * time.Time -// * map[string]interface{} (where interface{} is any of this list) -// * []interface{} (where interface{} is any of this list) -func (t *Tree) ToMap() map[string]interface{} { - result := map[string]interface{}{} - - for k, v := range t.values { - switch node := v.(type) { - case []*Tree: - var array []interface{} - for _, item := range node { - array = append(array, item.ToMap()) - } - result[k] = array - case *Tree: - result[k] = node.ToMap() - case *tomlValue: - result[k] = tomlValueToGo(node.value) - } - } - return result -} - -func tomlValueToGo(v interface{}) interface{} { - if tree, ok := v.(*Tree); ok { - return tree.ToMap() - } - - rv := reflect.ValueOf(v) - - if rv.Kind() != reflect.Slice { - return v - } - values := make([]interface{}, rv.Len()) - for i := 0; i < rv.Len(); i++ { - item := rv.Index(i).Interface() - values[i] = tomlValueToGo(item) - } - return values -} diff --git a/tomltree_write_test.go b/tomltree_write_test.go deleted file mode 100644 index b254d76..0000000 --- a/tomltree_write_test.go +++ /dev/null @@ -1,437 +0,0 @@ -package toml - -import ( - "bytes" - "errors" - "fmt" - "reflect" - "strings" - "testing" - "time" -) - -type failingWriter struct { - failAt int - written int - buffer bytes.Buffer -} - -func (f *failingWriter) Write(p []byte) (n int, err error) { - count := len(p) - toWrite := f.failAt - (count + f.written) - if toWrite < 0 { - toWrite = 0 - } - if toWrite > count { - f.written += count - f.buffer.Write(p) - return count, nil - } - - f.buffer.Write(p[:toWrite]) - f.written = f.failAt - return toWrite, fmt.Errorf("failingWriter failed after writing %d bytes", f.written) -} - -func assertErrorString(t *testing.T, expected string, err error) { - expectedErr := errors.New(expected) - if err == nil || err.Error() != expectedErr.Error() { - t.Errorf("expecting error %s, but got %s instead", expected, err) - } -} - -func TestTreeWriteToEmptyTable(t *testing.T) { - doc := `[[empty-tables]] -[[empty-tables]]` - - toml, err := Load(doc) - if err != nil { - t.Fatal("Unexpected Load error:", err) - } - tomlString, err := toml.ToTomlString() - if err != nil { - t.Fatal("Unexpected ToTomlString error:", err) - } - - expected := ` -[[empty-tables]] - -[[empty-tables]] -` - - if tomlString != expected { - t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString) - } -} - -func TestTreeWriteToTomlString(t *testing.T) { - toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" } -points = { x = 1, y = 2 }`) - - if err != nil { - t.Fatal("Unexpected error:", err) - } - - tomlString, _ := toml.ToTomlString() - reparsedTree, err := Load(tomlString) - - assertTree(t, reparsedTree, err, map[string]interface{}{ - "name": map[string]interface{}{ - "first": "Tom", - "last": "Preston-Werner", - }, - "points": map[string]interface{}{ - "x": int64(1), - "y": int64(2), - }, - }) -} - -func TestTreeWriteToTomlStringSimple(t *testing.T) { - tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n") - if err != nil { - t.Errorf("Test failed to parse: %v", err) - return - } - result, err := tree.ToTomlString() - if err != nil { - t.Errorf("Unexpected error: %s", err) - } - expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n" - if result != expected { - t.Errorf("Expected got '%s', expected '%s'", result, expected) - } -} - -func TestTreeWriteToTomlStringKeysOrders(t *testing.T) { - for i := 0; i < 100; i++ { - tree, _ := Load(` - foobar = true - bar = "baz" - foo = 1 - [qux] - foo = 1 - bar = "baz2"`) - - stringRepr, _ := tree.ToTomlString() - - t.Log("Intermediate string representation:") - t.Log(stringRepr) - - r := strings.NewReader(stringRepr) - toml, err := LoadReader(r) - - if err != nil { - t.Fatal("Unexpected error:", err) - } - - assertTree(t, toml, err, map[string]interface{}{ - "foobar": true, - "bar": "baz", - "foo": 1, - "qux": map[string]interface{}{ - "foo": 1, - "bar": "baz2", - }, - }) - } -} - -func testMaps(t *testing.T, actual, expected map[string]interface{}) { - if !reflect.DeepEqual(actual, expected) { - t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual) - } -} - -func TestTreeWriteToMapSimple(t *testing.T) { - tree, _ := Load("a = 42\nb = 17") - - expected := map[string]interface{}{ - "a": int64(42), - "b": int64(17), - } - - testMaps(t, tree.ToMap(), expected) -} - -func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) { - tree := Tree{values: map[string]interface{}{"foo": int8(1)}} - _, err := tree.ToTomlString() - assertErrorString(t, "invalid value type at foo: int8", err) -} - -func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) { - tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} - _, err := tree.ToTomlString() - assertErrorString(t, "unsupported value type int8: 1", err) -} - -func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) { - tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}} - _, err := tree.ToTomlString() - assertErrorString(t, "unsupported value type int8: 1", err) -} - -func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) { - toml, _ := Load(`a = 2`) - writer := failingWriter{failAt: 0, written: 0} - _, err := toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 0 bytes", err) -} - -func TestTreeWriteToFailingWriterInTable(t *testing.T) { - toml, _ := Load(` -[b] -a = 2`) - writer := failingWriter{failAt: 2, written: 0} - _, err := toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 2 bytes", err) - - writer = failingWriter{failAt: 13, written: 0} - _, err = toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 13 bytes", err) -} - -func TestTreeWriteToFailingWriterInArray(t *testing.T) { - toml, _ := Load(` -[[b]] -a = 2`) - writer := failingWriter{failAt: 2, written: 0} - _, err := toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 2 bytes", err) - - writer = failingWriter{failAt: 15, written: 0} - _, err = toml.WriteTo(&writer) - assertErrorString(t, "failingWriter failed after writing 15 bytes", err) -} - -func TestTreeWriteToMapExampleFile(t *testing.T) { - tree, _ := LoadFile("example.toml") - expected := map[string]interface{}{ - "title": "TOML Example", - "owner": map[string]interface{}{ - "name": "Tom Preston-Werner", - "organization": "GitHub", - "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", - "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), - }, - "database": map[string]interface{}{ - "server": "192.168.1.1", - "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, - "connection_max": int64(5000), - "enabled": true, - }, - "servers": map[string]interface{}{ - "alpha": map[string]interface{}{ - "ip": "10.0.0.1", - "dc": "eqdc10", - }, - "beta": map[string]interface{}{ - "ip": "10.0.0.2", - "dc": "eqdc10", - }, - }, - "clients": map[string]interface{}{ - "data": []interface{}{ - []interface{}{"gamma", "delta"}, - []interface{}{int64(1), int64(2)}, - }, - "score": 4e-08, - }, - } - testMaps(t, tree.ToMap(), expected) -} - -func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) { - tree, _ := Load(` - [[menu.main]] - a = "menu 1" - b = "menu 2" - [[menu.main]] - c = "menu 3" - d = "menu 4"`) - expected := map[string]interface{}{ - "menu": map[string]interface{}{ - "main": []interface{}{ - map[string]interface{}{"a": "menu 1", "b": "menu 2"}, - map[string]interface{}{"c": "menu 3", "d": "menu 4"}, - }, - }, - } - treeMap := tree.ToMap() - - testMaps(t, treeMap, expected) -} - -func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) { - tree, _ := Load(` - [params] - language_tabs = [ - { key = "shell", name = "Shell" }, - { key = "ruby", name = "Ruby" }, - { key = "python", name = "Python" } - ]`) - - expected := map[string]interface{}{ - "params": map[string]interface{}{ - "language_tabs": []interface{}{ - map[string]interface{}{ - "key": "shell", - "name": "Shell", - }, - map[string]interface{}{ - "key": "ruby", - "name": "Ruby", - }, - map[string]interface{}{ - "key": "python", - "name": "Python", - }, - }, - }, - } - - treeMap := tree.ToMap() - testMaps(t, treeMap, expected) -} - -func TestTreeWriteToMapWithTableInMixedArray(t *testing.T) { - tree, _ := Load(`a = [ - "foo", - [ - "bar", - {baz = "quux"}, - ], - [ - {a = "b"}, - {c = "d"}, - ], - ]`) - expected := map[string]interface{}{ - "a": []interface{}{ - "foo", - []interface{}{ - "bar", - map[string]interface{}{ - "baz": "quux", - }, - }, - []interface{}{ - map[string]interface{}{ - "a": "b", - }, - map[string]interface{}{ - "c": "d", - }, - }, - }, - } - treeMap := tree.ToMap() - - testMaps(t, treeMap, expected) -} - -func TestTreeWriteToFloat(t *testing.T) { - tree, err := Load(`a = 3.0`) - if err != nil { - t.Fatal(err) - } - str, err := tree.ToTomlString() - if err != nil { - t.Fatal(err) - } - expected := `a = 3.0` - if strings.TrimSpace(str) != strings.TrimSpace(expected) { - t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) - } -} - -func TestTreeWriteToSpecialFloat(t *testing.T) { - expected := `a = +inf -b = -inf -c = nan` - - tree, err := Load(expected) - if err != nil { - t.Fatal(err) - } - str, err := tree.ToTomlString() - if err != nil { - t.Fatal(err) - } - if strings.TrimSpace(str) != strings.TrimSpace(expected) { - t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str) - } -} - -func TestIssue290(t *testing.T) { - tomlString := - `[table] -"127.0.0.1" = "value" -"127.0.0.1:8028" = "value" -"character encoding" = "value" -"ʎǝʞ" = "value"` - - t1, err := Load(tomlString) - if err != nil { - t.Fatal("load err:", err) - } - - s, err := t1.ToTomlString() - if err != nil { - t.Fatal("ToTomlString err:", err) - } - - _, err = Load(s) - if err != nil { - t.Fatal("reload err:", err) - } -} - -func BenchmarkTreeToTomlString(b *testing.B) { - toml, err := Load(sampleHard) - if err != nil { - b.Fatal("Unexpected error:", err) - } - - for i := 0; i < b.N; i++ { - _, err := toml.ToTomlString() - if err != nil { - b.Fatal(err) - } - } -} - -var sampleHard = `# Test file for TOML -# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate -# This part you'll really hate - -[the] -test_string = "You'll hate me after this - #" # " Annoying, isn't it? - - [the.hard] - test_array = [ "] ", " # "] # ] There you go, parse this! - test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] - # You didn't think it'd as easy as chucking out the last #, did you? - another_test_string = " Same thing, but with a string #" - harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" - # Things will get harder - - [the.hard."bit#"] - "what?" = "You don't think some user won't do that?" - multi_line_array = [ - "]", - # ] Oh yes I did - ] - -# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test - -#[error] if you didn't catch this, your parser is broken -#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this -#array = [ -# "This might most likely happen in multiline arrays", -# Like here, -# "or here, -# and here" -# ] End of array comment, forgot the # -#number = 3.14 pi <--again forgot the # `