Added Position Support to TomlTree

TomlDocument provides an optional TOML processing path where position
informaiton is stored alongside a TomlTree.
* Added Position struct
* Revised TomlTree to contain position data
* Added tomlValue to bind positions to values
* Revised parser to emit position data
* Revised token to use new Position struct
* Added tests for new functionality
* Bugfixed table array duplicate key handling
* Applied gofmt to all code
This commit is contained in:
eanderton
2014-08-14 22:04:25 -04:00
parent bcbaee1079
commit 7c63fff960
9 changed files with 582 additions and 314 deletions
+9 -3
View File
@@ -20,7 +20,7 @@ func main() {
os.Exit(1)
}
typedTree := translate((map[string]interface{})(*tree))
typedTree := translate(*tree)
if err := json.NewEncoder(os.Stdout).Encode(typedTree); err != nil {
log.Fatalf("Error encoding JSON: %s", err)
@@ -30,7 +30,6 @@ func main() {
}
func translate(tomlData interface{}) interface{} {
switch orig := tomlData.(type) {
case map[string]interface{}:
typed := make(map[string]interface{}, len(orig))
@@ -39,7 +38,14 @@ func translate(tomlData interface{}) interface{} {
}
return typed
case *toml.TomlTree:
return translate((map[string]interface{})(*orig))
return translate(*orig)
case toml.TomlTree:
keys := orig.Keys()
typed := make(map[string]interface{}, len(keys))
for _, k := range keys {
typed[k] = translate(orig.GetPath([]string{k}))
}
return typed
case []*toml.TomlTree:
typed := make([]map[string]interface{}, len(orig))
for i, v := range orig {
+46 -14
View File
@@ -45,11 +45,39 @@ const (
tokenEOL
)
var tokenTypeNames []string = []string{
"EOF",
"Comment",
"Key",
"=",
"\"",
"Integer",
"True",
"False",
"Float",
"[",
"[",
"]]",
"[[",
"Date",
"KeyGroup",
"KeyGroupArray",
",",
"EOL",
}
type token struct {
Position
typ tokenType
val string
line int
col int
}
func (tt tokenType) String() string {
idx := int(tt)
if idx < len(tokenTypeNames) {
return tokenTypeNames[idx]
}
return "Unknown"
}
func (i token) String() string {
@@ -66,10 +94,6 @@ func (i token) String() string {
return fmt.Sprintf("%q", i.val)
}
func (i token) Pos() string {
return fmt.Sprintf("(%d, %d)", i.line+1, i.col+1)
}
func isSpace(r rune) bool {
return r == ' ' || r == '\t'
}
@@ -119,24 +143,31 @@ func (l *lexer) nextStart() {
r, width := utf8.DecodeRuneInString(l.input[i:])
if r == '\n' {
l.line += 1
l.col = 0
l.col = 1
} else {
l.col += 1
}
i += width
// fmt.Printf("'%c'\n", r)
}
// advance start position to next token
l.start = l.pos
}
func (l *lexer) emit(t tokenType) {
l.tokens <- token{t, l.input[l.start:l.pos], l.line, l.col}
l.tokens <- token{
Position: Position{l.line, l.col},
typ: t,
val: l.input[l.start:l.pos],
}
l.nextStart()
}
func (l *lexer) emitWithValue(t tokenType, value string) {
l.tokens <- token{t, value, l.line, l.col}
l.tokens <- token{
Position: Position{l.line, l.col},
typ: t,
val: value,
}
l.nextStart()
}
@@ -161,10 +192,9 @@ func (l *lexer) backup() {
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.tokens <- token{
tokenError,
fmt.Sprintf(format, args...),
l.line,
l.col,
Position: Position{l.line, l.col},
typ: tokenError,
val: fmt.Sprintf(format, args...),
}
return nil
}
@@ -534,6 +564,8 @@ func lex(input string) (*lexer, chan token) {
l := &lexer{
input: input,
tokens: make(chan token),
line: 1,
col: 1,
}
go l.run()
return l, l.tokens
+194 -194
View File
@@ -11,8 +11,8 @@ func testFlow(t *testing.T, input string, expectedFlow []token) {
t.Log("compared", token, "to", expected)
t.Log(token.val, "<->", expected.val)
t.Log(token.typ, "<->", expected.typ)
t.Log(token.line, "<->", expected.line)
t.Log(token.col, "<->", expected.col)
t.Log(token.Line, "<->", expected.Line)
t.Log(token.Col, "<->", expected.Col)
t.FailNow()
}
}
@@ -32,245 +32,245 @@ func testFlow(t *testing.T, input string, expectedFlow []token) {
func TestValidKeyGroup(t *testing.T) {
testFlow(t, "[hello world]", []token{
token{tokenLeftBracket, "[", 0, 0},
token{tokenKeyGroup, "hello world", 0, 1},
token{tokenRightBracket, "]", 0, 12},
token{tokenEOF, "", 0, 13},
token{Position{1, 1}, tokenLeftBracket, "["},
token{Position{1, 2}, tokenKeyGroup, "hello world"},
token{Position{1, 13}, tokenRightBracket, "]"},
token{Position{1, 14}, tokenEOF, ""},
})
}
func TestUnclosedKeyGroup(t *testing.T) {
testFlow(t, "[hello world", []token{
token{tokenLeftBracket, "[", 0, 0},
token{tokenError, "unclosed key group", 0, 1},
token{Position{1, 1}, tokenLeftBracket, "["},
token{Position{1, 2}, tokenError, "unclosed key group"},
})
}
func TestComment(t *testing.T) {
testFlow(t, "# blahblah", []token{
token{tokenEOF, "", 0, 10},
token{Position{1, 11}, tokenEOF, ""},
})
}
func TestKeyGroupComment(t *testing.T) {
testFlow(t, "[hello world] # blahblah", []token{
token{tokenLeftBracket, "[", 0, 0},
token{tokenKeyGroup, "hello world", 0, 1},
token{tokenRightBracket, "]", 0, 12},
token{tokenEOF, "", 0, 24},
token{Position{1, 1}, tokenLeftBracket, "["},
token{Position{1, 2}, tokenKeyGroup, "hello world"},
token{Position{1, 13}, tokenRightBracket, "]"},
token{Position{1, 25}, tokenEOF, ""},
})
}
func TestMultipleKeyGroupsComment(t *testing.T) {
testFlow(t, "[hello world] # blahblah\n[test]", []token{
token{tokenLeftBracket, "[", 0, 0},
token{tokenKeyGroup, "hello world", 0, 1},
token{tokenRightBracket, "]", 0, 12},
token{tokenLeftBracket, "[", 1, 0},
token{tokenKeyGroup, "test", 1, 1},
token{tokenRightBracket, "]", 1, 5},
token{tokenEOF, "", 1, 6},
token{Position{1, 1}, tokenLeftBracket, "["},
token{Position{1, 2}, tokenKeyGroup, "hello world"},
token{Position{1, 13}, tokenRightBracket, "]"},
token{Position{2, 1}, tokenLeftBracket, "["},
token{Position{2, 2}, tokenKeyGroup, "test"},
token{Position{2, 6}, tokenRightBracket, "]"},
token{Position{2, 7}, tokenEOF, ""},
})
}
func TestBasicKey(t *testing.T) {
testFlow(t, "hello", []token{
token{tokenKey, "hello", 0, 0},
token{tokenEOF, "", 0, 5},
token{Position{1, 1}, tokenKey, "hello"},
token{Position{1, 6}, tokenEOF, ""},
})
}
func TestBasicKeyWithUnderscore(t *testing.T) {
testFlow(t, "hello_hello", []token{
token{tokenKey, "hello_hello", 0, 0},
token{tokenEOF, "", 0, 11},
token{Position{1, 1}, tokenKey, "hello_hello"},
token{Position{1, 12}, tokenEOF, ""},
})
}
func TestBasicKeyWithDash(t *testing.T) {
testFlow(t, "hello-world", []token{
token{tokenKey, "hello-world", 0, 0},
token{tokenEOF, "", 0, 11},
token{Position{1, 1}, tokenKey, "hello-world"},
token{Position{1, 12}, tokenEOF, ""},
})
}
func TestBasicKeyWithUppercaseMix(t *testing.T) {
testFlow(t, "helloHELLOHello", []token{
token{tokenKey, "helloHELLOHello", 0, 0},
token{tokenEOF, "", 0, 15},
token{Position{1, 1}, tokenKey, "helloHELLOHello"},
token{Position{1, 16}, tokenEOF, ""},
})
}
func TestBasicKeyWithInternationalCharacters(t *testing.T) {
testFlow(t, "héllÖ", []token{
token{tokenKey, "héllÖ", 0, 0},
token{tokenEOF, "", 0, 5},
token{Position{1, 1}, tokenKey, "héllÖ"},
token{Position{1, 6}, tokenEOF, ""},
})
}
func TestBasicKeyAndEqual(t *testing.T) {
testFlow(t, "hello =", []token{
token{tokenKey, "hello", 0, 0},
token{tokenEqual, "=", 0, 6},
token{tokenEOF, "", 0, 7},
token{Position{1, 1}, tokenKey, "hello"},
token{Position{1, 7}, tokenEqual, "="},
token{Position{1, 8}, tokenEOF, ""},
})
}
func TestKeyWithSharpAndEqual(t *testing.T) {
testFlow(t, "key#name = 5", []token{
token{tokenKey, "key#name", 0, 0},
token{tokenEqual, "=", 0, 9},
token{tokenInteger, "5", 0, 11},
token{tokenEOF, "", 0, 12},
token{Position{1, 1}, tokenKey, "key#name"},
token{Position{1, 10}, tokenEqual, "="},
token{Position{1, 12}, tokenInteger, "5"},
token{Position{1, 13}, tokenEOF, ""},
})
}
func TestKeyWithSymbolsAndEqual(t *testing.T) {
testFlow(t, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{
token{tokenKey, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'", 0, 0},
token{tokenEqual, "=", 0, 38},
token{tokenInteger, "5", 0, 40},
token{tokenEOF, "", 0, 41},
token{Position{1, 1}, tokenKey, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'"},
token{Position{1, 39}, tokenEqual, "="},
token{Position{1, 41}, tokenInteger, "5"},
token{Position{1, 42}, tokenEOF, ""},
})
}
func TestKeyEqualStringEscape(t *testing.T) {
testFlow(t, `foo = "hello\""`, []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenString, "hello\"", 0, 7},
token{tokenEOF, "", 0, 15},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 8}, tokenString, "hello\""},
token{Position{1, 16}, tokenEOF, ""},
})
}
func TestKeyEqualStringUnfinished(t *testing.T) {
testFlow(t, `foo = "bar`, []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenError, "unclosed string", 0, 7},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 8}, tokenError, "unclosed string"},
})
}
func TestKeyEqualString(t *testing.T) {
testFlow(t, `foo = "bar"`, []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenString, "bar", 0, 7},
token{tokenEOF, "", 0, 11},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 8}, tokenString, "bar"},
token{Position{1, 12}, tokenEOF, ""},
})
}
func TestKeyEqualTrue(t *testing.T) {
testFlow(t, "foo = true", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenTrue, "true", 0, 6},
token{tokenEOF, "", 0, 10},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenTrue, "true"},
token{Position{1, 11}, tokenEOF, ""},
})
}
func TestKeyEqualFalse(t *testing.T) {
testFlow(t, "foo = false", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenFalse, "false", 0, 6},
token{tokenEOF, "", 0, 11},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenFalse, "false"},
token{Position{1, 12}, tokenEOF, ""},
})
}
func TestArrayNestedString(t *testing.T) {
testFlow(t, `a = [ ["hello", "world"] ]`, []token{
token{tokenKey, "a", 0, 0},
token{tokenEqual, "=", 0, 2},
token{tokenLeftBracket, "[", 0, 4},
token{tokenLeftBracket, "[", 0, 6},
token{tokenString, "hello", 0, 8},
token{tokenComma, ",", 0, 14},
token{tokenString, "world", 0, 17},
token{tokenRightBracket, "]", 0, 23},
token{tokenRightBracket, "]", 0, 25},
token{tokenEOF, "", 0, 26},
token{Position{1, 1}, tokenKey, "a"},
token{Position{1, 3}, tokenEqual, "="},
token{Position{1, 5}, tokenLeftBracket, "["},
token{Position{1, 7}, tokenLeftBracket, "["},
token{Position{1, 9}, tokenString, "hello"},
token{Position{1, 15}, tokenComma, ","},
token{Position{1, 18}, tokenString, "world"},
token{Position{1, 24}, tokenRightBracket, "]"},
token{Position{1, 26}, tokenRightBracket, "]"},
token{Position{1, 27}, tokenEOF, ""},
})
}
func TestArrayNestedInts(t *testing.T) {
testFlow(t, "a = [ [42, 21], [10] ]", []token{
token{tokenKey, "a", 0, 0},
token{tokenEqual, "=", 0, 2},
token{tokenLeftBracket, "[", 0, 4},
token{tokenLeftBracket, "[", 0, 6},
token{tokenInteger, "42", 0, 7},
token{tokenComma, ",", 0, 9},
token{tokenInteger, "21", 0, 11},
token{tokenRightBracket, "]", 0, 13},
token{tokenComma, ",", 0, 14},
token{tokenLeftBracket, "[", 0, 16},
token{tokenInteger, "10", 0, 17},
token{tokenRightBracket, "]", 0, 19},
token{tokenRightBracket, "]", 0, 21},
token{tokenEOF, "", 0, 22},
token{Position{1, 1}, tokenKey, "a"},
token{Position{1, 3}, tokenEqual, "="},
token{Position{1, 5}, tokenLeftBracket, "["},
token{Position{1, 7}, tokenLeftBracket, "["},
token{Position{1, 8}, tokenInteger, "42"},
token{Position{1, 10}, tokenComma, ","},
token{Position{1, 12}, tokenInteger, "21"},
token{Position{1, 14}, tokenRightBracket, "]"},
token{Position{1, 15}, tokenComma, ","},
token{Position{1, 17}, tokenLeftBracket, "["},
token{Position{1, 18}, tokenInteger, "10"},
token{Position{1, 20}, tokenRightBracket, "]"},
token{Position{1, 22}, tokenRightBracket, "]"},
token{Position{1, 23}, tokenEOF, ""},
})
}
func TestArrayInts(t *testing.T) {
testFlow(t, "a = [ 42, 21, 10, ]", []token{
token{tokenKey, "a", 0, 0},
token{tokenEqual, "=", 0, 2},
token{tokenLeftBracket, "[", 0, 4},
token{tokenInteger, "42", 0, 6},
token{tokenComma, ",", 0, 8},
token{tokenInteger, "21", 0, 10},
token{tokenComma, ",", 0, 12},
token{tokenInteger, "10", 0, 14},
token{tokenComma, ",", 0, 16},
token{tokenRightBracket, "]", 0, 18},
token{tokenEOF, "", 0, 19},
token{Position{1, 1}, tokenKey, "a"},
token{Position{1, 3}, tokenEqual, "="},
token{Position{1, 5}, tokenLeftBracket, "["},
token{Position{1, 7}, tokenInteger, "42"},
token{Position{1, 9}, tokenComma, ","},
token{Position{1, 11}, tokenInteger, "21"},
token{Position{1, 13}, tokenComma, ","},
token{Position{1, 15}, tokenInteger, "10"},
token{Position{1, 17}, tokenComma, ","},
token{Position{1, 19}, tokenRightBracket, "]"},
token{Position{1, 20}, tokenEOF, ""},
})
}
func TestMultilineArrayComments(t *testing.T) {
testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{
token{tokenKey, "a", 0, 0},
token{tokenEqual, "=", 0, 2},
token{tokenLeftBracket, "[", 0, 4},
token{tokenInteger, "1", 0, 5},
token{tokenComma, ",", 0, 6},
token{tokenInteger, "2", 1, 0},
token{tokenComma, ",", 1, 1},
token{tokenInteger, "3", 2, 0},
token{tokenComma, ",", 2, 1},
token{tokenRightBracket, "]", 3, 0},
token{tokenEOF, "", 3, 1},
token{Position{1, 1}, tokenKey, "a"},
token{Position{1, 3}, tokenEqual, "="},
token{Position{1, 5}, tokenLeftBracket, "["},
token{Position{1, 6}, tokenInteger, "1"},
token{Position{1, 7}, tokenComma, ","},
token{Position{2, 1}, tokenInteger, "2"},
token{Position{2, 2}, tokenComma, ","},
token{Position{3, 1}, tokenInteger, "3"},
token{Position{3, 2}, tokenComma, ","},
token{Position{4, 1}, tokenRightBracket, "]"},
token{Position{4, 2}, tokenEOF, ""},
})
}
func TestKeyEqualArrayBools(t *testing.T) {
testFlow(t, "foo = [true, false, true]", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenLeftBracket, "[", 0, 6},
token{tokenTrue, "true", 0, 7},
token{tokenComma, ",", 0, 11},
token{tokenFalse, "false", 0, 13},
token{tokenComma, ",", 0, 18},
token{tokenTrue, "true", 0, 20},
token{tokenRightBracket, "]", 0, 24},
token{tokenEOF, "", 0, 25},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenLeftBracket, "["},
token{Position{1, 8}, tokenTrue, "true"},
token{Position{1, 12}, tokenComma, ","},
token{Position{1, 14}, tokenFalse, "false"},
token{Position{1, 19}, tokenComma, ","},
token{Position{1, 21}, tokenTrue, "true"},
token{Position{1, 25}, tokenRightBracket, "]"},
token{Position{1, 26}, tokenEOF, ""},
})
}
func TestKeyEqualArrayBoolsWithComments(t *testing.T) {
testFlow(t, "foo = [true, false, true] # YEAH", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenLeftBracket, "[", 0, 6},
token{tokenTrue, "true", 0, 7},
token{tokenComma, ",", 0, 11},
token{tokenFalse, "false", 0, 13},
token{tokenComma, ",", 0, 18},
token{tokenTrue, "true", 0, 20},
token{tokenRightBracket, "]", 0, 24},
token{tokenEOF, "", 0, 32},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenLeftBracket, "["},
token{Position{1, 8}, tokenTrue, "true"},
token{Position{1, 12}, tokenComma, ","},
token{Position{1, 14}, tokenFalse, "false"},
token{Position{1, 19}, tokenComma, ","},
token{Position{1, 21}, tokenTrue, "true"},
token{Position{1, 25}, tokenRightBracket, "]"},
token{Position{1, 33}, tokenEOF, ""},
})
}
@@ -282,138 +282,138 @@ func TestDateRegexp(t *testing.T) {
func TestKeyEqualDate(t *testing.T) {
testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenDate, "1979-05-27T07:32:00Z", 0, 6},
token{tokenEOF, "", 0, 26},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenDate, "1979-05-27T07:32:00Z"},
token{Position{1, 27}, tokenEOF, ""},
})
}
func TestFloatEndingWithDot(t *testing.T) {
testFlow(t, "foo = 42.", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenError, "float cannot end with a dot", 0, 6},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenError, "float cannot end with a dot"},
})
}
func TestFloatWithTwoDots(t *testing.T) {
testFlow(t, "foo = 4.2.", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenError, "cannot have two dots in one float", 0, 6},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenError, "cannot have two dots in one float"},
})
}
func TestDoubleEqualKey(t *testing.T) {
testFlow(t, "foo= = 2", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 3},
token{tokenError, "cannot have multiple equals for the same key", 0, 4},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 4}, tokenEqual, "="},
token{Position{1, 5}, tokenError, "cannot have multiple equals for the same key"},
})
}
func TestInvalidEsquapeSequence(t *testing.T) {
testFlow(t, `foo = "\x"`, []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenError, "invalid escape sequence: \\x", 0, 7},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 8}, tokenError, "invalid escape sequence: \\x"},
})
}
func TestNestedArrays(t *testing.T) {
testFlow(t, "foo = [[[]]]", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenLeftBracket, "[", 0, 6},
token{tokenLeftBracket, "[", 0, 7},
token{tokenLeftBracket, "[", 0, 8},
token{tokenRightBracket, "]", 0, 9},
token{tokenRightBracket, "]", 0, 10},
token{tokenRightBracket, "]", 0, 11},
token{tokenEOF, "", 0, 12},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenLeftBracket, "["},
token{Position{1, 8}, tokenLeftBracket, "["},
token{Position{1, 9}, tokenLeftBracket, "["},
token{Position{1, 10}, tokenRightBracket, "]"},
token{Position{1, 11}, tokenRightBracket, "]"},
token{Position{1, 12}, tokenRightBracket, "]"},
token{Position{1, 13}, tokenEOF, ""},
})
}
func TestKeyEqualNumber(t *testing.T) {
testFlow(t, "foo = 42", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenInteger, "42", 0, 6},
token{tokenEOF, "", 0, 8},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenInteger, "42"},
token{Position{1, 9}, tokenEOF, ""},
})
testFlow(t, "foo = +42", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenInteger, "+42", 0, 6},
token{tokenEOF, "", 0, 9},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenInteger, "+42"},
token{Position{1, 10}, tokenEOF, ""},
})
testFlow(t, "foo = -42", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenInteger, "-42", 0, 6},
token{tokenEOF, "", 0, 9},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenInteger, "-42"},
token{Position{1, 10}, tokenEOF, ""},
})
testFlow(t, "foo = 4.2", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenFloat, "4.2", 0, 6},
token{tokenEOF, "", 0, 9},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenFloat, "4.2"},
token{Position{1, 10}, tokenEOF, ""},
})
testFlow(t, "foo = +4.2", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenFloat, "+4.2", 0, 6},
token{tokenEOF, "", 0, 10},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenFloat, "+4.2"},
token{Position{1, 11}, tokenEOF, ""},
})
testFlow(t, "foo = -4.2", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenFloat, "-4.2", 0, 6},
token{tokenEOF, "", 0, 10},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenFloat, "-4.2"},
token{Position{1, 11}, tokenEOF, ""},
})
}
func TestMultiline(t *testing.T) {
testFlow(t, "foo = 42\nbar=21", []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenInteger, "42", 0, 6},
token{tokenKey, "bar", 1, 0},
token{tokenEqual, "=", 1, 3},
token{tokenInteger, "21", 1, 4},
token{tokenEOF, "", 1, 6},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 7}, tokenInteger, "42"},
token{Position{2, 1}, tokenKey, "bar"},
token{Position{2, 4}, tokenEqual, "="},
token{Position{2, 5}, tokenInteger, "21"},
token{Position{2, 7}, tokenEOF, ""},
})
}
func TestKeyEqualStringUnicodeEscape(t *testing.T) {
testFlow(t, `foo = "hello \u2665"`, []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenString, "hello ♥", 0, 7},
token{tokenEOF, "", 0, 20},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 8}, tokenString, "hello ♥"},
token{Position{1, 21}, tokenEOF, ""},
})
}
func TestUnicodeString(t *testing.T) {
testFlow(t, `foo = "hello ♥ world"`, []token{
token{tokenKey, "foo", 0, 0},
token{tokenEqual, "=", 0, 4},
token{tokenString, "hello ♥ world", 0, 7},
token{tokenEOF, "", 0, 21},
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 5}, tokenEqual, "="},
token{Position{1, 8}, tokenString, "hello ♥ world"},
token{Position{1, 22}, tokenEOF, ""},
})
}
func TestKeyGroupArray(t *testing.T) {
testFlow(t, "[[foo]]", []token{
token{tokenDoubleLeftBracket, "[[", 0, 0},
token{tokenKeyGroupArray, "foo", 0, 2},
token{tokenDoubleRightBracket, "]]", 0, 5},
token{tokenEOF, "", 0, 7},
token{Position{1, 1}, tokenDoubleLeftBracket, "[["},
token{Position{1, 3}, tokenKeyGroupArray, "foo"},
token{Position{1, 6}, tokenDoubleRightBracket, "]]"},
token{Position{1, 8}, tokenEOF, ""},
})
}
+45 -18
View File
@@ -22,7 +22,7 @@ type parserStateFn func(*parser) parserStateFn
// Formats and panics an error message based on a token
func (p *parser) raiseError(tok *token, msg string, args ...interface{}) {
panic(tok.Pos() + ": " + fmt.Sprintf(msg, args...))
panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
}
func (p *parser) run() {
@@ -47,10 +47,10 @@ func (p *parser) peek() *token {
func (p *parser) assume(typ tokenType) {
tok := p.getToken()
if tok == nil {
p.raiseError(tok, "was expecting token %s, but token stream is empty", tok.typ)
p.raiseError(tok, "was expecting token %s, but token stream is empty", tok)
}
if tok.typ != typ {
p.raiseError(tok, "was expecting token %s, but got %s", typ, tok.typ)
p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok)
}
}
@@ -70,6 +70,9 @@ func (p *parser) getToken() *token {
func parseStart(p *parser) parserStateFn {
tok := p.peek()
// prime position data with root tree instance
p.tree.position = tok.Position
// end of stream, parsing is finished
if tok == nil {
return nil
@@ -91,15 +94,16 @@ func parseStart(p *parser) parserStateFn {
}
func parseGroupArray(p *parser) parserStateFn {
p.getToken() // discard the [[
start_token := p.getToken() // discard the [[
key := p.getToken()
if key.typ != tokenKeyGroupArray {
p.raiseError(key, "unexpected token %s, was expecting a key group array", key)
}
// get or create group array element at the indicated part in the path
p.currentGroup = strings.Split(key.val, ".")
dest_tree := p.tree.GetPath(p.currentGroup)
keys := strings.Split(key.val, ".")
p.tree.createSubTree(keys[:len(keys)-1]) // create parent entries
dest_tree := p.tree.GetPath(keys)
var array []*TomlTree
if dest_tree == nil {
array = make([]*TomlTree, 0)
@@ -108,14 +112,31 @@ func parseGroupArray(p *parser) parserStateFn {
} else {
p.raiseError(key, "key %s is already assigned and not of type group array", key)
}
p.currentGroup = keys
// add a new tree to the end of the group array
new_tree := make(TomlTree)
array = append(array, &new_tree)
new_tree := newTomlTree()
new_tree.position = start_token.Position
array = append(array, new_tree)
p.tree.SetPath(p.currentGroup, array)
// remove all keys that were children of this group array
prefix := key.val + "."
found := false
for ii := 0; ii < len(p.seenGroupKeys); {
groupKey := p.seenGroupKeys[ii]
if strings.HasPrefix(groupKey, prefix) {
p.seenGroupKeys = append(p.seenGroupKeys[:ii], p.seenGroupKeys[ii+1:]...)
} else {
found = (groupKey == key.val)
ii++
}
}
// keep this key name from use by other kinds of assignments
if !found {
p.seenGroupKeys = append(p.seenGroupKeys, key.val)
}
// move to next parser state
p.assume(tokenDoubleRightBracket)
@@ -123,7 +144,7 @@ func parseGroupArray(p *parser) parserStateFn {
}
func parseGroup(p *parser) parserStateFn {
p.getToken() // discard the [
start_token := p.getToken() // discard the [
key := p.getToken()
if key.typ != tokenKeyGroup {
p.raiseError(key, "unexpected token %s, was expecting a key group", key)
@@ -133,12 +154,16 @@ func parseGroup(p *parser) parserStateFn {
p.raiseError(key, "duplicated tables")
}
}
p.seenGroupKeys = append(p.seenGroupKeys, key.val)
if err := p.tree.createSubTree(key.val); err != nil {
keys := strings.Split(key.val, ".")
if err := p.tree.createSubTree(keys); err != nil {
p.raiseError(key, "%s", err)
}
p.assume(tokenRightBracket)
p.currentGroup = strings.Split(key.val, ".")
p.currentGroup = keys
target_tree := p.tree.GetPath(p.currentGroup).(*TomlTree)
target_tree.position = start_token.Position
return parseStart(p)
}
@@ -150,7 +175,7 @@ func parseAssign(p *parser) parserStateFn {
if len(p.currentGroup) > 0 {
group_key = p.currentGroup
} else {
group_key = make([]string, 0)
group_key = []string{}
}
// find the group to assign, looking out for arrays of groups
@@ -161,16 +186,18 @@ func parseAssign(p *parser) parserStateFn {
case *TomlTree:
target_node = node
default:
p.raiseError(key, "Unknown group type for path %s", group_key)
p.raiseError(key, "Unknown group type for path: %s",
strings.Join(group_key, "."))
}
// assign value to the found group
local_key := []string{key.val}
final_key := append(group_key, key.val)
if target_node.GetPath(local_key) != nil {
p.raiseError(key, "the following key was defined twice: %s", strings.Join(final_key, "."))
p.raiseError(key, "The following key was defined twice: %s",
strings.Join(final_key, "."))
}
target_node.SetPath(local_key, value)
target_node.values[key.val] = &tomlValue{value, key.Position}
return parseStart(p)
}
@@ -251,14 +278,14 @@ func parseArray(p *parser) []interface{} {
}
func parse(flow chan token) *TomlTree {
result := make(TomlTree)
result := newTomlTree()
parser := &parser{
flow: flow,
tree: &result,
tree: result,
tokensBuffer: make([]token, 0),
currentGroup: make([]string, 0),
seenGroupKeys: make([]string, 0),
}
parser.run()
return parser.tree
return result
}
+114 -28
View File
@@ -12,14 +12,15 @@ func assertTree(t *testing.T, tree *TomlTree, err error, ref map[string]interfac
return
}
for k, v := range ref {
node := tree.Get(k)
switch cast_node := node.(type) {
// NOTE: directly access key instead of resolve by path
// NOTE: see TestSpecialKV
switch node := tree.GetPath([]string{k}).(type) {
case []*TomlTree:
for idx, item := range cast_node {
for idx, item := range node {
assertTree(t, item, err, v.([]map[string]interface{})[idx])
}
case *TomlTree:
assertTree(t, cast_node, err, v.(map[string]interface{}))
assertTree(t, node, err, v.(map[string]interface{}))
default:
if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) {
t.Errorf("was expecting %v at %v but got %v", v, k, node)
@@ -29,8 +30,8 @@ func assertTree(t *testing.T, tree *TomlTree, err error, ref map[string]interfac
}
func TestCreateSubTree(t *testing.T) {
tree := make(TomlTree)
tree.createSubTree("a.b.c")
tree := newTomlTree()
tree.createSubTree([]string{"a", "b", "c"})
tree.Set("a.b.c", 42)
if tree.Get("a.b.c") != 42 {
t.Fail()
@@ -50,6 +51,15 @@ func TestSimpleKV(t *testing.T) {
})
}
// NOTE: from the BurntSushi test suite
// NOTE: this test is pure evil due to the embedded '.'
func TestSpecialKV(t *testing.T) {
tree, err := Load("~!@#$^&*()_+-`1234567890[]\\|/?><.,;: = 1")
assertTree(t, tree, err, map[string]interface{}{
"~!@#$^&*()_+-`1234567890[]\\|/?><.,;:": int64(1),
})
}
func TestSimpleNumbers(t *testing.T) {
tree, err := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1")
assertTree(t, tree, err, map[string]interface{}{
@@ -107,7 +117,13 @@ func TestBools(t *testing.T) {
func TestNestedKeys(t *testing.T) {
tree, err := Load("[a.b.c]\nd = 42")
assertTree(t, tree, err, map[string]interface{}{
"a.b.c.d": int64(42),
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": map[string]interface{}{
"d": int64(42),
},
},
},
})
}
@@ -221,7 +237,7 @@ func TestDuplicateGroups(t *testing.T) {
func TestDuplicateKeys(t *testing.T) {
_, err := Load("foo = 2\nfoo = 3")
if err.Error() != "(2, 1): the following key was defined twice: foo" {
if err.Error() != "(2, 1): The following key was defined twice: foo" {
t.Error("Bad error message:", err.Error())
}
}
@@ -271,19 +287,34 @@ func TestParseFile(t *testing.T) {
assertTree(t, tree, err, map[string]interface{}{
"title": "TOML Example",
"owner.name": "Tom Preston-Werner",
"owner.organization": "GitHub",
"owner.bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
"owner.dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
"database.server": "192.168.1.1",
"database.ports": []int64{8001, 8001, 8002},
"database.connection_max": 5000,
"database.enabled": true,
"servers.alpha.ip": "10.0.0.1",
"servers.alpha.dc": "eqdc10",
"servers.beta.ip": "10.0.0.2",
"servers.beta.dc": "eqdc10",
"clients.data": []interface{}{[]string{"gamma", "delta"}, []int64{1, 2}},
"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": []int64{8001, 8001, 8002},
"connection_max": 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{}{
[]string{"gamma", "delta"},
[]int64{1, 2},
},
},
})
}
@@ -333,13 +364,10 @@ func TestToTomlValue(t *testing.T) {
}
func TestToString(t *testing.T) {
tree := &TomlTree{
"foo": &TomlTree{
"bar": []*TomlTree{
{"a": int64(42)},
{"a": int64(69)},
},
},
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"
@@ -347,3 +375,61 @@ func TestToString(t *testing.T) {
t.Errorf("Expected got '%s', expected '%s'", result, expected)
}
}
func assertPosition(t *testing.T, text string, ref map[string]Position) {
tree, err := Load(text)
if err != nil {
t.Errorf("Error loading document text: `%v`", text)
t.Errorf("Error: %v", err)
}
for path, pos := range ref {
testPos := tree.GetPosition(path)
if testPos.Invalid() {
t.Errorf("Failed to query tree path: %s", path)
} else if pos != testPos {
t.Errorf("Expected position %v, got %v instead", pos, testPos)
}
}
}
func TestDocumentPositions(t *testing.T) {
assertPosition(t,
"[foo]\nbar=42\nbaz=69",
map[string]Position{
"foo": Position{1, 1},
"foo.bar": Position{2, 1},
"foo.baz": Position{3, 1},
})
}
func TestDocumentPositionsWithSpaces(t *testing.T) {
assertPosition(t,
" [foo]\n bar=42\n baz=69",
map[string]Position{
"foo": Position{1, 3},
"foo.bar": Position{2, 3},
"foo.baz": Position{3, 3},
})
}
func TestDocumentPositionsWithGroupArray(t *testing.T) {
assertPosition(t,
"[[foo]]\nbar=42\nbaz=69",
map[string]Position{
"foo": Position{1, 1},
"foo.bar": Position{2, 1},
"foo.baz": Position{3, 1},
})
}
func TestDocumentPositionsEmptyPath(t *testing.T) {
text := "[foo]\nbar=42\nbaz=69"
tree, err := Load(text)
if err != nil {
t.Errorf("Error loading document text: `%v`", text)
t.Errorf("Error: %v", err)
}
if pos := tree.GetPosition(""); !pos.Invalid() {
t.Errorf("Valid position was returned for empty path")
}
}
+26
View File
@@ -0,0 +1,26 @@
// Position support for go-toml
//
// BSD Licensed
// Copyright 2014 eric.t.anderton@gmail.com
package toml
import (
"fmt"
)
// position within a TOML document
type Position struct {
Line int // line within the document
Col int // column within the line
}
// String representation of the position.
// Displays 1-indexed line and column numbers.
func (p *Position) String() string {
return fmt.Sprintf("(%d, %d)", p.Line, p.Col)
}
func (p *Position) Invalid() bool {
return p.Line <= 0 || p.Col <= 0
}
+31
View File
@@ -0,0 +1,31 @@
// Testing support for go-toml
//
// BSD Licensed
// Copyright 2014 eric.t.anderton@gmail.com
package toml
import (
"testing"
)
func TestPositionString(t *testing.T) {
p := Position{123, 456}
expected := "(123, 456)"
value := p.String()
if value != expected {
t.Errorf("Expected %v, got %v instead", expected, value)
}
}
func TestInvalid(t *testing.T) {
for i, v := range []Position{
Position{0, 1234},
Position{1234, 0},
Position{0, 0},
} {
if !v.Invalid() {
t.Errorf("Position at %v is valid: %v", i, v)
}
}
}
+107 -49
View File
@@ -14,9 +14,24 @@ import (
"time"
)
type tomlValue struct {
value interface{}
position Position
}
// Definition of a TomlTree.
// This is the result of the parsing of a TOML file.
type TomlTree map[string]interface{}
type TomlTree struct {
values map[string]interface{}
position Position
}
func newTomlTree() *TomlTree {
return &TomlTree{
values: make(map[string]interface{}),
position: Position{0, 0},
}
}
// Has returns a boolean indicating if the given key exists.
func (t *TomlTree) Has(key string) bool {
@@ -28,35 +43,14 @@ func (t *TomlTree) Has(key string) bool {
// Returns true if the given path of keys exists, false otherwise.
func (t *TomlTree) HasPath(keys []string) bool {
if len(keys) == 0 {
return false
}
subtree := t
for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key]
if !exists {
return false
}
switch node := (*subtree)[intermediate_key].(type) {
case *TomlTree:
subtree = node
case []*TomlTree:
// go to most recent element
if len(node) == 0 {
return false
}
subtree = node[len(node)-1]
}
}
return true
return t.GetPath(keys) != nil
}
// Keys returns the keys of the toplevel tree.
// Warning: this is a costly operation.
func (t *TomlTree) Keys() []string {
keys := make([]string, 0)
mp := (map[string]interface{})(*t)
for k, _ := range mp {
for k, _ := range t.values {
keys = append(keys, k)
}
return keys
@@ -81,22 +75,79 @@ func (t *TomlTree) GetPath(keys []string) interface{} {
}
subtree := t
for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key]
value, exists := subtree.values[intermediate_key]
if !exists {
return nil
}
switch node := (*subtree)[intermediate_key].(type) {
switch node := value.(type) {
case *TomlTree:
subtree = node
case []*TomlTree:
// go to most recent element
if len(node) == 0 {
return nil //(*subtree)[intermediate_key] = append(node, &TomlTree{})
return nil
}
subtree = node[len(node)-1]
default:
return nil // cannot naigate through other node types
}
}
return (*subtree)[keys[len(keys)-1]]
// branch based on final node type
switch node := subtree.values[keys[len(keys)-1]].(type) {
case *tomlValue:
return node.value
default:
return node
}
}
func (t *TomlTree) GetPosition(key string) Position {
if key == "" {
return Position{0, 0}
}
return t.GetPositionPath(strings.Split(key, "."))
}
// Returns the element in the tree indicated by 'keys'.
// If keys is of length zero, the current tree is returned.
func (t *TomlTree) GetPositionPath(keys []string) Position {
if len(keys) == 0 {
return t.position
}
subtree := t
for _, intermediate_key := range keys[:len(keys)-1] {
value, exists := subtree.values[intermediate_key]
if !exists {
return Position{0, 0}
}
switch node := value.(type) {
case *TomlTree:
subtree = node
case []*TomlTree:
// go to most recent element
if len(node) == 0 {
return Position{0, 0}
}
subtree = node[len(node)-1]
default:
return Position{0, 0}
}
}
// branch based on final node type
switch node := subtree.values[keys[len(keys)-1]].(type) {
case *tomlValue:
return node.position
case *TomlTree:
return node.position
case []*TomlTree:
// go to most recent element
if len(node) == 0 {
return Position{0, 0}
}
return node[len(node)-1].position
default:
return Position{0, 0}
}
}
// Same as Get but with a default value
@@ -115,26 +166,30 @@ func (t *TomlTree) Set(key string, value interface{}) {
t.SetPath(strings.Split(key, "."), value)
}
// Set an element in the tree.
// Keys is an array of path elements (e.g. {"a","b","c"}).
// Creates all necessary intermediates trees, if needed.
func (t *TomlTree) SetPath(keys []string, value interface{}) {
subtree := t
for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key]
nextTree, exists := subtree.values[intermediate_key]
if !exists {
var new_tree TomlTree = make(TomlTree)
(*subtree)[intermediate_key] = &new_tree
nextTree = newTomlTree()
subtree.values[intermediate_key] = &nextTree // add new element here
}
switch node := (*subtree)[intermediate_key].(type) {
switch node := nextTree.(type) {
case *TomlTree:
subtree = node
case []*TomlTree:
// go to most recent element
if len(node) == 0 {
(*subtree)[intermediate_key] = append(node, &TomlTree{})
// create element if it does not exist
subtree.values[intermediate_key] = append(node, newTomlTree())
}
subtree = node[len(node)-1]
}
}
(*subtree)[keys[len(keys)-1]] = value
subtree.values[keys[len(keys)-1]] = value
}
// createSubTree takes a tree and a key and create the necessary intermediate
@@ -144,25 +199,26 @@ func (t *TomlTree) SetPath(keys []string, value interface{}) {
// and tree[a][b][c]
//
// Returns nil on success, error object on failure
func (t *TomlTree) createSubTree(key string) error {
func (t *TomlTree) createSubTree(keys []string) error {
subtree := t
for _, intermediate_key := range strings.Split(key, ".") {
for _, intermediate_key := range keys {
if intermediate_key == "" {
return fmt.Errorf("empty intermediate table")
}
_, exists := (*subtree)[intermediate_key]
nextTree, exists := subtree.values[intermediate_key]
if !exists {
var new_tree TomlTree = make(TomlTree)
(*subtree)[intermediate_key] = &new_tree
nextTree = newTomlTree()
subtree.values[intermediate_key] = nextTree
}
switch node := (*subtree)[intermediate_key].(type) {
switch node := nextTree.(type) {
case []*TomlTree:
subtree = node[len(node)-1]
case *TomlTree:
subtree = node
default:
return fmt.Errorf("unknown type for path %s", key)
return fmt.Errorf("unknown type for path %s (%s)",
strings.Join(keys, "."), intermediate_key)
}
}
return nil
@@ -231,9 +287,9 @@ func toTomlValue(item interface{}, indent int) string {
// Recursive support function for ToString()
// Outputs a tree, using the provided keyspace to prefix group names
func (t *TomlTree) toToml(keyspace string) string {
func (t *TomlTree) toToml(indent, keyspace string) string {
result := ""
for k, v := range (map[string]interface{})(*t) {
for k, v := range t.values {
// figure out the keyspace
combined_key := k
if keyspace != "" {
@@ -244,17 +300,19 @@ func (t *TomlTree) toToml(keyspace string) string {
case []*TomlTree:
for _, item := range node {
if len(item.Keys()) > 0 {
result += fmt.Sprintf("\n[[%s]]\n", combined_key)
result += fmt.Sprintf("\n%s[[%s]]\n", indent, combined_key)
}
result += item.toToml(combined_key)
result += item.toToml(indent+" ", combined_key)
}
case *TomlTree:
if len(node.Keys()) > 0 {
result += fmt.Sprintf("\n[%s]\n", combined_key)
result += fmt.Sprintf("\n%s[%s]\n", indent, combined_key)
}
result += node.toToml(combined_key)
result += node.toToml(indent+" ", combined_key)
case *tomlValue:
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
default:
result += fmt.Sprintf("%s = %s\n", k, toTomlValue(node, 0))
panic(fmt.Sprintf("unsupported node type: %v", node))
}
}
return result
@@ -263,7 +321,7 @@ func (t *TomlTree) toToml(keyspace 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) ToString() string {
return t.toToml("")
return t.toToml("", "")
}
// Create a TomlTree from a string.
+5 -3
View File
@@ -1,3 +1,5 @@
// Testing support for go-toml
package toml
import (
@@ -27,16 +29,16 @@ func TestTomlHasPath(t *testing.T) {
}
func TestTomlGetPath(t *testing.T) {
node := make(TomlTree)
node := newTomlTree()
//TODO: set other node data
for idx, item := range []struct {
Path []string
Expected interface{}
Expected *TomlTree
}{
{ // empty path test
[]string{},
&node,
node,
},
} {
result := node.GetPath(item.Path)