blob: 60bd682572849aeaf7966d7a7166399c57e6698e [file] [log] [blame]
//
// 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 (
"reflect"
"runtime"
"strings"
"sync"
"os"
)
func handleErr(err *os.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 s, ok := r.(string); ok {
*err = os.NewError("YAML error: " + s)
} else if e, ok := r.(os.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 last
// slash in the field tag will be used as the name. The value following
// the slash is used to tweak the marshalling process (see Marshal).
//
// For example:
//
// type T struct {
// F int "a/c"
// B int
// }
// var T t
// goyaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
//
func Unmarshal(in []byte, out interface{}) (err os.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 writes the object 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.
//
// Struct fields are only marshalled if they are exported (have an
// upper case first letter), and will be marshalled 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 last
// slash in the field tag will be used as the name. The characters
// following the slash are used as flags for the marshalling process,
// with 'c' meaning conditional (only marshal if non-zero), and 'f'
// meaning use flow style (useful for structs, sequences and maps).
//
// For example:
//
// type T struct {
// F int "a/c"
// 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 os.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
Conditional bool
Flow bool
}
var fieldMap = make(map[string]*structFields)
var fieldMapMutex sync.RWMutex
func getStructFields(st reflect.Type) (*structFields, os.Error) {
path := st.PkgPath()
name := st.Name()
fullName := path + "." + name
fieldMapMutex.RLock()
fields, found := fieldMap[fullName]
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}
if s := strings.LastIndex(field.Tag, "/"); s != -1 {
for _, c := range field.Tag[s+1:] {
switch c {
case int('c'):
info.Conditional = true
case int('f'):
info.Flow = true
default:
panic("Unsupported field flag: " + string([]int{c}))
}
}
field.Tag = field.Tag[:s]
}
if field.Tag != "" {
info.Key = field.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, os.NewError(msg)
}
fieldsList[len(fieldsMap)] = info
fieldsMap[info.Key] = info
}
fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]}
if fullName != "." {
fieldMapMutex.Lock()
fieldMap[fullName] = 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:
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
}