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:
+9
-3
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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, ""},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user