123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- package toml
- import (
- "bytes"
- "encoding"
- "fmt"
- "io"
- "reflect"
- "sort"
- "strconv"
- "time"
- "github.com/naoina/toml/ast"
- )
- const (
- tagOmitempty = "omitempty"
- tagSkip = "-"
- )
- // Marshal returns the TOML encoding of v.
- //
- // Struct values encode as TOML. Each exported struct field becomes a field of
- // the TOML structure unless
- // - the field's tag is "-", or
- // - the field is empty and its tag specifies the "omitempty" option.
- //
- // The "toml" key in the struct field's tag value is the key name, followed by
- // an optional comma and options. Examples:
- //
- // // Field is ignored by this package.
- // Field int `toml:"-"`
- //
- // // Field appears in TOML as key "myName".
- // Field int `toml:"myName"`
- //
- // // Field appears in TOML as key "myName" and the field is omitted from the
- // // result of encoding if its value is empty.
- // Field int `toml:"myName,omitempty"`
- //
- // // Field appears in TOML as key "field", but the field is skipped if
- // // empty. Note the leading comma.
- // Field int `toml:",omitempty"`
- func (cfg *Config) Marshal(v interface{}) ([]byte, error) {
- buf := new(bytes.Buffer)
- err := cfg.NewEncoder(buf).Encode(v)
- return buf.Bytes(), err
- }
- // A Encoder writes TOML to an output stream.
- type Encoder struct {
- w io.Writer
- cfg *Config
- }
- // NewEncoder returns a new Encoder that writes to w.
- func (cfg *Config) NewEncoder(w io.Writer) *Encoder {
- return &Encoder{w, cfg}
- }
- // Encode writes the TOML of v to the stream.
- // See the documentation for Marshal for details about the conversion of Go values to TOML.
- func (e *Encoder) Encode(v interface{}) error {
- var (
- buf = &tableBuf{typ: ast.TableTypeNormal}
- rv = reflect.ValueOf(v)
- err error
- )
- for rv.Kind() == reflect.Ptr {
- if rv.IsNil() {
- return &marshalNilError{rv.Type()}
- }
- rv = rv.Elem()
- }
- switch rv.Kind() {
- case reflect.Struct:
- err = buf.structFields(e.cfg, rv)
- case reflect.Map:
- err = buf.mapFields(e.cfg, rv)
- case reflect.Interface:
- return e.Encode(rv.Interface())
- default:
- err = &marshalTableError{rv.Type()}
- }
- if err != nil {
- return err
- }
- return buf.writeTo(e.w, "")
- }
- // Marshaler can be implemented to override the encoding of TOML values. The returned text
- // must be a simple TOML value (i.e. not a table) and is inserted into marshaler output.
- //
- // This interface exists for backwards-compatibility reasons. You probably want to
- // implement encoding.TextMarshaler or MarshalerRec instead.
- type Marshaler interface {
- MarshalTOML() ([]byte, error)
- }
- // MarshalerRec can be implemented to override the TOML encoding of a type.
- // The returned value is marshaled in place of the receiver.
- type MarshalerRec interface {
- MarshalTOML() (interface{}, error)
- }
- type tableBuf struct {
- name string // already escaped / quoted
- body []byte
- children []*tableBuf
- typ ast.TableType
- arrayDepth int
- }
- func (b *tableBuf) writeTo(w io.Writer, prefix string) error {
- key := b.name // TODO: escape dots
- if prefix != "" {
- key = prefix + "." + key
- }
- if b.name != "" {
- head := "[" + key + "]"
- if b.typ == ast.TableTypeArray {
- head = "[" + head + "]"
- }
- head += "\n"
- if _, err := io.WriteString(w, head); err != nil {
- return err
- }
- }
- if _, err := w.Write(b.body); err != nil {
- return err
- }
- for i, child := range b.children {
- if len(b.body) > 0 || i > 0 {
- if _, err := w.Write([]byte("\n")); err != nil {
- return err
- }
- }
- if err := child.writeTo(w, key); err != nil {
- return err
- }
- }
- return nil
- }
- func (b *tableBuf) newChild(name string) *tableBuf {
- child := &tableBuf{name: quoteName(name), typ: ast.TableTypeNormal}
- if b.arrayDepth > 0 {
- child.typ = ast.TableTypeArray
- }
- return child
- }
- func (b *tableBuf) addChild(child *tableBuf) {
- // Empty table elision: we can avoid writing a table that doesn't have any keys on its
- // own. Array tables can't be elided because they define array elements (which would
- // be missing if elided).
- if len(child.body) == 0 && child.typ == ast.TableTypeNormal {
- for _, gchild := range child.children {
- gchild.name = child.name + "." + gchild.name
- b.addChild(gchild)
- }
- return
- }
- b.children = append(b.children, child)
- }
- func (b *tableBuf) structFields(cfg *Config, rv reflect.Value) error {
- rt := rv.Type()
- for i := 0; i < rv.NumField(); i++ {
- ft := rt.Field(i)
- if ft.PkgPath != "" && !ft.Anonymous { // not exported
- continue
- }
- name, rest := extractTag(ft.Tag.Get(fieldTagName))
- if name == tagSkip {
- continue
- }
- fv := rv.Field(i)
- if rest == tagOmitempty && isEmptyValue(fv) {
- continue
- }
- if name == "" {
- name = cfg.FieldToKey(rt, ft.Name)
- }
- if err := b.field(cfg, name, fv); err != nil {
- return err
- }
- }
- return nil
- }
- type mapKeyList []struct {
- key string
- value reflect.Value
- }
- func (l mapKeyList) Len() int { return len(l) }
- func (l mapKeyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
- func (l mapKeyList) Less(i, j int) bool { return l[i].key < l[j].key }
- func (b *tableBuf) mapFields(cfg *Config, rv reflect.Value) error {
- keys := rv.MapKeys()
- keylist := make(mapKeyList, len(keys))
- for i, key := range keys {
- var err error
- keylist[i].key, err = encodeMapKey(key)
- if err != nil {
- return err
- }
- keylist[i].value = rv.MapIndex(key)
- }
- sort.Sort(keylist)
- for _, kv := range keylist {
- if err := b.field(cfg, kv.key, kv.value); err != nil {
- return err
- }
- }
- return nil
- }
- func (b *tableBuf) field(cfg *Config, name string, rv reflect.Value) error {
- off := len(b.body)
- b.body = append(b.body, quoteName(name)...)
- b.body = append(b.body, " = "...)
- isTable, err := b.value(cfg, rv, name)
- if isTable {
- b.body = b.body[:off] // rub out "key ="
- } else {
- b.body = append(b.body, '\n')
- }
- return err
- }
- func (b *tableBuf) value(cfg *Config, rv reflect.Value, name string) (bool, error) {
- isMarshaler, isTable, err := b.marshaler(cfg, rv, name)
- if isMarshaler {
- return isTable, err
- }
- switch rv.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- b.body = strconv.AppendInt(b.body, rv.Int(), 10)
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- b.body = strconv.AppendUint(b.body, rv.Uint(), 10)
- case reflect.Float32, reflect.Float64:
- b.body = strconv.AppendFloat(b.body, rv.Float(), 'e', -1, 64)
- case reflect.Bool:
- b.body = strconv.AppendBool(b.body, rv.Bool())
- case reflect.String:
- b.body = strconv.AppendQuote(b.body, rv.String())
- case reflect.Ptr, reflect.Interface:
- if rv.IsNil() {
- return false, &marshalNilError{rv.Type()}
- }
- return b.value(cfg, rv.Elem(), name)
- case reflect.Slice, reflect.Array:
- rvlen := rv.Len()
- if rvlen == 0 {
- b.body = append(b.body, '[', ']')
- return false, nil
- }
- b.arrayDepth++
- wroteElem := false
- b.body = append(b.body, '[')
- for i := 0; i < rvlen; i++ {
- isTable, err := b.value(cfg, rv.Index(i), name)
- if err != nil {
- return isTable, err
- }
- wroteElem = wroteElem || !isTable
- if wroteElem {
- if i < rvlen-1 {
- b.body = append(b.body, ',', ' ')
- } else {
- b.body = append(b.body, ']')
- }
- }
- }
- if !wroteElem {
- b.body = b.body[:len(b.body)-1] // rub out '['
- }
- b.arrayDepth--
- return !wroteElem, nil
- case reflect.Struct:
- child := b.newChild(name)
- err := child.structFields(cfg, rv)
- b.addChild(child)
- return true, err
- case reflect.Map:
- child := b.newChild(name)
- err := child.mapFields(cfg, rv)
- b.addChild(child)
- return true, err
- default:
- return false, fmt.Errorf("toml: marshal: unsupported type %v", rv.Kind())
- }
- return false, nil
- }
- func (b *tableBuf) marshaler(cfg *Config, rv reflect.Value, name string) (handled, isTable bool, err error) {
- switch t := rv.Interface().(type) {
- case encoding.TextMarshaler:
- enc, err := t.MarshalText()
- if err != nil {
- return true, false, err
- }
- b.body = encodeTextMarshaler(b.body, string(enc))
- return true, false, nil
- case MarshalerRec:
- newval, err := t.MarshalTOML()
- if err != nil {
- return true, false, err
- }
- isTable, err = b.value(cfg, reflect.ValueOf(newval), name)
- return true, isTable, err
- case Marshaler:
- enc, err := t.MarshalTOML()
- if err != nil {
- return true, false, err
- }
- b.body = append(b.body, enc...)
- return true, false, nil
- }
- return false, false, nil
- }
- func encodeTextMarshaler(buf []byte, v string) []byte {
- // Emit the value without quotes if possible.
- if v == "true" || v == "false" {
- return append(buf, v...)
- } else if _, err := time.Parse(time.RFC3339Nano, v); err == nil {
- return append(buf, v...)
- } else if _, err := strconv.ParseInt(v, 10, 64); err == nil {
- return append(buf, v...)
- } else if _, err := strconv.ParseUint(v, 10, 64); err == nil {
- return append(buf, v...)
- } else if _, err := strconv.ParseFloat(v, 64); err == nil {
- return append(buf, v...)
- }
- return strconv.AppendQuote(buf, v)
- }
- func encodeMapKey(rv reflect.Value) (string, error) {
- if rv.Kind() == reflect.String {
- return rv.String(), nil
- }
- if tm, ok := rv.Interface().(encoding.TextMarshaler); ok {
- b, err := tm.MarshalText()
- return string(b), err
- }
- switch rv.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return strconv.FormatInt(rv.Int(), 10), nil
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return strconv.FormatUint(rv.Uint(), 10), nil
- }
- return "", fmt.Errorf("toml: invalid map key type %v", rv.Type())
- }
- func isEmptyValue(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Array:
- // encoding/json treats all arrays with non-zero length as non-empty. We check the
- // array content here because zero-length arrays are almost never used.
- len := v.Len()
- for i := 0; i < len; i++ {
- if !isEmptyValue(v.Index(i)) {
- return false
- }
- }
- return true
- case reflect.Map, reflect.Slice, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
- }
- return false
- }
- func quoteName(s string) string {
- if len(s) == 0 {
- return strconv.Quote(s)
- }
- for _, r := range s {
- if r >= '0' && r <= '9' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r == '-' || r == '_' {
- continue
- }
- return strconv.Quote(s)
- }
- return s
- }
|