Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64ff1ea4d5 | |||
| b39f6ef1f9 | |||
| c187221f01 | |||
| 8e6ab94eec | |||
| 8d9c606c69 | |||
| 288bc57940 | |||
| e3b2497729 | |||
| 1a8565204c | |||
| e58cfd32d4 | |||
| a2ae216b47 | |||
| 8645be8dc7 | |||
| 99b9371c53 | |||
| 92c565e02b | |||
| 6e26017b00 | |||
| 9d93af61de | |||
| 4d8fb95ffe | |||
| 0e41db2176 | |||
| afca7f3334 | |||
| d6a90e60ed | |||
| fe63e9f76d |
+3
-3
@@ -1,9 +1,9 @@
|
||||
language: go
|
||||
script: "./test.sh"
|
||||
go:
|
||||
- 1.3.3
|
||||
- 1.4.2
|
||||
- 1.5.3
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
|
||||
@@ -7,7 +7,7 @@ This library supports TOML version
|
||||
|
||||
[](http://godoc.org/github.com/pelletier/go-toml)
|
||||
[](https://travis-ci.org/pelletier/go-toml)
|
||||
[](https://coveralls.io/github/pelletier/go-toml?branch=master)
|
||||
[](https://coveralls.io/github/pelletier/go-toml?branch=master)
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, `tomll can be used in two ways:
|
||||
Writing to STDIN and reading from STDOUT:
|
||||
cat file.toml | tomll > file.toml
|
||||
|
||||
Reading and updating a list of files:
|
||||
tomll a.toml b.toml c.toml
|
||||
|
||||
When given a list of files, tomll will modify all files in place without asking.
|
||||
`)
|
||||
}
|
||||
flag.Parse()
|
||||
// read from stdin and print to stdout
|
||||
if flag.NArg() == 0 {
|
||||
s, err := lintReader(os.Stdin)
|
||||
if err != nil {
|
||||
io.WriteString(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
io.WriteString(os.Stdout, s)
|
||||
} else {
|
||||
// otherwise modify a list of files
|
||||
for _, filename := range flag.Args() {
|
||||
s, err := lintFile(filename)
|
||||
if err != nil {
|
||||
io.WriteString(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
ioutil.WriteFile(filename, []byte(s), 0644)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lintFile(filename string) (string, error) {
|
||||
tree, err := toml.LoadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tree.String(), nil
|
||||
}
|
||||
|
||||
func lintReader(r io.Reader) (string, error) {
|
||||
tree, err := toml.LoadReader(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tree.String(), nil
|
||||
}
|
||||
@@ -83,9 +83,9 @@
|
||||
// The idea behind a query path is to allow quick access to any element, or set
|
||||
// of elements within TOML document, with a single expression.
|
||||
//
|
||||
// result := tree.Query("$.foo.bar.baz") // result is 'nil' if the path is not present
|
||||
// result, err := tree.Query("$.foo.bar.baz")
|
||||
//
|
||||
// This is equivalent to:
|
||||
// This is roughly equivalent to:
|
||||
//
|
||||
// next := tree.Get("foo")
|
||||
// if next != nil {
|
||||
@@ -96,6 +96,11 @@
|
||||
// }
|
||||
// result := next
|
||||
//
|
||||
// err is nil if any parsing exception occurs.
|
||||
//
|
||||
// If no node in the tree matches the query, result will simply contain an empty list of
|
||||
// items.
|
||||
//
|
||||
// As illustrated above, the query path is much more efficient, especially since
|
||||
// the structure of the TOML file can vary. Rather than making assumptions about
|
||||
// a document's structure, a query allows the programmer to make structured
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/pelletier/go-buffruneio"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pelletier/go-buffruneio"
|
||||
)
|
||||
|
||||
var dateRegexp *regexp.Regexp
|
||||
@@ -232,6 +234,7 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
|
||||
return l.lexKey
|
||||
}
|
||||
|
||||
return l.errorf("no value can start with %c", next)
|
||||
}
|
||||
|
||||
l.emit(tokenEOF)
|
||||
@@ -280,26 +283,35 @@ func (l *tomlLexer) lexComma() tomlLexStateFn {
|
||||
}
|
||||
|
||||
func (l *tomlLexer) lexKey() tomlLexStateFn {
|
||||
inQuotes := false
|
||||
growingString := ""
|
||||
|
||||
for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
|
||||
if r == '"' {
|
||||
inQuotes = !inQuotes
|
||||
l.next()
|
||||
str, err := l.lexStringAsString(`"`, false, true)
|
||||
if err != nil {
|
||||
return l.errorf(err.Error())
|
||||
}
|
||||
growingString += `"` + str + `"`
|
||||
l.next()
|
||||
continue
|
||||
} else if r == '\n' {
|
||||
return l.errorf("keys cannot contain new lines")
|
||||
} else if isSpace(r) && !inQuotes {
|
||||
} else if isSpace(r) {
|
||||
break
|
||||
} else if !isValidBareChar(r) && !inQuotes {
|
||||
} else if !isValidBareChar(r) {
|
||||
return l.errorf("keys cannot contain %c character", r)
|
||||
}
|
||||
growingString += string(r)
|
||||
l.next()
|
||||
}
|
||||
l.emit(tokenKey)
|
||||
l.emitWithValue(tokenKey, growingString)
|
||||
return l.lexVoid
|
||||
}
|
||||
|
||||
func (l *tomlLexer) lexComment() tomlLexStateFn {
|
||||
for next := l.peek(); next != '\n' && next != eof; next = l.peek() {
|
||||
if (next == '\r' && l.follow("\r\n")) {
|
||||
if next == '\r' && l.follow("\r\n") {
|
||||
break
|
||||
}
|
||||
l.next()
|
||||
@@ -314,18 +326,10 @@ func (l *tomlLexer) lexLeftBracket() tomlLexStateFn {
|
||||
return l.lexRvalue
|
||||
}
|
||||
|
||||
func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
||||
l.skip()
|
||||
func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) {
|
||||
growingString := ""
|
||||
|
||||
// handle special case for triple-quote
|
||||
terminator := "'"
|
||||
if l.follow("''") {
|
||||
l.skip()
|
||||
l.skip()
|
||||
terminator = "'''"
|
||||
|
||||
// special case: discard leading newline
|
||||
if discardLeadingNewLine {
|
||||
if l.follow("\r\n") {
|
||||
l.skip()
|
||||
l.skip()
|
||||
@@ -337,10 +341,7 @@ func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
||||
// find end of string
|
||||
for {
|
||||
if l.follow(terminator) {
|
||||
l.emitWithValue(tokenString, growingString)
|
||||
l.fastForward(len(terminator))
|
||||
l.ignore()
|
||||
return l.lexRvalue
|
||||
return growingString, nil
|
||||
}
|
||||
|
||||
next := l.peek()
|
||||
@@ -350,21 +351,40 @@ func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
||||
growingString += string(l.next())
|
||||
}
|
||||
|
||||
return l.errorf("unclosed string")
|
||||
return "", errors.New("unclosed string")
|
||||
}
|
||||
|
||||
func (l *tomlLexer) lexString() tomlLexStateFn {
|
||||
func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
||||
l.skip()
|
||||
growingString := ""
|
||||
|
||||
// handle special case for triple-quote
|
||||
terminator := "\""
|
||||
if l.follow("\"\"") {
|
||||
terminator := "'"
|
||||
discardLeadingNewLine := false
|
||||
if l.follow("''") {
|
||||
l.skip()
|
||||
l.skip()
|
||||
terminator = "\"\"\""
|
||||
terminator = "'''"
|
||||
discardLeadingNewLine = true
|
||||
}
|
||||
|
||||
// special case: discard leading newline
|
||||
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) {
|
||||
growingString := ""
|
||||
|
||||
if discardLeadingNewLine {
|
||||
if l.follow("\r\n") {
|
||||
l.skip()
|
||||
l.skip()
|
||||
@@ -375,10 +395,7 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
|
||||
|
||||
for {
|
||||
if l.follow(terminator) {
|
||||
l.emitWithValue(tokenString, growingString)
|
||||
l.fastForward(len(terminator))
|
||||
l.ignore()
|
||||
return l.lexRvalue
|
||||
return growingString, nil
|
||||
}
|
||||
|
||||
if l.follow("\\") {
|
||||
@@ -425,14 +442,14 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
|
||||
for i := 0; i < 4; i++ {
|
||||
c := l.peek()
|
||||
if !isHexDigit(c) {
|
||||
return l.errorf("unfinished unicode escape")
|
||||
return "", errors.New("unfinished unicode escape")
|
||||
}
|
||||
l.next()
|
||||
code = code + string(c)
|
||||
}
|
||||
intcode, err := strconv.ParseInt(code, 16, 32)
|
||||
if err != nil {
|
||||
return l.errorf("invalid unicode escape: \\u" + code)
|
||||
return "", errors.New("invalid unicode escape: \\u" + code)
|
||||
}
|
||||
growingString += string(rune(intcode))
|
||||
case 'U':
|
||||
@@ -441,23 +458,24 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
|
||||
for i := 0; i < 8; i++ {
|
||||
c := l.peek()
|
||||
if !isHexDigit(c) {
|
||||
return l.errorf("unfinished unicode escape")
|
||||
return "", errors.New("unfinished unicode escape")
|
||||
}
|
||||
l.next()
|
||||
code = code + string(c)
|
||||
}
|
||||
intcode, err := strconv.ParseInt(code, 16, 64)
|
||||
if err != nil {
|
||||
return l.errorf("invalid unicode escape: \\U" + code)
|
||||
return "", errors.New("invalid unicode escape: \\U" + code)
|
||||
}
|
||||
growingString += string(rune(intcode))
|
||||
default:
|
||||
return l.errorf("invalid escape sequence: \\" + string(l.peek()))
|
||||
return "", errors.New("invalid escape sequence: \\" + string(l.peek()))
|
||||
}
|
||||
} else {
|
||||
r := l.peek()
|
||||
if 0x00 <= r && r <= 0x1F {
|
||||
return l.errorf("unescaped control character %U", r)
|
||||
|
||||
if 0x00 <= r && r <= 0x1F && !(acceptNewLines && (r == '\n' || r == '\r')) {
|
||||
return "", fmt.Errorf("unescaped control character %U", r)
|
||||
}
|
||||
l.next()
|
||||
growingString += string(r)
|
||||
@@ -468,7 +486,34 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
|
||||
}
|
||||
}
|
||||
|
||||
return l.errorf("unclosed string")
|
||||
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) lexKeyGroup() tomlLexStateFn {
|
||||
@@ -591,7 +636,7 @@ func (l *tomlLexer) run() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
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})")
|
||||
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
|
||||
|
||||
+64
-2
@@ -87,7 +87,6 @@ func TestMultipleKeyGroupsComment(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func TestSimpleWindowsCRLF(t *testing.T) {
|
||||
testFlow(t, "a=4\r\nb=2", []token{
|
||||
token{Position{1, 1}, tokenKey, "a"},
|
||||
@@ -481,6 +480,12 @@ func TestKeyEqualNumber(t *testing.T) {
|
||||
token{Position{1, 8}, tokenFloat, "9_224_617.445_991_228_313"},
|
||||
token{Position{1, 33}, tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = +", []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{1, 7}, tokenError, "no digit in that number"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultiline(t *testing.T) {
|
||||
@@ -508,6 +513,16 @@ func TestKeyEqualStringUnicodeEscape(t *testing.T) {
|
||||
token{Position{1, 8}, tokenString, "hello δ"},
|
||||
token{Position{1, 25}, tokenEOF, ""},
|
||||
})
|
||||
testFlow(t, `foo = "\u2"`, []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{1, 8}, tokenError, "unfinished unicode escape"},
|
||||
})
|
||||
testFlow(t, `foo = "\U2"`, []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{1, 8}, tokenError, "unfinished unicode escape"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyEqualStringNoEscape(t *testing.T) {
|
||||
@@ -548,6 +563,11 @@ func TestLiteralString(t *testing.T) {
|
||||
token{Position{1, 8}, tokenString, `<\i\c*\s*>`},
|
||||
token{Position{1, 19}, tokenEOF, ""},
|
||||
})
|
||||
testFlow(t, `foo = 'C:\Users\nodejs\unfinis`, []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{1, 8}, tokenError, "unclosed string"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultilineLiteralString(t *testing.T) {
|
||||
@@ -564,6 +584,12 @@ func TestMultilineLiteralString(t *testing.T) {
|
||||
token{Position{2, 1}, tokenString, "hello\n'literal'\nworld"},
|
||||
token{Position{4, 9}, tokenEOF, ""},
|
||||
})
|
||||
testFlow(t, "foo = '''\r\nhello\r\n'literal'\r\nworld'''", []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{2, 1}, tokenString, "hello\r\n'literal'\r\nworld"},
|
||||
token{Position{4, 9}, tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultilineString(t *testing.T) {
|
||||
@@ -574,7 +600,7 @@ func TestMultilineString(t *testing.T) {
|
||||
token{Position{1, 34}, tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "foo = \"\"\"\nhello\\\n\"literal\"\\\nworld\"\"\"", []token{
|
||||
testFlow(t, "foo = \"\"\"\r\nhello\\\r\n\"literal\"\\\nworld\"\"\"", []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{2, 1}, tokenString, "hello\"literal\"world"},
|
||||
@@ -601,6 +627,20 @@ func TestMultilineString(t *testing.T) {
|
||||
token{Position{1, 11}, tokenString, "The quick brown fox jumps over the lazy dog."},
|
||||
token{Position{5, 11}, tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, `key2 = "Roses are red\nViolets are blue"`, []token{
|
||||
token{Position{1, 1}, tokenKey, "key2"},
|
||||
token{Position{1, 6}, tokenEqual, "="},
|
||||
token{Position{1, 9}, tokenString, "Roses are red\nViolets are blue"},
|
||||
token{Position{1, 41}, tokenEOF, ""},
|
||||
})
|
||||
|
||||
testFlow(t, "key2 = \"\"\"\nRoses are red\nViolets are blue\"\"\"", []token{
|
||||
token{Position{1, 1}, tokenKey, "key2"},
|
||||
token{Position{1, 6}, tokenEqual, "="},
|
||||
token{Position{2, 1}, tokenString, "Roses are red\nViolets are blue"},
|
||||
token{Position{3, 20}, tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnicodeString(t *testing.T) {
|
||||
@@ -611,6 +651,14 @@ func TestUnicodeString(t *testing.T) {
|
||||
token{Position{1, 22}, tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
func TestEscapeInString(t *testing.T) {
|
||||
testFlow(t, `foo = "\b\f\/"`, []token{
|
||||
token{Position{1, 1}, tokenKey, "foo"},
|
||||
token{Position{1, 5}, tokenEqual, "="},
|
||||
token{Position{1, 8}, tokenString, "\b\f/"},
|
||||
token{Position{1, 15}, tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyGroupArray(t *testing.T) {
|
||||
testFlow(t, "[[foo]]", []token{
|
||||
@@ -644,3 +692,17 @@ func TestInvalidFloat(t *testing.T) {
|
||||
token{Position{1, 7}, tokenEOF, ""},
|
||||
})
|
||||
}
|
||||
|
||||
func TestLexUnknownRvalue(t *testing.T) {
|
||||
testFlow(t, `a = !b`, []token{
|
||||
token{Position{1, 1}, tokenKey, "a"},
|
||||
token{Position{1, 3}, tokenEqual, "="},
|
||||
token{Position{1, 5}, tokenError, "no value can start with !"},
|
||||
})
|
||||
|
||||
testFlow(t, `a = \b`, []token{
|
||||
token{Position{1, 1}, tokenKey, "a"},
|
||||
token{Position{1, 3}, tokenEqual, "="},
|
||||
token{Position{1, 5}, tokenError, `no value can start with \`},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,7 +67,14 @@ func newMatchKeyFn(name string) *matchKeyFn {
|
||||
}
|
||||
|
||||
func (f *matchKeyFn) call(node interface{}, ctx *queryContext) {
|
||||
if tree, ok := node.(*TomlTree); ok {
|
||||
if array, ok := node.([]*TomlTree); ok {
|
||||
for _, tree := range array {
|
||||
item := tree.values[f.Name]
|
||||
if item != nil {
|
||||
f.next.call(item, ctx)
|
||||
}
|
||||
}
|
||||
} else if tree, ok := node.(*TomlTree); ok {
|
||||
item := tree.values[f.Name]
|
||||
if item != nil {
|
||||
f.next.call(item, ctx)
|
||||
|
||||
+3
-3
@@ -109,7 +109,7 @@ func TestPathSliceStart(t *testing.T) {
|
||||
assertPath(t,
|
||||
"$[123:]",
|
||||
buildPath(
|
||||
newMatchSliceFn(123, MaxInt, 1),
|
||||
newMatchSliceFn(123, maxInt, 1),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestPathSliceStartStep(t *testing.T) {
|
||||
assertPath(t,
|
||||
"$[123::7]",
|
||||
buildPath(
|
||||
newMatchSliceFn(123, MaxInt, 7),
|
||||
newMatchSliceFn(123, maxInt, 7),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestPathSliceStep(t *testing.T) {
|
||||
assertPath(t,
|
||||
"$[::7]",
|
||||
buildPath(
|
||||
newMatchSliceFn(0, MaxInt, 7),
|
||||
newMatchSliceFn(0, maxInt, 7),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -208,7 +208,15 @@ func (p *tomlParser) parseAssign() tomlParserStateFn {
|
||||
p.raiseError(key, "The following key was defined twice: %s",
|
||||
strings.Join(finalKey, "."))
|
||||
}
|
||||
targetNode.values[keyVal] = &tomlValue{value, key.Position}
|
||||
var toInsert interface{}
|
||||
|
||||
switch value.(type) {
|
||||
case *TomlTree:
|
||||
toInsert = value
|
||||
default:
|
||||
toInsert = &tomlValue{value, key.Position}
|
||||
}
|
||||
targetNode.values[keyVal] = toInsert
|
||||
return p.parseStart
|
||||
}
|
||||
|
||||
|
||||
+68
-3
@@ -2,26 +2,34 @@ package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func assertTree(t *testing.T, tree *TomlTree, err error, ref map[string]interface{}) {
|
||||
func assertSubTree(t *testing.T, path []string, tree *TomlTree, err error, ref map[string]interface{}) {
|
||||
if err != nil {
|
||||
t.Error("Non-nil error:", err.Error())
|
||||
return
|
||||
}
|
||||
for k, v := range ref {
|
||||
nextPath := append(path, k)
|
||||
t.Log("asserting path", nextPath)
|
||||
// NOTE: directly access key instead of resolve by path
|
||||
// NOTE: see TestSpecialKV
|
||||
switch node := tree.GetPath([]string{k}).(type) {
|
||||
case []*TomlTree:
|
||||
t.Log("\tcomparing key", nextPath, "by array iteration")
|
||||
for idx, item := range node {
|
||||
assertTree(t, item, err, v.([]map[string]interface{})[idx])
|
||||
assertSubTree(t, nextPath, item, err, v.([]map[string]interface{})[idx])
|
||||
}
|
||||
case *TomlTree:
|
||||
assertTree(t, node, err, v.(map[string]interface{}))
|
||||
t.Log("\tcomparing key", nextPath, "by subtree assestion")
|
||||
assertSubTree(t, nextPath, node, err, v.(map[string]interface{}))
|
||||
default:
|
||||
t.Log("\tcomparing key", nextPath, "by string representation because it's of type", reflect.TypeOf(node))
|
||||
if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) {
|
||||
t.Errorf("was expecting %v at %v but got %v", v, k, node)
|
||||
}
|
||||
@@ -29,6 +37,12 @@ func assertTree(t *testing.T, tree *TomlTree, err error, ref map[string]interfac
|
||||
}
|
||||
}
|
||||
|
||||
func assertTree(t *testing.T, tree *TomlTree, err error, ref map[string]interface{}) {
|
||||
t.Log("Asserting tree:\n", spew.Sdump(tree))
|
||||
assertSubTree(t, []string{}, tree, err, ref)
|
||||
t.Log("Finished tree assertion.")
|
||||
}
|
||||
|
||||
func TestCreateSubTree(t *testing.T) {
|
||||
tree := newTomlTree()
|
||||
tree.createSubTree([]string{"a", "b", "c"}, Position{})
|
||||
@@ -285,6 +299,18 @@ func TestArrayNestedStrings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseUnknownRvalue(t *testing.T) {
|
||||
_, err := Load("a = !bssss")
|
||||
if err == nil {
|
||||
t.Error("Expecting a parse error")
|
||||
}
|
||||
|
||||
_, err = Load("a = /b")
|
||||
if err == nil {
|
||||
t.Error("Expecting a parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingValue(t *testing.T) {
|
||||
_, err := Load("a = ")
|
||||
if err.Error() != "(1, 5): expecting a value" {
|
||||
@@ -542,6 +568,40 @@ func TestParseKeyGroupArray(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseKeyGroupArrayUnfinished(t *testing.T) {
|
||||
_, err := Load("[[foo.bar]\na = 42")
|
||||
if err.Error() != "(1, 10): was expecting token [[, but got unclosed key group array instead" {
|
||||
t.Error("Bad error message:", err.Error())
|
||||
}
|
||||
|
||||
_, err = Load("[[foo.[bar]\na = 42")
|
||||
if err.Error() != "(1, 3): unexpected token group name cannot contain ']', was expecting a key group array" {
|
||||
t.Error("Bad error message:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKeyGroupArrayQueryExample(t *testing.T) {
|
||||
tree, err := Load(`
|
||||
[[book]]
|
||||
title = "The Stand"
|
||||
author = "Stephen King"
|
||||
[[book]]
|
||||
title = "For Whom the Bell Tolls"
|
||||
author = "Ernest Hemmingway"
|
||||
[[book]]
|
||||
title = "Neuromancer"
|
||||
author = "William Gibson"
|
||||
`)
|
||||
|
||||
assertTree(t, tree, err, map[string]interface{}{
|
||||
"book": []map[string]interface{}{
|
||||
{"title": "The Stand", "author": "Stephen King"},
|
||||
{"title": "For Whom the Bell Tolls", "author": "Ernest Hemmingway"},
|
||||
{"title": "Neuromancer", "author": "William Gibson"},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseKeyGroupArraySpec(t *testing.T) {
|
||||
tree, err := Load("[[fruit]]\n name=\"apple\"\n [fruit.physical]\n color=\"red\"\n shape=\"round\"\n [[fruit]]\n name=\"banana\"")
|
||||
assertTree(t, tree, err, map[string]interface{}{
|
||||
@@ -654,6 +714,11 @@ func TestInvalidGroupArray(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Should error")
|
||||
}
|
||||
|
||||
_, err = Load("[foo.[bar]\na = 42")
|
||||
if err.Error() != "(1, 2): unexpected token group name cannot contain ']', was expecting a key group" {
|
||||
t.Error("Bad error message:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleEqual(t *testing.T) {
|
||||
|
||||
@@ -4,36 +4,47 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Type of a user-defined filter function, for use with Query.SetFilter().
|
||||
// NodeFilterFn represents a user-defined filter function, for use with
|
||||
// Query.SetFilter().
|
||||
//
|
||||
// The return value of the function must indicate if 'node' is to be included
|
||||
// at this stage of the TOML path. Returning true will include the node, and
|
||||
// returning false will exclude it.
|
||||
// The return value of the function must indicate if 'node' is to be included
|
||||
// at this stage of the TOML path. Returning true will include the node, and
|
||||
// returning false will exclude it.
|
||||
//
|
||||
// NOTE: Care should be taken to write script callbacks such that they are safe
|
||||
// to use from multiple goroutines.
|
||||
// NOTE: Care should be taken to write script callbacks such that they are safe
|
||||
// to use from multiple goroutines.
|
||||
type NodeFilterFn func(node interface{}) bool
|
||||
|
||||
// The result of Executing a Query
|
||||
// QueryResult is the result of Executing a Query.
|
||||
type QueryResult struct {
|
||||
items []interface{}
|
||||
positions []Position
|
||||
}
|
||||
|
||||
// appends a value/position pair to the result set
|
||||
// appends a value/position pair to the result set.
|
||||
func (r *QueryResult) appendResult(node interface{}, pos Position) {
|
||||
r.items = append(r.items, node)
|
||||
r.positions = append(r.positions, pos)
|
||||
}
|
||||
|
||||
// Set of values within a QueryResult. The order of values is not guaranteed
|
||||
// to be in document order, and may be different each time a query is executed.
|
||||
// Values is a set of values within a QueryResult. The order of values is not
|
||||
// guaranteed to be in document order, and may be different each time a query is
|
||||
// executed.
|
||||
func (r *QueryResult) Values() []interface{} {
|
||||
return r.items
|
||||
values := make([]interface{}, len(r.items))
|
||||
for i, v := range r.items {
|
||||
o, ok := v.(*tomlValue)
|
||||
if ok {
|
||||
values[i] = o.value
|
||||
} else {
|
||||
values[i] = v
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Set of positions for values within a QueryResult. Each index in Positions()
|
||||
// corresponds to the entry in Value() of the same index.
|
||||
// Positions is a set of positions for values within a QueryResult. Each index
|
||||
// in Positions() corresponds to the entry in Value() of the same index.
|
||||
func (r *QueryResult) Positions() []Position {
|
||||
return r.positions
|
||||
}
|
||||
@@ -77,13 +88,13 @@ func (q *Query) appendPath(next pathFn) {
|
||||
next.setNext(newTerminatingFn()) // init the next functor
|
||||
}
|
||||
|
||||
// Compiles a TOML path expression. The returned Query can be used to match
|
||||
// elements within a TomlTree and its descendants.
|
||||
// CompileQuery compiles a TOML path expression. The returned Query can be used
|
||||
// to match elements within a TomlTree and its descendants.
|
||||
func CompileQuery(path string) (*Query, error) {
|
||||
return parseQuery(lexQuery(path))
|
||||
}
|
||||
|
||||
// Executes a query against a TomlTree, and returns the result of the query.
|
||||
// Execute executes a query against a TomlTree, and returns the result of the query.
|
||||
func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
||||
result := &QueryResult{
|
||||
items: []interface{}{},
|
||||
@@ -101,8 +112,8 @@ func (q *Query) Execute(tree *TomlTree) *QueryResult {
|
||||
return result
|
||||
}
|
||||
|
||||
// Sets a user-defined filter function. These may be used inside "?(..)" query
|
||||
// expressions to filter TOML document elements within a query.
|
||||
// SetFilter sets a user-defined filter function. These may be used inside
|
||||
// "?(..)" query expressions to filter TOML document elements within a query.
|
||||
func (q *Query) SetFilter(name string, fn NodeFilterFn) {
|
||||
if q.filters == &defaultFilterFunctions {
|
||||
// clone the static table
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertArrayContainsInAnyOrder(t *testing.T, array []interface{}, objects ...interface{}) {
|
||||
if len(array) != len(objects) {
|
||||
t.Fatalf("array contains %d objects but %d are expected", len(array), len(objects))
|
||||
}
|
||||
|
||||
for _, o := range objects {
|
||||
found := false
|
||||
for _, a := range array {
|
||||
if a == o {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal(o, "not found in array", array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryExample(t *testing.T) {
|
||||
config, _ := Load(`
|
||||
[[book]]
|
||||
title = "The Stand"
|
||||
author = "Stephen King"
|
||||
[[book]]
|
||||
title = "For Whom the Bell Tolls"
|
||||
author = "Ernest Hemmingway"
|
||||
[[book]]
|
||||
title = "Neuromancer"
|
||||
author = "William Gibson"
|
||||
`)
|
||||
|
||||
authors, _ := config.Query("$.book.author")
|
||||
names := authors.Values()
|
||||
if len(names) != 3 {
|
||||
t.Fatalf("query should return 3 names but returned %d", len(names))
|
||||
}
|
||||
assertArrayContainsInAnyOrder(t, names, "Stephen King", "Ernest Hemmingway", "William Gibson")
|
||||
}
|
||||
|
||||
func TestQueryReadmeExample(t *testing.T) {
|
||||
config, _ := Load(`
|
||||
[postgres]
|
||||
user = "pelletier"
|
||||
password = "mypassword"
|
||||
`)
|
||||
results, _ := config.Query("$..[user,password]")
|
||||
values := results.Values()
|
||||
if len(values) != 2 {
|
||||
t.Fatalf("query should return 2 values but returned %d", len(values))
|
||||
}
|
||||
assertArrayContainsInAnyOrder(t, values, "pelletier", "mypassword")
|
||||
}
|
||||
|
||||
func TestQueryPathNotPresent(t *testing.T) {
|
||||
config, _ := Load(`a = "hello"`)
|
||||
results, err := config.Query("$.foo.bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err should be nil. got %s instead", err)
|
||||
}
|
||||
if len(results.items) != 0 {
|
||||
t.Fatalf("no items should be matched. %d matched instead", len(results.items))
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -105,7 +105,7 @@ func (l *queryLexer) peek() rune {
|
||||
}
|
||||
|
||||
func (l *queryLexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const MaxInt = int(^uint(0) >> 1)
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
|
||||
type queryParser struct {
|
||||
flow chan token
|
||||
@@ -203,7 +203,7 @@ loop: // labeled loop for easy breaking
|
||||
|
||||
func (p *queryParser) parseSliceExpr() queryParserStateFn {
|
||||
// init slice to grab all elements
|
||||
start, end, step := 0, MaxInt, 1
|
||||
start, end, step := 0, maxInt, 1
|
||||
|
||||
// parse optional start
|
||||
tok := p.getToken()
|
||||
|
||||
@@ -20,6 +20,7 @@ function git_clone() {
|
||||
}
|
||||
|
||||
go get github.com/pelletier/go-buffruneio
|
||||
go get github.com/davecgh/go-spew/spew
|
||||
|
||||
# get code for BurntSushi TOML validation
|
||||
# pinning all to 'HEAD' for version 0.3.x work (TODO: pin to commit hash when tests stabilize)
|
||||
|
||||
@@ -107,9 +107,6 @@ func (t token) String() string {
|
||||
return t.val
|
||||
}
|
||||
|
||||
if len(t.val) > 10 {
|
||||
return fmt.Sprintf("%.10q...", t.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", t.val)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package toml
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTokenStringer(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tt tokenType
|
||||
expect string
|
||||
}{
|
||||
{tokenError, "Error"},
|
||||
{tokenEOF, "EOF"},
|
||||
{tokenComment, "Comment"},
|
||||
{tokenKey, "Key"},
|
||||
{tokenString, "String"},
|
||||
{tokenInteger, "Integer"},
|
||||
{tokenTrue, "True"},
|
||||
{tokenFalse, "False"},
|
||||
{tokenFloat, "Float"},
|
||||
{tokenEqual, "="},
|
||||
{tokenLeftBracket, "["},
|
||||
{tokenRightBracket, "]"},
|
||||
{tokenLeftCurlyBrace, "{"},
|
||||
{tokenRightCurlyBrace, "}"},
|
||||
{tokenLeftParen, "("},
|
||||
{tokenRightParen, ")"},
|
||||
{tokenDoubleLeftBracket, "]]"},
|
||||
{tokenDoubleRightBracket, "[["},
|
||||
{tokenDate, "Date"},
|
||||
{tokenKeyGroup, "KeyGroup"},
|
||||
{tokenKeyGroupArray, "KeyGroupArray"},
|
||||
{tokenComma, ","},
|
||||
{tokenColon, ":"},
|
||||
{tokenDollar, "$"},
|
||||
{tokenStar, "*"},
|
||||
{tokenQuestion, "?"},
|
||||
{tokenDot, "."},
|
||||
{tokenDotDot, ".."},
|
||||
{tokenEOL, "EOL"},
|
||||
{tokenEOL + 1, "Unknown"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got := test.tt.String()
|
||||
if got != test.expect {
|
||||
t.Errorf("[%d] invalid string of token type; got %q, expected %q", i, got, test.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenString(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tok token
|
||||
expect string
|
||||
}{
|
||||
{token{Position{1, 1}, tokenEOF, ""}, "EOF"},
|
||||
{token{Position{1, 1}, tokenError, "Δt"}, "Δt"},
|
||||
{token{Position{1, 1}, tokenString, "bar"}, `"bar"`},
|
||||
{token{Position{1, 1}, tokenString, "123456789012345"}, `"123456789012345"`},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got := test.tok.String()
|
||||
if got != test.expect {
|
||||
t.Errorf("[%d] invalid of string token; got %q, expected %q", i, got, test.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tomlValue struct {
|
||||
@@ -29,6 +27,7 @@ func newTomlTree() *TomlTree {
|
||||
}
|
||||
}
|
||||
|
||||
// TreeFromMap initializes a new TomlTree object using the given map.
|
||||
func TreeFromMap(m map[string]interface{}) *TomlTree {
|
||||
return &TomlTree{
|
||||
values: m,
|
||||
@@ -95,7 +94,7 @@ func (t *TomlTree) GetPath(keys []string) interface{} {
|
||||
}
|
||||
subtree = node[len(node)-1]
|
||||
default:
|
||||
return nil // cannot naigate through other node types
|
||||
return nil // cannot navigate through other node types
|
||||
}
|
||||
}
|
||||
// branch based on final node type
|
||||
@@ -247,118 +246,13 @@ func (t *TomlTree) createSubTree(keys []string, pos Position) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 int64:
|
||||
return tab + strconv.FormatInt(value, 10)
|
||||
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 + "]"
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported value type: %v", 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 {
|
||||
result := ""
|
||||
for k, v := range t.values {
|
||||
// figure out the keyspace
|
||||
combinedKey := k
|
||||
if keyspace != "" {
|
||||
combinedKey = keyspace + "." + combinedKey
|
||||
}
|
||||
// output based on type
|
||||
switch node := v.(type) {
|
||||
case []*TomlTree:
|
||||
for _, item := range node {
|
||||
if len(item.Keys()) > 0 {
|
||||
result += fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey)
|
||||
}
|
||||
result += item.toToml(indent+" ", combinedKey)
|
||||
}
|
||||
case *TomlTree:
|
||||
if len(node.Keys()) > 0 {
|
||||
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
||||
}
|
||||
result += node.toToml(indent+" ", combinedKey)
|
||||
case map[string]interface{}:
|
||||
sub := TreeFromMap(node)
|
||||
|
||||
if len(sub.Keys()) > 0 {
|
||||
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
||||
}
|
||||
result += sub.toToml(indent+" ", combinedKey)
|
||||
case *tomlValue:
|
||||
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
|
||||
default:
|
||||
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Query compiles and executes a query on a tree and returns the query result.
|
||||
func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
||||
if q, err := CompileQuery(query); err != nil {
|
||||
q, err := CompileQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return q.Execute(t), nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToString 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) ToString() string {
|
||||
return t.toToml("", "")
|
||||
return q.Execute(t), nil
|
||||
}
|
||||
|
||||
// LoadReader creates a TomlTree from any io.Reader.
|
||||
|
||||
@@ -15,6 +15,47 @@ func TestTomlHas(t *testing.T) {
|
||||
if !tree.Has("test.key") {
|
||||
t.Errorf("Has - expected test.key to exists")
|
||||
}
|
||||
|
||||
if tree.Has("") {
|
||||
t.Errorf("Should return false if the key is not provided")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlGet(t *testing.T) {
|
||||
tree, _ := Load(`
|
||||
[test]
|
||||
key = "value"
|
||||
`)
|
||||
|
||||
if tree.Get("") != tree {
|
||||
t.Errorf("Get should return the tree itself when given an empty path")
|
||||
}
|
||||
|
||||
if tree.Get("test.key") != "value" {
|
||||
t.Errorf("Get should return the value")
|
||||
}
|
||||
if tree.Get(`\`) != nil {
|
||||
t.Errorf("should return nil when the key is malformed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlGetDefault(t *testing.T) {
|
||||
tree, _ := Load(`
|
||||
[test]
|
||||
key = "value"
|
||||
`)
|
||||
|
||||
if tree.GetDefault("", "hello") != tree {
|
||||
t.Error("GetDefault should return the tree itself when given an empty path")
|
||||
}
|
||||
|
||||
if tree.GetDefault("test.key", "hello") != "value" {
|
||||
t.Error("Get should return the value")
|
||||
}
|
||||
|
||||
if tree.GetDefault("whatever", "hello") != "hello" {
|
||||
t.Error("GetDefault should return the default value if the key does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlHasPath(t *testing.T) {
|
||||
@@ -46,6 +87,11 @@ func TestTomlGetPath(t *testing.T) {
|
||||
t.Errorf("GetPath[%d] %v - expected %v, got %v instead.", idx, item.Path, item.Expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6")
|
||||
if tree.GetPath([]string{"whatever"}) != nil {
|
||||
t.Error("GetPath should return nil when the key does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlQuery(t *testing.T) {
|
||||
@@ -72,3 +118,11 @@ func TestTomlQuery(t *testing.T) {
|
||||
t.Errorf("Expected 'b' with a value 2: %v", tt.Get("b"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlFromMap(t *testing.T) {
|
||||
simpleMap := map[string]interface{}{"hello": 42}
|
||||
tree := TreeFromMap(simpleMap)
|
||||
if tree.Get("hello") != 42 {
|
||||
t.Fatal("hello should be 42, not", tree.Get("hello"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Tools to convert a TomlTree to different representations
|
||||
package toml
|
||||
|
||||
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 int64:
|
||||
return tab + strconv.FormatInt(value, 10)
|
||||
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 + "]"
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported value type: %v", 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 {
|
||||
result := ""
|
||||
for k, v := range t.values {
|
||||
// figure out the keyspace
|
||||
combinedKey := k
|
||||
if keyspace != "" {
|
||||
combinedKey = keyspace + "." + combinedKey
|
||||
}
|
||||
// output based on type
|
||||
switch node := v.(type) {
|
||||
case []*TomlTree:
|
||||
for _, item := range node {
|
||||
if len(item.Keys()) > 0 {
|
||||
result += fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey)
|
||||
}
|
||||
result += item.toToml(indent+" ", combinedKey)
|
||||
}
|
||||
case *TomlTree:
|
||||
if len(node.Keys()) > 0 {
|
||||
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
||||
}
|
||||
result += node.toToml(indent+" ", combinedKey)
|
||||
case map[string]interface{}:
|
||||
sub := TreeFromMap(node)
|
||||
|
||||
if len(sub.Keys()) > 0 {
|
||||
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
||||
}
|
||||
result += sub.toToml(indent+" ", combinedKey)
|
||||
case *tomlValue:
|
||||
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
|
||||
default:
|
||||
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ToString is an alias for String
|
||||
func (t *TomlTree) ToString() string {
|
||||
return t.String()
|
||||
}
|
||||
|
||||
// ToString 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:
|
||||
result[k] = make([]interface{}, 0)
|
||||
for _, item := range node {
|
||||
result[k] = item.ToMap()
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 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)
|
||||
}
|
||||
Reference in New Issue
Block a user