Merge pull request #21 from eanderton/master
Element Position Support
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..266bfcb
--- /dev/null
+++ b/position.go
@@ -0,0 +1,23 @@
+// Position support for go-toml
+
+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..4cf0ebd
--- /dev/null
+++ b/position_test.go
@@ -0,0 +1,29 @@
+// Testing support for go-toml
+
+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)