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
diff --git a/cmd/test_program.go b/cmd/test_program.go index 06ec8b5..65dd8a3 100644 --- a/cmd/test_program.go +++ b/cmd/test_program.go
@@ -20,7 +20,7 @@ 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 translate(tomlData interface{}) interface{} { - switch orig := tomlData.(type) { case map[string]interface{}: typed := make(map[string]interface{}, len(orig)) @@ -39,7 +38,14 @@ } 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 {
diff --git a/lexer.go b/lexer.go index b13c87b..b6d28b4 100644 --- a/lexer.go +++ b/lexer.go
@@ -45,11 +45,39 @@ tokenEOL ) +var tokenTypeNames []string = []string{ + "EOF", + "Comment", + "Key", + "=", + "\"", + "Integer", + "True", + "False", + "Float", + "[", + "[", + "]]", + "[[", + "Date", + "KeyGroup", + "KeyGroupArray", + ",", + "EOL", +} + type token struct { - typ tokenType - val string - line int - col int + Position + typ tokenType + val string +} + +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 @@ 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 @@ 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) 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 @@ l := &lexer{ input: input, tokens: make(chan token), + line: 1, + col: 1, } go l.run() return l, l.tokens
diff --git a/lexer_test.go b/lexer_test.go index afe8d61..20483d7 100644 --- a/lexer_test.go +++ b/lexer_test.go
@@ -11,8 +11,8 @@ 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 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 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, ""}, }) }
diff --git a/parser.go b/parser.go index 17cd308..9945d39 100644 --- a/parser.go +++ b/parser.go
@@ -22,7 +22,7 @@ // 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) 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 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 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 @@ } 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 - p.seenGroupKeys = append(p.seenGroupKeys, key.val) + if !found { + p.seenGroupKeys = append(p.seenGroupKeys, key.val) + } // move to next parser state p.assume(tokenDoubleRightBracket) @@ -123,7 +144,7 @@ } 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 @@ 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 @@ 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 @@ 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 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 }
diff --git a/parser_test.go b/parser_test.go index 20b9dd8..761a181 100644 --- a/parser_test.go +++ b/parser_test.go
@@ -12,14 +12,15 @@ 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 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 @@ }) } +// 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 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 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()) } } @@ -270,20 +286,35 @@ tree, err := LoadFile("example.toml") 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}}, + "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": []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,17 +364,72 @@ } 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]]\na = 42\n\n[[foo.bar]]\na = 69\n" + 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 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") + } +}
diff --git a/position.go b/position.go new file mode 100644 index 0000000..83f0eb9 --- /dev/null +++ b/position.go
@@ -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 +}
diff --git a/position_test.go b/position_test.go new file mode 100644 index 0000000..8c4cdaf --- /dev/null +++ b/position_test.go
@@ -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) + } + } +}
diff --git a/toml.go b/toml.go index 8beb392..aa2ab6b 100644 --- a/toml.go +++ b/toml.go
@@ -14,9 +14,24 @@ "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 @@ // 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 @@ } 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 @@ 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 @@ // 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 @@ // 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 @@ 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 @@ // 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.
diff --git a/toml_test.go b/toml_test.go index 05a217e..09950b9 100644 --- a/toml_test.go +++ b/toml_test.go
@@ -1,3 +1,5 @@ +// Testing support for go-toml + package toml import ( @@ -27,16 +29,16 @@ } 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)