package toml import ( "fmt" "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 ( // 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: 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, ", ")) 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) openTable(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, value := range key.Data { keyName := value.(string) if subItem, ok := node[keyName]; 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[keyName] = 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() }