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
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..9b2a263 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..dbc633e 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 {