246 lines
7.7 KiB
Go
246 lines
7.7 KiB
Go
package toml
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"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 (
|
|
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
|
|
pArrayOfTables itemType = "array" // defined using an [[array.of.tables]]
|
|
pArray 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}
|
|
}
|
|
|
|
// newKey instantiates a new key.
|
|
func newKey(key ...string) []string {
|
|
return key
|
|
}
|
|
|
|
// 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
|
|
CurrentKey []string // the key for the currently active TOML table
|
|
}
|
|
|
|
func newParser() *parser {
|
|
p := &parser{Root: make(table)}
|
|
p.Current = p.Root
|
|
return p
|
|
}
|
|
|
|
func (t *parser) setKeyValuePair(key []string, value *item) error {
|
|
// First make sure the table structure for storing the value exists.
|
|
node, lastKeyPart, err := t.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)
|
|
}
|
|
// 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
|
|
// 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.
|
|
node, lastKeyPart, err := t.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)
|
|
}
|
|
// The subtable does not exist yet. Create the subtable.
|
|
subTable := make(table)
|
|
node[lastKeyPart] = newItem(pTable, subTable)
|
|
node = subTable
|
|
|
|
// From here on, key/value pairs are added to the newly defined table.
|
|
t.Current = node
|
|
t.CurrentKey = key
|
|
return nil
|
|
}
|
|
|
|
func (t *parser) openArrayOfTables(key []string) error {
|
|
t.CurrentKey = nil
|
|
t.Current = 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 the last level, either
|
|
// no item 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)
|
|
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 != pArrayOfTables {
|
|
path := t.formatKeyPath(key, len(key)-1)
|
|
return fmt.Errorf("invalid table array: %s item 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)
|
|
tables := array.Data
|
|
tables = append(tables, newItem(pTable, 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(pArrayOfTables, newItem(pTable, subTable))
|
|
node = subTable
|
|
}
|
|
|
|
// From here on, key/value pairs are added to the newly defined table.
|
|
t.Current = node
|
|
t.CurrentKey = key
|
|
return nil
|
|
}
|
|
|
|
func (t *parser) makeTablePath(key []string) (table, string, error) {
|
|
node := t.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.
|
|
isLast := i == len(key)-1
|
|
if isLast {
|
|
return node, keyPart, nil
|
|
}
|
|
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 != pTable {
|
|
path := t.formatKeyPath(key, i)
|
|
return nil, "", fmt.Errorf("%s item already exists at key %s", subItem.Type, path)
|
|
}
|
|
// All is okay, traverse to the subtable.
|
|
node = subItem.Data[0].(table)
|
|
} else {
|
|
// The subtable does not exist yet. Create the subtable.
|
|
subTable := make(table)
|
|
node[keyPart] = newItem(pTable, 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 {
|
|
var sb strings.Builder
|
|
sb.WriteRune('[')
|
|
if t.CurrentKey != nil {
|
|
for i, keyPart := range t.CurrentKey {
|
|
if i > 0 {
|
|
sb.WriteString("->")
|
|
}
|
|
sb.WriteString(formatKeyName(keyPart))
|
|
}
|
|
}
|
|
for i, keyPart := range key {
|
|
if t.CurrentKey != nil || i > 0 {
|
|
sb.WriteString("->")
|
|
}
|
|
sb.WriteString(formatKeyName(keyPart))
|
|
if i == end {
|
|
break
|
|
}
|
|
}
|
|
sb.WriteRune(']')
|
|
return sb.String()
|
|
}
|
|
|
|
func formatKeyName(key string) string {
|
|
if ok, _ := regexp.Match(`^\w+$`, []byte(key)); ok {
|
|
return key
|
|
}
|
|
return fmt.Sprintf("%q", key)
|
|
}
|
|
|
|
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 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 pArrayOfTables:
|
|
fallthrough
|
|
case pArray:
|
|
items := make([]string, len(parseItem.Data))
|
|
for i, value := range parseItem.Data {
|
|
items[i] = value.(*item).String()
|
|
}
|
|
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
|
|
case pTable:
|
|
pairs := parseItem.Data[0].(table)
|
|
keys := make([]string, len(pairs))
|
|
i := 0
|
|
for k := range pairs {
|
|
keys[i] = k
|
|
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, ", "))
|
|
default:
|
|
return fmt.Sprintf("%v", parseItem.Data[0])
|
|
}
|
|
}
|