| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- //
- // 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
- }
|