package toml import ( "fmt" "regexp" "sort" "strings" "time" ) // item represents a TOML item. type item struct { Type valueType Data []interface{} } // 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} } // 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 { 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(tTable, 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 != tArrayOfTables { 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(tTable, 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)) 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 != tTable { 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(tTable, 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(tTable, t).String() } // String() produces a JSON-like (but not JSON) string representation of the value. func (parseItem item) String() string { switch parseItem.Type { case tString: return fmt.Sprintf("%q", parseItem.Data[0]) case tOffsetDateTime: return parseItem.Data[0].(time.Time).Format(time.RFC3339Nano) case tLocalDateTime: return parseItem.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999") case tLocalDate: return parseItem.Data[0].(time.Time).Format("2006-01-02") case tLocalTime: return parseItem.Data[0].(time.Time).Format("15:04:05.999999999") case tArrayOfTables: fallthrough case tArray: 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 tTable: 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]) } }