200 lines
6.1 KiB
Go
200 lines
6.1 KiB
Go
package toml
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// item represents a TOML item.
|
|
type item struct {
|
|
Type itemType
|
|
Data []interface{}
|
|
}
|
|
|
|
// table represents a TOML table.
|
|
type table map[string]item
|
|
|
|
// itemType identifies the semantic role of a TOML item.
|
|
type itemType string
|
|
|
|
const (
|
|
// TODO Would be nice to not need these in the end.
|
|
pComment itemType = "comment"
|
|
pKey itemType = "key"
|
|
pAssign itemType = "assign"
|
|
|
|
// TODO and just use these data types.
|
|
pString itemType = "string" // "various", 'types', """of""", '''strings'''
|
|
pInteger itemType = "integer" // 12345, 0xffee12a0, 0o0755, 0b101101011
|
|
pFloat itemType = "float" // 10.1234, 143E-12, 43.28377e+4, +inf, -inf, nan
|
|
pBoolean itemType = "boolean" // true or false
|
|
pOffsetDateTime itemType = "offset datetime" // 2019-06-18 10:32:15.173645362+0200
|
|
pLocalDateTime itemType = "datetime" // 2018-12-25 12:12:18.876772533
|
|
pLocalDate itemType = "date" // 2017-05-17
|
|
pLocalTime itemType = "time" // 23:01:22
|
|
pArray itemType = "array" // defined using an [[array.of.tables]]
|
|
pStaticArray itemType = "static array" // defined using ["an", "inline", "array"]
|
|
pTable itemType = "table" // defined using { "inline" = "table" } or [standard.table]
|
|
)
|
|
|
|
// newItem instantiates a new item struct.
|
|
func newItem(itemType itemType, data ...interface{}) item {
|
|
return item{Type: itemType, Data: data}
|
|
}
|
|
|
|
func (t table) String() string {
|
|
return newItem(pTable, t).String()
|
|
}
|
|
|
|
func (parseItem item) String() string {
|
|
switch parseItem.Type {
|
|
case pString:
|
|
return fmt.Sprintf("%q", parseItem.Data[0])
|
|
case pInteger:
|
|
return fmt.Sprintf("%v", parseItem.Data[0])
|
|
case pFloat:
|
|
return fmt.Sprintf("%v", parseItem.Data[0])
|
|
case pBoolean:
|
|
return fmt.Sprintf("%v", parseItem.Data[0])
|
|
case pOffsetDateTime:
|
|
return parseItem.Data[0].(time.Time).Format(time.RFC3339Nano)
|
|
case pLocalDateTime:
|
|
return parseItem.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999")
|
|
case pLocalDate:
|
|
return parseItem.Data[0].(time.Time).Format("2006-01-02")
|
|
case pLocalTime:
|
|
return parseItem.Data[0].(time.Time).Format("15:04:05.999999999")
|
|
case pArray:
|
|
fallthrough
|
|
case pStaticArray:
|
|
items := make([]string, len(parseItem.Data[0].([]item)))
|
|
for i, d := range parseItem.Data[0].([]item) {
|
|
items[i] = d.String()
|
|
}
|
|
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
|
|
case pTable:
|
|
items := make([]string, len(parseItem.Data))
|
|
pairs := parseItem.Data[0].(table)
|
|
i := 0
|
|
for k, v := range pairs {
|
|
items[i] = fmt.Sprintf("%q: %s", k, v.String())
|
|
i++
|
|
}
|
|
return fmt.Sprintf("{%s}", strings.Join(items, ", "))
|
|
case pComment:
|
|
return fmt.Sprintf("comment(%q)", parseItem.Data[0])
|
|
case pKey:
|
|
items := make([]string, len(parseItem.Data))
|
|
for i, e := range parseItem.Data {
|
|
items[i] = fmt.Sprintf("%q", e)
|
|
}
|
|
return fmt.Sprintf("key(%s)", strings.Join(items, ", "))
|
|
case pAssign:
|
|
return "="
|
|
default:
|
|
panic(fmt.Sprintf("Missing String() formatting for item type '%s'", parseItem.Type))
|
|
}
|
|
}
|
|
|
|
// parser holds the state for the TOML parser. All parsing functions are
|
|
// methods of this struct.
|
|
type parser struct {
|
|
Items []item // a buffer for holding parsed items
|
|
Root table // the root-level TOML table (each TOML doc is implicitly a table)
|
|
Current table // the currently active TOML table
|
|
}
|
|
|
|
func newParser() *parser {
|
|
p := &parser{Root: make(table)}
|
|
p.Current = p.Root
|
|
return p
|
|
}
|
|
|
|
func (t *parser) addParsedItem(itemType itemType, data ...interface{}) {
|
|
t.Items = append(t.Items, newItem(itemType, data...))
|
|
}
|
|
|
|
func (t *parser) setValue(key item, value item) error {
|
|
// When the key has multiple elements, then first make sure the table structure
|
|
// for storing the value exists.
|
|
var valueKey string
|
|
node := t.Current
|
|
l := len(key.Data)
|
|
if l > 1 {
|
|
pathKeys := key.Data[0 : l-1]
|
|
valueKey = key.Data[l-1].(string)
|
|
for i, name := range pathKeys {
|
|
name := name.(string)
|
|
if subItem, ok := node[name]; ok {
|
|
// An item was found at the current key. It is expected to be a table.
|
|
if subItem.Type != pTable {
|
|
path := formatKeyPath(key, i)
|
|
return fmt.Errorf("invalid key used: %s item already exists at key %s", subItem.Type, path)
|
|
}
|
|
node = subItem.Data[0].(table)
|
|
} else {
|
|
// No item was found at the current key. Create a new subtable.
|
|
subTable := make(table)
|
|
node[name] = newItem(pTable, subTable)
|
|
node = subTable
|
|
}
|
|
}
|
|
} else {
|
|
valueKey = key.Data[0].(string)
|
|
node = t.Current
|
|
}
|
|
|
|
if existing, ok := node[valueKey]; ok {
|
|
path := "moetnog"
|
|
return fmt.Errorf("Cannot store value: %s item already exists at key %s", existing.Type, path)
|
|
}
|
|
|
|
node[valueKey] = value
|
|
return nil
|
|
}
|
|
|
|
func (t *parser) newTable(key item) (table, error) {
|
|
node := t.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,
|
|
// no table or value must exist, because that would mean we are overwriting
|
|
// an existing key/value pair, which is not allowed.
|
|
for i, e := range key.Data {
|
|
name := e.(string)
|
|
if subItem, ok := node[name]; ok {
|
|
// You cannot overwrite an already defined key, regardless its value.
|
|
if subItem.Type != pTable {
|
|
path := formatKeyPath(key, i)
|
|
return nil, fmt.Errorf("Cannot create table: %s item already exists at key %s", subItem.Type, path)
|
|
}
|
|
// Like keys, you cannot define any table more than once. Doing so is invalid.
|
|
isLast := i == len(key.Data)-1
|
|
if isLast {
|
|
path := formatKeyPath(key, i)
|
|
return nil, fmt.Errorf("Cannot create table: table for key %s already exists", path)
|
|
}
|
|
node = subItem.Data[0].(table)
|
|
} else {
|
|
// Create the subtable.
|
|
subTable := make(table)
|
|
node[name] = newItem(pTable, subTable)
|
|
node = subTable
|
|
}
|
|
}
|
|
// From here on, key/value pairs are added to the newly defined table.
|
|
t.Current = node
|
|
return node, nil
|
|
}
|
|
|
|
func formatKeyPath(key item, end int) string {
|
|
var sb strings.Builder
|
|
for i := 0; i <= end; i++ {
|
|
if i > 0 {
|
|
sb.WriteRune('.')
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%q", key.Data[i].(string)))
|
|
}
|
|
return sb.String()
|
|
}
|