From 54cb75955dc37d900c0a2f94aa2f953d232a5775 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 26 Jun 2019 15:38:32 +0000 Subject: [PATCH] Went over the full TOML AST implementation to give stuff good names. Also pushed all tests to ast_test to make sure that the public interface is sufficient for using the package --- ast/ast.go | 209 +++++++++++++++++++++++++++------------------ ast/ast_test.go | 180 +++++++++++++++++++------------------- ast/string.go | 40 +++++---- ast/string_test.go | 36 ++++---- 4 files changed, 257 insertions(+), 208 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 644783c..3e02459 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -1,3 +1,4 @@ +// Package ast implements an Abstract Syntax Tree that represents a TOML document. package ast import ( @@ -6,141 +7,183 @@ import ( "strings" ) -// item represents a TOML item. -type item struct { - Type valueType - Data []interface{} +// Document is a struct holds the data for a TOML Document. +// +// Methods on this struct provide functionality to construct a full data +// structure that represents a TOML document. +type Document struct { + Root Table // the root-level TOML Table (each TOML doc is implicitly a Table) + Current Table // the currently active TOML Table + CurrentKey Key // the key for the currently active TOML Table } -// table represents a TOML table. -type table map[string]*item - -// valueType identifies the type of a TOML value. -type valueType string - -const ( - tString valueType = "string" // "various", 'types', """of""", '''strings''' - tInteger valueType = "integer" // 12345, 0xffee12a0, 0o0755, 0b101101011 - tFloat valueType = "float" // 10.1234, 143E-12, 43.28377e+4, +inf, -inf, nan - tBoolean valueType = "boolean" // true or false - tOffsetDateTime valueType = "offset datetime" // 2019-06-18 10:32:15.173645362+0200 - tLocalDateTime valueType = "datetime" // 2018-12-25 12:12:18.876772533 - tLocalDate valueType = "date" // 2017-05-17 - tLocalTime valueType = "time" // 23:01:22 - tArrayOfTables valueType = "array" // defined using an [[array.of.tables]] - tArray valueType = "static array" // defined using ["an", "inline", "array"] - tTable valueType = "table" // defined using { "inline" = "table" } or [standard.table] -) - -// newItem instantiates a new item struct. -func newItem(valueType valueType, data ...interface{}) *item { - return &item{Type: valueType, Data: data} +// NewDocument instantiates a new TOML Document. +func NewDocument() *Document { + doc := &Document{Root: make(Table)} + doc.Current = doc.Root + return doc } -// newKey instantiates a new key. -func newKey(key ...string) []string { +// Table represents a TOML table: a set of key/value pairs. +type Table map[string]*Value + +// Key represents a TOML table key: one or more strings, where multiple strings +// can be used to represent nested tables. +type Key []string + +// NewKey instantiates a new Key. +func NewKey(key ...string) Key { return key } -// parser holds the state for the TOML parser. All parsing functions are -// methods of this struct. -type parser struct { - Root table // the root-level TOML table (each TOML doc is implicitly a table) - Current table // the currently active TOML table - CurrentKey []string // the key for the currently active TOML table +// Value represents a TOML value. +type Value struct { + Type ValueType + Data []interface{} } -func newParser() *parser { - p := &parser{Root: make(table)} - p.Current = p.Root - return p +// NewValue instantiates a new Value. +func NewValue(valueType ValueType, data ...interface{}) *Value { + return &Value{ + Type: valueType, + Data: data, + } } -func (t *parser) setKeyValuePair(key []string, value *item) error { +// ValueType identifies the type of a TOML value, as specified by the +// TOML specification. Because these types do not map Go types one-on-one, +// we have to keep track of the TOML type ourselves. +type ValueType string + +const ( + // TypeString identifies a string value ("various", 'types', """of""", '''strings'''). + TypeString ValueType = "string" + + // TypeInteger identifies an integer number value (12345, 0xffee12a0, 0o0755, 0b101101011). + TypeInteger ValueType = "integer" + + // TypeFloat identifies a floating point number (10.1234, 143E-12, 43.28377e+4, +inf, -inf, nan). + TypeFloat ValueType = "float" + + // TypeBoolean identifies a boolean value (true or false). + TypeBoolean ValueType = "boolean" + + // TypeOffsetDateTime identifies a date/time value, including timezone info (2019-06-18 10:32:15.173645362+0200). + TypeOffsetDateTime ValueType = "offset datetime" + + // TypeLocalDateTime identifies a date/time value, without timezone info (2018-12-25 12:12:18.876772533). + TypeLocalDateTime ValueType = "datetime" + + // TypeLocalDate identifies a date value (2017-05-17). + TypeLocalDate ValueType = "date" + + // TypeLocalTime identifies a time value (23:01:22). + TypeLocalTime ValueType = "time" + + // TypeArrayOfTables identifies an [[array.of.tables]]. + TypeArrayOfTables ValueType = "array" + + // TypeArray identifies ["an", "inline", "static", "array"]. + TypeArray ValueType = "static array" + + // TypeTable identifies an { "inline" = "table" } or [standard.table]. + TypeTable ValueType = "table" +) + +// SetKeyValuePair is used to set a key and an accompanying value in the +// currently active TOML table of the TOML Document. +func (doc *Document) SetKeyValuePair(key Key, value *Value) error { // First make sure the table structure for storing the value exists. - node, lastKeyPart, err := t.makeTablePath(key) + node, lastKeyPart, err := doc.makeTablePath(key) if err != nil { return fmt.Errorf("invalid key/value pair: %s", err) } // Check if the key is still free for use. if existing, ok := node[lastKeyPart]; ok { - path := t.formatKeyPath(key, len(key)-1) - return fmt.Errorf("invalid key/value pair: %s item already exists at key %s", existing.Type, path) + path := doc.formatKeyPath(key, len(key)-1) + return fmt.Errorf("invalid key/value pair: %s value already exists at key %s", existing.Type, path) } // It is, store the value in the table. node[lastKeyPart] = value return nil } -func (t *parser) openTable(key []string) error { - t.CurrentKey = nil - t.Current = t.Root +// OpenTable creates a new table at the provided Key path and makes this the +// active table for the TOML Document. This means that subsequent calls to +// SetKeyValuePair() will add the key/value pairs to this table. +func (doc *Document) OpenTable(key Key) error { + doc.CurrentKey = nil + doc.Current = doc.Root // 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 // an existing key/value pair, which is not allowed. - node, lastKeyPart, err := t.makeTablePath(key) + node, lastKeyPart, err := doc.makeTablePath(key) if err != nil { return fmt.Errorf("invalid table: %s", err) } // Check if the key is still free for use. if existing, ok := node[lastKeyPart]; ok { - path := t.formatKeyPath(key, len(key)-1) - return fmt.Errorf("invalid table: %s item already exists at key %s", existing.Type, path) + path := doc.formatKeyPath(key, len(key)-1) + return fmt.Errorf("invalid table: %s value already exists at key %s", existing.Type, path) } // The subtable does not exist yet. Create the subtable. - subTable := make(table) - node[lastKeyPart] = newItem(tTable, subTable) + subTable := make(Table) + node[lastKeyPart] = NewValue(TypeTable, subTable) node = subTable // From here on, key/value pairs are added to the newly defined table. - t.Current = node - t.CurrentKey = key + doc.Current = node + doc.CurrentKey = key return nil } -func (t *parser) openArrayOfTables(key []string) error { - t.CurrentKey = nil - t.Current = t.Root +// OpenArrayOfTables creates a new table and adds it to a (possibly newly +// created) table array at the provided Key path. The new table will be +// made the active table for the TOML Document. This means that subsequent +// calls to SetKeyValuePair() will add the key/value pairs to this table. +func (doc *Document) OpenArrayOfTables(key Key) error { + doc.CurrentKey = nil + doc.Current = doc.Root // Go over all requested levels of the key. For all levels, except the last - // one, it is okay if a table already exists. For the last level, either - // no item must exist (in which case a table array will be created), or a + // one, it is okay if a Table already exists. For the last level, either + // no value must exist (in which case a table array will be created), or a // table array must exist. // Other cases would mean we are overwriting an existing key/value pair, // which is not allowed. - node, lastKeyPart, err := t.makeTablePath(key) + node, lastKeyPart, err := doc.makeTablePath(key) if err != nil { return fmt.Errorf("invalid table array: %s", err) } // At the last key position, there must be either no value yet, or the // existing value must be a table array. Other values are invalid. if existing, ok := node[lastKeyPart]; ok { - if existing.Type != tArrayOfTables { - path := t.formatKeyPath(key, len(key)-1) - return fmt.Errorf("invalid table array: %s item already exists at key %s", existing.Type, path) + if existing.Type != TypeArrayOfTables { + path := doc.formatKeyPath(key, len(key)-1) + return fmt.Errorf("invalid table array: %s value already exists at key %s", existing.Type, path) } // A table array exists. Add a new table to this array. array := node[lastKeyPart] - subTable := make(table) + subTable := make(Table) tables := array.Data - tables = append(tables, newItem(tTable, subTable)) + tables = append(tables, NewValue(TypeTable, subTable)) array.Data = tables node = subTable } else { // No value exists at the defined key path. Create a new table array. - subTable := make(table) - node[lastKeyPart] = newItem(tArrayOfTables, newItem(tTable, subTable)) + subTable := make(Table) + node[lastKeyPart] = NewValue(TypeArrayOfTables, NewValue(TypeTable, subTable)) node = subTable } // From here on, key/value pairs are added to the newly defined table. - t.Current = node - t.CurrentKey = key + doc.Current = node + doc.CurrentKey = key return nil } -func (t *parser) makeTablePath(key []string) (table, string, error) { - node := t.Current +func (doc *Document) makeTablePath(key Key) (Table, string, error) { + node := doc.Current for i, keyPart := range key { // Arrived at the last key part? Then the path towards that key is // setup correctly. Return the last part, so the caller can use it. @@ -151,27 +194,27 @@ func (t *parser) makeTablePath(key []string) (table, string, error) { if subItem, ok := node[keyPart]; ok { // You cannot overwrite an already defined key, regardless its value. // When a value already exists at the current key, this can only be a table. - if subItem.Type != tTable { - path := t.formatKeyPath(key, i) - return nil, "", fmt.Errorf("%s item already exists at key %s", subItem.Type, path) + if subItem.Type != TypeTable { + path := doc.formatKeyPath(key, i) + return nil, "", fmt.Errorf("%s value already exists at key %s", subItem.Type, path) } // All is okay, traverse to the subtable. - node = subItem.Data[0].(table) + node = subItem.Data[0].(Table) } else { // The subtable does not exist yet. Create the subtable. - subTable := make(table) - node[keyPart] = newItem(tTable, subTable) + subTable := make(Table) + node[keyPart] = NewValue(TypeTable, subTable) node = subTable } } panic("makeTablePath(): empty key provided; a key must have at least one key part") } -func (t *parser) formatKeyPath(key []string, end int) string { +func (doc *Document) formatKeyPath(key Key, end int) string { var sb strings.Builder sb.WriteRune('[') - if t.CurrentKey != nil { - for i, keyPart := range t.CurrentKey { + if doc.CurrentKey != nil { + for i, keyPart := range doc.CurrentKey { if i > 0 { sb.WriteString("->") } @@ -179,7 +222,7 @@ func (t *parser) formatKeyPath(key []string, end int) string { } } for i, keyPart := range key { - if t.CurrentKey != nil || i > 0 { + if doc.CurrentKey != nil || i > 0 { sb.WriteString("->") } sb.WriteString(formatKeyName(keyPart)) @@ -197,7 +240,3 @@ func formatKeyName(key string) string { } return fmt.Sprintf("%q", key) } - -func (t table) String() string { - return newItem(tTable, t).String() -} diff --git a/ast/ast_test.go b/ast/ast_test.go index a94ae55..f0b333a 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -1,27 +1,29 @@ -package ast +package ast_test import ( "testing" + + "git.makaay.nl/mauricem/go-toml/ast" ) func Test_ConstructSlightlyComplexStructure(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.setKeyValuePair(newKey("ding"), newItem(tInteger, 10)) - p.setKeyValuePair(newKey("dong"), newItem(tString, "not a song")) - p.openTable(newKey("key1", "key2 a")) - p.setKeyValuePair(newKey("dooh"), newItem(tBoolean, true)) - p.setKeyValuePair(newKey("dah"), newItem(tBoolean, false)) - p.openTable(newKey("key1", "key2 b")) - p.setKeyValuePair(newKey("dieh"), newItem(tFloat, 1.111)) - p.setKeyValuePair(newKey("duh"), newItem(tFloat, 1.18e-12)) - p.setKeyValuePair(newKey("foo", "bar"), newItem(tArrayOfTables, newItem(tInteger, 1), newItem(tInteger, 2))) - p.openArrayOfTables(newKey("aaah", "table array")) - p.setKeyValuePair(newKey("a"), newItem(tFloat, 1.234)) - p.openArrayOfTables(newKey("aaah", "table array")) - p.setKeyValuePair(newKey("b"), newItem(tFloat, 2.345)) - p.setKeyValuePair(newKey("c"), newItem(tString, "bingo!")) - p.openArrayOfTables(newKey("aaah", "table array")) + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.SetKeyValuePair(ast.NewKey("ding"), ast.NewValue(ast.TypeInteger, 10)) + p.SetKeyValuePair(ast.NewKey("dong"), ast.NewValue(ast.TypeString, "not a song")) + p.OpenTable(ast.NewKey("key1", "key2 a")) + p.SetKeyValuePair(ast.NewKey("dooh"), ast.NewValue(ast.TypeBoolean, true)) + p.SetKeyValuePair(ast.NewKey("dah"), ast.NewValue(ast.TypeBoolean, false)) + p.OpenTable(ast.NewKey("key1", "key2 b")) + p.SetKeyValuePair(ast.NewKey("dieh"), ast.NewValue(ast.TypeFloat, 1.111)) + p.SetKeyValuePair(ast.NewKey("duh"), ast.NewValue(ast.TypeFloat, 1.18e-12)) + p.SetKeyValuePair(ast.NewKey("foo", "bar"), ast.NewValue(ast.TypeArrayOfTables, ast.NewValue(ast.TypeInteger, 1), ast.NewValue(ast.TypeInteger, 2))) + p.OpenArrayOfTables(ast.NewKey("aaah", "table array")) + p.SetKeyValuePair(ast.NewKey("a"), ast.NewValue(ast.TypeFloat, 1.234)) + p.OpenArrayOfTables(ast.NewKey("aaah", "table array")) + p.SetKeyValuePair(ast.NewKey("b"), ast.NewValue(ast.TypeFloat, 2.345)) + p.SetKeyValuePair(ast.NewKey("c"), ast.NewValue(ast.TypeString, "bingo!")) + p.OpenArrayOfTables(ast.NewKey("aaah", "table array")) return nil, p }, "", @@ -37,146 +39,146 @@ func Test_EmptyKeyForCreatingTablePath_Panics(t *testing.T) { t.Fatalf("Did not get the expected panic message") } }() - p := newParser() - p.openTable(newKey()) + p := ast.NewDocument() + p.OpenTable(ast.NewKey()) } -func Test_StoreValueInRootTable(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.setKeyValuePair(newKey("key1"), newItem(tString, "value1")) - return p.setKeyValuePair(newKey("key2"), newItem(tString, "value2")), p +func Test_StoreValueInRooTypeTable(t *testing.T) { + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.SetKeyValuePair(ast.NewKey("key1"), ast.NewValue(ast.TypeString, "value1")) + return p.SetKeyValuePair(ast.NewKey("key2"), ast.NewValue(ast.TypeString, "value2")), p }, "", `{"key1": "value1", "key2": "value2"}`) } func Test_StoreValueWithMultipartKey_CreatesTableHierarchy(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - return p.setKeyValuePair(newKey("key1", "key2", "key3"), newItem(tString, "value")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + return p.SetKeyValuePair(ast.NewKey("key1", "key2", "key3"), ast.NewValue(ast.TypeString, "value")), p }, "", `{"key1": {"key2": {"key3": "value"}}}`) } -func Test_StoreDuplicateKeyInRootTable_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.setKeyValuePair(newKey("key"), newItem(tString, "value")) - return p.setKeyValuePair(newKey("key"), newItem(tInteger, 321)), p +func Test_StoreDuplicateKeyInRooTypeTable_ReturnsError(t *testing.T) { + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeString, "value")) + return p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeInteger, 321)), p }, - `invalid key/value pair: string item already exists at key [key]`, + `invalid key/value pair: string value already exists at key [key]`, `{"key": "value"}`) } func Test_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.openTable(newKey("tablekey1", "tablekey2")) - return p.setKeyValuePair(newKey("valuekey1", "valuekey2", "valuekey3"), newItem(tString, "value")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.OpenTable(ast.NewKey("tablekey1", "tablekey2")) + return p.SetKeyValuePair(ast.NewKey("valuekey1", "valuekey2", "valuekey3"), ast.NewValue(ast.TypeString, "value")), p }, "", `{"tablekey1": {"tablekey2": {"valuekey1": {"valuekey2": {"valuekey3": "value"}}}}}`) } func Test_StoreKeyPathWherePathContainsNonTableAlready_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.openTable(newKey("key1")) - p.setKeyValuePair(newKey("key2"), newItem(tInteger, 0)) - return p.setKeyValuePair(newKey("key2", "key3"), newItem(tString, "value")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.OpenTable(ast.NewKey("key1")) + p.SetKeyValuePair(ast.NewKey("key2"), ast.NewValue(ast.TypeInteger, 0)) + return p.SetKeyValuePair(ast.NewKey("key2", "key3"), ast.NewValue(ast.TypeString, "value")), p }, - `invalid key/value pair: integer item already exists at key [key1->key2]`, + `invalid key/value pair: integer value already exists at key [key1->key2]`, `{"key1": {"key2": 0}}`) } func Test_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.openTable(newKey("key1", "key2")) - return p.openTable(newKey("key1", "key2")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.OpenTable(ast.NewKey("key1", "key2")) + return p.OpenTable(ast.NewKey("key1", "key2")), p }, - `invalid table: table item already exists at key [key1->key2]`, + `invalid table: table value already exists at key [key1->key2]`, `{"key1": {"key2": {}}}`) } func Test_GivenExistingItemAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.setKeyValuePair(newKey("key"), newItem(tString, "value")) - return p.openTable(newKey("key")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeString, "value")) + return p.OpenTable(ast.NewKey("key")), p }, - `invalid table: string item already exists at key [key]`, + `invalid table: string value already exists at key [key]`, `{"key": "value"}`) } func Test_GivenExistingItemInKeyPath_CreatingTable_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.setKeyValuePair(newKey("key"), newItem(tString, "value")) - return p.openTable(newKey("key", "subkey")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeString, "value")) + return p.OpenTable(ast.NewKey("key", "subkey")), p }, - `invalid table: string item already exists at key [key]`, + `invalid table: string value already exists at key [key]`, `{"key": "value"}`) } func Test_GivenExistingItemAtDeepKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.openTable(newKey("deep", "table")) - p.setKeyValuePair(newKey("key"), newItem(tInteger, 0)) - return p.openTable(newKey("deep", "table", "key")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.OpenTable(ast.NewKey("deep", "table")) + p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeInteger, 0)) + return p.OpenTable(ast.NewKey("deep", "table", "key")), p }, - `invalid table: integer item already exists at key [deep->table->key]`, + `invalid table: integer value already exists at key [deep->table->key]`, `{"deep": {"table": {"key": 0}}}`) } func Test_GivenExistingItemAtDeepKeyFromSubTable_CreatingTableAtSameKey_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.openTable(newKey("deep", "table")) - p.setKeyValuePair(newKey("key1", "key2"), newItem(tInteger, 0)) - return p.setKeyValuePair(newKey("key1", "key2"), newItem(tBoolean, true)), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.OpenTable(ast.NewKey("deep", "table")) + p.SetKeyValuePair(ast.NewKey("key1", "key2"), ast.NewValue(ast.TypeInteger, 0)) + return p.SetKeyValuePair(ast.NewKey("key1", "key2"), ast.NewValue(ast.TypeBoolean, true)), p }, // This test mainly tests the formatting of [deep->table->key1->key2], being a concatenation - // of the currently active table plus the multipart key for setKeyValuePair(). - `invalid key/value pair: integer item already exists at key [deep->table->key1->key2]`, + // of the currently active table plus the multipart key for SetKeyValuePair(). + `invalid key/value pair: integer value already exists at key [deep->table->key1->key2]`, `{"deep": {"table": {"key1": {"key2": 0}}}}`) } func Test_FormattingOfQuotedPathPartInError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.openTable(newKey("must be quoted")) - p.setKeyValuePair(newKey("this one too"), newItem(tInteger, 0)) - return p.setKeyValuePair(newKey("this one too"), newItem(tInteger, 0)), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.OpenTable(ast.NewKey("must be quoted")) + p.SetKeyValuePair(ast.NewKey("this one too"), ast.NewValue(ast.TypeInteger, 0)) + return p.SetKeyValuePair(ast.NewKey("this one too"), ast.NewValue(ast.TypeInteger, 0)), p }, - `invalid key/value pair: integer item already exists at key ["must be quoted"->"this one too"]`, + `invalid key/value pair: integer value already exists at key ["must be quoted"->"this one too"]`, `{"must be quoted": {"this one too": 0}}`) } func Test_GivenExistingItemAtKey_CreatingArrayOfTablesAtSameKey_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.Root["key"] = newItem(tString, "value") - return p.openArrayOfTables(newKey("key")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.Root["key"] = ast.NewValue(ast.TypeString, "value") + return p.OpenArrayOfTables(ast.NewKey("key")), p }, - `invalid table array: string item already exists at key [key]`, + `invalid table array: string value already exists at key [key]`, `{"key": "value"}`) } func Test_GivenExistingItemInKeyPath_CreatingArrayOfTables_ReturnsError(t *testing.T) { - testAST(t, func() (error, *parser) { - p := newParser() - p.Root["key"] = newItem(tString, "value") - return p.openArrayOfTables(newKey("key", "subkey")), p + testAST(t, func() (error, *ast.Document) { + p := ast.NewDocument() + p.Root["key"] = ast.NewValue(ast.TypeString, "value") + return p.OpenArrayOfTables(ast.NewKey("key", "subkey")), p }, - `invalid table array: string item already exists at key [key]`, + `invalid table array: string value already exists at key [key]`, `{"key": "value"}`) } -func testAST(t *testing.T, code func() (error, *parser), expectedError string, expectedData string) { +func testAST(t *testing.T, code func() (error, *ast.Document), expectedError string, expectedData string) { err, p := code() if expectedError == "" { if err != nil { diff --git a/ast/string.go b/ast/string.go index 8b5d056..08b0816 100644 --- a/ast/string.go +++ b/ast/string.go @@ -7,30 +7,36 @@ import ( "time" ) +// String() produces a JSON-like (but not JSON) string representation of the TOML Document. +// This string version is mainly useful for testing and debugging purposes. +func (t Table) String() string { + return NewValue(TypeTable, t).String() +} + // String() produces a JSON-like (but not JSON) string representation of the value. -// This string version is useful for testing and debugging purposes. -func (parseItem item) String() string { +// This string version is mainly useful for testing and debugging purposes. +func (parseItem Value) String() string { switch parseItem.Type { - case tString: + case TypeString: return fmt.Sprintf("%q", parseItem.Data[0]) - case tOffsetDateTime: + case TypeOffsetDateTime: return parseItem.Data[0].(time.Time).Format(time.RFC3339Nano) - case tLocalDateTime: + case TypeLocalDateTime: return parseItem.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999") - case tLocalDate: + case TypeLocalDate: return parseItem.Data[0].(time.Time).Format("2006-01-02") - case tLocalTime: + case TypeLocalTime: return parseItem.Data[0].(time.Time).Format("15:04:05.999999999") - case tArrayOfTables: + case TypeArrayOfTables: fallthrough - case tArray: - items := make([]string, len(parseItem.Data)) + case TypeArray: + values := make(Key, len(parseItem.Data)) for i, value := range parseItem.Data { - items[i] = value.(*item).String() + values[i] = value.(*Value).String() } - return fmt.Sprintf("[%s]", strings.Join(items, ", ")) - case tTable: - pairs := parseItem.Data[0].(table) + return fmt.Sprintf("[%s]", strings.Join(values, ", ")) + case TypeTable: + pairs := parseItem.Data[0].(Table) keys := make([]string, len(pairs)) i := 0 for k := range pairs { @@ -38,11 +44,11 @@ func (parseItem item) String() string { i++ } sort.Strings(keys) - items := make([]string, len(pairs)) + values := make([]string, len(pairs)) for i, k := range keys { - items[i] = fmt.Sprintf("%q: %s", k, pairs[k].String()) + values[i] = fmt.Sprintf("%q: %s", k, pairs[k].String()) } - return fmt.Sprintf("{%s}", strings.Join(items, ", ")) + return fmt.Sprintf("{%s}", strings.Join(values, ", ")) default: return fmt.Sprintf("%v", parseItem.Data[0]) } diff --git a/ast/string_test.go b/ast/string_test.go index c67e73a..22c57f3 100644 --- a/ast/string_test.go +++ b/ast/string_test.go @@ -1,28 +1,30 @@ -package ast +package ast_test import ( "testing" "time" + + "git.makaay.nl/mauricem/go-toml/ast" ) func Test_StringFormatting(t *testing.T) { - testAST(t, func() (error, *parser) { - tableData := make(table) - tableData["x"] = newItem(tBoolean, true) - tableData["y"] = newItem(tInteger, 42) + testAST(t, func() (error, *ast.Document) { + tableData := make(ast.Table) + tableData["x"] = ast.NewValue(ast.TypeBoolean, true) + tableData["y"] = ast.NewValue(ast.TypeInteger, 42) dateTime, _ := time.Parse(time.RFC3339Nano, "2003-11-01T01:02:03.999999999+10:00") - p := newParser() - p.setKeyValuePair(newKey("a"), newItem(tInteger, 1)) - p.setKeyValuePair(newKey("b"), newItem(tFloat, 2.3)) - p.setKeyValuePair(newKey("c"), newItem(tBoolean, true)) - p.setKeyValuePair(newKey("d"), newItem(tString, "foo")) - p.setKeyValuePair(newKey("e"), newItem(tArray, newItem(tInteger, 1), newItem(tInteger, 2))) - p.setKeyValuePair(newKey("f"), newItem(tTable, tableData)) - p.setKeyValuePair(newKey("g"), newItem(tOffsetDateTime, dateTime)) - p.setKeyValuePair(newKey("h"), newItem(tLocalDateTime, dateTime)) - p.setKeyValuePair(newKey("i"), newItem(tLocalDate, dateTime)) - p.setKeyValuePair(newKey("j"), newItem(tLocalTime, dateTime)) - return nil, p + doc := ast.NewDocument() + doc.SetKeyValuePair(ast.NewKey("a"), ast.NewValue(ast.TypeInteger, 1)) + doc.SetKeyValuePair(ast.NewKey("b"), ast.NewValue(ast.TypeFloat, 2.3)) + doc.SetKeyValuePair(ast.NewKey("c"), ast.NewValue(ast.TypeBoolean, true)) + doc.SetKeyValuePair(ast.NewKey("d"), ast.NewValue(ast.TypeString, "foo")) + doc.SetKeyValuePair(ast.NewKey("e"), ast.NewValue(ast.TypeArray, ast.NewValue(ast.TypeInteger, 1), ast.NewValue(ast.TypeInteger, 2))) + doc.SetKeyValuePair(ast.NewKey("f"), ast.NewValue(ast.TypeTable, tableData)) + doc.SetKeyValuePair(ast.NewKey("g"), ast.NewValue(ast.TypeOffsetDateTime, dateTime)) + doc.SetKeyValuePair(ast.NewKey("h"), ast.NewValue(ast.TypeLocalDateTime, dateTime)) + doc.SetKeyValuePair(ast.NewKey("i"), ast.NewValue(ast.TypeLocalDate, dateTime)) + doc.SetKeyValuePair(ast.NewKey("j"), ast.NewValue(ast.TypeLocalTime, dateTime)) + return nil, doc }, "", `{"a": 1, `+ `"b": 2.3, `+