diff --git a/encode.go b/encode.go index be7cd6b9..9046061a 100644 --- a/encode.go +++ b/encode.go @@ -112,7 +112,7 @@ func NewEncoder(w io.Writer) *Encoder { } } -func (enc *Encoder) MetaData(m *MetaData) { enc.meta = m } +func (enc *Encoder) MetaData(m MetaData) { enc.meta = &m } // Encode writes a TOML representation of the Go value to the Encoder's writer. // @@ -142,12 +142,13 @@ func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { func (enc *Encoder) encode(key Key, rv reflect.Value) { if enc.meta != nil && enc.meta.comments != nil { - c, ok := enc.meta.comments[key.String()] - if ok { - enc.w.WriteByte('\n') - enc.w.WriteString("# ") - enc.w.WriteString(strings.ReplaceAll(c, "\n", "\n# ")) - enc.w.WriteByte('\n') + comments := enc.meta.comments[key.String()] + for _, c := range comments { + if c.where == commentDoc { + enc.w.WriteString("# ") + enc.w.WriteString(strings.ReplaceAll(c.text, "\n", "\n# ")) + enc.w.WriteByte('\n') + } } } @@ -161,6 +162,7 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { enc.writeKeyValue(key, rv, false) return // TODO: #76 would make this superfluous after implemented. + // TODO: remove in v2 case Primitive: enc.encode(key, reflect.ValueOf(t.undecoded)) return @@ -175,7 +177,7 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: enc.writeKeyValue(key, rv, false) case reflect.Array, reflect.Slice: - if typeEqual(tomlArrayTable, tomlTypeOfGo(rv)) { + if typeEqual(ArrayTable{}, tomlTypeOfGo(rv)) { enc.eArrayOfTables(key, rv) } else { enc.writeKeyValue(key, rv, false) @@ -200,26 +202,45 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { default: encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k)) } + + // TODO: there's a newline printed already. + // if enc.meta != nil && enc.meta.comments != nil { + // comments := enc.meta.comments[key.String()] + // for _, c := range comments { + // if c.where == commentComment { + // enc.w.WriteString(" # ") + // enc.w.WriteString(strings.ReplaceAll(c.text, "\n", "\n# ")) + // enc.w.WriteByte('\n') + // } + // } + // } } -func (enc *Encoder) writeIntBase(as formatAs) int { - base := 10 - switch { - case as&IntBinary != 0: +func (enc *Encoder) writeInt(typ tomlType, v uint64) { + var ( + iTyp = asInt(typ) + base = int(iTyp.Base) + ) + switch iTyp.Base { + case 0: + base = 10 + case 2: enc.wf("0b") - base = 2 - case as&IntOctal != 0: + case 8: enc.wf("0o") - base = 8 - case as&IntHex != 0: + case 16: enc.wf("0x") - base = 16 } - return base + + n := strconv.FormatUint(uint64(v), base) + if base != 10 && iTyp.Width > 0 && len(n) < int(iTyp.Width) { + enc.wf(strings.Repeat("0", int(iTyp.Width)-len(n))) + } + enc.wf(n) } // eElement encodes any value that can be an array element. -func (enc *Encoder) eElement(rv reflect.Value, as formatAs) { +func (enc *Encoder) eElement(rv reflect.Value, typ tomlType) { switch v := rv.Interface().(type) { case time.Time: // Using TextMarshaler adds extra quotes, which we don't want. format := time.RFC3339Nano @@ -232,7 +253,9 @@ func (enc *Encoder) eElement(rv reflect.Value, as formatAs) { format = "15:04:05.999999999" } - // XXX: this breaks some tests; should fix those. + // XXX: this breaks some tests; should fix those. Can also remove + // internal/tz.go I think. + // format := time.RFC3339Nano // switch { // case as&DatetimeLocal != 0: @@ -254,14 +277,14 @@ func (enc *Encoder) eElement(rv reflect.Value, as formatAs) { if err != nil { encPanic(err) } - enc.writeQuoted(string(s), as) + enc.writeQuoted(string(s), asString(typ)) return case encoding.TextMarshaler: s, err := v.MarshalText() if err != nil { encPanic(err) } - enc.writeQuoted(string(s), as) + enc.writeQuoted(string(s), asString(typ)) return } @@ -269,19 +292,16 @@ func (enc *Encoder) eElement(rv reflect.Value, as formatAs) { case reflect.Bool: enc.wf(strconv.FormatBool(rv.Bool())) case reflect.String: - enc.writeQuoted(rv.String(), as) - + enc.writeQuoted(rv.String(), asString(typ)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v := rv.Int() if v < 0 { // Make sure sign is before "0x". enc.wf("-") v = -v } - base := enc.writeIntBase(as) - enc.wf(strconv.FormatUint(uint64(v), base)) + enc.writeInt(typ, uint64(v)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - base := enc.writeIntBase(as) - enc.wf(strconv.FormatUint(rv.Uint(), base)) + enc.writeInt(typ, rv.Uint()) case reflect.Float32, reflect.Float64: f := rv.Float() @@ -294,7 +314,7 @@ func (enc *Encoder) eElement(rv reflect.Value, as formatAs) { if rv.Kind() == reflect.Float32 { n = 32 } - if as&FloatExponent != 0 { + if asFloat(typ).Exponent { enc.wf(strconv.FormatFloat(f, 'e', -1, n)) } else { enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, n))) @@ -308,7 +328,7 @@ func (enc *Encoder) eElement(rv reflect.Value, as formatAs) { case reflect.Map: enc.eMap(nil, rv, true) case reflect.Interface: - enc.eElement(rv.Elem(), as) + enc.eElement(rv.Elem(), typ) default: encPanic(fmt.Errorf("unexpected primitive type: %T", rv.Interface())) } @@ -323,16 +343,15 @@ func floatAddDecimal(fstr string) string { return fstr } -func (enc *Encoder) writeQuoted(s string, as formatAs) { - multi := as&StringMultiline != 0 - if as&StringLiteral != 0 { - if multi { +func (enc *Encoder) writeQuoted(s string, typ String) { + if typ.Literal { + if typ.Multiline { enc.wf("'''%s'''\n", s) } else { enc.wf(`'%s'`, s) } } else { - if multi { + if typ.Multiline { enc.wf(`"""%s"""`+"\n", strings.ReplaceAll(dblQuotedReplacer.Replace(s), "\\n", "\n")) } else { @@ -346,7 +365,7 @@ func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { enc.wf("[") for i := 0; i < length; i++ { elem := rv.Index(i) - enc.eElement(elem, 0) // TODO: add formatAs + enc.eElement(elem, nil) // TODO: add type if i != length-1 { enc.wf(", ") } @@ -565,46 +584,46 @@ func tomlTypeOfGo(rv reflect.Value) tomlType { } switch rv.Kind() { case reflect.Bool: - return tomlBool + return Bool{} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return tomlInt{} + return Int{} case reflect.Float32, reflect.Float64: - return tomlFloat{} + return Float{} case reflect.Array, reflect.Slice: - if typeEqual(tomlTable{}, tomlArrayType(rv)) { - return tomlArrayTable + if typeEqual(Table{}, tomlArrayType(rv)) { + return ArrayTable{} } - return tomlArray{} + return Array{} case reflect.Ptr, reflect.Interface: return tomlTypeOfGo(rv.Elem()) case reflect.String: - return tomlString{} + return String{} case reflect.Map: - return tomlTable{} + return Table{} case reflect.Struct: switch rv.Interface().(type) { case time.Time: - return tomlDatetime{} + return Datetime{} case encoding.TextMarshaler: - return tomlString{} + return String{} default: // Someone used a pointer receiver: we can make it work for pointer // values. if rv.CanAddr() { _, ok := rv.Addr().Interface().(encoding.TextMarshaler) if ok { - return tomlString{} + return String{} } } - return tomlTable{} + return Table{} } default: _, ok := rv.Interface().(encoding.TextMarshaler) if ok { - return tomlString{} + return String{} } encPanic(errors.New("unsupported type: " + rv.Kind().String())) panic("") // Need *some* return value @@ -709,13 +728,13 @@ func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) { } enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) - var as formatAs + var typ tomlType if enc.meta != nil { if t, ok := enc.meta.types[key.String()]; ok { - as = t.formatAs() + typ = t } } - enc.eElement(val, as) + enc.eElement(val, typ) if !inline { enc.newline() } diff --git a/encode_test.go b/encode_test.go index b9c5a418..173498e0 100644 --- a/encode_test.go +++ b/encode_test.go @@ -11,6 +11,80 @@ import ( "time" ) +// Copy from _example/example.go +type ( + example struct { + Title string `toml:"title"` + Integers []int `toml:"integers"` + Times []fmtTime `toml:"times"` + Duration []duration `toml:"duration"` + Distros []distro `toml:"distros"` + Servers map[string]server `toml:"servers"` + Characters map[string][]struct { + Name string `toml:"name"` + Rank string `toml:"rank"` + } `toml:"characters"` + } + + server struct { + IP string `toml:"ip"` + Hostname string `toml:"hostname"` + Enabled bool `toml:"enabled"` + } + + distro struct { + Name string `toml:"name"` + Packages string `toml:"packages"` + } + + duration struct{ time.Duration } + fmtTime struct{ time.Time } +) + +func (d *duration) UnmarshalText(text []byte) (err error) { + d.Duration, err = time.ParseDuration(string(text)) + return err +} + +func (d duration) MarshalText() ([]byte, error) { + return []byte(d.Duration.String()), nil +} + +func (t fmtTime) String() string { + f := "2006-01-02 15:04:05.999999999" + if t.Time.Hour() == 0 { + f = "2006-01-02" + } + if t.Time.Year() == 0 { + f = "15:04:05.999999999" + } + if t.Time.Location() == time.UTC { + f += " UTC" + } else { + f += " -0700" + } + return t.Time.Format(`"` + f + `"`) +} + +func TestXXX(t *testing.T) { + var decoded example + meta, err := DecodeFile("_example/example.toml", &decoded) + if err != nil { + t.Fatal(err) + } + + buf := new(bytes.Buffer) + enc := NewEncoder(buf) + enc.MetaData(meta) + err = enc.Encode(decoded) + if err != nil { + t.Fatal(err) + } + + fmt.Println(buf) + +} + func TestEncodeRoundTrip(t *testing.T) { type Config struct { Age int @@ -499,27 +573,12 @@ func TestEncodeHints(t *testing.T) { t.Fatal(err) } - fmt.Printf("mapping: %#v\n", meta.mapping) - fmt.Printf("types: %#v\n", meta.types) - fmt.Printf("keys : %#v\n", meta.keys) - - // meta.Format() // Set format. - // meta.Doc() // Doc comment above key. - // meta.Comment() // Inline comment (like this) - - //meta.Format("ml", toml.StringMultiline) - //meta.Doc("ml", "Some comment.") - - //meta.Format("n", toml.IntHex | toml.IntLeadingZero(2)) - - // FormatString("ml", StringMultiline). - // FormatString("lit", StringLiteral). - // FormatString("cmt", StringLiteral|StringMultiline). - // Comment("cmt", "Well, hello there!")) + meta.Doc("ml", "Hello").Comment("ml", "inline") + meta.SetType("n", Int{Width: 4, Base: 16}) buf := new(bytes.Buffer) enc := NewEncoder(buf) - enc.MetaData(&meta) + enc.MetaData(meta) err = enc.Encode(foo) if err != nil { t.Fatal(err) diff --git a/meta.go b/meta.go index 278cc3c2..b652b9f2 100644 --- a/meta.go +++ b/meta.go @@ -1,123 +1,51 @@ package toml -import "strings" - -// TODO: the downside of using bitmasks for this is that we can't add other -// information to this. For example, if someone writes: -// -// character = 0x0066 -// -// Then we should really preserve those two leading zeros as well. -// -// Another downside of this is that it adds quite a few symbols to the toml.* -// namespace. -// -// Also, we can re-use such a system for encoding comments. -// -// Anyway, now that we have (Go) types for all the TOML types in type_check.go, -// we can modify it to use struct fields: -// -// type String struct { -// Literal, Multiline bool -// Doc, Comment string -// } -// -// type Int struct { -// Base uint8 -// Width uint8 -// Doc, Comment string -// } -// -// type Float struct { -// Exponent bool -// Doc, Comment string -// } -// -// type Datetime struct { -// Format uint // enum: local, date, time -// Doc, Comment string -// } -// -// type Array struct { -// SingleLine bool -// Doc, Comment string -// } -// -// type Table struct { -// Dotted bool -// Inline bool -// Merge bool -// Doc, Comment string -// } -// -// And then use it as: -// -// meta.formatAs("someKey", toml.Int{Base: 8, Width: 4, Doc: "Hello, world"}) -// -// Or alternatively, store as uint64; every type has one byte and a maximum of 8 -// flags (doesn't really need to allign, but it makes some things easier). -// -// The last 4 remaining bytes can be used to store various other information. -// +// MetaData allows access to meta information about TOML. // -// ┌───────┬─ Number width -// │ │ ┌──table┐ ┌──array┐ ┌───date┐ ┌──float┐ ┌────int┐ ┌─string┐ -// 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 - -type formatAs uint +// It allows determining whether a key has been defined, the TOML type of a +// key, and how it's formatted. It also records comments in the TOML file. +type MetaData struct { + mapping map[string]interface{} + types map[string]tomlType // TOML types. + keys []Key // List of defined keys. + decoded map[string]bool // Decoded keys. + context Key // Used only during decoding. + comments map[string][]comment // Record comments. +} const ( - StringLiteral formatAs = 1 << iota - StringMultiline - - IntHex // Hex number. - IntOctal // Octal number. - IntBinary // Binary number - - FloatExponent // floats as exp notation. - - DatetimeDate // 1979-05-27 - DatetimeTime // 07:32:00 - DatetimeLocal // Don't add timezone - //DatetimeMilliseconds // .999999 (only for time, datetime) - - ArraySingleLine // Array on single line ([1, 2]) - - TableDotted // Use foo.bar for maps, rather than [foo] bar = - TableInline // Format as an inline table. - TableMerge // Merge in to existing value(s). https://github.com/BurntSushi/toml/issues/192 + _ = iota + commentDoc // Above the key. + commentComment // "Inline" after the key. ) -// MetaData allows access to meta information about TOML data that may not be -// inferable via reflection. In particular, whether a key has been defined and -// the TOML type of a key. -type MetaData struct { - mapping map[string]interface{} - types map[string]tomlType // TOML types. - keys []Key // List of defined keys. - decoded map[string]bool // Decoded keys. - context Key // Used only during decoding. +type comment struct { + where int + text string +} - comments map[string]string // Comments to add. - as map[string]formatAs // How to format keys. +func NewMetaData() MetaData { + return MetaData{} } -func NewMetaData() *MetaData { - return &MetaData{} +func (enc *MetaData) SetType(key string, t tomlType) *MetaData { + enc.types[key] = t + return enc } -func (enc *MetaData) FormatAs(key string, as formatAs) *MetaData { - if enc.as == nil { - enc.as = make(map[string]formatAs) +func (enc *MetaData) Doc(key string, doc string) *MetaData { + if enc.comments == nil { + enc.comments = make(map[string][]comment) } - enc.as[key] = as + enc.comments[key] = append(enc.comments[key], comment{where: commentDoc, text: doc}) return enc } -func (enc *MetaData) Comment(key, cmt string) *MetaData { + +func (enc *MetaData) Comment(key string, doc string) *MetaData { if enc.comments == nil { - enc.comments = make(map[string]string) + enc.comments = make(map[string][]comment) } - enc.comments[key] = cmt + enc.comments[key] = append(enc.comments[key], comment{where: commentComment, text: doc}) return enc } @@ -153,13 +81,23 @@ func (md *MetaData) IsDefined(key ...string) bool { // Type will return the empty string if given an empty key or a key that does // not exist. Keys are case sensitive. func (md *MetaData) Type(key ...string) string { - fullkey := strings.Join(key, ".") - if typ, ok := md.types[fullkey]; ok { - return typ.String() + if t, ok := md.types[Key(key).String()]; ok { + return t.String() } return "" } +// func (md *MetaData) TypeInfo(key ...string) tomlType { +// TODO: Type() would be a better name for this, but that's already used. +// +// Change in v2: +// +// meta.TypeInfo() → meta.Type() +// meta.IsDefined() → meta.Type() == nil +// +// return md.types[Key(key).String()] +// } + // Keys returns a slice of every key in the TOML data, including key groups. // // Each key is itself a slice, where the first element is the top of the diff --git a/parse.go b/parse.go index d4882d22..2c42d8a0 100644 --- a/parse.go +++ b/parse.go @@ -146,7 +146,7 @@ func (p *parser) topLevel(item item) { p.assertEqual(itemTableEnd, name.typ) p.addContext(key, false) - p.setType("", tomlTable{}) + p.setType("", Table{}) p.ordered = append(p.ordered, key) case itemArrayTableStart: // [[ .. ]] name := p.nextPos() @@ -158,7 +158,7 @@ func (p *parser) topLevel(item item) { p.assertEqual(itemArrayTableEnd, name.typ) p.addContext(key, true) - p.setType("", tomlArrayTable) + p.setType("", ArrayTable{}) p.ordered = append(p.ordered, key) case itemKeyStart: // key = .. outerContext := p.context @@ -218,13 +218,13 @@ var datetimeRepl = strings.NewReplacer( func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) { switch it.typ { case itemString: - return p.replaceEscapes(it, it.val), tomlString{} + return p.replaceEscapes(it, it.val), String{} case itemMultilineString: - return p.replaceEscapes(it, stripFirstNewline(stripEscapedNewlines(it.val))), tomlString{as: StringMultiline} + return p.replaceEscapes(it, stripFirstNewline(stripEscapedNewlines(it.val))), String{Multiline: true} case itemRawString: - return it.val, tomlString{as: StringLiteral} + return it.val, String{Literal: true} case itemRawMultilineString: - return stripFirstNewline(it.val), tomlString{as: StringMultiline | StringLiteral} + return stripFirstNewline(it.val), String{Literal: true, Multiline: true} case itemInteger: return p.valueInteger(it) case itemFloat: @@ -232,9 +232,9 @@ func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) { case itemBool: switch it.val { case "true": - return true, tomlBool + return true, Bool{} case "false": - return false, tomlBool + return false, Bool{} default: p.bug("Expected boolean value, but got '%s'.", it.val) } @@ -275,16 +275,16 @@ func (p *parser) valueInteger(it item) (interface{}, tomlType) { if len(v) > 0 && (v[0] == '-' || v[0] == '+') { v = v[1:] } - var as formatAs + var base uint8 switch { case strings.HasPrefix(v, "0b"): - as = IntBinary + base = 2 case strings.HasPrefix(v, "0o"): - as = IntOctal + base = 8 case strings.HasPrefix(v, "0x"): - as = IntHex + base = 16 } - return num, tomlInt{as: as} + return num, Int{Base: base} } func (p *parser) valueFloat(it item) (interface{}, tomlType) { @@ -320,22 +320,21 @@ func (p *parser) valueFloat(it item) (interface{}, tomlType) { p.panicItemf(it, "Invalid float value: %q", it.val) } } - var as formatAs + exp := false if strings.ContainsAny(val, "eE") { - as = FloatExponent + exp = true } - return num, tomlFloat{as: as} + return num, Float{Exponent: exp} } var dtTypes = []struct { fmt string zone *time.Location - as formatAs }{ - {time.RFC3339Nano, time.Local, 0}, - {"2006-01-02T15:04:05.999999999", internal.LocalDatetime, DatetimeLocal}, - {"2006-01-02", internal.LocalDate, DatetimeDate}, - {"15:04:05.999999999", internal.LocalTime, DatetimeTime}, + {time.RFC3339Nano, time.Local}, + {"2006-01-02T15:04:05.999999999", internal.LocalDatetime}, + {"2006-01-02", internal.LocalDate}, + {"15:04:05.999999999", internal.LocalTime}, } func (p *parser) valueDatetime(it item) (interface{}, tomlType) { @@ -344,24 +343,23 @@ func (p *parser) valueDatetime(it item) (interface{}, tomlType) { t time.Time ok bool err error - as formatAs ) + // XXX: add "as" for _, dt := range dtTypes { t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) if err == nil { ok = true - as = dt.as break } } if !ok { p.panicItemf(it, "Invalid TOML Datetime: %q.", it.val) } - return t, tomlDatetime{as: as} + return t, Datetime{} } func (p *parser) valueArray(it item) (interface{}, tomlType) { - p.setType(p.currentKey, tomlArray{}) + p.setType(p.currentKey, Array{}) // p.setType(p.currentKey, typ) var ( @@ -378,7 +376,7 @@ func (p *parser) valueArray(it item) (interface{}, tomlType) { array = append(array, val) types = append(types, typ) } - return array, tomlArray{} + return array, Array{} } func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) { @@ -431,7 +429,7 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tom } p.context = outerContext p.currentKey = outerKey - return hash, tomlTable{} + return hash, Table{} } // numHasLeadingZero checks if this number has leading zeroes, allowing for '0', @@ -624,7 +622,7 @@ func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = true } func (p *parser) removeImplicit(key Key) { p.implicits[key.String()] = false } func (p *parser) isImplicit(key Key) bool { return p.implicits[key.String()] } func (p *parser) isArray(key Key) bool { - _, ok := p.types[key.String()].(tomlArray) + _, ok := p.types[key.String()].(Array) return ok } func (p *parser) addImplicitContext(key Key) { diff --git a/type_check.go b/type_check.go deleted file mode 100644 index 989c868c..00000000 --- a/type_check.go +++ /dev/null @@ -1,49 +0,0 @@ -package toml - -// tomlType represents any Go type that corresponds to a TOML type. -type tomlType interface { - formatAs() formatAs - String() string -} - -var ( - tomlBool tomlBaseType = "Bool" - tomlArrayTable tomlBaseType = "ArrayTable" -) - -type ( - tomlBaseType string - tomlString struct{ as formatAs } - tomlInt struct{ as formatAs } - tomlFloat struct{ as formatAs } - tomlDatetime struct{ as formatAs } - tomlTable struct{ as formatAs } - tomlArray struct{ as formatAs } -) - -func (t tomlBaseType) String() string { return string(t) } -func (t tomlString) String() string { return "String" } -func (t tomlInt) String() string { return "Integer" } -func (t tomlFloat) String() string { return "Float" } -func (t tomlDatetime) String() string { return "Datetime" } -func (t tomlTable) String() string { return "Table" } -func (t tomlArray) String() string { return "Array" } -func (t tomlBaseType) formatAs() formatAs { return 0 } -func (t tomlString) formatAs() formatAs { return t.as } -func (t tomlInt) formatAs() formatAs { return t.as } -func (t tomlFloat) formatAs() formatAs { return t.as } -func (t tomlDatetime) formatAs() formatAs { return t.as } -func (t tomlTable) formatAs() formatAs { return t.as } -func (t tomlArray) formatAs() formatAs { return t.as } - -// typeEqual accepts any two types and returns true if they are equal. -func typeEqual(t1, t2 tomlType) bool { - if t1 == nil || t2 == nil { - return false - } - return t1.String() == t2.String() -} - -func typeIsTable(t tomlType) bool { - return typeEqual(t, tomlTable{}) || typeEqual(t, tomlArrayTable) -} diff --git a/type_toml.go b/type_toml.go new file mode 100644 index 00000000..017d720a --- /dev/null +++ b/type_toml.go @@ -0,0 +1,116 @@ +package toml + +// tomlType represents a TOML type. +type tomlType interface { + tomlType() + String() string +} + +type ( + // Bool represents a TOML boolean. + Bool struct{} + + // String represents a TOML string. + String struct { + Literal bool // As literal string ('..'). + Multiline bool // As multi-line string ("""..""" or '''..'''). + } + + // Int represents a TOML integer. + Int struct { + Base uint8 // Base 2, 8, 10, 16, or 0 (same as 10). + Width uint8 // Print leading zeros up to width; ignored for base 10. + } + + // Float represents a TOML float. + Float struct { + Exponent bool // As exponent notation. + } + + // Datetime represents a TOML datetime. + Datetime struct { + Format uint // enum: local, date, time + } + + // Table represents a TOML table. + Table struct { + Inline bool // As inline table. + //Dotted bool + //Merge bool + } + + // Array represents a TOML array. + Array struct { + SingleLine bool // Print on single line. + } + + // ArrayTable represents a TOML array table ([[...]]). + ArrayTable struct{} +) + +func (t Bool) tomlType() {} +func (t String) tomlType() {} +func (t Int) tomlType() {} +func (t Float) tomlType() {} +func (t Datetime) tomlType() {} +func (t Table) tomlType() {} +func (t Array) tomlType() {} +func (t ArrayTable) tomlType() {} +func (t Bool) String() string { return "Bool" } +func (t String) String() string { return "String" } +func (t Int) String() string { return "Integer" } +func (t Float) String() string { return "Float" } +func (t Datetime) String() string { return "Datetime" } +func (t Table) String() string { return "Table" } +func (t Array) String() string { return "Array" } +func (t ArrayTable) String() string { return "ArrayTable" } + +// meta.types may not be defined for a key, so return a zero value. +func asString(t tomlType) String { + if t == nil { + return String{} + } + return t.(String) +} +func asInt(t tomlType) Int { + if t == nil { + return Int{} + } + return t.(Int) +} +func asFloat(t tomlType) Float { + if t == nil { + return Float{} + } + return t.(Float) +} +func asDatetime(t tomlType) Datetime { + if t == nil { + return Datetime{} + } + return t.(Datetime) +} +func asTable(t tomlType) Table { + if t == nil { + return Table{} + } + return t.(Table) +} +func asArray(t tomlType) Array { + if t == nil { + return Array{} + } + return t.(Array) +} + +// typeEqual accepts any two types and returns true if they are equal. +func typeEqual(t1, t2 tomlType) bool { + if t1 == nil || t2 == nil { + return false + } + return t1.String() == t2.String() +} + +func typeIsTable(t tomlType) bool { + return typeEqual(t, Table{}) || typeEqual(t, ArrayTable{}) +}