Compare commits

...

13 Commits

Author SHA1 Message Date
Thomas Pelletier 13d49d4606 Fix coveralls (#136) 2017-03-02 09:43:01 -08:00
Thomas Pelletier 7e6e4b1314 Rewrite TomlTree encoding (#133)
* Rewrite `TomlTree` encoding
* Introduce `TomlTree.WriteTo`
2017-03-02 09:17:06 -08:00
Thomas Pelletier 3616783228 Run go vet as part of the test suite (#132)
* Run go vet as part of the test suite
2017-02-27 14:29:04 -08:00
Thomas Pelletier d0ec4317d3 Fix compatibility with latest go-buffruneio (#131) 2017-02-27 14:18:12 -08:00
Thomas Pelletier 22139eb546 Test with go 1.8 (#129) 2017-02-16 17:27:36 -08:00
Thomas Pelletier c9506ee963 Update license (#128)
* Update LICENSE badge
* Update license year to 2017
2017-02-09 13:38:35 -08:00
David Brown 3a6d01f7a0 Fix syntax errors in package-level documentation (#126) 2017-02-09 13:23:28 -08:00
Thomas Pelletier d1fa2118c1 Bump test go to 1.7.5 (#127)
* Bump test go to 1.7.5
* Use travis container infrastructure
* Don't run the tests twice on PRs
2017-02-03 13:36:21 -08:00
Thomas Pelletier a1f048ba24 Make ToString() return an error instead of panic (#117)
Fixes #100
2017-01-15 18:49:11 -08:00
Jordan Bach ee2c0b51cf Fix typo in README tomljson installation instructions (#125) 2017-01-15 18:48:04 -08:00
Thomas Pelletier 439fbba1f8 Make lexComment jump back to the previous state (#122)
When a comment appears in an rvalue, the lexer needs to jump back to
lexRValue, not to lexVoid.

Fixes #120.
2016-12-29 19:51:04 +01:00
Christopher Mancini 017119f7a7 Use a single line for slice encoding (#119) 2016-12-13 15:20:06 +01:00
Thomas Pelletier ce7be745f0 Rename group to table (#115)
* Rename Group to Table Fixes #45 
* Change fmt.Errorf to errors.new for simple strings
2016-12-03 12:32:16 +01:00
15 changed files with 628 additions and 501 deletions
+6 -3
View File
@@ -1,8 +1,9 @@
sudo: false
language: go language: go
go: go:
- 1.5.4
- 1.6.4 - 1.6.4
- 1.7.4 - 1.7.5
- 1.8
- tip - tip
matrix: matrix:
allow_failures: allow_failures:
@@ -14,5 +15,7 @@ before_install:
- go get github.com/axw/gocov/gocov - go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
branches:
only: [master]
after_success: after_success:
- $HOME/gopath/bin/goveralls -service=travis-ci - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=coverage.out -repotoken $COVERALLS_TOKEN
+1 -2
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013 - 2016 Thomas Pelletier, Eric Anderton Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
+2 -2
View File
@@ -6,7 +6,7 @@ This library supports TOML version
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
[![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/github.com/pelletier/go-toml) [![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/github.com/pelletier/go-toml)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/goadesign/goa/blob/master/LICENSE) [![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/pelletier/go-toml.svg?branch=master)](https://travis-ci.org/pelletier/go-toml) [![Build Status](https://travis-ci.org/pelletier/go-toml.svg?branch=master)](https://travis-ci.org/pelletier/go-toml)
[![Coverage Status](https://coveralls.io/repos/github/pelletier/go-toml/badge.svg?branch=master)](https://coveralls.io/github/pelletier/go-toml?branch=master) [![Coverage Status](https://coveralls.io/repos/github/pelletier/go-toml/badge.svg?branch=master)](https://coveralls.io/github/pelletier/go-toml?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml) [![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml)
@@ -96,7 +96,7 @@ Go-toml provides two handy command line tools:
* `tomljson`: Reads a TOML file and outputs its JSON representation. * `tomljson`: Reads a TOML file and outputs its JSON representation.
``` ```
go install github.com/pelletier/go-toml/cmd/tomjson go install github.com/pelletier/go-toml/cmd/tomljson
tomljson --help tomljson --help
``` ```
+4 -4
View File
@@ -22,8 +22,8 @@
// After parsing TOML data with Load() or LoadFile(), use the Has() and Get() // After parsing TOML data with Load() or LoadFile(), use the Has() and Get()
// methods on the returned TomlTree, to find your way through the document data. // methods on the returned TomlTree, to find your way through the document data.
// //
// if tree.Has('foo') { // if tree.Has("foo") {
// fmt.Prinln("foo is: %v", tree.Get('foo')) // fmt.Println("foo is:", tree.Get("foo"))
// } // }
// //
// Working with Paths // Working with Paths
@@ -44,10 +44,10 @@
// it avoids having to parse the passed key for '.' delimiters. // it avoids having to parse the passed key for '.' delimiters.
// //
// // looks for a key named 'baz', within struct 'bar', within struct 'foo' // // looks for a key named 'baz', within struct 'bar', within struct 'foo'
// tree.HasPath(string{}{"foo","bar","baz"}) // tree.HasPath([]string{"foo","bar","baz"})
// //
// // returns the key at this path, if it is there // // returns the key at this path, if it is there
// tree.GetPath(string{}{"foo","bar","baz"}) // tree.GetPath([]string{"foo","bar","baz"})
// //
// Note that this is distinct from the heavyweight query syntax supported by // Note that this is distinct from the heavyweight query syntax supported by
// TomlTree.Query() and the Query() struct (see below). // TomlTree.Query() and the Query() struct (see below).
+6 -5
View File
@@ -4,6 +4,7 @@ package toml
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"unicode" "unicode"
) )
@@ -47,7 +48,7 @@ func parseKey(key string) ([]string, error) {
} else { } else {
if !wasInQuotes { if !wasInQuotes {
if buffer.Len() == 0 { if buffer.Len() == 0 {
return nil, fmt.Errorf("empty key group") return nil, errors.New("empty table key")
} }
groups = append(groups, buffer.String()) groups = append(groups, buffer.String())
buffer.Reset() buffer.Reset()
@@ -67,23 +68,23 @@ func parseKey(key string) ([]string, error) {
return nil, fmt.Errorf("invalid bare character: %c", char) return nil, fmt.Errorf("invalid bare character: %c", char)
} }
if !inQuotes && expectDot { if !inQuotes && expectDot {
return nil, fmt.Errorf("what?") return nil, errors.New("what?")
} }
buffer.WriteRune(char) buffer.WriteRune(char)
expectDot = false expectDot = false
} }
} }
if inQuotes { if inQuotes {
return nil, fmt.Errorf("mismatched quotes") return nil, errors.New("mismatched quotes")
} }
if escapeNext { if escapeNext {
return nil, fmt.Errorf("unfinished escape sequence") 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())
} }
if len(groups) == 0 { if len(groups) == 0 {
return nil, fmt.Errorf("empty key") return nil, errors.New("empty key")
} }
return groups, nil return groups, nil
} }
+27 -25
View File
@@ -36,7 +36,7 @@ type tomlLexer struct {
// Basic read operations on input // Basic read operations on input
func (l *tomlLexer) read() rune { func (l *tomlLexer) read() rune {
r, err := l.input.ReadRune() r, _, err := l.input.ReadRune()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -89,7 +89,7 @@ func (l *tomlLexer) emit(t tokenType) {
} }
func (l *tomlLexer) peek() rune { func (l *tomlLexer) peek() rune {
r, err := l.input.ReadRune() r, _, err := l.input.ReadRune()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -99,7 +99,7 @@ func (l *tomlLexer) peek() rune {
func (l *tomlLexer) follow(next string) bool { func (l *tomlLexer) follow(next string) bool {
for _, expectedRune := range next { for _, expectedRune := range next {
r, err := l.input.ReadRune() r, _, err := l.input.ReadRune()
defer l.input.UnreadRune() defer l.input.UnreadRune()
if err != nil { if err != nil {
panic(err) panic(err)
@@ -129,9 +129,9 @@ func (l *tomlLexer) lexVoid() tomlLexStateFn {
next := l.peek() next := l.peek()
switch next { switch next {
case '[': case '[':
return l.lexKeyGroup return l.lexTableKey
case '#': case '#':
return l.lexComment return l.lexComment(l.lexVoid)
case '=': case '=':
return l.lexEqual return l.lexEqual
case '\r': case '\r':
@@ -182,7 +182,7 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
case '}': case '}':
return l.lexRightCurlyBrace return l.lexRightCurlyBrace
case '#': case '#':
return l.lexComment return l.lexComment(l.lexRvalue)
case '"': case '"':
return l.lexString return l.lexString
case '\'': case '\'':
@@ -219,7 +219,7 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
break break
} }
possibleDate := string(l.input.Peek(35)) possibleDate := string(l.input.PeekRunes(35))
dateMatch := dateRegexp.FindString(possibleDate) dateMatch := dateRegexp.FindString(possibleDate)
if dateMatch != "" { if dateMatch != "" {
l.fastForward(len(dateMatch)) l.fastForward(len(dateMatch))
@@ -309,15 +309,17 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
return l.lexVoid return l.lexVoid
} }
func (l *tomlLexer) lexComment() tomlLexStateFn { func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn {
for next := l.peek(); next != '\n' && next != eof; next = l.peek() { return func() tomlLexStateFn {
if next == '\r' && l.follow("\r\n") { for next := l.peek(); next != '\n' && next != eof; next = l.peek() {
break if next == '\r' && l.follow("\r\n") {
break
}
l.next()
} }
l.next() l.ignore()
return previousState
} }
l.ignore()
return l.lexVoid
} }
func (l *tomlLexer) lexLeftBracket() tomlLexStateFn { func (l *tomlLexer) lexLeftBracket() tomlLexStateFn {
@@ -516,21 +518,21 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
return l.lexRvalue return l.lexRvalue
} }
func (l *tomlLexer) lexKeyGroup() tomlLexStateFn { func (l *tomlLexer) lexTableKey() tomlLexStateFn {
l.next() l.next()
if l.peek() == '[' { if l.peek() == '[' {
// token '[[' signifies an array of anonymous key groups // token '[[' signifies an array of tables
l.next() l.next()
l.emit(tokenDoubleLeftBracket) l.emit(tokenDoubleLeftBracket)
return l.lexInsideKeyGroupArray return l.lexInsideTableArrayKey
} }
// vanilla key group // vanilla table key
l.emit(tokenLeftBracket) l.emit(tokenLeftBracket)
return l.lexInsideKeyGroup return l.lexInsideTableKey
} }
func (l *tomlLexer) lexInsideKeyGroupArray() 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 {
case ']': case ']':
@@ -545,15 +547,15 @@ func (l *tomlLexer) lexInsideKeyGroupArray() tomlLexStateFn {
l.emit(tokenDoubleRightBracket) l.emit(tokenDoubleRightBracket)
return l.lexVoid return l.lexVoid
case '[': case '[':
return l.errorf("group name cannot contain ']'") return l.errorf("table array key cannot contain ']'")
default: default:
l.next() l.next()
} }
} }
return l.errorf("unclosed key group array") return l.errorf("unclosed table array key")
} }
func (l *tomlLexer) lexInsideKeyGroup() 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 {
case ']': case ']':
@@ -564,12 +566,12 @@ func (l *tomlLexer) lexInsideKeyGroup() tomlLexStateFn {
l.emit(tokenRightBracket) l.emit(tokenRightBracket)
return l.lexVoid return l.lexVoid
case '[': case '[':
return l.errorf("group name cannot contain ']'") return l.errorf("table key cannot contain ']'")
default: default:
l.next() l.next()
} }
} }
return l.errorf("unclosed key group") return l.errorf("unclosed table key")
} }
func (l *tomlLexer) lexRightBracket() tomlLexStateFn { func (l *tomlLexer) lexRightBracket() tomlLexStateFn {
+19 -1
View File
@@ -56,7 +56,7 @@ func TestNestedQuotedUnicodeKeyGroup(t *testing.T) {
func TestUnclosedKeyGroup(t *testing.T) { func TestUnclosedKeyGroup(t *testing.T) {
testFlow(t, "[hello world", []token{ testFlow(t, "[hello world", []token{
{Position{1, 1}, tokenLeftBracket, "["}, {Position{1, 1}, tokenLeftBracket, "["},
{Position{1, 2}, tokenError, "unclosed key group"}, {Position{1, 2}, tokenError, "unclosed table key"},
}) })
} }
@@ -264,6 +264,24 @@ func TestMultilineArrayComments(t *testing.T) {
}) })
} }
func TestNestedArraysComment(t *testing.T) {
toml := `
someArray = [
# does not work
["entry1"]
]`
testFlow(t, toml, []token{
{Position{2, 1}, tokenKey, "someArray"},
{Position{2, 11}, tokenEqual, "="},
{Position{2, 13}, tokenLeftBracket, "["},
{Position{4, 1}, tokenLeftBracket, "["},
{Position{4, 3}, tokenString, "entry1"},
{Position{4, 10}, tokenRightBracket, "]"},
{Position{5, 1}, tokenRightBracket, "]"},
{Position{5, 2}, tokenEOF, ""},
})
}
func TestKeyEqualArrayBools(t *testing.T) { func TestKeyEqualArrayBools(t *testing.T) {
testFlow(t, "foo = [true, false, true]", []token{ testFlow(t, "foo = [true, false, true]", []token{
{Position{1, 1}, tokenKey, "foo"}, {Position{1, 1}, tokenKey, "foo"},
+35 -34
View File
@@ -3,6 +3,7 @@
package toml package toml
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
"regexp" "regexp"
@@ -15,8 +16,8 @@ type tomlParser struct {
flow chan token flow chan token
tree *TomlTree tree *TomlTree
tokensBuffer []token tokensBuffer []token
currentGroup []string currentTable []string
seenGroupKeys []string seenTableKeys []string
} }
type tomlParserStateFn func() tomlParserStateFn type tomlParserStateFn func() tomlParserStateFn
@@ -95,13 +96,13 @@ func (p *tomlParser) parseGroupArray() tomlParserStateFn {
startToken := p.getToken() // discard the [[ startToken := p.getToken() // discard the [[
key := p.getToken() key := p.getToken()
if key.typ != tokenKeyGroupArray { if key.typ != tokenKeyGroupArray {
p.raiseError(key, "unexpected token %s, was expecting a key group array", key) p.raiseError(key, "unexpected token %s, was expecting a table array key", key)
} }
// get or create group array element at the indicated part in the path // get or create table array element at the indicated part in the path
keys, err := parseKey(key.val) keys, err := parseKey(key.val)
if err != nil { if err != nil {
p.raiseError(key, "invalid group array key: %s", err) p.raiseError(key, "invalid table array key: %s", err)
} }
p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries
destTree := p.tree.GetPath(keys) destTree := p.tree.GetPath(keys)
@@ -111,32 +112,32 @@ func (p *tomlParser) parseGroupArray() tomlParserStateFn {
} else if target, ok := destTree.([]*TomlTree); ok && target != nil { } else if target, ok := destTree.([]*TomlTree); ok && target != nil {
array = destTree.([]*TomlTree) array = destTree.([]*TomlTree)
} else { } else {
p.raiseError(key, "key %s is already assigned and not of type group array", key) p.raiseError(key, "key %s is already assigned and not of type table array", key)
} }
p.currentGroup = keys p.currentTable = keys
// add a new tree to the end of the group array // add a new tree to the end of the table array
newTree := newTomlTree() newTree := newTomlTree()
newTree.position = startToken.Position newTree.position = startToken.Position
array = append(array, newTree) array = append(array, newTree)
p.tree.SetPath(p.currentGroup, array) p.tree.SetPath(p.currentTable, array)
// remove all keys that were children of this group array // remove all keys that were children of this table array
prefix := key.val + "." prefix := key.val + "."
found := false found := false
for ii := 0; ii < len(p.seenGroupKeys); { for ii := 0; ii < len(p.seenTableKeys); {
groupKey := p.seenGroupKeys[ii] tableKey := p.seenTableKeys[ii]
if strings.HasPrefix(groupKey, prefix) { if strings.HasPrefix(tableKey, prefix) {
p.seenGroupKeys = append(p.seenGroupKeys[:ii], p.seenGroupKeys[ii+1:]...) p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...)
} else { } else {
found = (groupKey == key.val) found = (tableKey == key.val)
ii++ ii++
} }
} }
// keep this key name from use by other kinds of assignments // keep this key name from use by other kinds of assignments
if !found { if !found {
p.seenGroupKeys = append(p.seenGroupKeys, key.val) p.seenTableKeys = append(p.seenTableKeys, key.val)
} }
// move to next parser state // move to next parser state
@@ -148,24 +149,24 @@ func (p *tomlParser) parseGroup() tomlParserStateFn {
startToken := p.getToken() // discard the [ startToken := p.getToken() // discard the [
key := p.getToken() key := p.getToken()
if key.typ != tokenKeyGroup { if key.typ != tokenKeyGroup {
p.raiseError(key, "unexpected token %s, was expecting a key group", key) p.raiseError(key, "unexpected token %s, was expecting a table key", key)
} }
for _, item := range p.seenGroupKeys { for _, item := range p.seenTableKeys {
if item == key.val { if item == key.val {
p.raiseError(key, "duplicated tables") p.raiseError(key, "duplicated tables")
} }
} }
p.seenGroupKeys = append(p.seenGroupKeys, key.val) p.seenTableKeys = append(p.seenTableKeys, key.val)
keys, err := parseKey(key.val) keys, err := parseKey(key.val)
if err != nil { if err != nil {
p.raiseError(key, "invalid group array key: %s", err) p.raiseError(key, "invalid table array key: %s", err)
} }
if err := p.tree.createSubTree(keys, startToken.Position); err != nil { if err := p.tree.createSubTree(keys, startToken.Position); err != nil {
p.raiseError(key, "%s", err) p.raiseError(key, "%s", err)
} }
p.assume(tokenRightBracket) p.assume(tokenRightBracket)
p.currentGroup = keys p.currentTable = keys
return p.parseStart return p.parseStart
} }
@@ -174,26 +175,26 @@ func (p *tomlParser) parseAssign() tomlParserStateFn {
p.assume(tokenEqual) p.assume(tokenEqual)
value := p.parseRvalue() value := p.parseRvalue()
var groupKey []string var tableKey []string
if len(p.currentGroup) > 0 { if len(p.currentTable) > 0 {
groupKey = p.currentGroup tableKey = p.currentTable
} else { } else {
groupKey = []string{} tableKey = []string{}
} }
// find the group to assign, looking out for arrays of groups // find the table to assign, looking out for arrays of tables
var targetNode *TomlTree var targetNode *TomlTree
switch node := p.tree.GetPath(groupKey).(type) { switch node := p.tree.GetPath(tableKey).(type) {
case []*TomlTree: case []*TomlTree:
targetNode = node[len(node)-1] targetNode = node[len(node)-1]
case *TomlTree: case *TomlTree:
targetNode = node targetNode = node
default: default:
p.raiseError(key, "Unknown group type for path: %s", p.raiseError(key, "Unknown table type for path: %s",
strings.Join(groupKey, ".")) strings.Join(tableKey, "."))
} }
// assign value to the found group // assign value to the found table
keyVals, err := parseKey(key.val) keyVals, err := parseKey(key.val)
if err != nil { if err != nil {
p.raiseError(key, "%s", err) p.raiseError(key, "%s", err)
@@ -203,7 +204,7 @@ func (p *tomlParser) parseAssign() tomlParserStateFn {
} }
keyVal := keyVals[0] keyVal := keyVals[0]
localKey := []string{keyVal} localKey := []string{keyVal}
finalKey := append(groupKey, keyVal) finalKey := append(tableKey, keyVal)
if targetNode.GetPath(localKey) != nil { if targetNode.GetPath(localKey) != nil {
p.raiseError(key, "The following key was defined twice: %s", p.raiseError(key, "The following key was defined twice: %s",
strings.Join(finalKey, ".")) strings.Join(finalKey, "."))
@@ -224,7 +225,7 @@ var numberUnderscoreInvalidRegexp *regexp.Regexp
func cleanupNumberToken(value string) (string, error) { func cleanupNumberToken(value string) (string, error) {
if numberUnderscoreInvalidRegexp.MatchString(value) { if numberUnderscoreInvalidRegexp.MatchString(value) {
return "", fmt.Errorf("invalid use of _ in number") return "", errors.New("invalid use of _ in number")
} }
cleanedVal := strings.Replace(value, "_", "", -1) cleanedVal := strings.Replace(value, "_", "", -1)
return cleanedVal, nil return cleanedVal, nil
@@ -380,8 +381,8 @@ func parseToml(flow chan token) *TomlTree {
flow: flow, flow: flow,
tree: result, tree: result,
tokensBuffer: make([]token, 0), tokensBuffer: make([]token, 0),
currentGroup: make([]string, 0), currentTable: make([]string, 0),
seenGroupKeys: make([]string, 0), seenTableKeys: make([]string, 0),
} }
parser.run() parser.run()
return result return result
+25 -43
View File
@@ -283,6 +283,17 @@ func TestArrayNested(t *testing.T) {
}) })
} }
func TestNestedArrayComment(t *testing.T) {
tree, err := Load(`
someArray = [
# does not work
["entry1"]
]`)
assertTree(t, tree, err, map[string]interface{}{
"someArray": [][]string{{"entry1"}},
})
}
func TestNestedEmptyArrays(t *testing.T) { func TestNestedEmptyArrays(t *testing.T) {
tree, err := Load("a = [[[]]]") tree, err := Load("a = [[[]]]")
assertTree(t, tree, err, map[string]interface{}{ assertTree(t, tree, err, map[string]interface{}{
@@ -456,7 +467,7 @@ func TestDuplicateKeys(t *testing.T) {
func TestEmptyIntermediateTable(t *testing.T) { func TestEmptyIntermediateTable(t *testing.T) {
_, err := Load("[foo..bar]") _, err := Load("[foo..bar]")
if err.Error() != "(1, 2): invalid group array key: empty key group" { if err.Error() != "(1, 2): invalid table array key: empty table key" {
t.Error("Bad error message:", err.Error()) t.Error("Bad error message:", err.Error())
} }
} }
@@ -580,12 +591,12 @@ func TestParseKeyGroupArray(t *testing.T) {
func TestParseKeyGroupArrayUnfinished(t *testing.T) { func TestParseKeyGroupArrayUnfinished(t *testing.T) {
_, err := Load("[[foo.bar]\na = 42") _, err := Load("[[foo.bar]\na = 42")
if err.Error() != "(1, 10): was expecting token [[, but got unclosed key group array instead" { if err.Error() != "(1, 10): was expecting token [[, but got unclosed table array key instead" {
t.Error("Bad error message:", err.Error()) t.Error("Bad error message:", err.Error())
} }
_, err = Load("[[foo.[bar]\na = 42") _, err = Load("[[foo.[bar]\na = 42")
if err.Error() != "(1, 3): unexpected token group name cannot contain ']', was expecting a key group array" { if err.Error() != "(1, 3): unexpected token table array key cannot contain ']', was expecting a table array key" {
t.Error("Bad error message:", err.Error()) t.Error("Bad error message:", err.Error())
} }
} }
@@ -622,22 +633,13 @@ func TestParseKeyGroupArraySpec(t *testing.T) {
}) })
} }
func TestToTomlValue(t *testing.T) { func TestTomlValueStringRepresentation(t *testing.T) {
for idx, item := range []struct { for idx, item := range []struct {
Value interface{} Value interface{}
Expect string Expect string
}{ }{
{int(1), "1"},
{int8(2), "2"},
{int16(3), "3"},
{int32(4), "4"},
{int64(12345), "12345"}, {int64(12345), "12345"},
{uint(10), "10"},
{uint8(20), "20"},
{uint16(30), "30"},
{uint32(40), "40"},
{uint64(50), "50"}, {uint64(50), "50"},
{float32(12.456), "12.456"},
{float64(123.45), "123.45"}, {float64(123.45), "123.45"},
{bool(true), "true"}, {bool(true), "true"},
{"hello world", "\"hello world\""}, {"hello world", "\"hello world\""},
@@ -646,42 +648,22 @@ func TestToTomlValue(t *testing.T) {
{time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), {time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
"1979-05-27T07:32:00Z"}, "1979-05-27T07:32:00Z"},
{[]interface{}{"gamma", "delta"}, {[]interface{}{"gamma", "delta"},
"[\n \"gamma\",\n \"delta\",\n]"}, "[\"gamma\",\"delta\"]"},
{nil, ""}, {nil, ""},
} { } {
result := toTomlValue(item.Value, 0) result, err := tomlValueStringRepresentation(item.Value)
if err != nil {
t.Errorf("Test %d - unexpected error: %s", idx, err)
}
if result != item.Expect { if result != item.Expect {
t.Errorf("Test %d - got '%s', expected '%s'", idx, result, item.Expect) t.Errorf("Test %d - got '%s', expected '%s'", idx, result, item.Expect)
} }
} }
} }
func TestToString(t *testing.T) {
tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n")
if err != nil {
t.Errorf("Test failed to parse: %v", err)
return
}
result := tree.ToString()
expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n"
if result != expected {
t.Errorf("Expected got '%s', expected '%s'", result, expected)
}
}
func TestToStringMapStringString(t *testing.T) { func TestToStringMapStringString(t *testing.T) {
in := map[string]interface{}{"m": map[string]string{"v": "abc"}} in := map[string]interface{}{"m": TreeFromMap(map[string]interface{}{
want := "\n[m]\n v = \"abc\"\n" "v": &tomlValue{"abc", Position{0, 0}}})}
tree := TreeFromMap(in)
got := tree.String()
if got != want {
t.Errorf("want:\n%q\ngot:\n%q", want, got)
}
}
func TestToStringMapInterfaceInterface(t *testing.T) {
in := map[string]interface{}{"m": map[interface{}]interface{}{"v": "abc"}}
want := "\n[m]\n v = \"abc\"\n" want := "\n[m]\n v = \"abc\"\n"
tree := TreeFromMap(in) tree := TreeFromMap(in)
got := tree.String() got := tree.String()
@@ -753,13 +735,13 @@ func TestNestedTreePosition(t *testing.T) {
} }
func TestInvalidGroupArray(t *testing.T) { func TestInvalidGroupArray(t *testing.T) {
_, err := Load("[key#group]\nanswer = 42") _, err := Load("[table#key]\nanswer = 42")
if err == nil { if err == nil {
t.Error("Should error") t.Error("Should error")
} }
_, err = Load("[foo.[bar]\na = 42") _, err = Load("[foo.[bar]\na = 42")
if err.Error() != "(1, 2): unexpected token group name cannot contain ']', was expecting a key group" { if err.Error() != "(1, 2): unexpected token table key cannot contain ']', was expecting a table key" {
t.Error("Bad error message:", err.Error()) t.Error("Bad error message:", err.Error())
} }
} }
@@ -773,7 +755,7 @@ func TestDoubleEqual(t *testing.T) {
func TestGroupArrayReassign(t *testing.T) { func TestGroupArrayReassign(t *testing.T) {
_, err := Load("[hello]\n[[hello]]") _, err := Load("[hello]\n[[hello]]")
if err.Error() != "(2, 3): key \"hello\" is already assigned and not of type group array" { if err.Error() != "(2, 3): key \"hello\" is already assigned and not of type table array" {
t.Error("Bad error message:", err.Error()) t.Error("Bad error message:", err.Error())
} }
} }
+5 -2
View File
@@ -19,6 +19,9 @@ function git_clone() {
popd popd
} }
# 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
@@ -38,8 +41,8 @@ cp -R cmd/* src/github.com/pelletier/go-toml/cmd
go build -o test_program_bin src/github.com/pelletier/go-toml/cmd/test_program.go go build -o test_program_bin src/github.com/pelletier/go-toml/cmd/test_program.go
# Run basic unit tests # Run basic unit tests
go test github.com/pelletier/go-toml \ go test github.com/pelletier/go-toml -v -covermode=count -coverprofile=coverage.out
github.com/pelletier/go-toml/cmd/tomljson go test github.com/pelletier/go-toml/cmd/tomljson
# run the entire BurntSushi test suite # run the entire BurntSushi test suite
if [[ $# -eq 0 ]] ; then if [[ $# -eq 0 ]] ; then
+2 -2
View File
@@ -10,13 +10,13 @@ import (
) )
type tomlValue struct { type tomlValue struct {
value interface{} value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list
position Position position Position
} }
// TomlTree is the result of the parsing of a TOML file. // TomlTree is the result of the parsing of a TOML file.
type TomlTree struct { type TomlTree struct {
values map[string]interface{} values map[string]interface{} // string -> *tomlValue, *TomlTree, []*TomlTree
position Position position Position
} }
-207
View File
@@ -1,207 +0,0 @@
package toml
// Tools to convert a TomlTree to different representations
import (
"fmt"
"strconv"
"strings"
"time"
)
// encodes a string to a TOML-compliant string value
func encodeTomlString(value string) string {
result := ""
for _, rr := range value {
intRr := uint16(rr)
switch rr {
case '\b':
result += "\\b"
case '\t':
result += "\\t"
case '\n':
result += "\\n"
case '\f':
result += "\\f"
case '\r':
result += "\\r"
case '"':
result += "\\\""
case '\\':
result += "\\\\"
default:
if intRr < 0x001F {
result += fmt.Sprintf("\\u%0.4X", intRr)
} else {
result += string(rr)
}
}
}
return result
}
// Value print support function for ToString()
// Outputs the TOML compliant string representation of a value
func toTomlValue(item interface{}, indent int) string {
tab := strings.Repeat(" ", indent)
switch value := item.(type) {
case int:
return tab + strconv.FormatInt(int64(value), 10)
case int8:
return tab + strconv.FormatInt(int64(value), 10)
case int16:
return tab + strconv.FormatInt(int64(value), 10)
case int32:
return tab + strconv.FormatInt(int64(value), 10)
case int64:
return tab + strconv.FormatInt(value, 10)
case uint:
return tab + strconv.FormatUint(uint64(value), 10)
case uint8:
return tab + strconv.FormatUint(uint64(value), 10)
case uint16:
return tab + strconv.FormatUint(uint64(value), 10)
case uint32:
return tab + strconv.FormatUint(uint64(value), 10)
case uint64:
return tab + strconv.FormatUint(value, 10)
case float32:
return tab + strconv.FormatFloat(float64(value), 'f', -1, 32)
case float64:
return tab + strconv.FormatFloat(value, 'f', -1, 64)
case string:
return tab + "\"" + encodeTomlString(value) + "\""
case bool:
if value {
return "true"
}
return "false"
case time.Time:
return tab + value.Format(time.RFC3339)
case []interface{}:
result := tab + "[\n"
for _, item := range value {
result += toTomlValue(item, indent+2) + ",\n"
}
return result + tab + "]"
case nil:
return ""
default:
panic(fmt.Sprintf("unsupported value type %T: %v", value, value))
}
}
// Recursive support function for ToString()
// Outputs a tree, using the provided keyspace to prefix group names
func (t *TomlTree) toToml(indent, keyspace string) string {
resultChunks := []string{}
for k, v := range t.values {
// figure out the keyspace
combinedKey := k
if keyspace != "" {
combinedKey = keyspace + "." + combinedKey
}
resultChunk := ""
// output based on type
switch node := v.(type) {
case []*TomlTree:
for _, item := range node {
if len(item.Keys()) > 0 {
resultChunk += fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey)
}
resultChunk += item.toToml(indent+" ", combinedKey)
}
resultChunks = append(resultChunks, resultChunk)
case *TomlTree:
if len(node.Keys()) > 0 {
resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
}
resultChunk += node.toToml(indent+" ", combinedKey)
resultChunks = append(resultChunks, resultChunk)
case map[string]interface{}:
sub := TreeFromMap(node)
if len(sub.Keys()) > 0 {
resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
}
resultChunk += sub.toToml(indent+" ", combinedKey)
resultChunks = append(resultChunks, resultChunk)
case map[string]string:
sub := TreeFromMap(convertMapStringString(node))
if len(sub.Keys()) > 0 {
resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
}
resultChunk += sub.toToml(indent+" ", combinedKey)
resultChunks = append(resultChunks, resultChunk)
case map[interface{}]interface{}:
sub := TreeFromMap(convertMapInterfaceInterface(node))
if len(sub.Keys()) > 0 {
resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
}
resultChunk += sub.toToml(indent+" ", combinedKey)
resultChunks = append(resultChunks, resultChunk)
case *tomlValue:
resultChunk = fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
resultChunks = append([]string{resultChunk}, resultChunks...)
default:
resultChunk = fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0))
resultChunks = append([]string{resultChunk}, resultChunks...)
}
}
return strings.Join(resultChunks, "")
}
func convertMapStringString(in map[string]string) map[string]interface{} {
result := make(map[string]interface{}, len(in))
for k, v := range in {
result[k] = v
}
return result
}
func convertMapInterfaceInterface(in map[interface{}]interface{}) map[string]interface{} {
result := make(map[string]interface{}, len(in))
for k, v := range in {
result[k.(string)] = v
}
return result
}
// ToString is an alias for String
func (t *TomlTree) ToString() string {
return t.String()
}
// String generates a human-readable representation of the current tree.
// Output spans multiple lines, and is suitable for ingest by a TOML parser
func (t *TomlTree) String() string {
return t.toToml("", "")
}
// ToMap recursively generates a representation of the current tree using map[string]interface{}.
func (t *TomlTree) ToMap() map[string]interface{} {
result := map[string]interface{}{}
for k, v := range t.values {
switch node := v.(type) {
case []*TomlTree:
var array []interface{}
for _, item := range node {
array = append(array, item.ToMap())
}
result[k] = array
case *TomlTree:
result[k] = node.ToMap()
case map[string]interface{}:
sub := TreeFromMap(node)
result[k] = sub.ToMap()
case *tomlValue:
result[k] = node.value
}
}
return result
}
-171
View File
@@ -1,171 +0,0 @@
package toml
import (
"reflect"
"testing"
"time"
"strings"
)
func TestTomlTreeConversionToString(t *testing.T) {
toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
points = { x = 1, y = 2 }`)
if err != nil {
t.Fatal("Unexpected error:", err)
}
reparsedTree, err := Load(toml.ToString())
assertTree(t, reparsedTree, err, map[string]interface{}{
"name": map[string]interface{}{
"first": "Tom",
"last": "Preston-Werner",
},
"points": map[string]interface{}{
"x": int64(1),
"y": int64(2),
},
})
}
func TestTomlTreeConversionToStringKeysOrders(t *testing.T) {
for i := 0; i < 100; i++ {
tree, _ := Load(`
foobar = true
bar = "baz"
foo = 1
[qux]
foo = 1
bar = "baz2"`)
stringRepr := tree.ToString()
t.Log("Intermediate string representation:")
t.Log(stringRepr)
r := strings.NewReader(stringRepr)
toml, err := LoadReader(r)
if err != nil {
t.Fatal("Unexpected error:", err)
}
assertTree(t, toml, err, map[string]interface{}{
"foobar": true,
"bar": "baz",
"foo": 1,
"qux": map[string]interface{}{
"foo": 1,
"bar": "baz2",
},
})
}
}
func testMaps(t *testing.T, actual, expected map[string]interface{}) {
if !reflect.DeepEqual(actual, expected) {
t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual)
}
}
func TestTomlTreeConversionToMapSimple(t *testing.T) {
tree, _ := Load("a = 42\nb = 17")
expected := map[string]interface{}{
"a": int64(42),
"b": int64(17),
}
testMaps(t, tree.ToMap(), expected)
}
func TestTomlTreeConversionToMapExampleFile(t *testing.T) {
tree, _ := LoadFile("example.toml")
expected := map[string]interface{}{
"title": "TOML Example",
"owner": map[string]interface{}{
"name": "Tom Preston-Werner",
"organization": "GitHub",
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
"dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
},
"database": map[string]interface{}{
"server": "192.168.1.1",
"ports": []interface{}{int64(8001), int64(8001), int64(8002)},
"connection_max": int64(5000),
"enabled": true,
},
"servers": map[string]interface{}{
"alpha": map[string]interface{}{
"ip": "10.0.0.1",
"dc": "eqdc10",
},
"beta": map[string]interface{}{
"ip": "10.0.0.2",
"dc": "eqdc10",
},
},
"clients": map[string]interface{}{
"data": []interface{}{
[]interface{}{"gamma", "delta"},
[]interface{}{int64(1), int64(2)},
},
},
}
testMaps(t, tree.ToMap(), expected)
}
func TestTomlTreeConversionToMapWithTablesInMultipleChunks(t *testing.T) {
tree, _ := Load(`
[[menu.main]]
a = "menu 1"
b = "menu 2"
[[menu.main]]
c = "menu 3"
d = "menu 4"`)
expected := map[string]interface{}{
"menu": map[string]interface{}{
"main": []interface{}{
map[string]interface{}{"a": "menu 1", "b": "menu 2"},
map[string]interface{}{"c": "menu 3", "d": "menu 4"},
},
},
}
treeMap := tree.ToMap()
testMaps(t, treeMap, expected)
}
func TestTomlTreeConversionToMapWithArrayOfInlineTables(t *testing.T) {
tree, _ := Load(`
[params]
language_tabs = [
{ key = "shell", name = "Shell" },
{ key = "ruby", name = "Ruby" },
{ key = "python", name = "Python" }
]`)
expected := map[string]interface{}{
"params": map[string]interface{}{
"language_tabs": []interface{}{
map[string]interface{}{
"key": "shell",
"name": "Shell",
},
map[string]interface{}{
"key": "ruby",
"name": "Ruby",
},
map[string]interface{}{
"key": "python",
"name": "Python",
},
},
},
}
treeMap := tree.ToMap()
testMaps(t, treeMap, expected)
}
+212
View File
@@ -0,0 +1,212 @@
package toml
import (
"bytes"
"fmt"
"io"
"sort"
"strconv"
"strings"
"time"
)
// encodes a string to a TOML-compliant string value
func encodeTomlString(value string) string {
result := ""
for _, rr := range value {
switch rr {
case '\b':
result += "\\b"
case '\t':
result += "\\t"
case '\n':
result += "\\n"
case '\f':
result += "\\f"
case '\r':
result += "\\r"
case '"':
result += "\\\""
case '\\':
result += "\\\\"
default:
intRr := uint16(rr)
if intRr < 0x001F {
result += fmt.Sprintf("\\u%0.4X", intRr)
} else {
result += string(rr)
}
}
}
return result
}
func tomlValueStringRepresentation(v interface{}) (string, error) {
switch value := v.(type) {
case uint64:
return strconv.FormatUint(value, 10), nil
case int64:
return strconv.FormatInt(value, 10), nil
case float64:
return strconv.FormatFloat(value, 'f', -1, 32), nil
case string:
return "\"" + encodeTomlString(value) + "\"", nil
case bool:
if value {
return "true", nil
}
return "false", nil
case time.Time:
return value.Format(time.RFC3339), nil
case nil:
return "", nil
case []interface{}:
values := []string{}
for _, item := range value {
itemRepr, err := tomlValueStringRepresentation(item)
if err != nil {
return "", err
}
values = append(values, itemRepr)
}
return "[" + strings.Join(values, ",") + "]", nil
default:
return "", fmt.Errorf("unsupported value type %T: %v", value, value)
}
}
func (t *TomlTree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (int64, error) {
simpleValuesKeys := make([]string, 0)
complexValuesKeys := make([]string, 0)
for k := range t.values {
v := t.values[k]
switch v.(type) {
case *TomlTree, []*TomlTree:
complexValuesKeys = append(complexValuesKeys, k)
default:
simpleValuesKeys = append(simpleValuesKeys, k)
}
}
sort.Strings(simpleValuesKeys)
sort.Strings(complexValuesKeys)
for _, k := range simpleValuesKeys {
v, ok := t.values[k].(*tomlValue)
if !ok {
return bytesCount, fmt.Errorf("invalid key type at %s: %T", k, t.values[k])
}
repr, err := tomlValueStringRepresentation(v.value)
if err != nil {
return bytesCount, err
}
kvRepr := fmt.Sprintf("%s%s = %s\n", indent, k, repr)
writtenBytesCount, err := w.Write([]byte(kvRepr))
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
}
for _, k := range complexValuesKeys {
v := t.values[k]
combinedKey := 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 *TomlTree:
tableName := fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
writtenBytesCount, err := w.Write([]byte(tableName))
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount)
if err != nil {
return bytesCount, err
}
case []*TomlTree:
for _, subTree := range node {
if len(subTree.values) > 0 {
tableArrayName := fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey)
writtenBytesCount, err := w.Write([]byte(tableArrayName))
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount)
if err != nil {
return bytesCount, err
}
}
}
}
}
return bytesCount, nil
}
// WriteTo encode the TomlTree 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 *TomlTree) WriteTo(w io.Writer) (int64, error) {
return t.writeTo(w, "", "", 0)
}
// 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 *TomlTree) ToTomlString() (string, error) {
var buf bytes.Buffer
_, err := t.WriteTo(&buf)
if err != nil {
return "", err
}
return buf.String(), nil
}
// String generates a human-readable representation of the current tree.
// Alias of ToString. Present to implement the fmt.Stringer interface.
func (t *TomlTree) 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:
// * uint64
// * int64
// * bool
// * string
// * time.Time
// * map[string]interface{} (where interface{} is any of this list)
// * []interface{} (where interface{} is any of this list)
func (t *TomlTree) ToMap() map[string]interface{} {
result := map[string]interface{}{}
for k, v := range t.values {
switch node := v.(type) {
case []*TomlTree:
var array []interface{}
for _, item := range node {
array = append(array, item.ToMap())
}
result[k] = array
case *TomlTree:
result[k] = node.ToMap()
case map[string]interface{}:
sub := TreeFromMap(node)
result[k] = sub.ToMap()
case *tomlValue:
result[k] = node.value
}
}
return result
}
+284
View File
@@ -0,0 +1,284 @@
package toml
import (
"bytes"
"errors"
"fmt"
"reflect"
"strings"
"testing"
"time"
)
type failingWriter struct {
failAt int
written int
buffer bytes.Buffer
}
func (f failingWriter) Write(p []byte) (n int, err error) {
count := len(p)
toWrite := f.failAt - count + f.written
if toWrite < 0 {
toWrite = 0
}
if toWrite > count {
f.written += count
f.buffer.WriteString(string(p))
return count, nil
}
f.buffer.WriteString(string(p[:toWrite]))
f.written = f.failAt
return f.written, fmt.Errorf("failingWriter failed after writting %d bytes", f.written)
}
func assertErrorString(t *testing.T, expected string, err error) {
expectedErr := errors.New(expected)
if err.Error() != expectedErr.Error() {
t.Errorf("expecting error %s, but got %s instead", expected, err)
}
}
func TestTomlTreeWriteToTomlString(t *testing.T) {
toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
points = { x = 1, y = 2 }`)
if err != nil {
t.Fatal("Unexpected error:", err)
}
tomlString, _ := toml.ToTomlString()
reparsedTree, err := Load(tomlString)
assertTree(t, reparsedTree, err, map[string]interface{}{
"name": map[string]interface{}{
"first": "Tom",
"last": "Preston-Werner",
},
"points": map[string]interface{}{
"x": int64(1),
"y": int64(2),
},
})
}
func TestTomlTreeWriteToTomlStringSimple(t *testing.T) {
tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n")
if err != nil {
t.Errorf("Test failed to parse: %v", err)
return
}
result, err := tree.ToTomlString()
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
expected := "\n[foo]\n\n [[foo.bar]]\n a = 42\n\n [[foo.bar]]\n a = 69\n"
if result != expected {
t.Errorf("Expected got '%s', expected '%s'", result, expected)
}
}
func TestTomlTreeWriteToTomlStringKeysOrders(t *testing.T) {
for i := 0; i < 100; i++ {
tree, _ := Load(`
foobar = true
bar = "baz"
foo = 1
[qux]
foo = 1
bar = "baz2"`)
stringRepr, _ := tree.ToTomlString()
t.Log("Intermediate string representation:")
t.Log(stringRepr)
r := strings.NewReader(stringRepr)
toml, err := LoadReader(r)
if err != nil {
t.Fatal("Unexpected error:", err)
}
assertTree(t, toml, err, map[string]interface{}{
"foobar": true,
"bar": "baz",
"foo": 1,
"qux": map[string]interface{}{
"foo": 1,
"bar": "baz2",
},
})
}
}
func testMaps(t *testing.T, actual, expected map[string]interface{}) {
if !reflect.DeepEqual(actual, expected) {
t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual)
}
}
func TestToTomlStringTypeConversionError(t *testing.T) {
tree := TomlTree{
values: map[string]interface{}{
"thing": &tomlValue{[]string{"unsupported"}, Position{}},
},
}
_, err := tree.ToTomlString()
expected := errors.New("unsupported value type []string: [unsupported]")
if err.Error() != expected.Error() {
t.Errorf("expecting error %s, but got %s instead", expected, err)
}
}
func TestTomlTreeWriteToMapSimple(t *testing.T) {
tree, _ := Load("a = 42\nb = 17")
expected := map[string]interface{}{
"a": int64(42),
"b": int64(17),
}
testMaps(t, tree.ToMap(), expected)
}
func TestTomlTreeWriteToInvalidTreeSimpleValue(t *testing.T) {
tree := TomlTree{values: map[string]interface{}{"foo": int8(1)}}
_, err := tree.ToTomlString()
assertErrorString(t, "invalid key type at foo: int8", err)
}
func TestTomlTreeWriteToInvalidTreeTomlValue(t *testing.T) {
tree := TomlTree{values: map[string]interface{}{"foo": &tomlValue{int8(1), Position{}}}}
_, err := tree.ToTomlString()
assertErrorString(t, "unsupported value type int8: 1", err)
}
func TestTomlTreeWriteToInvalidTreeTomlValueArray(t *testing.T) {
tree := TomlTree{values: map[string]interface{}{"foo": &tomlValue{[]interface{}{int8(1)}, Position{}}}}
_, err := tree.ToTomlString()
assertErrorString(t, "unsupported value type int8: 1", err)
}
func TestTomlTreeWriteToFailingWriterInSimpleValue(t *testing.T) {
toml, _ := Load(`a = 2`)
writer := failingWriter{failAt: 0, written: 0}
_, err := toml.WriteTo(writer)
assertErrorString(t, "failingWriter failed after writting 0 bytes", err)
}
func TestTomlTreeWriteToFailingWriterInTable(t *testing.T) {
toml, _ := Load(`
[b]
a = 2`)
writer := failingWriter{failAt: 2, written: 0}
_, err := toml.WriteTo(writer)
assertErrorString(t, "failingWriter failed after writting 2 bytes", err)
writer = failingWriter{failAt: 13, written: 0}
_, err = toml.WriteTo(writer)
assertErrorString(t, "failingWriter failed after writting 13 bytes", err)
}
func TestTomlTreeWriteToFailingWriterInArray(t *testing.T) {
toml, _ := Load(`
[[b]]
a = 2`)
writer := failingWriter{failAt: 2, written: 0}
_, err := toml.WriteTo(writer)
assertErrorString(t, "failingWriter failed after writting 2 bytes", err)
writer = failingWriter{failAt: 15, written: 0}
_, err = toml.WriteTo(writer)
assertErrorString(t, "failingWriter failed after writting 15 bytes", err)
}
func TestTomlTreeWriteToMapExampleFile(t *testing.T) {
tree, _ := LoadFile("example.toml")
expected := map[string]interface{}{
"title": "TOML Example",
"owner": map[string]interface{}{
"name": "Tom Preston-Werner",
"organization": "GitHub",
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
"dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
},
"database": map[string]interface{}{
"server": "192.168.1.1",
"ports": []interface{}{int64(8001), int64(8001), int64(8002)},
"connection_max": int64(5000),
"enabled": true,
},
"servers": map[string]interface{}{
"alpha": map[string]interface{}{
"ip": "10.0.0.1",
"dc": "eqdc10",
},
"beta": map[string]interface{}{
"ip": "10.0.0.2",
"dc": "eqdc10",
},
},
"clients": map[string]interface{}{
"data": []interface{}{
[]interface{}{"gamma", "delta"},
[]interface{}{int64(1), int64(2)},
},
},
}
testMaps(t, tree.ToMap(), expected)
}
func TestTomlTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) {
tree, _ := Load(`
[[menu.main]]
a = "menu 1"
b = "menu 2"
[[menu.main]]
c = "menu 3"
d = "menu 4"`)
expected := map[string]interface{}{
"menu": map[string]interface{}{
"main": []interface{}{
map[string]interface{}{"a": "menu 1", "b": "menu 2"},
map[string]interface{}{"c": "menu 3", "d": "menu 4"},
},
},
}
treeMap := tree.ToMap()
testMaps(t, treeMap, expected)
}
func TestTomlTreeWriteToMapWithArrayOfInlineTables(t *testing.T) {
tree, _ := Load(`
[params]
language_tabs = [
{ key = "shell", name = "Shell" },
{ key = "ruby", name = "Ruby" },
{ key = "python", name = "Python" }
]`)
expected := map[string]interface{}{
"params": map[string]interface{}{
"language_tabs": []interface{}{
map[string]interface{}{
"key": "shell",
"name": "Shell",
},
map[string]interface{}{
"key": "ruby",
"name": "Ruby",
},
map[string]interface{}{
"key": "python",
"name": "Python",
},
},
},
}
treeMap := tree.ToMap()
testMaps(t, treeMap, expected)
}