Merged from tip.
diff --git a/decode.go b/decode.go index e7fb29b..5dd2ce5 100644 --- a/decode.go +++ b/decode.go
@@ -433,19 +433,24 @@ } func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { - fields, err := getStructFields(out.Type()) + sinfo, err := getStructInfo(out.Type()) if err != nil { panic(err) } name := settableValueOf("") - fieldsMap := fields.Map l := len(n.children) for i := 0; i < l; i += 2 { if !d.unmarshal(n.children[i], name) { continue } - if info, ok := fieldsMap[name.String()]; ok { - d.unmarshal(n.children[i+1], out.Field(info.Num)) + if info, ok := sinfo.FieldsMap[name.String()]; ok { + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) } } return true
diff --git a/decode_test.go b/decode_test.go index bac544b..9b71562 100644 --- a/decode_test.go +++ b/decode_test.go
@@ -332,6 +332,24 @@ "Generic line break (glyphed)\n" + "Line separator\u2028Paragraph separator\u2029", }, + + // Struct inlining + { + "a: 1\nb: 2\nc: 3\n", + &struct { + A int + C inlineB `yaml:",inline"` + }{1, inlineB{2, inlineC{3}}}, + }, +} + +type inlineB struct { + B int + inlineC `yaml:",inline"` +} + +type inlineC struct { + C int } func (s *S) TestUnmarshal(c *C) {
diff --git a/encode.go b/encode.go index ad7853c..b228a10 100644 --- a/encode.go +++ b/encode.go
@@ -109,13 +109,18 @@ } func (e *encoder) structv(tag string, in reflect.Value) { - fields, err := getStructFields(in.Type()) + sinfo, err := getStructInfo(in.Type()) if err != nil { panic(err) } e.mappingv(tag, func() { - for _, info := range fields.List { - value := in.Field(info.Num) + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } if info.OmitEmpty && isZero(value) { continue }
diff --git a/encode_test.go b/encode_test.go index 0d34d88..5d16426 100644 --- a/encode_test.go +++ b/encode_test.go
@@ -200,6 +200,15 @@ }{1, 2}, "a: 1\n", }, + + // Struct inlining + { + &struct { + A int + C inlineB `yaml:",inline"` + }{1, inlineB{2, inlineC{3}}}, + "a: 1\nb: 2\nc: 3\n", + }, } func (s *S) TestMarshal(c *C) { @@ -210,17 +219,25 @@ } } -//var unmarshalErrorTests = []struct{data, error string}{ -// {"v: !!float 'error'", "Can't decode !!str 'error' as a !!float"}, -//} -// -//func (s *S) TestUnmarshalErrors(c *C) { -// for _, item := range unmarshalErrorTests { -// var value interface{} -// err := goyaml.Unmarshal([]byte(item.data), &value) -// c.Assert(err, Matches, item.error) -// } -//} +var marshalErrorTests = []struct { + value interface{} + error string +}{ + { + &struct { + B int + inlineB ",inline" + }{1, inlineB{2, inlineC{3}}}, + `Duplicated key 'b' in struct struct \{ B int; .*`, + }, +} + +func (s *S) TestMarshalErrors(c *C) { + for _, item := range marshalErrorTests { + _, err := goyaml.Marshal(item.value) + c.Assert(err, ErrorMatches, item.error) + } +} var marshalTaggedIfaceTest interface{} = &struct{ A string }{"B"}
diff --git a/goyaml.go b/goyaml.go index 1b71c8e..e52a2dd 100644 --- a/goyaml.go +++ b/goyaml.go
@@ -1,12 +1,4 @@ -// -// 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 implements YAML support for the Go language. package goyaml import ( @@ -74,16 +66,20 @@ // 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). +// Conflicting names result in a runtime error. // // For example: // // type T struct { -// F int "a,omitempty" +// F int `yaml:"a,omitempty"` // B int // } // var T t // goyaml.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() @@ -104,9 +100,8 @@ // 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>]]" +// the field. Conflicting names result in a runtime error. The tag format +// accepted is: // // `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)` // @@ -119,6 +114,10 @@ // 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: @@ -131,7 +130,7 @@ // goyaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" // func Marshal(in interface{}) (out []byte, err error) { - //defer handleErr(&err) + defer handleErr(&err) e := newEncoder() defer e.destroy() e.marshal("", reflect.ValueOf(in)) @@ -145,9 +144,15 @@ // The code in this section was copied from gobson. -type structFields struct { - Map map[string]fieldInfo - List []fieldInfo +// 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 { @@ -155,9 +160,12 @@ Num int OmitEmpty bool Flow bool + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int } -var fieldMap = make(map[reflect.Type]*structFields) +var structMap = make(map[reflect.Type]*structInfo) var fieldMapMutex sync.RWMutex type externalPanic string @@ -166,17 +174,18 @@ return string(e) } -func getStructFields(st reflect.Type) (*structFields, error) { +func getStructInfo(st reflect.Type) (*structInfo, error) { fieldMapMutex.RLock() - fields, found := fieldMap[st] + sinfo, found := structMap[st] fieldMapMutex.RUnlock() if found { - return fields, nil + return sinfo, nil } n := st.NumField() fieldsMap := make(map[string]fieldInfo) - fieldsList := make([]fieldInfo, n) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 for i := 0; i != n; i++ { field := st.Field(i) if field.PkgPath != "" { @@ -193,24 +202,7 @@ 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)) - } - + inline := false fields := strings.Split(tag, ",") if len(fields) > 1 { for _, flag := range fields[1:] { @@ -219,6 +211,8 @@ 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)) @@ -227,6 +221,41 @@ 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 { @@ -238,16 +267,16 @@ return nil, errors.New(msg) } - fieldsList[len(fieldsMap)] = info + fieldsList = append(fieldsList, info) fieldsMap[info.Key] = info } - fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]} + sinfo = &structInfo{fieldsMap, fieldsList, inlineMap} fieldMapMutex.Lock() - fieldMap[st] = fields + structMap[st] = sinfo fieldMapMutex.Unlock() - return fields, nil + return sinfo, nil } func isZero(v reflect.Value) bool {