diff options
Diffstat (limited to 'vendor/github.com/naoina/toml/encode.go')
-rw-r--r-- | vendor/github.com/naoina/toml/encode.go | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/vendor/github.com/naoina/toml/encode.go b/vendor/github.com/naoina/toml/encode.go new file mode 100644 index 000000000..ae6bfd575 --- /dev/null +++ b/vendor/github.com/naoina/toml/encode.go @@ -0,0 +1,398 @@ +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 { + rv := reflect.ValueOf(v) + for rv.Kind() == reflect.Ptr { + if rv.IsNil() { + return &marshalNilError{rv.Type()} + } + rv = rv.Elem() + } + buf := &tableBuf{typ: ast.TableTypeNormal} + var err error + switch rv.Kind() { + case reflect.Struct: + err = buf.structFields(e.cfg, rv) + case reflect.Map: + err = buf.mapFields(e.cfg, rv) + 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 +} |