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]) } }