Unverified Commit a9e87251 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #65383 from sigma/pr/update-toml

Automatic merge from submit-queue (batch tested with PRs 64122, 64936, 65288, 65383). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. update github.com/pelletier/go-toml to 1.2.0 **What this PR does / why we need it**: Rationale: github.com/pelletier/go-toml is the only package that currently prevents the future vendoring of github.com/golang/dep as it depends on functions introduced in 1.1.0. The only consumers of this package are github.com/spf13/viper (used to run e2e tests) and github.com/bazelbuild/bazel-gazelle (bazel helper), so that's a pretty low-risk change. **Special notes for your reviewer**: This should help reducing the noise when #64731 lands **Release note**: ```release-note NONE ```
parents 21912b39 ed10dc79
...@@ -2490,8 +2490,8 @@ ...@@ -2490,8 +2490,8 @@
}, },
{ {
"ImportPath": "github.com/pelletier/go-toml", "ImportPath": "github.com/pelletier/go-toml",
"Comment": "v1.0.1", "Comment": "v1.2.0",
"Rev": "16398bac157da96aa88f98a2df640c7f32af1da2" "Rev": "c01d1270ff3e442a8a57cddc1c92dc1138598194"
}, },
{ {
"ImportPath": "github.com/peterbourgon/diskv", "ImportPath": "github.com/peterbourgon/diskv",
......
test_program/test_program_bin test_program/test_program_bin
fuzz/
sudo: false sudo: false
language: go language: go
go: go:
- 1.7.6 - 1.8.x
- 1.8.3 - 1.9.x
- 1.9 - 1.10.x
- tip - tip
matrix: matrix:
allow_failures: allow_failures:
......
...@@ -57,9 +57,9 @@ type Config struct { ...@@ -57,9 +57,9 @@ type Config struct {
} }
doc := []byte(` doc := []byte(`
[postgres] [Postgres]
user = "pelletier" User = "pelletier"
password = "mypassword"`) Password = "mypassword"`)
config := Config{} config := Config{}
toml.Unmarshal(doc, &config) toml.Unmarshal(doc, &config)
...@@ -114,6 +114,18 @@ You have to make sure two kind of tests run: ...@@ -114,6 +114,18 @@ You have to make sure two kind of tests run:
You can run both of them using `./test.sh`. You can run both of them using `./test.sh`.
### 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 ## License
The MIT License (MIT). Read [LICENSE](LICENSE). The MIT License (MIT). Read [LICENSE](LICENSE).
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// JSONPath-like queries // JSONPath-like queries
// //
// The package github.com/pelletier/go-toml/query implements a system // The package github.com/pelletier/go-toml/query implements a system
// similar to JSONPath to quickly retrive elements of a TOML document using a // similar to JSONPath to quickly retrieve elements of a TOML document using a
// single expression. See the package documentation for more information. // single expression. See the package documentation for more information.
// //
package toml package toml
// +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
}
#! /bin/sh
set -eu
go get github.com/dvyukov/go-fuzz/go-fuzz
go get github.com/dvyukov/go-fuzz/go-fuzz-build
if [ ! -e toml-fuzz.zip ]; then
go-fuzz-build github.com/pelletier/go-toml
fi
rm -fr fuzz
mkdir -p fuzz/corpus
cp *.toml fuzz/corpus
go-fuzz -bin=toml-fuzz.zip -workdir=fuzz
...@@ -9,12 +9,14 @@ import ( ...@@ -9,12 +9,14 @@ import (
"unicode" "unicode"
) )
// Convert the bare key group string to an array.
// The input supports double quotation to allow "." inside the key name,
// but escape sequences are not supported. Lexers must unescape them beforehand.
func parseKey(key string) ([]string, error) { func parseKey(key string) ([]string, error) {
groups := []string{} groups := []string{}
var buffer bytes.Buffer var buffer bytes.Buffer
inQuotes := false inQuotes := false
wasInQuotes := false wasInQuotes := false
escapeNext := false
ignoreSpace := true ignoreSpace := true
expectDot := false expectDot := false
...@@ -25,15 +27,7 @@ func parseKey(key string) ([]string, error) { ...@@ -25,15 +27,7 @@ func parseKey(key string) ([]string, error) {
} }
ignoreSpace = false ignoreSpace = false
} }
if escapeNext {
buffer.WriteRune(char)
escapeNext = false
continue
}
switch char { switch char {
case '\\':
escapeNext = true
continue
case '"': case '"':
if inQuotes { if inQuotes {
groups = append(groups, buffer.String()) groups = append(groups, buffer.String())
...@@ -77,9 +71,6 @@ func parseKey(key string) ([]string, error) { ...@@ -77,9 +71,6 @@ func parseKey(key string) ([]string, error) {
if inQuotes { if inQuotes {
return nil, errors.New("mismatched quotes") return nil, errors.New("mismatched quotes")
} }
if escapeNext {
return nil, errors.New("unfinished escape sequence")
}
if buffer.Len() > 0 { if buffer.Len() > 0 {
groups = append(groups, buffer.String()) groups = append(groups, buffer.String())
} }
......
...@@ -204,6 +204,14 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn { ...@@ -204,6 +204,14 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
return l.lexFalse return l.lexFalse
} }
if l.follow("inf") {
return l.lexInf
}
if l.follow("nan") {
return l.lexNan
}
if isSpace(next) { if isSpace(next) {
l.skip() l.skip()
continue continue
...@@ -265,6 +273,18 @@ func (l *tomlLexer) lexFalse() tomlLexStateFn { ...@@ -265,6 +273,18 @@ func (l *tomlLexer) lexFalse() tomlLexStateFn {
return l.lexRvalue 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 { func (l *tomlLexer) lexEqual() tomlLexStateFn {
l.next() l.next()
l.emit(tokenEqual) l.emit(tokenEqual)
...@@ -277,6 +297,8 @@ func (l *tomlLexer) lexComma() tomlLexStateFn { ...@@ -277,6 +297,8 @@ func (l *tomlLexer) lexComma() tomlLexStateFn {
return l.lexRvalue 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 { func (l *tomlLexer) lexKey() tomlLexStateFn {
growingString := "" growingString := ""
...@@ -287,7 +309,16 @@ func (l *tomlLexer) lexKey() tomlLexStateFn { ...@@ -287,7 +309,16 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
if err != nil { if err != nil {
return l.errorf(err.Error()) return l.errorf(err.Error())
} }
growingString += `"` + str + `"` growingString += str
l.next()
continue
} else if r == '\'' {
l.next()
str, err := l.lexLiteralStringAsString(`'`, false)
if err != nil {
return l.errorf(err.Error())
}
growingString += str
l.next() l.next()
continue continue
} else if r == '\n' { } else if r == '\n' {
...@@ -527,6 +558,7 @@ func (l *tomlLexer) lexTableKey() tomlLexStateFn { ...@@ -527,6 +558,7 @@ func (l *tomlLexer) lexTableKey() tomlLexStateFn {
return l.lexInsideTableKey return l.lexInsideTableKey
} }
// Parse the key till "]]", but only bare keys are supported
func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn { func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn {
for r := l.peek(); r != eof; r = l.peek() { for r := l.peek(); r != eof; r = l.peek() {
switch r { switch r {
...@@ -550,6 +582,7 @@ func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn { ...@@ -550,6 +582,7 @@ func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn {
return l.errorf("unclosed table array key") return l.errorf("unclosed table array key")
} }
// Parse the key till "]" but only bare keys are supported
func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn { func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn {
for r := l.peek(); r != eof; r = l.peek() { for r := l.peek(); r != eof; r = l.peek() {
switch r { switch r {
...@@ -575,11 +608,77 @@ func (l *tomlLexer) lexRightBracket() tomlLexStateFn { ...@@ -575,11 +608,77 @@ func (l *tomlLexer) lexRightBracket() tomlLexStateFn {
return l.lexRvalue 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 { func (l *tomlLexer) lexNumber() tomlLexStateFn {
r := l.peek() 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 == '-' { if r == '+' || r == '-' {
l.next() l.next()
if l.follow("inf") {
return l.lexInf
}
if l.follow("nan") {
return l.lexNan
}
} }
pointSeen := false pointSeen := false
expSeen := false expSeen := false
digitSeen := false digitSeen := false
......
...@@ -5,6 +5,7 @@ package toml ...@@ -5,6 +5,7 @@ package toml
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"reflect" "reflect"
"regexp" "regexp"
"strconv" "strconv"
...@@ -185,10 +186,7 @@ func (p *tomlParser) parseAssign() tomlParserStateFn { ...@@ -185,10 +186,7 @@ func (p *tomlParser) parseAssign() tomlParserStateFn {
} }
// assign value to the found table // assign value to the found table
keyVals, err := parseKey(key.val) keyVals := []string{key.val}
if err != nil {
p.raiseError(key, "%s", err)
}
if len(keyVals) != 1 { if len(keyVals) != 1 {
p.raiseError(key, "Invalid key") p.raiseError(key, "Invalid key")
} }
...@@ -205,20 +203,32 @@ func (p *tomlParser) parseAssign() tomlParserStateFn { ...@@ -205,20 +203,32 @@ func (p *tomlParser) parseAssign() tomlParserStateFn {
case *Tree, []*Tree: case *Tree, []*Tree:
toInsert = value toInsert = value
default: default:
toInsert = &tomlValue{value, key.Position} toInsert = &tomlValue{value: value, position: key.Position}
} }
targetNode.values[keyVal] = toInsert targetNode.values[keyVal] = toInsert
return p.parseStart return p.parseStart
} }
var numberUnderscoreInvalidRegexp *regexp.Regexp var numberUnderscoreInvalidRegexp *regexp.Regexp
var hexNumberUnderscoreInvalidRegexp *regexp.Regexp
func cleanupNumberToken(value string) (string, error) { func numberContainsInvalidUnderscore(value string) error {
if numberUnderscoreInvalidRegexp.MatchString(value) { if numberUnderscoreInvalidRegexp.MatchString(value) {
return "", errors.New("invalid use of _ in number") 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) cleanedVal := strings.Replace(value, "_", "", -1)
return cleanedVal, nil return cleanedVal
} }
func (p *tomlParser) parseRvalue() interface{} { func (p *tomlParser) parseRvalue() interface{} {
...@@ -234,21 +244,57 @@ func (p *tomlParser) parseRvalue() interface{} { ...@@ -234,21 +244,57 @@ func (p *tomlParser) parseRvalue() interface{} {
return true return true
case tokenFalse: case tokenFalse:
return false return false
case tokenInf:
if tok.val[0] == '-' {
return math.Inf(-1)
}
return math.Inf(1)
case tokenNan:
return math.NaN()
case tokenInteger: case tokenInteger:
cleanedVal, err := cleanupNumberToken(tok.val) cleanedVal := cleanupNumberToken(tok.val)
if err != nil { var err error
p.raiseError(tok, "%s", err) 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)
} }
val, err := strconv.ParseInt(cleanedVal, 10, 64)
if err != nil { if err != nil {
p.raiseError(tok, "%s", err) p.raiseError(tok, "%s", err)
} }
return val return val
case tokenFloat: case tokenFloat:
cleanedVal, err := cleanupNumberToken(tok.val) err := numberContainsInvalidUnderscore(tok.val)
if err != nil { if err != nil {
p.raiseError(tok, "%s", err) p.raiseError(tok, "%s", err)
} }
cleanedVal := cleanupNumberToken(tok.val)
val, err := strconv.ParseFloat(cleanedVal, 64) val, err := strconv.ParseFloat(cleanedVal, 64)
if err != nil { if err != nil {
p.raiseError(tok, "%s", err) p.raiseError(tok, "%s", err)
...@@ -309,7 +355,7 @@ Loop: ...@@ -309,7 +355,7 @@ Loop:
} }
p.getToken() p.getToken()
default: default:
p.raiseError(follow, "unexpected token type in inline table: %s", follow.typ.String()) p.raiseError(follow, "unexpected token type in inline table: %s", follow.String())
} }
previous = follow previous = follow
} }
...@@ -379,5 +425,6 @@ func parseToml(flow []token) *Tree { ...@@ -379,5 +425,6 @@ func parseToml(flow []token) *Tree {
} }
func init() { func init() {
numberUnderscoreInvalidRegexp = regexp.MustCompile(`([^\d]_|_[^\d]|_$|^_)`) numberUnderscoreInvalidRegexp = regexp.MustCompile(`([^\d]_|_[^\d])|_$|^_`)
hexNumberUnderscoreInvalidRegexp = regexp.MustCompile(`(^0x_)|([^\da-f]_|_[^\da-f])|_$|^_`)
} }
#!/bin/bash #!/bin/bash
# fail out of the script if anything here fails # fail out of the script if anything here fails
set -e set -e
set -o pipefail
# set the path to the present working directory # set the path to the present working directory
export GOPATH=`pwd` export GOPATH=`pwd`
...@@ -22,9 +23,6 @@ function git_clone() { ...@@ -22,9 +23,6 @@ function git_clone() {
# Remove potential previous runs # Remove potential previous runs
rm -rf src test_program_bin toml-test rm -rf src test_program_bin toml-test
# Run go vet
go vet ./...
go get github.com/pelletier/go-buffruneio go get github.com/pelletier/go-buffruneio
go get github.com/davecgh/go-spew/spew go get github.com/davecgh/go-spew/spew
go get gopkg.in/yaml.v2 go get gopkg.in/yaml.v2
......
...@@ -23,6 +23,8 @@ const ( ...@@ -23,6 +23,8 @@ const (
tokenTrue tokenTrue
tokenFalse tokenFalse
tokenFloat tokenFloat
tokenInf
tokenNan
tokenEqual tokenEqual
tokenLeftBracket tokenLeftBracket
tokenRightBracket tokenRightBracket
...@@ -55,6 +57,8 @@ var tokenTypeNames = []string{ ...@@ -55,6 +57,8 @@ var tokenTypeNames = []string{
"True", "True",
"False", "False",
"Float", "Float",
"Inf",
"NaN",
"=", "=",
"[", "[",
"]", "]",
......
...@@ -11,14 +11,19 @@ import ( ...@@ -11,14 +11,19 @@ import (
) )
type tomlValue struct { type tomlValue struct {
value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list
position Position comment string
commented bool
multiline bool
position Position
} }
// Tree is the result of the parsing of a TOML file. // Tree is the result of the parsing of a TOML file.
type Tree struct { type Tree struct {
values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree
position Position comment string
commented bool
position Position
} }
func newTree() *Tree { func newTree() *Tree {
...@@ -67,18 +72,15 @@ func (t *Tree) Keys() []string { ...@@ -67,18 +72,15 @@ func (t *Tree) Keys() []string {
} }
// Get the value at key in the Tree. // Get the value at key in the Tree.
// Key is a dot-separated path (e.g. a.b.c). // 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. // Returns nil if the path does not exist in the tree.
// If keys is of length zero, the current tree is returned. // If keys is of length zero, the current tree is returned.
func (t *Tree) Get(key string) interface{} { func (t *Tree) Get(key string) interface{} {
if key == "" { if key == "" {
return t return t
} }
comps, err := parseKey(key) return t.GetPath(strings.Split(key, "."))
if err != nil {
return nil
}
return t.GetPath(comps)
} }
// GetPath returns the element in the tree indicated by 'keys'. // GetPath returns the element in the tree indicated by 'keys'.
...@@ -174,17 +176,86 @@ func (t *Tree) GetDefault(key string, def interface{}) interface{} { ...@@ -174,17 +176,86 @@ func (t *Tree) GetDefault(key string, def interface{}) interface{} {
return val 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 _, intermediateKey := range keys[:len(keys)-1] {
nextTree, exists := subtree.values[intermediateKey]
if !exists {
nextTree = newTree()
subtree.values[intermediateKey] = nextTree // add new element here
}
switch node := nextTree.(type) {
case *Tree:
subtree = node
case []*Tree:
// go to most recent element
if len(node) == 0 {
// create element if it does not exist
subtree.values[intermediateKey] = append(node, newTree())
}
subtree = node[len(node)-1]
}
}
var toInsert interface{}
switch value.(type) {
case *Tree:
tt := value.(*Tree)
tt.comment = opts.Comment
toInsert = value
case []*Tree:
toInsert = value
case *tomlValue:
tt := value.(*tomlValue)
tt.comment = opts.Comment
toInsert = tt
default:
toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline}
}
subtree.values[keys[len(keys)-1]] = toInsert
}
// Set an element in the tree. // Set an element in the tree.
// Key is a dot-separated path (e.g. a.b.c). // Key is a dot-separated path (e.g. a.b.c).
// Creates all necessary intermediate trees, if needed. // Creates all necessary intermediate trees, if needed.
func (t *Tree) Set(key string, value interface{}) { func (t *Tree) Set(key string, value interface{}) {
t.SetPath(strings.Split(key, "."), value) 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. // SetPath sets an element in the tree.
// Keys is an array of path elements (e.g. {"a","b","c"}). // Keys is an array of path elements (e.g. {"a","b","c"}).
// Creates all necessary intermediate trees, if needed. // Creates all necessary intermediate trees, if needed.
func (t *Tree) SetPath(keys []string, value interface{}) { 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{}) {
subtree := t subtree := t
for _, intermediateKey := range keys[:len(keys)-1] { for _, intermediateKey := range keys[:len(keys)-1] {
nextTree, exists := subtree.values[intermediateKey] nextTree, exists := subtree.values[intermediateKey]
...@@ -209,13 +280,17 @@ func (t *Tree) SetPath(keys []string, value interface{}) { ...@@ -209,13 +280,17 @@ func (t *Tree) SetPath(keys []string, value interface{}) {
switch value.(type) { switch value.(type) {
case *Tree: case *Tree:
tt := value.(*Tree)
tt.comment = comment
toInsert = value toInsert = value
case []*Tree: case []*Tree:
toInsert = value toInsert = value
case *tomlValue: case *tomlValue:
toInsert = value tt := value.(*tomlValue)
tt.comment = comment
toInsert = tt
default: default:
toInsert = &tomlValue{value: value} toInsert = &tomlValue{value: value, comment: comment, commented: commented}
} }
subtree.values[keys[len(keys)-1]] = toInsert subtree.values[keys[len(keys)-1]] = toInsert
......
...@@ -104,7 +104,7 @@ func sliceToTree(object interface{}) (interface{}, error) { ...@@ -104,7 +104,7 @@ func sliceToTree(object interface{}) (interface{}, error) {
} }
arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue))
} }
return &tomlValue{arrayValue.Interface(), Position{}}, nil return &tomlValue{value: arrayValue.Interface(), position: Position{}}, nil
} }
func toTree(object interface{}) (interface{}, error) { func toTree(object interface{}) (interface{}, error) {
...@@ -127,7 +127,7 @@ func toTree(object interface{}) (interface{}, error) { ...@@ -127,7 +127,7 @@ func toTree(object interface{}) (interface{}, error) {
} }
values[key.String()] = newValue values[key.String()] = newValue
} }
return &Tree{values, Position{}}, nil return &Tree{values: values, position: Position{}}, nil
} }
if value.Kind() == reflect.Array || value.Kind() == reflect.Slice { if value.Kind() == reflect.Array || value.Kind() == reflect.Slice {
...@@ -138,5 +138,5 @@ func toTree(object interface{}) (interface{}, error) { ...@@ -138,5 +138,5 @@ func toTree(object interface{}) (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &tomlValue{simpleValue, Position{}}, nil return &tomlValue{value: simpleValue, position: Position{}}, nil
} }
...@@ -12,7 +12,41 @@ import ( ...@@ -12,7 +12,41 @@ import (
"time" "time"
) )
// encodes a string to a TOML-compliant string value // Encodes a string to a TOML-compliant multi-line string value
// This function is a clone of the existing encodeTomlString function, except that whitespace characters
// are preserved. Quotation marks and backslashes are also not escaped.
func encodeMultilineTomlString(value string) string {
var b bytes.Buffer
for _, rr := range value {
switch rr {
case '\b':
b.WriteString(`\b`)
case '\t':
b.WriteString("\t")
case '\n':
b.WriteString("\n")
case '\f':
b.WriteString(`\f`)
case '\r':
b.WriteString("\r")
case '"':
b.WriteString(`"`)
case '\\':
b.WriteString(`\`)
default:
intRr := uint16(rr)
if intRr < 0x001F {
b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
} else {
b.WriteRune(rr)
}
}
}
return b.String()
}
// Encodes a string to a TOML-compliant string value
func encodeTomlString(value string) string { func encodeTomlString(value string) string {
var b bytes.Buffer var b bytes.Buffer
...@@ -44,7 +78,16 @@ func encodeTomlString(value string) string { ...@@ -44,7 +78,16 @@ func encodeTomlString(value string) string {
return b.String() return b.String()
} }
func tomlValueStringRepresentation(v interface{}) (string, error) { func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElementPerLine bool) (string, error) {
// this interface check is added to dereference the change made in the writeTo function.
// That change was made to allow this function to see formatting options.
tv, ok := v.(*tomlValue)
if ok {
v = tv.value
} else {
tv = &tomlValue{}
}
switch value := v.(type) { switch value := v.(type) {
case uint64: case uint64:
return strconv.FormatUint(value, 10), nil return strconv.FormatUint(value, 10), nil
...@@ -54,14 +97,17 @@ func tomlValueStringRepresentation(v interface{}) (string, error) { ...@@ -54,14 +97,17 @@ func tomlValueStringRepresentation(v interface{}) (string, error) {
// Ensure a round float does contain a decimal point. Otherwise feeding // Ensure a round float does contain a decimal point. Otherwise feeding
// the output back to the parser would convert to an integer. // the output back to the parser would convert to an integer.
if math.Trunc(value) == value { if math.Trunc(value) == value {
return strconv.FormatFloat(value, 'f', 1, 32), nil return strings.ToLower(strconv.FormatFloat(value, 'f', 1, 32)), nil
} }
return strconv.FormatFloat(value, 'f', -1, 32), nil return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil
case string: case string:
if tv.multiline {
return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil
}
return "\"" + encodeTomlString(value) + "\"", nil return "\"" + encodeTomlString(value) + "\"", nil
case []byte: case []byte:
b, _ := v.([]byte) b, _ := v.([]byte)
return tomlValueStringRepresentation(string(b)) return tomlValueStringRepresentation(string(b), indent, arraysOneElementPerLine)
case bool: case bool:
if value { if value {
return "true", nil return "true", nil
...@@ -76,21 +122,38 @@ func tomlValueStringRepresentation(v interface{}) (string, error) { ...@@ -76,21 +122,38 @@ func tomlValueStringRepresentation(v interface{}) (string, error) {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Slice { if rv.Kind() == reflect.Slice {
values := []string{} var values []string
for i := 0; i < rv.Len(); i++ { for i := 0; i < rv.Len(); i++ {
item := rv.Index(i).Interface() item := rv.Index(i).Interface()
itemRepr, err := tomlValueStringRepresentation(item) itemRepr, err := tomlValueStringRepresentation(item, indent, arraysOneElementPerLine)
if err != nil { if err != nil {
return "", err return "", err
} }
values = append(values, itemRepr) values = append(values, itemRepr)
} }
if arraysOneElementPerLine && len(values) > 1 {
stringBuffer := bytes.Buffer{}
valueIndent := indent + ` ` // TODO: move that to a shared encoder state
stringBuffer.WriteString("[\n")
for _, value := range values {
stringBuffer.WriteString(valueIndent)
stringBuffer.WriteString(value)
stringBuffer.WriteString(`,`)
stringBuffer.WriteString("\n")
}
stringBuffer.WriteString(indent + "]")
return stringBuffer.String(), nil
}
return "[" + strings.Join(values, ",") + "]", nil return "[" + strings.Join(values, ",") + "]", nil
} }
return "", fmt.Errorf("unsupported value type %T: %v", v, v) return "", fmt.Errorf("unsupported value type %T: %v", v, v)
} }
func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (int64, error) { func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
simpleValuesKeys := make([]string, 0) simpleValuesKeys := make([]string, 0)
complexValuesKeys := make([]string, 0) complexValuesKeys := make([]string, 0)
...@@ -113,12 +176,29 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) ( ...@@ -113,12 +176,29 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (
return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
} }
repr, err := tomlValueStringRepresentation(v.value) repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
writtenBytesCount, err := writeStrings(w, indent, k, " = ", repr, "\n") if v.comment != "" {
comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
start := "# "
if strings.HasPrefix(comment, "#") {
start = ""
}
writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
bytesCount += int64(writtenBytesCountComment)
if errc != nil {
return bytesCount, errc
}
}
var commented string
if v.commented {
commented = "# "
}
writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n")
bytesCount += int64(writtenBytesCount) bytesCount += int64(writtenBytesCount)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
...@@ -132,28 +212,48 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) ( ...@@ -132,28 +212,48 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (
if keyspace != "" { if keyspace != "" {
combinedKey = keyspace + "." + combinedKey combinedKey = keyspace + "." + combinedKey
} }
var commented string
if t.commented {
commented = "# "
}
switch node := v.(type) { switch node := v.(type) {
// node has to be of those two types given how keys are sorted above // node has to be of those two types given how keys are sorted above
case *Tree: case *Tree:
writtenBytesCount, err := writeStrings(w, "\n", indent, "[", combinedKey, "]\n") tv, ok := t.values[k].(*Tree)
if !ok {
return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
}
if tv.comment != "" {
comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
start := "# "
if strings.HasPrefix(comment, "#") {
start = ""
}
writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
bytesCount += int64(writtenBytesCountComment)
if errc != nil {
return bytesCount, errc
}
}
writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
bytesCount += int64(writtenBytesCount) bytesCount += int64(writtenBytesCount)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount) bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
case []*Tree: case []*Tree:
for _, subTree := range node { for _, subTree := range node {
writtenBytesCount, err := writeStrings(w, "\n", indent, "[[", combinedKey, "]]\n") writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
bytesCount += int64(writtenBytesCount) bytesCount += int64(writtenBytesCount)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount) bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine)
if err != nil { if err != nil {
return bytesCount, err return bytesCount, err
} }
...@@ -179,7 +279,7 @@ func writeStrings(w io.Writer, s ...string) (int, error) { ...@@ -179,7 +279,7 @@ func writeStrings(w io.Writer, s ...string) (int, error) {
// WriteTo encode the Tree as Toml and writes it to the writer w. // 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. // 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) { func (t *Tree) WriteTo(w io.Writer) (int64, error) {
return t.writeTo(w, "", "", 0) return t.writeTo(w, "", "", 0, false)
} }
// ToTomlString generates a human-readable representation of the current tree. // ToTomlString generates a human-readable representation of the current tree.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment