Sfoglia il codice sorgente

Add support for ,inline flag with struct values.

This also adds the code to do inlining of maps, as supported by bson
(the code was copied from mgo/bson), but it's disabled for the moment
as I'll need more time to implement it.

R=jameinel, rog
CC=
https://codereview.appspot.com/10366044
Gustavo Niemeyer 13 anni fa
parent
commit
9ca8abd688
5 ha cambiato i file con 136 aggiunte e 62 eliminazioni
  1. 9 4
      decode.go
  2. 18 0
      decode_test.go
  3. 8 3
      encode.go
  4. 28 11
      encode_test.go
  5. 73 44
      goyaml.go

+ 9 - 4
decode.go

@@ -433,19 +433,24 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
 }
 
 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

+ 18 - 0
decode_test.go

@@ -332,6 +332,24 @@ var unmarshalTests = []struct {
 			"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) {

+ 8 - 3
encode.go

@@ -109,13 +109,18 @@ func (e *encoder) mapv(tag string, in reflect.Value) {
 }
 
 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
 			}

+ 28 - 11
encode_test.go

@@ -200,6 +200,15 @@ var marshalTests = []struct {
 		}{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 @@ func (s *S) TestMarshal(c *C) {
 	}
 }
 
-//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"}
 

+ 73 - 44
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 @@ type Getter interface {
 // 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 @@ func Unmarshal(in []byte, out interface{}) (err error) {
 // 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 @@ func Unmarshal(in []byte, out interface{}) (err error) {
 //     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 @@ func Unmarshal(in []byte, out interface{}) (err error) {
 //     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 @@ func Marshal(in interface{}) (out []byte, err error) {
 
 // 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 @@ type fieldInfo struct {
 	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 @@ func (e externalPanic) String() string {
 	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 @@ func getStructFields(st reflect.Type) (*structFields, error) {
 			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 @@ func getStructFields(st reflect.Type) (*structFields, error) {
 					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 @@ func getStructFields(st reflect.Type) (*structFields, error) {
 			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 @@ func getStructFields(st reflect.Type) (*structFields, error) {
 			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 {