Added a good string formatter for the AST, to making testing more straight forward.
This commit is contained in:
parent
8838dc9c44
commit
15560b29b0
22
ast.go
22
ast.go
|
@ -2,6 +2,7 @@ package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -74,13 +75,18 @@ func (parseItem item) String() string {
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
|
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
|
||||||
case pTable:
|
case pTable:
|
||||||
items := make([]string, len(parseItem.Data))
|
|
||||||
pairs := parseItem.Data[0].(table)
|
pairs := parseItem.Data[0].(table)
|
||||||
|
keys := make([]string, len(pairs))
|
||||||
i := 0
|
i := 0
|
||||||
for k, v := range pairs {
|
for k := range pairs {
|
||||||
items[i] = fmt.Sprintf("%q: %s", k, v.String())
|
keys[i] = k
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
items := make([]string, len(pairs))
|
||||||
|
for i, k := range keys {
|
||||||
|
items[i] = fmt.Sprintf("%q: %s", k, pairs[k].String())
|
||||||
|
}
|
||||||
return fmt.Sprintf("{%s}", strings.Join(items, ", "))
|
return fmt.Sprintf("{%s}", strings.Join(items, ", "))
|
||||||
case pComment:
|
case pComment:
|
||||||
return fmt.Sprintf("comment(%q)", parseItem.Data[0])
|
return fmt.Sprintf("comment(%q)", parseItem.Data[0])
|
||||||
|
@ -154,15 +160,15 @@ func (t *parser) setValue(key item, value item) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *parser) newTable(key item) (table, error) {
|
func (t *parser) openTable(key item) (table, error) {
|
||||||
node := t.Root
|
node := t.Root
|
||||||
// Go over all requested levels of the key. For all levels, except the last
|
// Go over all requested levels of the key. For all levels, except the last
|
||||||
// one, it is okay if a table already exists. For at least the last level,
|
// one, it is okay if a table already exists. For at least the last level,
|
||||||
// no table or value must exist, because that would mean we are overwriting
|
// no table or value must exist, because that would mean we are overwriting
|
||||||
// an existing key/value pair, which is not allowed.
|
// an existing key/value pair, which is not allowed.
|
||||||
for i, e := range key.Data {
|
for i, value := range key.Data {
|
||||||
name := e.(string)
|
keyName := value.(string)
|
||||||
if subItem, ok := node[name]; ok {
|
if subItem, ok := node[keyName]; ok {
|
||||||
// You cannot overwrite an already defined key, regardless its value.
|
// You cannot overwrite an already defined key, regardless its value.
|
||||||
if subItem.Type != pTable {
|
if subItem.Type != pTable {
|
||||||
path := formatKeyPath(key, i)
|
path := formatKeyPath(key, i)
|
||||||
|
@ -178,7 +184,7 @@ func (t *parser) newTable(key item) (table, error) {
|
||||||
} else {
|
} else {
|
||||||
// Create the subtable.
|
// Create the subtable.
|
||||||
subTable := make(table)
|
subTable := make(table)
|
||||||
node[name] = newItem(pTable, subTable)
|
node[keyName] = newItem(pTable, subTable)
|
||||||
node = subTable
|
node = subTable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
71
ast_test.go
71
ast_test.go
|
@ -1,72 +1,69 @@
|
||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAST_ConstructStructure(t *testing.T) {
|
func TestAST_ConstructStructure(t *testing.T) {
|
||||||
p := newParser()
|
testAST(t, func() (error, *parser) {
|
||||||
p.Root["ding"] = newItem(pInteger, 10)
|
p := newParser()
|
||||||
p.Root["dong"] = newItem(pString, "not a song")
|
p.setValue(newItem(pKey, "ding"), newItem(pInteger, 10))
|
||||||
subTable1, _ := p.newTable(newItem(pKey, "key1", "key2 a"))
|
p.setValue(newItem(pKey, "dong"), newItem(pString, "not a song"))
|
||||||
subTable1["dooh"] = newItem(pBoolean, true)
|
p.openTable(newItem(pKey, "key1", "key2 a"))
|
||||||
subTable1["dah"] = newItem(pBoolean, false)
|
p.setValue(newItem(pKey, "dooh"), newItem(pBoolean, true))
|
||||||
subTable2, _ := p.newTable(newItem(pKey, "key1", "key2 b"))
|
p.setValue(newItem(pKey, "dah"), newItem(pBoolean, false))
|
||||||
subTable2["dieh"] = newItem(pFloat, 1.111)
|
p.openTable(newItem(pKey, "key1", "key2 b"))
|
||||||
subTable2["duhh"] = newItem(pFloat, 1.18e-12)
|
p.setValue(newItem(pKey, "dieh"), newItem(pFloat, 1.111))
|
||||||
|
p.setValue(newItem(pKey, "duh"), newItem(pFloat, 1.18e-12))
|
||||||
|
return nil, p
|
||||||
|
}, "", `{"ding": 10, "dong": "not a song", "key1": {"key2 a": {"dah": false, "dooh": true}, "key2 b": {"dieh": 1.111, "duh": 1.18e-12}}}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAST_StoreValueInRootTable(t *testing.T) {
|
func TestAST_StoreValueInRootTable(t *testing.T) {
|
||||||
testError(t, func() error {
|
testAST(t, func() (error, *parser) {
|
||||||
p := newParser()
|
p := newParser()
|
||||||
p.setValue(newItem(pKey, "key1"), newItem(pString, "value1"))
|
p.setValue(newItem(pKey, "key1"), newItem(pString, "value1"))
|
||||||
return p.setValue(newItem(pKey, "key2"), newItem(pString, "value2"))
|
return p.setValue(newItem(pKey, "key2"), newItem(pString, "value2")), p
|
||||||
}, "")
|
}, "", `{"key1": "value1", "key2": "value2"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAST_StoreValueWithMultipartKey_CreatesTableHierarchy(t *testing.T) {
|
func TestAST_StoreValueWithMultipartKey_CreatesTableHierarchy(t *testing.T) {
|
||||||
testError(t, func() error {
|
testAST(t, func() (error, *parser) {
|
||||||
p := newParser()
|
p := newParser()
|
||||||
return p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value"))
|
return p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value")), p
|
||||||
}, "")
|
}, "", `{"key1": {"key2": {"key3": "value"}}}`)
|
||||||
// TODO an actual test assertion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAST_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) {
|
func TestAST_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) {
|
||||||
testError(t, func() error {
|
testAST(t, func() (error, *parser) {
|
||||||
p := newParser()
|
p := newParser()
|
||||||
p.newTable(newItem(pKey, "subkey1", "subkey2"))
|
p.openTable(newItem(pKey, "tablekey1", "tablekey2"))
|
||||||
err := p.setValue(newItem(pKey, "key1", "key2", "key3"), newItem(pString, "value"))
|
return p.setValue(newItem(pKey, "valuekey1", "valuekey2", "valuekey3"), newItem(pString, "value")), p
|
||||||
fmt.Printf("%s", p.Root)
|
}, "", `{"tablekey1": {"tablekey2": {"valuekey1": {"valuekey2": {"valuekey3": "value"}}}}}`)
|
||||||
return err
|
|
||||||
}, "")
|
|
||||||
t.Fail()
|
|
||||||
// TODO an actual test assertion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAST_StoreDuplicateKeyInRootTable_ReturnsError(t *testing.T) {
|
func TestAST_StoreDuplicateKeyInRootTable_ReturnsError(t *testing.T) {
|
||||||
testError(t, func() error {
|
testAST(t, func() (error, *parser) {
|
||||||
p := newParser()
|
p := newParser()
|
||||||
p.setValue(newItem(pKey, "key"), newItem(pString, "value"))
|
p.setValue(newItem(pKey, "key"), newItem(pString, "value"))
|
||||||
return p.setValue(newItem(pKey, "key"), newItem(pInteger, 321))
|
return p.setValue(newItem(pKey, "key"), newItem(pInteger, 321)), p
|
||||||
}, `Cannot store value: string item already exists at key "key"`)
|
}, `Cannot store value: string item already exists at key "key"`, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAST_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
|
func TestAST_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
|
||||||
testError(t, func() error {
|
testAST(t, func() (error, *parser) {
|
||||||
p := newParser()
|
p := newParser()
|
||||||
p.newTable(newItem(pKey, "key1", "key2"))
|
p.openTable(newItem(pKey, "key1", "key2"))
|
||||||
_, err := p.newTable(newItem(pKey, "key1", "key2"))
|
_, err := p.openTable(newItem(pKey, "key1", "key2"))
|
||||||
return err
|
return err, p
|
||||||
}, `Cannot create table: table for key "key1"."key2" already exists`)
|
}, `Cannot create table: table for key "key1"."key2" already exists`, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAST_GivenExistingItemAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
|
func TestAST_GivenExistingItemAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) {
|
||||||
testError(t, func() error {
|
testAST(t, func() (error, *parser) {
|
||||||
p := newParser()
|
p := newParser()
|
||||||
p.Root["key"] = newItem(pString, "value")
|
p.Root["key"] = newItem(pString, "value")
|
||||||
_, err := p.newTable(newItem(pKey, "key"))
|
_, err := p.openTable(newItem(pKey, "key"))
|
||||||
return err
|
return err, p
|
||||||
}, `Cannot create table: string item already exists at key "key"`)
|
}, `Cannot create table: string item already exists at key "key"`, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,17 +54,23 @@ func testParseHandler(t *testing.T, p *parser, handler parse.Handler, test parse
|
||||||
err = parse.New(handler)(test.input)
|
err = parse.New(handler)(test.input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testError(t *testing.T, code func() error, expected string) {
|
func testAST(t *testing.T, code func() (error, *parser), expectedError string, expectedData string) {
|
||||||
err := code()
|
err, p := code()
|
||||||
if expected == "" {
|
if expectedError == "" {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %s", err)
|
t.Fatalf("Unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("An error was expected, but no error was returned")
|
t.Fatalf("An error was expected, but no error was returned")
|
||||||
} else if err.Error() != expected {
|
} else if err.Error() != expectedError {
|
||||||
t.Fatalf("Unexpected error:\nexpected: %s\nactual: %s\n", expected, err.Error())
|
t.Fatalf("Unexpected error:\nexpected: %s\nactual: %s\n", expectedError, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if expectedData == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expectedData != p.Root.String() {
|
||||||
|
t.Fatalf("Unexpected data after parsing:\nexpected: %s\nactual: %s\n", expectedData, p.Root.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,5 +107,5 @@ func (t *parser) startPlainTable(p *parse.API) {
|
||||||
}
|
}
|
||||||
key := t.Items[0]
|
key := t.Items[0]
|
||||||
t.Items = t.Items[1:]
|
t.Items = t.Items[1:]
|
||||||
t.newTable(key)
|
t.openTable(key)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue