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) os.Exit(1)
} }
typedTree := translate((map[string]interface{})(*tree)) typedTree := translate(*tree)
if err := json.NewEncoder(os.Stdout).Encode(typedTree); err != nil { if err := json.NewEncoder(os.Stdout).Encode(typedTree); err != nil {
log.Fatalf("Error encoding JSON: %s", err) log.Fatalf("Error encoding JSON: %s", err)
@@ -30,7 +30,6 @@ func main() {
} }
func translate(tomlData interface{}) interface{} { func translate(tomlData interface{}) interface{} {
switch orig := tomlData.(type) { switch orig := tomlData.(type) {
case map[string]interface{}: case map[string]interface{}:
typed := make(map[string]interface{}, len(orig)) typed := make(map[string]interface{}, len(orig))
@@ -39,7 +38,14 @@ func translate(tomlData interface{}) interface{} {
} }
return typed return typed
case *toml.TomlTree: 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: case []*toml.TomlTree:
typed := make([]map[string]interface{}, len(orig)) typed := make([]map[string]interface{}, len(orig))
for i, v := range orig { for i, v := range orig {
+48 -16
View File
@@ -45,11 +45,39 @@ const (
tokenEOL tokenEOL
) )
var tokenTypeNames []string = []string{
"EOF",
"Comment",
"Key",
"=",
"\"",
"Integer",
"True",
"False",
"Float",
"[",
"[",
"]]",
"[[",
"Date",
"KeyGroup",
"KeyGroupArray",
",",
"EOL",
}
type token struct { type token struct {
typ tokenType Position
val string typ tokenType
line int val string
col int }
func (tt tokenType) String() string {
idx := int(tt)
if idx < len(tokenTypeNames) {
return tokenTypeNames[idx]
}
return "Unknown"
} }
func (i token) String() string { func (i token) String() string {
@@ -66,10 +94,6 @@ func (i token) String() string {
return fmt.Sprintf("%q", i.val) 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 { func isSpace(r rune) bool {
return r == ' ' || r == '\t' return r == ' ' || r == '\t'
} }
@@ -119,24 +143,31 @@ func (l *lexer) nextStart() {
r, width := utf8.DecodeRuneInString(l.input[i:]) r, width := utf8.DecodeRuneInString(l.input[i:])
if r == '\n' { if r == '\n' {
l.line += 1 l.line += 1
l.col = 0 l.col = 1
} else { } else {
l.col += 1 l.col += 1
} }
i += width i += width
// fmt.Printf("'%c'\n", r)
} }
// advance start position to next token // advance start position to next token
l.start = l.pos l.start = l.pos
} }
func (l *lexer) emit(t tokenType) { 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() l.nextStart()
} }
func (l *lexer) emitWithValue(t tokenType, value string) { 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() l.nextStart()
} }
@@ -161,10 +192,9 @@ func (l *lexer) backup() {
func (l *lexer) errorf(format string, args ...interface{}) stateFn { func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.tokens <- token{ l.tokens <- token{
tokenError, Position: Position{l.line, l.col},
fmt.Sprintf(format, args...), typ: tokenError,
l.line, val: fmt.Sprintf(format, args...),
l.col,
} }
return nil return nil
} }
@@ -534,6 +564,8 @@ func lex(input string) (*lexer, chan token) {
l := &lexer{ l := &lexer{
input: input, input: input,
tokens: make(chan token), tokens: make(chan token),
line: 1,
col: 1,
} }
go l.run() go l.run()
return l, l.tokens 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("compared", token, "to", expected)
t.Log(token.val, "<->", expected.val) t.Log(token.val, "<->", expected.val)
t.Log(token.typ, "<->", expected.typ) t.Log(token.typ, "<->", expected.typ)
t.Log(token.line, "<->", expected.line) t.Log(token.Line, "<->", expected.Line)
t.Log(token.col, "<->", expected.col) t.Log(token.Col, "<->", expected.Col)
t.FailNow() t.FailNow()
} }
} }
@@ -32,245 +32,245 @@ func testFlow(t *testing.T, input string, expectedFlow []token) {
func TestValidKeyGroup(t *testing.T) { func TestValidKeyGroup(t *testing.T) {
testFlow(t, "[hello world]", []token{ testFlow(t, "[hello world]", []token{
token{tokenLeftBracket, "[", 0, 0}, token{Position{1, 1}, tokenLeftBracket, "["},
token{tokenKeyGroup, "hello world", 0, 1}, token{Position{1, 2}, tokenKeyGroup, "hello world"},
token{tokenRightBracket, "]", 0, 12}, token{Position{1, 13}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 13}, token{Position{1, 14}, tokenEOF, ""},
}) })
} }
func TestUnclosedKeyGroup(t *testing.T) { func TestUnclosedKeyGroup(t *testing.T) {
testFlow(t, "[hello world", []token{ testFlow(t, "[hello world", []token{
token{tokenLeftBracket, "[", 0, 0}, token{Position{1, 1}, tokenLeftBracket, "["},
token{tokenError, "unclosed key group", 0, 1}, token{Position{1, 2}, tokenError, "unclosed key group"},
}) })
} }
func TestComment(t *testing.T) { func TestComment(t *testing.T) {
testFlow(t, "# blahblah", []token{ testFlow(t, "# blahblah", []token{
token{tokenEOF, "", 0, 10}, token{Position{1, 11}, tokenEOF, ""},
}) })
} }
func TestKeyGroupComment(t *testing.T) { func TestKeyGroupComment(t *testing.T) {
testFlow(t, "[hello world] # blahblah", []token{ testFlow(t, "[hello world] # blahblah", []token{
token{tokenLeftBracket, "[", 0, 0}, token{Position{1, 1}, tokenLeftBracket, "["},
token{tokenKeyGroup, "hello world", 0, 1}, token{Position{1, 2}, tokenKeyGroup, "hello world"},
token{tokenRightBracket, "]", 0, 12}, token{Position{1, 13}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 24}, token{Position{1, 25}, tokenEOF, ""},
}) })
} }
func TestMultipleKeyGroupsComment(t *testing.T) { func TestMultipleKeyGroupsComment(t *testing.T) {
testFlow(t, "[hello world] # blahblah\n[test]", []token{ testFlow(t, "[hello world] # blahblah\n[test]", []token{
token{tokenLeftBracket, "[", 0, 0}, token{Position{1, 1}, tokenLeftBracket, "["},
token{tokenKeyGroup, "hello world", 0, 1}, token{Position{1, 2}, tokenKeyGroup, "hello world"},
token{tokenRightBracket, "]", 0, 12}, token{Position{1, 13}, tokenRightBracket, "]"},
token{tokenLeftBracket, "[", 1, 0}, token{Position{2, 1}, tokenLeftBracket, "["},
token{tokenKeyGroup, "test", 1, 1}, token{Position{2, 2}, tokenKeyGroup, "test"},
token{tokenRightBracket, "]", 1, 5}, token{Position{2, 6}, tokenRightBracket, "]"},
token{tokenEOF, "", 1, 6}, token{Position{2, 7}, tokenEOF, ""},
}) })
} }
func TestBasicKey(t *testing.T) { func TestBasicKey(t *testing.T) {
testFlow(t, "hello", []token{ testFlow(t, "hello", []token{
token{tokenKey, "hello", 0, 0}, token{Position{1, 1}, tokenKey, "hello"},
token{tokenEOF, "", 0, 5}, token{Position{1, 6}, tokenEOF, ""},
}) })
} }
func TestBasicKeyWithUnderscore(t *testing.T) { func TestBasicKeyWithUnderscore(t *testing.T) {
testFlow(t, "hello_hello", []token{ testFlow(t, "hello_hello", []token{
token{tokenKey, "hello_hello", 0, 0}, token{Position{1, 1}, tokenKey, "hello_hello"},
token{tokenEOF, "", 0, 11}, token{Position{1, 12}, tokenEOF, ""},
}) })
} }
func TestBasicKeyWithDash(t *testing.T) { func TestBasicKeyWithDash(t *testing.T) {
testFlow(t, "hello-world", []token{ testFlow(t, "hello-world", []token{
token{tokenKey, "hello-world", 0, 0}, token{Position{1, 1}, tokenKey, "hello-world"},
token{tokenEOF, "", 0, 11}, token{Position{1, 12}, tokenEOF, ""},
}) })
} }
func TestBasicKeyWithUppercaseMix(t *testing.T) { func TestBasicKeyWithUppercaseMix(t *testing.T) {
testFlow(t, "helloHELLOHello", []token{ testFlow(t, "helloHELLOHello", []token{
token{tokenKey, "helloHELLOHello", 0, 0}, token{Position{1, 1}, tokenKey, "helloHELLOHello"},
token{tokenEOF, "", 0, 15}, token{Position{1, 16}, tokenEOF, ""},
}) })
} }
func TestBasicKeyWithInternationalCharacters(t *testing.T) { func TestBasicKeyWithInternationalCharacters(t *testing.T) {
testFlow(t, "héllÖ", []token{ testFlow(t, "héllÖ", []token{
token{tokenKey, "héllÖ", 0, 0}, token{Position{1, 1}, tokenKey, "héllÖ"},
token{tokenEOF, "", 0, 5}, token{Position{1, 6}, tokenEOF, ""},
}) })
} }
func TestBasicKeyAndEqual(t *testing.T) { func TestBasicKeyAndEqual(t *testing.T) {
testFlow(t, "hello =", []token{ testFlow(t, "hello =", []token{
token{tokenKey, "hello", 0, 0}, token{Position{1, 1}, tokenKey, "hello"},
token{tokenEqual, "=", 0, 6}, token{Position{1, 7}, tokenEqual, "="},
token{tokenEOF, "", 0, 7}, token{Position{1, 8}, tokenEOF, ""},
}) })
} }
func TestKeyWithSharpAndEqual(t *testing.T) { func TestKeyWithSharpAndEqual(t *testing.T) {
testFlow(t, "key#name = 5", []token{ testFlow(t, "key#name = 5", []token{
token{tokenKey, "key#name", 0, 0}, token{Position{1, 1}, tokenKey, "key#name"},
token{tokenEqual, "=", 0, 9}, token{Position{1, 10}, tokenEqual, "="},
token{tokenInteger, "5", 0, 11}, token{Position{1, 12}, tokenInteger, "5"},
token{tokenEOF, "", 0, 12}, token{Position{1, 13}, tokenEOF, ""},
}) })
} }
func TestKeyWithSymbolsAndEqual(t *testing.T) { func TestKeyWithSymbolsAndEqual(t *testing.T) {
testFlow(t, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{ testFlow(t, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:' = 5", []token{
token{tokenKey, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'", 0, 0}, token{Position{1, 1}, tokenKey, "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'"},
token{tokenEqual, "=", 0, 38}, token{Position{1, 39}, tokenEqual, "="},
token{tokenInteger, "5", 0, 40}, token{Position{1, 41}, tokenInteger, "5"},
token{tokenEOF, "", 0, 41}, token{Position{1, 42}, tokenEOF, ""},
}) })
} }
func TestKeyEqualStringEscape(t *testing.T) { func TestKeyEqualStringEscape(t *testing.T) {
testFlow(t, `foo = "hello\""`, []token{ testFlow(t, `foo = "hello\""`, []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenString, "hello\"", 0, 7}, token{Position{1, 8}, tokenString, "hello\""},
token{tokenEOF, "", 0, 15}, token{Position{1, 16}, tokenEOF, ""},
}) })
} }
func TestKeyEqualStringUnfinished(t *testing.T) { func TestKeyEqualStringUnfinished(t *testing.T) {
testFlow(t, `foo = "bar`, []token{ testFlow(t, `foo = "bar`, []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenError, "unclosed string", 0, 7}, token{Position{1, 8}, tokenError, "unclosed string"},
}) })
} }
func TestKeyEqualString(t *testing.T) { func TestKeyEqualString(t *testing.T) {
testFlow(t, `foo = "bar"`, []token{ testFlow(t, `foo = "bar"`, []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenString, "bar", 0, 7}, token{Position{1, 8}, tokenString, "bar"},
token{tokenEOF, "", 0, 11}, token{Position{1, 12}, tokenEOF, ""},
}) })
} }
func TestKeyEqualTrue(t *testing.T) { func TestKeyEqualTrue(t *testing.T) {
testFlow(t, "foo = true", []token{ testFlow(t, "foo = true", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenTrue, "true", 0, 6}, token{Position{1, 7}, tokenTrue, "true"},
token{tokenEOF, "", 0, 10}, token{Position{1, 11}, tokenEOF, ""},
}) })
} }
func TestKeyEqualFalse(t *testing.T) { func TestKeyEqualFalse(t *testing.T) {
testFlow(t, "foo = false", []token{ testFlow(t, "foo = false", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenFalse, "false", 0, 6}, token{Position{1, 7}, tokenFalse, "false"},
token{tokenEOF, "", 0, 11}, token{Position{1, 12}, tokenEOF, ""},
}) })
} }
func TestArrayNestedString(t *testing.T) { func TestArrayNestedString(t *testing.T) {
testFlow(t, `a = [ ["hello", "world"] ]`, []token{ testFlow(t, `a = [ ["hello", "world"] ]`, []token{
token{tokenKey, "a", 0, 0}, token{Position{1, 1}, tokenKey, "a"},
token{tokenEqual, "=", 0, 2}, token{Position{1, 3}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 4}, token{Position{1, 5}, tokenLeftBracket, "["},
token{tokenLeftBracket, "[", 0, 6}, token{Position{1, 7}, tokenLeftBracket, "["},
token{tokenString, "hello", 0, 8}, token{Position{1, 9}, tokenString, "hello"},
token{tokenComma, ",", 0, 14}, token{Position{1, 15}, tokenComma, ","},
token{tokenString, "world", 0, 17}, token{Position{1, 18}, tokenString, "world"},
token{tokenRightBracket, "]", 0, 23}, token{Position{1, 24}, tokenRightBracket, "]"},
token{tokenRightBracket, "]", 0, 25}, token{Position{1, 26}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 26}, token{Position{1, 27}, tokenEOF, ""},
}) })
} }
func TestArrayNestedInts(t *testing.T) { func TestArrayNestedInts(t *testing.T) {
testFlow(t, "a = [ [42, 21], [10] ]", []token{ testFlow(t, "a = [ [42, 21], [10] ]", []token{
token{tokenKey, "a", 0, 0}, token{Position{1, 1}, tokenKey, "a"},
token{tokenEqual, "=", 0, 2}, token{Position{1, 3}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 4}, token{Position{1, 5}, tokenLeftBracket, "["},
token{tokenLeftBracket, "[", 0, 6}, token{Position{1, 7}, tokenLeftBracket, "["},
token{tokenInteger, "42", 0, 7}, token{Position{1, 8}, tokenInteger, "42"},
token{tokenComma, ",", 0, 9}, token{Position{1, 10}, tokenComma, ","},
token{tokenInteger, "21", 0, 11}, token{Position{1, 12}, tokenInteger, "21"},
token{tokenRightBracket, "]", 0, 13}, token{Position{1, 14}, tokenRightBracket, "]"},
token{tokenComma, ",", 0, 14}, token{Position{1, 15}, tokenComma, ","},
token{tokenLeftBracket, "[", 0, 16}, token{Position{1, 17}, tokenLeftBracket, "["},
token{tokenInteger, "10", 0, 17}, token{Position{1, 18}, tokenInteger, "10"},
token{tokenRightBracket, "]", 0, 19}, token{Position{1, 20}, tokenRightBracket, "]"},
token{tokenRightBracket, "]", 0, 21}, token{Position{1, 22}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 22}, token{Position{1, 23}, tokenEOF, ""},
}) })
} }
func TestArrayInts(t *testing.T) { func TestArrayInts(t *testing.T) {
testFlow(t, "a = [ 42, 21, 10, ]", []token{ testFlow(t, "a = [ 42, 21, 10, ]", []token{
token{tokenKey, "a", 0, 0}, token{Position{1, 1}, tokenKey, "a"},
token{tokenEqual, "=", 0, 2}, token{Position{1, 3}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 4}, token{Position{1, 5}, tokenLeftBracket, "["},
token{tokenInteger, "42", 0, 6}, token{Position{1, 7}, tokenInteger, "42"},
token{tokenComma, ",", 0, 8}, token{Position{1, 9}, tokenComma, ","},
token{tokenInteger, "21", 0, 10}, token{Position{1, 11}, tokenInteger, "21"},
token{tokenComma, ",", 0, 12}, token{Position{1, 13}, tokenComma, ","},
token{tokenInteger, "10", 0, 14}, token{Position{1, 15}, tokenInteger, "10"},
token{tokenComma, ",", 0, 16}, token{Position{1, 17}, tokenComma, ","},
token{tokenRightBracket, "]", 0, 18}, token{Position{1, 19}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 19}, token{Position{1, 20}, tokenEOF, ""},
}) })
} }
func TestMultilineArrayComments(t *testing.T) { func TestMultilineArrayComments(t *testing.T) {
testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{ testFlow(t, "a = [1, # wow\n2, # such items\n3, # so array\n]", []token{
token{tokenKey, "a", 0, 0}, token{Position{1, 1}, tokenKey, "a"},
token{tokenEqual, "=", 0, 2}, token{Position{1, 3}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 4}, token{Position{1, 5}, tokenLeftBracket, "["},
token{tokenInteger, "1", 0, 5}, token{Position{1, 6}, tokenInteger, "1"},
token{tokenComma, ",", 0, 6}, token{Position{1, 7}, tokenComma, ","},
token{tokenInteger, "2", 1, 0}, token{Position{2, 1}, tokenInteger, "2"},
token{tokenComma, ",", 1, 1}, token{Position{2, 2}, tokenComma, ","},
token{tokenInteger, "3", 2, 0}, token{Position{3, 1}, tokenInteger, "3"},
token{tokenComma, ",", 2, 1}, token{Position{3, 2}, tokenComma, ","},
token{tokenRightBracket, "]", 3, 0}, token{Position{4, 1}, tokenRightBracket, "]"},
token{tokenEOF, "", 3, 1}, token{Position{4, 2}, tokenEOF, ""},
}) })
} }
func TestKeyEqualArrayBools(t *testing.T) { func TestKeyEqualArrayBools(t *testing.T) {
testFlow(t, "foo = [true, false, true]", []token{ testFlow(t, "foo = [true, false, true]", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 6}, token{Position{1, 7}, tokenLeftBracket, "["},
token{tokenTrue, "true", 0, 7}, token{Position{1, 8}, tokenTrue, "true"},
token{tokenComma, ",", 0, 11}, token{Position{1, 12}, tokenComma, ","},
token{tokenFalse, "false", 0, 13}, token{Position{1, 14}, tokenFalse, "false"},
token{tokenComma, ",", 0, 18}, token{Position{1, 19}, tokenComma, ","},
token{tokenTrue, "true", 0, 20}, token{Position{1, 21}, tokenTrue, "true"},
token{tokenRightBracket, "]", 0, 24}, token{Position{1, 25}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 25}, token{Position{1, 26}, tokenEOF, ""},
}) })
} }
func TestKeyEqualArrayBoolsWithComments(t *testing.T) { func TestKeyEqualArrayBoolsWithComments(t *testing.T) {
testFlow(t, "foo = [true, false, true] # YEAH", []token{ testFlow(t, "foo = [true, false, true] # YEAH", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 6}, token{Position{1, 7}, tokenLeftBracket, "["},
token{tokenTrue, "true", 0, 7}, token{Position{1, 8}, tokenTrue, "true"},
token{tokenComma, ",", 0, 11}, token{Position{1, 12}, tokenComma, ","},
token{tokenFalse, "false", 0, 13}, token{Position{1, 14}, tokenFalse, "false"},
token{tokenComma, ",", 0, 18}, token{Position{1, 19}, tokenComma, ","},
token{tokenTrue, "true", 0, 20}, token{Position{1, 21}, tokenTrue, "true"},
token{tokenRightBracket, "]", 0, 24}, token{Position{1, 25}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 32}, token{Position{1, 33}, tokenEOF, ""},
}) })
} }
@@ -282,138 +282,138 @@ func TestDateRegexp(t *testing.T) {
func TestKeyEqualDate(t *testing.T) { func TestKeyEqualDate(t *testing.T) {
testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{ testFlow(t, "foo = 1979-05-27T07:32:00Z", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenDate, "1979-05-27T07:32:00Z", 0, 6}, token{Position{1, 7}, tokenDate, "1979-05-27T07:32:00Z"},
token{tokenEOF, "", 0, 26}, token{Position{1, 27}, tokenEOF, ""},
}) })
} }
func TestFloatEndingWithDot(t *testing.T) { func TestFloatEndingWithDot(t *testing.T) {
testFlow(t, "foo = 42.", []token{ testFlow(t, "foo = 42.", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenError, "float cannot end with a dot", 0, 6}, token{Position{1, 7}, tokenError, "float cannot end with a dot"},
}) })
} }
func TestFloatWithTwoDots(t *testing.T) { func TestFloatWithTwoDots(t *testing.T) {
testFlow(t, "foo = 4.2.", []token{ testFlow(t, "foo = 4.2.", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenError, "cannot have two dots in one float", 0, 6}, token{Position{1, 7}, tokenError, "cannot have two dots in one float"},
}) })
} }
func TestDoubleEqualKey(t *testing.T) { func TestDoubleEqualKey(t *testing.T) {
testFlow(t, "foo= = 2", []token{ testFlow(t, "foo= = 2", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 3}, token{Position{1, 4}, tokenEqual, "="},
token{tokenError, "cannot have multiple equals for the same key", 0, 4}, token{Position{1, 5}, tokenError, "cannot have multiple equals for the same key"},
}) })
} }
func TestInvalidEsquapeSequence(t *testing.T) { func TestInvalidEsquapeSequence(t *testing.T) {
testFlow(t, `foo = "\x"`, []token{ testFlow(t, `foo = "\x"`, []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenError, "invalid escape sequence: \\x", 0, 7}, token{Position{1, 8}, tokenError, "invalid escape sequence: \\x"},
}) })
} }
func TestNestedArrays(t *testing.T) { func TestNestedArrays(t *testing.T) {
testFlow(t, "foo = [[[]]]", []token{ testFlow(t, "foo = [[[]]]", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenLeftBracket, "[", 0, 6}, token{Position{1, 7}, tokenLeftBracket, "["},
token{tokenLeftBracket, "[", 0, 7}, token{Position{1, 8}, tokenLeftBracket, "["},
token{tokenLeftBracket, "[", 0, 8}, token{Position{1, 9}, tokenLeftBracket, "["},
token{tokenRightBracket, "]", 0, 9}, token{Position{1, 10}, tokenRightBracket, "]"},
token{tokenRightBracket, "]", 0, 10}, token{Position{1, 11}, tokenRightBracket, "]"},
token{tokenRightBracket, "]", 0, 11}, token{Position{1, 12}, tokenRightBracket, "]"},
token{tokenEOF, "", 0, 12}, token{Position{1, 13}, tokenEOF, ""},
}) })
} }
func TestKeyEqualNumber(t *testing.T) { func TestKeyEqualNumber(t *testing.T) {
testFlow(t, "foo = 42", []token{ testFlow(t, "foo = 42", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenInteger, "42", 0, 6}, token{Position{1, 7}, tokenInteger, "42"},
token{tokenEOF, "", 0, 8}, token{Position{1, 9}, tokenEOF, ""},
}) })
testFlow(t, "foo = +42", []token{ testFlow(t, "foo = +42", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenInteger, "+42", 0, 6}, token{Position{1, 7}, tokenInteger, "+42"},
token{tokenEOF, "", 0, 9}, token{Position{1, 10}, tokenEOF, ""},
}) })
testFlow(t, "foo = -42", []token{ testFlow(t, "foo = -42", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenInteger, "-42", 0, 6}, token{Position{1, 7}, tokenInteger, "-42"},
token{tokenEOF, "", 0, 9}, token{Position{1, 10}, tokenEOF, ""},
}) })
testFlow(t, "foo = 4.2", []token{ testFlow(t, "foo = 4.2", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenFloat, "4.2", 0, 6}, token{Position{1, 7}, tokenFloat, "4.2"},
token{tokenEOF, "", 0, 9}, token{Position{1, 10}, tokenEOF, ""},
}) })
testFlow(t, "foo = +4.2", []token{ testFlow(t, "foo = +4.2", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenFloat, "+4.2", 0, 6}, token{Position{1, 7}, tokenFloat, "+4.2"},
token{tokenEOF, "", 0, 10}, token{Position{1, 11}, tokenEOF, ""},
}) })
testFlow(t, "foo = -4.2", []token{ testFlow(t, "foo = -4.2", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenFloat, "-4.2", 0, 6}, token{Position{1, 7}, tokenFloat, "-4.2"},
token{tokenEOF, "", 0, 10}, token{Position{1, 11}, tokenEOF, ""},
}) })
} }
func TestMultiline(t *testing.T) { func TestMultiline(t *testing.T) {
testFlow(t, "foo = 42\nbar=21", []token{ testFlow(t, "foo = 42\nbar=21", []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenInteger, "42", 0, 6}, token{Position{1, 7}, tokenInteger, "42"},
token{tokenKey, "bar", 1, 0}, token{Position{2, 1}, tokenKey, "bar"},
token{tokenEqual, "=", 1, 3}, token{Position{2, 4}, tokenEqual, "="},
token{tokenInteger, "21", 1, 4}, token{Position{2, 5}, tokenInteger, "21"},
token{tokenEOF, "", 1, 6}, token{Position{2, 7}, tokenEOF, ""},
}) })
} }
func TestKeyEqualStringUnicodeEscape(t *testing.T) { func TestKeyEqualStringUnicodeEscape(t *testing.T) {
testFlow(t, `foo = "hello \u2665"`, []token{ testFlow(t, `foo = "hello \u2665"`, []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenString, "hello ♥", 0, 7}, token{Position{1, 8}, tokenString, "hello ♥"},
token{tokenEOF, "", 0, 20}, token{Position{1, 21}, tokenEOF, ""},
}) })
} }
func TestUnicodeString(t *testing.T) { func TestUnicodeString(t *testing.T) {
testFlow(t, `foo = "hello ♥ world"`, []token{ testFlow(t, `foo = "hello ♥ world"`, []token{
token{tokenKey, "foo", 0, 0}, token{Position{1, 1}, tokenKey, "foo"},
token{tokenEqual, "=", 0, 4}, token{Position{1, 5}, tokenEqual, "="},
token{tokenString, "hello ♥ world", 0, 7}, token{Position{1, 8}, tokenString, "hello ♥ world"},
token{tokenEOF, "", 0, 21}, token{Position{1, 22}, tokenEOF, ""},
}) })
} }
func TestKeyGroupArray(t *testing.T) { func TestKeyGroupArray(t *testing.T) {
testFlow(t, "[[foo]]", []token{ testFlow(t, "[[foo]]", []token{
token{tokenDoubleLeftBracket, "[[", 0, 0}, token{Position{1, 1}, tokenDoubleLeftBracket, "[["},
token{tokenKeyGroupArray, "foo", 0, 2}, token{Position{1, 3}, tokenKeyGroupArray, "foo"},
token{tokenDoubleRightBracket, "]]", 0, 5}, token{Position{1, 6}, tokenDoubleRightBracket, "]]"},
token{tokenEOF, "", 0, 7}, token{Position{1, 8}, tokenEOF, ""},
}) })
} }
+46 -19
View File
@@ -22,7 +22,7 @@ type parserStateFn func(*parser) parserStateFn
// Formats and panics an error message based on a token // Formats and panics an error message based on a token
func (p *parser) raiseError(tok *token, msg string, args ...interface{}) { 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() { func (p *parser) run() {
@@ -47,10 +47,10 @@ func (p *parser) peek() *token {
func (p *parser) assume(typ tokenType) { func (p *parser) assume(typ tokenType) {
tok := p.getToken() tok := p.getToken()
if tok == nil { 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 { 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 { func parseStart(p *parser) parserStateFn {
tok := p.peek() tok := p.peek()
// prime position data with root tree instance
p.tree.position = tok.Position
// end of stream, parsing is finished // end of stream, parsing is finished
if tok == nil { if tok == nil {
return nil return nil
@@ -91,15 +94,16 @@ func parseStart(p *parser) parserStateFn {
} }
func parseGroupArray(p *parser) parserStateFn { func parseGroupArray(p *parser) parserStateFn {
p.getToken() // discard the [[ start_token := p.getToken() // discard the [[
key := p.getToken() key := p.getToken()
if key.typ != tokenKeyGroupArray { if key.typ != tokenKeyGroupArray {
p.raiseError(key, "unexpected token %s, was expecting a key group array", key) p.raiseError(key, "unexpected token %s, was expecting a key group array", key)
} }
// get or create group array element at the indicated part in the path // get or create group array element at the indicated part in the path
p.currentGroup = strings.Split(key.val, ".") keys := strings.Split(key.val, ".")
dest_tree := p.tree.GetPath(p.currentGroup) p.tree.createSubTree(keys[:len(keys)-1]) // create parent entries
dest_tree := p.tree.GetPath(keys)
var array []*TomlTree var array []*TomlTree
if dest_tree == nil { if dest_tree == nil {
array = make([]*TomlTree, 0) array = make([]*TomlTree, 0)
@@ -108,14 +112,31 @@ func parseGroupArray(p *parser) parserStateFn {
} else { } else {
p.raiseError(key, "key %s is already assigned and not of type group array", key) p.raiseError(key, "key %s is already assigned and not of type group array", key)
} }
p.currentGroup = keys
// add a new tree to the end of the group array // add a new tree to the end of the group array
new_tree := make(TomlTree) new_tree := newTomlTree()
array = append(array, &new_tree) new_tree.position = start_token.Position
array = append(array, new_tree)
p.tree.SetPath(p.currentGroup, array) 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 // 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 // move to next parser state
p.assume(tokenDoubleRightBracket) p.assume(tokenDoubleRightBracket)
@@ -123,7 +144,7 @@ func parseGroupArray(p *parser) parserStateFn {
} }
func parseGroup(p *parser) parserStateFn { func parseGroup(p *parser) parserStateFn {
p.getToken() // discard the [ start_token := p.getToken() // discard the [
key := p.getToken() key := p.getToken()
if key.typ != tokenKeyGroup { if key.typ != tokenKeyGroup {
p.raiseError(key, "unexpected token %s, was expecting a key group", key) p.raiseError(key, "unexpected token %s, was expecting a key group", key)
@@ -133,12 +154,16 @@ func parseGroup(p *parser) parserStateFn {
p.raiseError(key, "duplicated tables") p.raiseError(key, "duplicated tables")
} }
} }
p.seenGroupKeys = append(p.seenGroupKeys, key.val) 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.raiseError(key, "%s", err)
} }
p.assume(tokenRightBracket) 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) return parseStart(p)
} }
@@ -150,7 +175,7 @@ func parseAssign(p *parser) parserStateFn {
if len(p.currentGroup) > 0 { if len(p.currentGroup) > 0 {
group_key = p.currentGroup group_key = p.currentGroup
} else { } else {
group_key = make([]string, 0) group_key = []string{}
} }
// find the group to assign, looking out for arrays of groups // find the group to assign, looking out for arrays of groups
@@ -161,16 +186,18 @@ func parseAssign(p *parser) parserStateFn {
case *TomlTree: case *TomlTree:
target_node = node target_node = node
default: 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 // assign value to the found group
local_key := []string{key.val} local_key := []string{key.val}
final_key := append(group_key, key.val) final_key := append(group_key, key.val)
if target_node.GetPath(local_key) != nil { 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) return parseStart(p)
} }
@@ -251,14 +278,14 @@ func parseArray(p *parser) []interface{} {
} }
func parse(flow chan token) *TomlTree { func parse(flow chan token) *TomlTree {
result := make(TomlTree) result := newTomlTree()
parser := &parser{ parser := &parser{
flow: flow, flow: flow,
tree: &result, tree: result,
tokensBuffer: make([]token, 0), tokensBuffer: make([]token, 0),
currentGroup: make([]string, 0), currentGroup: make([]string, 0),
seenGroupKeys: make([]string, 0), seenGroupKeys: make([]string, 0),
} }
parser.run() parser.run()
return parser.tree return result
} }
+116 -30
View File
@@ -12,14 +12,15 @@ func assertTree(t *testing.T, tree *TomlTree, err error, ref map[string]interfac
return return
} }
for k, v := range ref { for k, v := range ref {
node := tree.Get(k) // NOTE: directly access key instead of resolve by path
switch cast_node := node.(type) { // NOTE: see TestSpecialKV
switch node := tree.GetPath([]string{k}).(type) {
case []*TomlTree: case []*TomlTree:
for idx, item := range cast_node { for idx, item := range node {
assertTree(t, item, err, v.([]map[string]interface{})[idx]) assertTree(t, item, err, v.([]map[string]interface{})[idx])
} }
case *TomlTree: case *TomlTree:
assertTree(t, cast_node, err, v.(map[string]interface{})) assertTree(t, node, err, v.(map[string]interface{}))
default: default:
if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) { if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", v) {
t.Errorf("was expecting %v at %v but got %v", v, k, node) 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) { func TestCreateSubTree(t *testing.T) {
tree := make(TomlTree) tree := newTomlTree()
tree.createSubTree("a.b.c") tree.createSubTree([]string{"a", "b", "c"})
tree.Set("a.b.c", 42) tree.Set("a.b.c", 42)
if tree.Get("a.b.c") != 42 { if tree.Get("a.b.c") != 42 {
t.Fail() 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) { func TestSimpleNumbers(t *testing.T) {
tree, err := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1") tree, err := Load("a = +42\nb = -21\nc = +4.2\nd = -2.1")
assertTree(t, tree, err, map[string]interface{}{ assertTree(t, tree, err, map[string]interface{}{
@@ -107,7 +117,13 @@ func TestBools(t *testing.T) {
func TestNestedKeys(t *testing.T) { func TestNestedKeys(t *testing.T) {
tree, err := Load("[a.b.c]\nd = 42") tree, err := Load("[a.b.c]\nd = 42")
assertTree(t, tree, err, map[string]interface{}{ 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) { func TestDuplicateKeys(t *testing.T) {
_, err := Load("foo = 2\nfoo = 3") _, 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()) t.Error("Bad error message:", err.Error())
} }
} }
@@ -270,20 +286,35 @@ func TestParseFile(t *testing.T) {
tree, err := LoadFile("example.toml") tree, err := LoadFile("example.toml")
assertTree(t, tree, err, map[string]interface{}{ assertTree(t, tree, err, map[string]interface{}{
"title": "TOML Example", "title": "TOML Example",
"owner.name": "Tom Preston-Werner", "owner": map[string]interface{}{
"owner.organization": "GitHub", "name": "Tom Preston-Werner",
"owner.bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "organization": "GitHub",
"owner.dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
"database.server": "192.168.1.1", "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
"database.ports": []int64{8001, 8001, 8002}, },
"database.connection_max": 5000, "database": map[string]interface{}{
"database.enabled": true, "server": "192.168.1.1",
"servers.alpha.ip": "10.0.0.1", "ports": []int64{8001, 8001, 8002},
"servers.alpha.dc": "eqdc10", "connection_max": 5000,
"servers.beta.ip": "10.0.0.2", "enabled": true,
"servers.beta.dc": "eqdc10", },
"clients.data": []interface{}{[]string{"gamma", "delta"}, []int64{1, 2}}, "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 TestToTomlValue(t *testing.T) {
} }
func TestToString(t *testing.T) { func TestToString(t *testing.T) {
tree := &TomlTree{ tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n")
"foo": &TomlTree{ if err != nil {
"bar": []*TomlTree{ t.Errorf("Test failed to parse: %v", err)
{"a": int64(42)}, return
{"a": int64(69)},
},
},
} }
result := tree.ToString() 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 { if result != expected {
t.Errorf("Expected got '%s', expected '%s'", 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")
}
}
+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" "time"
) )
type tomlValue struct {
value interface{}
position Position
}
// Definition of a TomlTree. // Definition of a TomlTree.
// This is the result of the parsing of a TOML file. // 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. // Has returns a boolean indicating if the given key exists.
func (t *TomlTree) Has(key string) bool { 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. // Returns true if the given path of keys exists, false otherwise.
func (t *TomlTree) HasPath(keys []string) bool { func (t *TomlTree) HasPath(keys []string) bool {
if len(keys) == 0 { return t.GetPath(keys) != nil
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
} }
// Keys returns the keys of the toplevel tree. // Keys returns the keys of the toplevel tree.
// Warning: this is a costly operation. // Warning: this is a costly operation.
func (t *TomlTree) Keys() []string { func (t *TomlTree) Keys() []string {
keys := make([]string, 0) keys := make([]string, 0)
mp := (map[string]interface{})(*t) for k, _ := range t.values {
for k, _ := range mp {
keys = append(keys, k) keys = append(keys, k)
} }
return keys return keys
@@ -81,22 +75,79 @@ func (t *TomlTree) GetPath(keys []string) interface{} {
} }
subtree := t subtree := t
for _, intermediate_key := range keys[:len(keys)-1] { for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key] value, exists := subtree.values[intermediate_key]
if !exists { if !exists {
return nil return nil
} }
switch node := (*subtree)[intermediate_key].(type) { switch node := value.(type) {
case *TomlTree: case *TomlTree:
subtree = node subtree = node
case []*TomlTree: case []*TomlTree:
// go to most recent element // go to most recent element
if len(node) == 0 { if len(node) == 0 {
return nil //(*subtree)[intermediate_key] = append(node, &TomlTree{}) return nil
} }
subtree = node[len(node)-1] 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 // 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) 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{}) { func (t *TomlTree) SetPath(keys []string, value interface{}) {
subtree := t subtree := t
for _, intermediate_key := range keys[:len(keys)-1] { for _, intermediate_key := range keys[:len(keys)-1] {
_, exists := (*subtree)[intermediate_key] nextTree, exists := subtree.values[intermediate_key]
if !exists { if !exists {
var new_tree TomlTree = make(TomlTree) nextTree = newTomlTree()
(*subtree)[intermediate_key] = &new_tree subtree.values[intermediate_key] = &nextTree // add new element here
} }
switch node := (*subtree)[intermediate_key].(type) { switch node := nextTree.(type) {
case *TomlTree: case *TomlTree:
subtree = node subtree = node
case []*TomlTree: case []*TomlTree:
// go to most recent element // go to most recent element
if len(node) == 0 { 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 = 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 // 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] // and tree[a][b][c]
// //
// Returns nil on success, error object on failure // Returns nil on success, error object on failure
func (t *TomlTree) createSubTree(key string) error { func (t *TomlTree) createSubTree(keys []string) error {
subtree := t subtree := t
for _, intermediate_key := range strings.Split(key, ".") { for _, intermediate_key := range keys {
if intermediate_key == "" { if intermediate_key == "" {
return fmt.Errorf("empty intermediate table") return fmt.Errorf("empty intermediate table")
} }
_, exists := (*subtree)[intermediate_key] nextTree, exists := subtree.values[intermediate_key]
if !exists { if !exists {
var new_tree TomlTree = make(TomlTree) nextTree = newTomlTree()
(*subtree)[intermediate_key] = &new_tree subtree.values[intermediate_key] = nextTree
} }
switch node := (*subtree)[intermediate_key].(type) { switch node := nextTree.(type) {
case []*TomlTree: case []*TomlTree:
subtree = node[len(node)-1] subtree = node[len(node)-1]
case *TomlTree: case *TomlTree:
subtree = node subtree = node
default: 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 return nil
@@ -231,9 +287,9 @@ func toTomlValue(item interface{}, indent int) string {
// Recursive support function for ToString() // Recursive support function for ToString()
// Outputs a tree, using the provided keyspace to prefix group names // 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 := "" result := ""
for k, v := range (map[string]interface{})(*t) { for k, v := range t.values {
// figure out the keyspace // figure out the keyspace
combined_key := k combined_key := k
if keyspace != "" { if keyspace != "" {
@@ -244,17 +300,19 @@ func (t *TomlTree) toToml(keyspace string) string {
case []*TomlTree: case []*TomlTree:
for _, item := range node { for _, item := range node {
if len(item.Keys()) > 0 { 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: case *TomlTree:
if len(node.Keys()) > 0 { 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: default:
result += fmt.Sprintf("%s = %s\n", k, toTomlValue(node, 0)) panic(fmt.Sprintf("unsupported node type: %v", node))
} }
} }
return result return result
@@ -263,7 +321,7 @@ func (t *TomlTree) toToml(keyspace string) string {
// Generates a human-readable representation of the current tree. // Generates a human-readable representation of the current tree.
// Output spans multiple lines, and is suitable for ingest by a TOML parser // Output spans multiple lines, and is suitable for ingest by a TOML parser
func (t *TomlTree) ToString() string { func (t *TomlTree) ToString() string {
return t.toToml("") return t.toToml("", "")
} }
// Create a TomlTree from a string. // Create a TomlTree from a string.
+5 -3
View File
@@ -1,3 +1,5 @@
// Testing support for go-toml
package toml package toml
import ( import (
@@ -27,16 +29,16 @@ func TestTomlHasPath(t *testing.T) {
} }
func TestTomlGetPath(t *testing.T) { func TestTomlGetPath(t *testing.T) {
node := make(TomlTree) node := newTomlTree()
//TODO: set other node data //TODO: set other node data
for idx, item := range []struct { for idx, item := range []struct {
Path []string Path []string
Expected interface{} Expected *TomlTree
}{ }{
{ // empty path test { // empty path test
[]string{}, []string{},
&node, node,
}, },
} { } {
result := node.GetPath(item.Path) result := node.GetPath(item.Path)