libcontainerd/supervisor: replace BurntSushi/toml with pelletier/go-toml

Taking the same approach as was taken in containerd

The new library has a slightly different output;

- keys at the same level are sorted alphabetically
- empty sections not omitted (`proxy_plugins`, `stream_processors`, `timeouts`),
  which could possibly be be addressed with an "omitempty" in containerd's struct.
- empty slices are not omitted (`imports`, `required_plugins`)

After sorting the "before" configuration the diff looks like this:

```patch
diff --git a/config-before-sorted.toml b/config-after.toml
index cc771ce7ab..43a727f589 100644
--- a/config-before-sorted.toml
+++ b/config-after.toml
@@ -1,6 +1,8 @@
 disabled_plugins = ["cri"]
+imports = []
 oom_score = 0
 plugin_dir = ""
+required_plugins = []
 root = "/var/lib/docker/containerd/daemon"
 state = "/var/run/docker/containerd/daemon"
 version = 0
@@ -37,6 +39,12 @@ version = 0
     shim = "containerd-shim"
     shim_debug = true

+[proxy_plugins]
+
+[stream_processors]
+
+[timeouts]
+
 [ttrpc]
   address = ""
   gid = 0
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2021-04-02 17:22:22 +02:00
parent 1d4a273487
commit 2a7c1cc1d6
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
19 changed files with 4569 additions and 19 deletions

View file

@ -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
@ -48,7 +46,6 @@ type remote struct {
rootDir string
stateDir string
pluginConfs pluginConfigs
}
// 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 := toml.NewEncoder(f).Encode(r); err != nil {
return "", errors.Wrapf(err, "failed to write containerd config file (%s)", path)
}
if err = enc.Encode(r.pluginConfs); err != nil {
return "", errors.Wrapf(err, "failed to encode plugin configs")
}
return path, nil
}

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

@ -139,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
vendor/github.com/pelletier/go-toml/LICENSE generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/README.md generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/doc.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/fuzz.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/go.mod generated vendored Normal file
View file

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

112
vendor/github.com/pelletier/go-toml/keysparsing.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/lexer.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/localtime.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/marshal.go generated vendored Normal file

File diff suppressed because it is too large Load diff

493
vendor/github.com/pelletier/go-toml/parser.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/position.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/token.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/toml.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/tomltree_create.go generated vendored Normal file
View file

@ -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
vendor/github.com/pelletier/go-toml/tomltree_write.go generated vendored Normal file
View file

@ -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
}