forked from go-yaml/yaml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,301 @@ | ||
// Package yaml implements YAML support for the Go language. | ||
package yaml | ||
|
||
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) | ||
} | ||
} | ||
} | ||
|
||
// The Setter interface may be implemented by types to do their own custom | ||
// unmarshalling of YAML values, rather than being implicitly assigned by | ||
// the yaml package machinery. If setting the value works, the method should | ||
// return true. If it returns false, the value is considered unsupported | ||
// and is omitted from maps and slices. | ||
type Setter interface { | ||
SetYAML(tag string, value interface{}) bool | ||
} | ||
|
||
// The Setter interface is implemented by types to do their own custom | ||
// marshalling into a YAML tag and value. | ||
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 out value. | ||
// | ||
// Maps and pointers (to a struct, string, int, etc) are accepted as out | ||
// values. If an internal pointer within a struct is not initialized, | ||
// the yaml package will initialize it if necessary for unmarshalling | ||
// the provided data. The out parameter must not be nil. | ||
// | ||
// 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 are unmarshalled using the field name | ||
// lowercased as the default key. Custom keys may be defined via the | ||
// "yaml" name in the field tag: the content preceding the first comma | ||
// is used as the key, and the following comma-separated options are | ||
// used to tweak the marshalling process (see Marshal). | ||
// Conflicting names result in a runtime error. | ||
// | ||
// For example: | ||
// | ||
// type T struct { | ||
// F int `yaml:"a,omitempty"` | ||
// B int | ||
// } | ||
// var T t | ||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) | ||
// | ||
// See the documentation of Marshal for the format of tags and a list of | ||
// supported tag options. | ||
// | ||
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 and pointers (to struct, string, int, etc) are accepted as the in value. | ||
// | ||
// Struct fields are only unmarshalled if they are exported (have an upper case | ||
// first letter), and are unmarshalled using the field name lowercased as the | ||
// default key. Custom keys may be defined via the "yaml" name in the field | ||
// tag: the content preceding the first comma is used as the key, and the | ||
// following comma-separated options are used to tweak the marshalling process. | ||
// Conflicting names result in a runtime error. | ||
// | ||
// The field tag format accepted is: | ||
// | ||
// `(...) 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. | ||
// | ||
// inline Inline the struct it's applied to, so its fields | ||
// are processed as if they were part of the outer | ||
// struct. | ||
// | ||
// In addition, if the key is "-", the field is ignored. | ||
// | ||
// For example: | ||
// | ||
// type T struct { | ||
// F int "a,omitempty" | ||
// B int | ||
// } | ||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" | ||
// yaml.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. | ||
|
||
// structInfo holds details for the serialization of fields of | ||
// a given struct. | ||
type structInfo struct { | ||
FieldsMap map[string]fieldInfo | ||
FieldsList []fieldInfo | ||
|
||
// InlineMap is the number of the field in the struct that | ||
// contains an ,inline map, or -1 if there's none. | ||
InlineMap int | ||
} | ||
|
||
type fieldInfo struct { | ||
Key string | ||
Num int | ||
OmitEmpty bool | ||
Flow bool | ||
|
||
// Inline holds the field index if the field is part of an inlined struct. | ||
Inline []int | ||
} | ||
|
||
var structMap = make(map[reflect.Type]*structInfo) | ||
var fieldMapMutex sync.RWMutex | ||
|
||
type externalPanic string | ||
|
||
func (e externalPanic) String() string { | ||
return string(e) | ||
} | ||
|
||
func getStructInfo(st reflect.Type) (*structInfo, error) { | ||
fieldMapMutex.RLock() | ||
sinfo, found := structMap[st] | ||
fieldMapMutex.RUnlock() | ||
if found { | ||
return sinfo, nil | ||
} | ||
|
||
n := st.NumField() | ||
fieldsMap := make(map[string]fieldInfo) | ||
fieldsList := make([]fieldInfo, 0, n) | ||
inlineMap := -1 | ||
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 | ||
} | ||
|
||
inline := false | ||
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 | ||
case "inline": | ||
inline = true | ||
default: | ||
msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st) | ||
panic(externalPanic(msg)) | ||
} | ||
} | ||
tag = fields[0] | ||
} | ||
|
||
if inline { | ||
switch field.Type.Kind() { | ||
//case reflect.Map: | ||
// if inlineMap >= 0 { | ||
// return nil, errors.New("Multiple ,inline maps in struct " + st.String()) | ||
// } | ||
// if field.Type.Key() != reflect.TypeOf("") { | ||
// return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) | ||
// } | ||
// inlineMap = info.Num | ||
case reflect.Struct: | ||
sinfo, err := getStructInfo(field.Type) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for _, finfo := range sinfo.FieldsList { | ||
if _, found := fieldsMap[finfo.Key]; found { | ||
msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() | ||
return nil, errors.New(msg) | ||
} | ||
if finfo.Inline == nil { | ||
finfo.Inline = []int{i, finfo.Num} | ||
} else { | ||
finfo.Inline = append([]int{i}, finfo.Inline...) | ||
} | ||
fieldsMap[finfo.Key] = finfo | ||
fieldsList = append(fieldsList, finfo) | ||
} | ||
default: | ||
//panic("Option ,inline needs a struct value or map field") | ||
panic("Option ,inline needs a struct value field") | ||
} | ||
continue | ||
} | ||
|
||
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 = append(fieldsList, info) | ||
fieldsMap[info.Key] = info | ||
} | ||
|
||
sinfo = &structInfo{fieldsMap, fieldsList, inlineMap} | ||
|
||
fieldMapMutex.Lock() | ||
structMap[st] = sinfo | ||
fieldMapMutex.Unlock() | ||
return sinfo, 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 | ||
} |