Przeglądaj źródła

Merge pull request #42246 from thaJeztah/replace_toml

Replace BurntSushi/toml with pelletier/go-toml
Sebastiaan van Stijn 4 lat temu
rodzic
commit
1cb7ee4cd9

+ 5 - 5
Dockerfile

@@ -151,12 +151,12 @@ RUN --mount=type=cache,sharing=locked,id=moby-cross-true-aptlib,target=/var/lib/
 
 FROM runtime-dev-cross-${CROSS} AS runtime-dev
 
-FROM base AS tomlv
-ARG TOMLV_COMMIT
+FROM base AS tomll
+ARG GOTOML_VERSION
 RUN --mount=type=cache,target=/root/.cache/go-build \
     --mount=type=cache,target=/go/pkg/mod \
-    --mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
-        PREFIX=/build /tmp/install/install.sh tomlv
+    --mount=type=bind,src=hack/dockerfile/install/tomll.installer,target=/tmp/install/tomll.installer \
+        . /tmp/install/tomll.installer && PREFIX=/build install_tomll
 
 FROM base AS vndr
 ARG VNDR_COMMIT
@@ -305,7 +305,7 @@ RUN pip3 install yamllint==1.16.0
 COPY --from=dockercli     /build/ /usr/local/cli
 COPY --from=frozen-images /build/ /docker-frozen-images
 COPY --from=swagger       /build/ /usr/local/bin/
-COPY --from=tomlv         /build/ /usr/local/bin/
+COPY --from=tomll         /build/ /usr/local/bin/
 COPY --from=tini          /build/ /usr/local/bin/
 COPY --from=registry      /build/ /usr/local/bin/
 COPY --from=criu          /build/ /usr/local/

+ 9 - 0
hack/dockerfile/install/tomll.installer

@@ -0,0 +1,9 @@
+#!/bin/sh
+
+: "${GOTOML_VERSION:=v1.8.1}"
+
+install_tomll() {
+	echo "Install go-toml version ${GOTOML_VERSION}"
+	# TODO remove GO111MODULE=on and change to 'go install -mod=mod ...' once we're at go 1.16+
+	GO111MODULE=on GOBIN="${PREFIX}" go get -v "github.com/pelletier/go-toml/cmd/tomll@${GOTOML_VERSION}"
+}

+ 0 - 12
hack/dockerfile/install/tomlv.installer

@@ -1,12 +0,0 @@
-#!/bin/sh
-
-# When updating TOMLV_COMMIT, consider updating github.com/BurntSushi/toml
-# in vendor.conf accordingly
-: ${TOMLV_COMMIT:=3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005} # v0.3.1
-
-install_tomlv() {
-	echo "Install tomlv version $TOMLV_COMMIT"
-	git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml"
-	cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT"
-	go build -v ${GO_BUILDMODE} -o "${PREFIX}/tomlv" "github.com/BurntSushi/toml/cmd/tomlv"
-}

+ 1 - 1
hack/validate/toml

@@ -10,7 +10,7 @@ unset IFS
 badFiles=()
 for f in "${files[@]}"; do
 	# we use "git show" here to validate that what's committed has valid TOML syntax
-	if ! git show "$VALIDATE_HEAD:$f" | tomlv /proc/self/fd/0; then
+	if ! git show "$VALIDATE_HEAD:$f" | tomll /proc/self/fd/0; then
 		badFiles+=("$f")
 	fi
 done

+ 8 - 16
libcontainerd/supervisor/remote_daemon.go

@@ -13,10 +13,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/BurntSushi/toml"
 	"github.com/containerd/containerd"
 	"github.com/containerd/containerd/services/server/config"
 	"github.com/docker/docker/pkg/system"
+	"github.com/pelletier/go-toml"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -31,13 +31,11 @@ const (
 	pidFile                 = "containerd.pid"
 )
 
