зеркало из https://github.com/go-gitea/yaml.git
272 строки
7.4 KiB
Go
272 строки
7.4 KiB
Go
//
|
|
// goyaml - YAML support for the Go language
|
|
//
|
|
// https://wiki.ubuntu.com/goyaml
|
|
//
|
|
// Copyright (c) 2011 Canonical Ltd.
|
|
//
|
|
// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
|
|
//
|
|
package goyaml
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
func handleErr(err *error) {
|
|
if r := recover(); r != nil {
|
|
if _, ok := r.(runtime.Error); ok {
|
|
panic(r)
|
|
} else if _, ok := r.(*reflect.ValueError); ok {
|
|
panic(r)
|
|
} else if _, ok := r.(externalPanic); ok {
|
|
panic(r)
|
|
} else if s, ok := r.(string); ok {
|
|
*err = errors.New("YAML error: " + s)
|
|
} else if e, ok := r.(error); ok {
|
|
*err = e
|
|
} else {
|
|
panic(r)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Objects implementing the goyaml.Setter interface will receive the YAML
|
|
// tag and value via the SetYAML method during unmarshaling, rather than
|
|
// being implicitly assigned by the goyaml machinery. If setting the value
|
|
// works, the method should return true. If it returns false, the given
|
|
// value will be omitted from maps and slices.
|
|
type Setter interface {
|
|
SetYAML(tag string, value interface{}) bool
|
|
}
|
|
|
|
// Objects implementing the goyaml.Getter interface will get the GetYAML()
|
|
// method called when goyaml is requested to marshal the given value, and
|
|
// the result of this method will be marshaled in place of the actual object.
|
|
type Getter interface {
|
|
GetYAML() (tag string, value interface{})
|
|
}
|
|
|
|
// Unmarshal decodes the first document found within the in byte slice
|
|
// and assigns decoded values into the object pointed by out.
|
|
//
|
|
// Maps, pointers to structs and ints, etc, may all be used as out values.
|
|
// If an internal pointer within a struct is not initialized, goyaml
|
|
// will initialize it if necessary for unmarshalling the provided data,
|
|
// but the struct provided as out must not be a nil pointer.
|
|
//
|
|
// The type of the decoded values and the type of out will be considered,
|
|
// and Unmarshal() will do the best possible job to unmarshal values
|
|
// appropriately. It is NOT considered an error, though, to skip values
|
|
// because they are not available in the decoded YAML, or if they are not
|
|
// compatible with the out value. To ensure something was properly
|
|
// unmarshaled use a map or compare against the previous value for the
|
|
// field (usually the zero value).
|
|
//
|
|
// Struct fields are only unmarshalled if they are exported (have an
|
|
// upper case first letter), and will be unmarshalled using the field
|
|
// name lowercased by default. When custom field names are desired, the
|
|
// tag value may be used to tweak the name. Everything before the first
|
|
// comma in the field tag will be used as the name. The values following
|
|
// the comma are used to tweak the marshalling process (see Marshal).
|
|
//
|
|
// For example:
|
|
//
|
|
// type T struct {
|
|
// F int "a,omitempty"
|
|
// B int
|
|
// }
|
|
// var T t
|
|
// goyaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
|
//
|
|
func Unmarshal(in []byte, out interface{}) (err error) {
|
|
defer handleErr(&err)
|
|
d := newDecoder()
|
|
p := newParser(in)
|
|
defer p.destroy()
|
|
node := p.parse()
|
|
if node != nil {
|
|
d.unmarshal(node, reflect.ValueOf(out))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Marshal serializes the value provided into a YAML document. The structure
|
|
// of the generated document will reflect the structure of the value itself.
|
|
// Maps, pointers to structs and ints, etc, may all be used as the in value.
|
|
//
|
|
// In the case of struct values, only exported fields will be serialized.
|
|
// The lowercased field name is used as the key for each exported field,
|
|
// but this behavior may be changed using the respective field tag.
|
|
// The tag may also contain flags to tweak the marshalling behavior for
|
|
// the field. The tag formats accepted are:
|
|
//
|
|
// "[<key>][,<flag1>[,<flag2>]]"
|
|
//
|
|
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
|
//
|
|
// The following flags are currently supported:
|
|
//
|
|
// omitempty Only include the field if it's not set to the zero
|
|
// value for the type or to empty slices or maps.
|
|
// Does not apply to zero valued structs.
|
|
//
|
|
// flow Marshal using a flow style (useful for structs,
|
|
// sequences and maps.
|
|
//
|
|
// In addition, if the key is "-", the field is ignored.
|
|
//
|
|
// For example:
|
|
//
|
|
// type T struct {
|
|
// F int "a,omitempty"
|
|
// B int
|
|
// }
|
|
// goyaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
|
// goyaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
|
//
|
|
func Marshal(in interface{}) (out []byte, err error) {
|
|
//defer handleErr(&err)
|
|
e := newEncoder()
|
|
defer e.destroy()
|
|
e.marshal("", reflect.ValueOf(in))
|
|
e.finish()
|
|
out = e.out
|
|
return
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Maintain a mapping of keys to structure field indexes
|
|
|
|
// The code in this section was copied from gobson.
|
|
|
|
type structFields struct {
|
|
Map map[string]fieldInfo
|
|
List []fieldInfo
|
|
}
|
|
|
|
type fieldInfo struct {
|
|
Key string
|
|
Num int
|
|
OmitEmpty bool
|
|
Flow bool
|
|
}
|
|
|
|
var fieldMap = make(map[reflect.Type]*structFields)
|
|
var fieldMapMutex sync.RWMutex
|
|
|
|
type externalPanic string
|
|
|
|
func (e externalPanic) String() string {
|
|
return string(e)
|
|
}
|
|
|
|
func getStructFields(st reflect.Type) (*structFields, error) {
|
|
fieldMapMutex.RLock()
|
|
fields, found := fieldMap[st]
|
|
fieldMapMutex.RUnlock()
|
|
if found {
|
|
return fields, nil
|
|
}
|
|
|
|
n := st.NumField()
|
|
fieldsMap := make(map[string]fieldInfo)
|
|
fieldsList := make([]fieldInfo, n)
|
|
for i := 0; i != n; i++ {
|
|
field := st.Field(i)
|
|
if field.PkgPath != "" {
|
|
continue // Private field
|
|
}
|
|
|
|
info := fieldInfo{Num: i}
|
|
|
|
tag := field.Tag.Get("yaml")
|
|
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
|
tag = string(field.Tag)
|
|
}
|
|
if tag == "-" {
|
|
continue
|
|
}
|
|
|
|
// XXX Drop this after a few releases.
|
|
if s := strings.Index(tag, "/"); s >= 0 {
|
|
recommend := tag[:s]
|
|
for _, c := range tag[s+1:] {
|
|
switch c {
|
|
case 'c':
|
|
recommend += ",omitempty"
|
|
case 'f':
|
|
recommend += ",flow"
|
|
default:
|
|
msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", string([]byte{uint8(c)}), tag, st)
|
|
panic(externalPanic(msg))
|
|
}
|
|
}
|
|
msg := fmt.Sprintf("Replace tag %q in field %s of type %s by %q", tag, field.Name, st, recommend)
|
|
panic(externalPanic(msg))
|
|
}
|
|
|
|
fields := strings.Split(tag, ",")
|
|
if len(fields) > 1 {
|
|
for _, flag := range fields[1:] {
|
|
switch flag {
|
|
case "omitempty":
|
|
info.OmitEmpty = true
|
|
case "flow":
|
|
info.Flow = true
|
|
default:
|
|
msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
|
|
panic(externalPanic(msg))
|
|
}
|
|
}
|
|
tag = fields[0]
|
|
}
|
|
|
|
if tag != "" {
|
|
info.Key = tag
|
|
} else {
|
|
info.Key = strings.ToLower(field.Name)
|
|
}
|
|
|
|
if _, found = fieldsMap[info.Key]; found {
|
|
msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
|
|
return nil, errors.New(msg)
|
|
}
|
|
|
|
fieldsList[len(fieldsMap)] = info
|
|
fieldsMap[info.Key] = info
|
|
}
|
|
|
|
fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]}
|
|
|
|
fieldMapMutex.Lock()
|
|
fieldMap[st] = fields
|
|
fieldMapMutex.Unlock()
|
|
return fields, nil
|
|
}
|
|
|
|
func isZero(v reflect.Value) bool {
|
|
switch v.Kind() {
|
|
case reflect.String:
|
|
return len(v.String()) == 0
|
|
case reflect.Interface, reflect.Ptr:
|
|
return v.IsNil()
|
|
case reflect.Slice:
|
|
return v.Len() == 0
|
|
case reflect.Map:
|
|
return v.Len() == 0
|
|
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.Bool:
|
|
return !v.Bool()
|
|
}
|
|
return false
|
|
}
|