-type pluginConfigs struct {
-	Plugins map[string]interface{} `toml:"plugins"`
-}
-
 type remote struct {
 	sync.RWMutex
 	config.Config
+	// Plugins overrides `Plugins map[string]toml.Tree` in config config.
+	Plugins map[string]interface{} `toml:"plugins"`
 
 	daemonPid int
 	logger    *logrus.Entry
@@ -46,9 +44,8 @@ type remote struct {
 	daemonStartCh chan error
 	daemonStopCh  chan struct{}
 
-	rootDir     string
-	stateDir    string
-	pluginConfs pluginConfigs
+	rootDir  string
+	stateDir string
 }
 
 // Daemon represents a running containerd daemon
@@ -69,7 +66,7 @@ func Start(ctx context.Context, rootDir, stateDir string, opts ...DaemonOpt) (Da
 			Root:  filepath.Join(rootDir, "daemon"),
 			State: filepath.Join(stateDir, "daemon"),
 		},
-		pluginConfs:   pluginConfigs{make(map[string]interface{})},
+		Plugins:       make(map[string]interface{}),
 		daemonPid:     -1,
 		logger:        logrus.WithField("module", "libcontainerd"),
 		daemonStartCh: make(chan error, 1),
@@ -157,14 +154,9 @@ func (r *remote) getContainerdConfig() (string, error) {
 	}
 	defer f.Close()
 
-	enc := toml.NewEncoder(f)
-	if err = enc.Encode(r.Config); err != nil {
-		return "", errors.Wrapf(err, "failed to encode general config")
-	}
-	if err = enc.Encode(r.pluginConfs); err != nil {
-		return "", errors.Wrapf(err, "failed to encode plugin configs")
+	if err := toml.NewEncoder(f).Encode(r); err != nil {
+		return "", errors.Wrapf(err, "failed to write containerd config file (%s)", path)
 	}
-
 	return path, nil
 }
 

+ 2 - 2
libcontainerd/supervisor/remote_daemon_linux.go

@@ -29,10 +29,10 @@ func (r *remote) setDefaults() {
 		r.Debug.Address = filepath.Join(r.stateDir, debugSockFile)
 	}
 
-	for key, conf := range r.pluginConfs.Plugins {
+	for key, conf := range r.Plugins {
 		if conf == nil {
 			r.DisabledPlugins = append(r.DisabledPlugins, key)
-			delete(r.pluginConfs.Plugins, key)
+			delete(r.Plugins, key)
 		}
 	}
 }

+ 1 - 1
libcontainerd/supervisor/remote_daemon_options.go

@@ -49,7 +49,7 @@ func WithMetricsAddress(addr string) DaemonOpt {
 // the toml format.
 func WithPlugin(name string, conf interface{}) DaemonOpt {
 	return func(r *remote) error {
-		r.pluginConfs.Plugins[name] = conf
+		r.Plugins[name] = conf
 		return nil
 	}
 }

+ 1 - 1
vendor.conf

@@ -63,7 +63,6 @@ github.com/vishvananda/netns                        db3c7e526aae966c4ccfa6c8189b
 github.com/vishvananda/netlink                      f049be6f391489d3f374498fe0c8df8449258372 # v1.1.0
 github.com/moby/ipvs                                4566ccea0e08d68e9614c3e7a64a23b850c4bb35 # v1.0.1
 
-# When updating, consider updating TOMLV_COMMIT in hack/dockerfile/install/tomlv.installer accordingly
 github.com/BurntSushi/toml                          3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005 # v0.3.1
 github.com/samuel/go-zookeeper                      d0e0d8e11f318e000a8cc434616d69e329edc374
 github.com/deckarep/golang-set                      ef32fa3046d9f249d399f98ebaf9be944430fd1d
@@ -140,6 +139,7 @@ github.com/containerd/typeurl                       cd3ce7159eae562a4f60ceff37da
 github.com/containerd/ttrpc                         bfba540dc45464586c106b1f31c8547933c1eb41 # v1.0.2
 github.com/gogo/googleapis                          01e0f9cca9b92166042241267ee2a5cdf5cff46c # v1.3.2
 github.com/cilium/ebpf                              1c8d4c9ef7759622653a1d319284a44652333b28
+github.com/pelletier/go-toml                        65ca8064882c8c308e5c804c5d5443d409e0738c # v1.8.1
 
 # cluster
 github.com/docker/swarmkit                          17d8d4e4d8bdec33d386e6362d3537fa9493ba00

+ 21 - 0
vendor/github.com/pelletier/go-toml/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 151 - 0
vendor/github.com/pelletier/go-toml/README.md

@@ -0,0 +1,151 @@
+# go-toml
+
+Go library for the [TOML](https://github.com/mojombo/toml) format.
+
+This library supports TOML version
+[v1.0.0-rc.1](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v1.0.0-rc.1.md)
+
+[![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/github.com/pelletier/go-toml)
+[![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE)
+[![Build Status](https://dev.azure.com/pelletierthomas/go-toml-ci/_apis/build/status/pelletier.go-toml?branchName=master)](https://dev.azure.com/pelletierthomas/go-toml-ci/_build/latest?definitionId=1&branchName=master)
+[![codecov](https://codecov.io/gh/pelletier/go-toml/branch/master/graph/badge.svg)](https://codecov.io/gh/pelletier/go-toml)
+[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml)
+[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml?ref=badge_shield)
+
+## Features
+
+Go-toml provides the following features for using data parsed from TOML documents:
+
+* Load TOML documents from files and string data
+* Easily navigate TOML structure using Tree
+* Marshaling and unmarshaling to and from data structures
+* Line & column position data for all parsed elements
+* [Query support similar to JSON-Path](query/)
+* Syntax errors contain line and column numbers
+
+## Import
+
+```go
+import "github.com/pelletier/go-toml"
+```
+
+## Usage example
+
+Read a TOML document:
+
+```go
+config, _ := toml.Load(`
+[postgres]
+user = "pelletier"
+password = "mypassword"`)
+// retrieve data directly
+user := config.Get("postgres.user").(string)
+
+// or using an intermediate object
+postgresConfig := config.Get("postgres").(*toml.Tree)
+password := postgresConfig.Get("password").(string)
+```
+
+Or use Unmarshal:
+
+```go
+type Postgres struct {
+    User     string
+    Password string
+}
+type Config struct {
+    Postgres Postgres
+}
+
+doc := []byte(`
+[Postgres]
+User = "pelletier"
+Password = "mypassword"`)
+
+config := Config{}
+toml.Unmarshal(doc, &config)
+fmt.Println("user=", config.Postgres.User)
+```
+
+Or use a query:
+
+```go
+// use a query to gather elements without walking the tree
+q, _ := query.Compile("$..[user,password]")
+results := q.Execute(config)
+for ii, item := range results.Values() {
+    fmt.Printf("Query result %d: %v\n", ii, item)
+}
+```
+
+## Documentation
+
+The documentation and additional examples are available at
+[godoc.org](http://godoc.org/github.com/pelletier/go-toml).
+
+## Tools
+
+Go-toml provides two handy command line tools:
+
+* `tomll`: Reads TOML files and lints them.
+
+    ```
+    go install github.com/pelletier/go-toml/cmd/tomll
+    tomll --help
+    ```
+* `tomljson`: Reads a TOML file and outputs its JSON representation.
+
+    ```
+    go install github.com/pelletier/go-toml/cmd/tomljson
+    tomljson --help
+    ```
+
+ * `jsontoml`: Reads a JSON file and outputs a TOML representation.
+
+    ```
+    go install github.com/pelletier/go-toml/cmd/jsontoml
+    jsontoml --help
+    ```
+
+### Docker image
+
+Those tools are also availble as a Docker image from
+[dockerhub](https://hub.docker.com/r/pelletier/go-toml). For example, to
+use `tomljson`:
+
+```
+docker run -v $PWD:/workdir pelletier/go-toml tomljson /workdir/example.toml
+```
+
+Only master (`latest`) and tagged versions are published to dockerhub. You
+can build your own image as usual:
+
+```
+docker build -t go-toml .
+```
+
+## Contribute
+
+Feel free to report bugs and patches using GitHub's pull requests system on
+[pelletier/go-toml](https://github.com/pelletier/go-toml). Any feedback would be
+much appreciated!
+
+### Run tests
+
+`go test ./...`
+
+### Fuzzing
+
+The script `./fuzz.sh` is available to
+run [go-fuzz](https://github.com/dvyukov/go-fuzz) on go-toml.
+
+## Versioning
+
+Go-toml follows [Semantic Versioning](http://semver.org/). The supported version
+of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of
+this document. The last two major versions of Go are supported
+(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)).
+
+## License
+
+The MIT License (MIT). Read [LICENSE](LICENSE).

+ 23 - 0
vendor/github.com/pelletier/go-toml/doc.go

@@ -0,0 +1,23 @@
+// 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

+ 31 - 0
vendor/github.com/pelletier/go-toml/fuzz.go

@@ -0,0 +1,31 @@
+// +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
+}

+ 5 - 0
vendor/github.com/pelletier/go-toml/go.mod

@@ -0,0 +1,5 @@
+module github.com/pelletier/go-toml
+
+go 1.12
+
+require github.com/davecgh/go-spew v1.1.1

+ 112 - 0
vendor/github.com/pelletier/go-toml/keysparsing.go

@@ -0,0 +1,112 @@
+// 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)
+}

+ 807 - 0
vendor/github.com/pelletier/go-toml/lexer.go

@@ -0,0 +1,807 @@
+// TOML lexer.
+//
+// Written using the principles developed by Rob Pike in
+// http://www.youtube.com/watch?v=HxaD_trXwRE
+
+package toml
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var dateRegexp *regexp.Regexp
+
+// Define state functions
+type tomlLexStateFn func() tomlLexStateFn
+
+// Define lexer
+type tomlLexer struct {
+	inputIdx          int
+	input             []rune // Textual source
+	currentTokenStart int
+	currentTokenStop  int
+	tokens            []token
+	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
+		}
+
+		possibleDate := l.peekString(35)
+		dateSubmatches := dateRegexp.FindStringSubmatch(possibleDate)
+		if dateSubmatches != nil && dateSubmatches[0] != "" {
+			l.fastForward(len(dateSubmatches[0]))
+			if dateSubmatches[2] == "" { // no timezone information => local date
+				return l.lexLocalDate
+			}
+			return l.lexDate
+		}
+
+		if next == '+' || next == '-' || isDigit(next) {
+			return l.lexNumber
+		}
+
+		return l.errorf("no value can start with %c", next)
+	}
+
+	l.emit(tokenEOF)
+	return nil
+}
+
+func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn {
+	l.next()
+	l.emit(tokenLeftCurlyBrace)
+	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) lexDate() tomlLexStateFn {
+	l.emit(tokenDate)
+	return l.lexRvalue
+}
+
+func (l *tomlLexer) lexLocalDate() tomlLexStateFn {
+	l.emit(tokenLocalDate)
+	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()
+	}
+}
+
+func init() {
+	// Regexp for all date/time formats supported by TOML.
+	// Group 1: nano precision
+	// Group 2: timezone
+	//
+	// /!\ also matches the empty string
+	//
+	// 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
+	dateRegexp = regexp.MustCompile(`^(?:\d{1,4}-\d{2}-\d{2})?(?:[T ]?\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})?)?`)
+}
+
+// Entry point
+func lexToml(inputBytes []byte) []token {
+	runes := bytes.Runes(inputBytes)
+	l := &tomlLexer{
+		input:         runes,
+		tokens:        make([]token, 0, 256),
+		line:          1,
+		col:           1,
+		endbufferLine: 1,
+		endbufferCol:  1,
+	}
+	l.run()
+	return l.tokens
+}

+ 281 - 0
vendor/github.com/pelletier/go-toml/localtime.go

@@ -0,0 +1,281 @@
+// 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
+}

+ 1269 - 0
vendor/github.com/pelletier/go-toml/marshal.go

@@ -0,0 +1,1269 @@
+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:
+						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()
+		// Check if pointer to value implements the encoding.TextUnmarshaler.
+		if mvalPtr := reflect.New(mtype); 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{}{}
+		}
+	}
+}

+ 493 - 0
vendor/github.com/pelletier/go-toml/parser.go

@@ -0,0 +1,493 @@
+// TOML Parser.
+
+package toml
+
+import (
+	"errors"
+	"fmt"
+	"math"
+	"reflect"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type tomlParser struct {
+	flowIdx       int
+	flow          []token
+	tree          *Tree
+	currentTable  []string
+	seenTableKeys []string
+}
+
+type tomlParserStateFn func() tomlParserStateFn
+
+// Formats and panics an error message based on a token
+func (p *tomlParser) raiseError(tok *token, msg string, args ...interface{}) {
+	panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
+}
+
+func (p *tomlParser) run() {
+	for state := p.parseStart; state != nil; {
+		state = state()
+	}
+}
+
+func (p *tomlParser) peek() *token {
+	if p.flowIdx >= len(p.flow) {
+		return nil
+	}
+	return &p.flow[p.flowIdx]
+}
+
+func (p *tomlParser) assume(typ tokenType) {
+	tok := p.getToken()
+	if tok == nil {
+		p.raiseError(tok, "was expecting token %s, but token stream is empty", tok)
+	}
+	if tok.typ != typ {
+		p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok)
+	}
+}
+
+func (p *tomlParser) getToken() *token {
+	tok := p.peek()
+	if tok == nil {
+		return nil
+	}
+	p.flowIdx++
+	return tok
+}
+
+func (p *tomlParser) parseStart() tomlParserStateFn {
+	tok := p.peek()
+
+	// end of stream, parsing is finished
+	if tok == nil {
+		return nil
+	}
+
+	switch tok.typ {
+	case tokenDoubleLeftBracket:
+		return p.parseGroupArray
+	case tokenLeftBracket:
+		return p.parseGroup
+	case tokenKey:
+		return p.parseAssign
+	case tokenEOF:
+		return nil
+	case tokenError:
+		p.raiseError(tok, "parsing error: %s", tok.String())
+	default:
+		p.raiseError(tok, "unexpected token %s", tok.typ)
+	}
+	return nil
+}
+
+func (p *tomlParser) parseGroupArray() tomlParserStateFn {
+	startToken := p.getToken() // discard the [[
+	key := p.getToken()
+	if key.typ != tokenKeyGroupArray {
+		p.raiseError(key, "unexpected token %s, was expecting a table array key", key)
+	}
+
+	// get or create table array element at the indicated part in the path
+	keys, err := parseKey(key.val)
+	if err != nil {
+		p.raiseError(key, "invalid table array key: %s", err)
+	}
+	p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries
+	destTree := p.tree.GetPath(keys)
+	var array []*Tree
+	if destTree == nil {
+		array = make([]*Tree, 0)
+	} else if target, ok := destTree.([]*Tree); ok && target != nil {
+		array = destTree.([]*Tree)
+	} else {
+		p.raiseError(key, "key %s is already assigned and not of type table array", key)
+	}
+	p.currentTable = keys
+
+	// add a new tree to the end of the table array
+	newTree := newTree()
+	newTree.position = startToken.Position
+	array = append(array, newTree)
+	p.tree.SetPath(p.currentTable, array)
+
+	// remove all keys that were children of this table array
+	prefix := key.val + "."
+	found := false
+	for ii := 0; ii < len(p.seenTableKeys); {
+		tableKey := p.seenTableKeys[ii]
+		if strings.HasPrefix(tableKey, prefix) {
+			p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...)
+		} else {
+			found = (tableKey == key.val)
+			ii++
+		}
+	}
+
+	// keep this key name from use by other kinds of assignments
+	if !found {
+		p.seenTableKeys = append(p.seenTableKeys, key.val)
+	}
+
+	// move to next parser state
+	p.assume(tokenDoubleRightBracket)
+	return p.parseStart
+}
+
+func (p *tomlParser) parseGroup() tomlParserStateFn {
+	startToken := p.getToken() // discard the [
+	key := p.getToken()
+	if key.typ != tokenKeyGroup {
+		p.raiseError(key, "unexpected token %s, was expecting a table key", key)
+	}
+	for _, item := range p.seenTableKeys {
+		if item == key.val {
+			p.raiseError(key, "duplicated tables")
+		}
+	}
+
+	p.seenTableKeys = append(p.seenTableKeys, key.val)
+	keys, err := parseKey(key.val)
+	if err != nil {
+		p.raiseError(key, "invalid table array key: %s", err)
+	}
+	if err := p.tree.createSubTree(keys, startToken.Position); err != nil {
+		p.raiseError(key, "%s", err)
+	}
+	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 numberUnderscoreInvalidRegexp *regexp.Regexp
+var hexNumberUnderscoreInvalidRegexp *regexp.Regexp
+
+func numberContainsInvalidUnderscore(value string) error {
+	if numberUnderscoreInvalidRegexp.MatchString(value) {
+		return errors.New("invalid use of _ in number")
+	}
+	return nil
+}
+
+func hexNumberContainsInvalidUnderscore(value string) error {
+	if hexNumberUnderscoreInvalidRegexp.MatchString(value) {
+		return errors.New("invalid use of _ in hex number")
+	}
+	return nil
+}
+
+func cleanupNumberToken(value string) string {
+	cleanedVal := strings.Replace(value, "_", "", -1)
+	return cleanedVal
+}
+
+func (p *tomlParser) parseRvalue() interface{} {
+	tok := p.getToken()
+	if tok == nil || tok.typ == tokenEOF {
+		p.raiseError(tok, "expecting a value")
+	}
+
+	switch tok.typ {
+	case tokenString:
+		return tok.val
+	case tokenTrue:
+		return true
+	case tokenFalse:
+		return false
+	case tokenInf:
+		if tok.val[0] == '-' {
+			return math.Inf(-1)
+		}
+		return math.Inf(1)
+	case tokenNan:
+		return math.NaN()
+	case tokenInteger:
+		cleanedVal := cleanupNumberToken(tok.val)
+		var err error
+		var val int64
+		if len(cleanedVal) >= 3 && cleanedVal[0] == '0' {
+			switch cleanedVal[1] {
+			case 'x':
+				err = hexNumberContainsInvalidUnderscore(tok.val)
+				if err != nil {
+					p.raiseError(tok, "%s", err)
+				}
+				val, err = strconv.ParseInt(cleanedVal[2:], 16, 64)
+			case 'o':
+				err = numberContainsInvalidUnderscore(tok.val)
+				if err != nil {
+					p.raiseError(tok, "%s", err)
+				}
+				val, err = strconv.ParseInt(cleanedVal[2:], 8, 64)
+			case 'b':
+				err = numberContainsInvalidUnderscore(tok.val)
+				if err != nil {
+					p.raiseError(tok, "%s", err)
+				}
+				val, err = strconv.ParseInt(cleanedVal[2:], 2, 64)
+			default:
+				panic("invalid base") // the lexer should catch this first
+			}
+		} else {
+			err = numberContainsInvalidUnderscore(tok.val)
+			if err != nil {
+				p.raiseError(tok, "%s", err)
+			}
+			val, err = strconv.ParseInt(cleanedVal, 10, 64)
+		}
+		if err != nil {
+			p.raiseError(tok, "%s", err)
+		}
+		return val
+	case tokenFloat:
+		err := numberContainsInvalidUnderscore(tok.val)
+		if err != nil {
+			p.raiseError(tok, "%s", err)
+		}
+		cleanedVal := cleanupNumberToken(tok.val)
+		val, err := strconv.ParseFloat(cleanedVal, 64)
+		if err != nil {
+			p.raiseError(tok, "%s", err)
+		}
+		return val
+	case tokenDate:
+		layout := time.RFC3339Nano
+		if !strings.Contains(tok.val, "T") {
+			layout = strings.Replace(layout, "T", " ", 1)
+		}
+		val, err := time.ParseInLocation(layout, tok.val, time.UTC)
+		if err != nil {
+			p.raiseError(tok, "%s", err)
+		}
+		return val
+	case tokenLocalDate:
+		v := strings.Replace(tok.val, " ", "T", -1)
+		isDateTime := false
+		isTime := false
+		for _, c := range v {
+			if c == 'T' || c == 't' {
+				isDateTime = true
+				break
+			}
+			if c == ':' {
+				isTime = true
+				break
+			}
+		}
+
+		var val interface{}
+		var err error
+
+		if isDateTime {
+			val, err = ParseLocalDateTime(v)
+		} else if isTime {
+			val, err = ParseLocalTime(v)
+		} else {
+			val, err = ParseLocalDate(v)
+		}
+
+		if err != nil {
+			p.raiseError(tok, "%s", err)
+		}
+		return val
+	case tokenLeftBracket:
+		return p.parseArray()
+	case tokenLeftCurlyBrace:
+		return p.parseInlineTable()
+	case tokenEqual:
+		p.raiseError(tok, "cannot have multiple equals for the same key")
+	case tokenError:
+		p.raiseError(tok, "%s", tok)
+	}
+
+	p.raiseError(tok, "never reached")
+
+	return nil
+}
+
+func tokenIsComma(t *token) bool {
+	return t != nil && t.typ == tokenComma
+}
+
+func (p *tomlParser) parseInlineTable() *Tree {
+	tree := newTree()
+	var previous *token
+Loop:
+	for {
+		follow := p.peek()
+		if follow == nil || follow.typ == tokenEOF {
+			p.raiseError(follow, "unterminated inline table")
+		}
+		switch follow.typ {
+		case tokenRightCurlyBrace:
+			p.getToken()
+			break Loop
+		case tokenKey, tokenInteger, tokenString:
+			if !tokenIsComma(previous) && previous != nil {
+				p.raiseError(follow, "comma expected between fields in inline table")
+			}
+			key := p.getToken()
+			p.assume(tokenEqual)
+
+			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
+}
+
+func init() {
+	numberUnderscoreInvalidRegexp = regexp.MustCompile(`([^\d]_|_[^\d])|_$|^_`)
+	hexNumberUnderscoreInvalidRegexp = regexp.MustCompile(`(^0x_)|([^\da-f]_|_[^\da-f])|_$|^_`)
+}

+ 29 - 0
vendor/github.com/pelletier/go-toml/position.go

@@ -0,0 +1,29 @@
+// 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
+}

+ 134 - 0
vendor/github.com/pelletier/go-toml/token.go

@@ -0,0 +1,134 @@
+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
+	tokenDate
+	tokenLocalDate
+	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",
+	"LocalDate",
+	"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')
+}

+ 529 - 0
vendor/github.com/pelletier/go-toml/toml.go

@@ -0,0 +1,529 @@
+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)
+}

+ 155 - 0
vendor/github.com/pelletier/go-toml/tomltree_create.go

@@ -0,0 +1,155 @@
+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
+}

+ 517 - 0
vendor/github.com/pelletier/go-toml/tomltree_write.go

@@ -0,0 +1,517 @@
+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] = node.value
+		}
+	}
+	return result
+}