Merge lp:~niemeyer/goyaml/inline-struct into lp:goyaml

Proposed by Gustavo Niemeyer
Status: Merged
Merged at revision: 46
Proposed branch: lp:~niemeyer/goyaml/inline-struct
Merge into: lp:goyaml
Diff against target: 221 lines (+82/-26)
5 files modified
decode.go (+7/-1)
decode_test.go (+9/-0)
encode.go (+6/-1)
encode_test.go (+9/-0)
goyaml.go (+51/-24)
To merge this branch: bzr merge lp:~niemeyer/goyaml/inline-struct
Reviewer Review Type Date Requested Status
goyaml maintainers Pending
Review via email: mp+169981@code.launchpad.net

Description of the change

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.

https://codereview.appspot.com/10366044/

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Reviewers: mp+169981_code.launchpad.net,

Message:
Please take a look.

Description:
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.

https://code.launchpad.net/~niemeyer/goyaml/inline-struct/+merge/169981

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/10366044/

Affected files:
   A [revision details]
   M decode.go
   M decode_test.go
   M encode.go
   M encode_test.go
   M goyaml.go

Revision history for this message
John A Meinel (jameinel) wrote :

The encode side of code looks reasonable, but what happens on decode if
you have ',inline' set ?

https://codereview.appspot.com/10366044/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

On 2013/06/19 04:47:34, jameinel wrote:
> The encode side of code looks reasonable, but what happens on decode
if you have
> ',inline' set ?

I don't understand the question. The logic is symmetrical, and tested?

https://codereview.appspot.com/10366044/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

LGTM with some more tests and some other thoughts.

https://codereview.appspot.com/10366044/diff/1/encode.go
File encode.go (right):

https://codereview.appspot.com/10366044/diff/1/encode.go#newcode122
encode.go:122: value = in.FieldByIndex(info.Inline)
cool, i didn't know you could use FieldByIndex on arbitrary
non-embedded structs.

is there a particular reason you can't unify Num and Inline, BTW?

https://codereview.appspot.com/10366044/diff/1/encode_test.go
File encode_test.go (right):

https://codereview.appspot.com/10366044/diff/1/encode_test.go#newcode211
encode_test.go:211: },
i'd like to see more tests of the inline functionality.
in particular:
- more than one inline struct
- nested inlines.
- errors from field conflicts.

https://codereview.appspot.com/10366044/diff/1/goyaml.go
File goyaml.go (right):

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode124
goyaml.go:124: // struct.
// Any clash of field names will result in an error.
?

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode155
goyaml.go:155: InlineMap int
i'd find some comments on these fields helpful for understanding the
code.

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode163
goyaml.go:163: Inline []int
// Inline holds the set of field indexes for
// looking up the given field.

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode232
goyaml.go:232: case reflect.Struct:
what about pointers to struct?

it's reasonable, i think, to be able to do:

type Foo struct {
    One string
    Other *Other `goyaml:",inline"`
}

so you can inline the same kinds of things that you can
embed.

also, for the future, you might want to consider allowing
interface types too - the treatment could be similar to
inline maps.

https://codereview.appspot.com/10366044/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Thanks for the review, Roger.

https://codereview.appspot.com/10366044/diff/1/encode.go
File encode.go (right):

https://codereview.appspot.com/10366044/diff/1/encode.go#newcode122
encode.go:122: value = in.FieldByIndex(info.Inline)
On 2013/06/19 15:11:12, rog wrote:
> is there a particular reason you can't unify Num and Inline, BTW?

No big reason, other than Num being very cheap, both to implement and to
execute, and being the common case by far.

https://codereview.appspot.com/10366044/diff/1/encode_test.go
File encode_test.go (right):

https://codereview.appspot.com/10366044/diff/1/encode_test.go#newcode211
encode_test.go:211: },
On 2013/06/19 15:11:12, rog wrote:
> i'd like to see more tests of the inline functionality.
> in particular:
> - more than one inline struct
> - nested inlines.
> - errors from field conflicts.

Done.

https://codereview.appspot.com/10366044/diff/1/goyaml.go
File goyaml.go (right):

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode124
goyaml.go:124: // struct.
On 2013/06/19 15:11:12, rog wrote:
> // Any clash of field names will result in an error.
> ?

That's not specific to ,inline. I've added a note both to Marshal and
Unmarshal pointing out that conflicting names result in a runtime error.

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode155
goyaml.go:155: InlineMap int
On 2013/06/19 15:11:12, rog wrote:
> i'd find some comments on these fields helpful for understanding the
code.

Improved the struct names and documentation.

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode163
goyaml.go:163: Inline []int
On 2013/06/19 15:11:12, rog wrote:
> // Inline holds the set of field indexes for
> // looking up the given field.

Not quite, but documented appropriately.

https://codereview.appspot.com/10366044/diff/1/goyaml.go#newcode232
goyaml.go:232: case reflect.Struct:
On 2013/06/19 15:11:12, rog wrote:
> what about pointers to struct?

We can add support for them later, but I'd prefer to keep it simple at
this time. Pointers makes the implementation more complex because the
struct has to be initialized if one of the fields is found, and it also
means we're creating garbage on every iteration of a set of documents.
We can do it, but I'd prefer to wait until people need it. I've never
had anyone asking for it with bson.

https://codereview.appspot.com/10366044/

lp:~niemeyer/goyaml/inline-struct updated
46. By Gustavo Niemeyer

Review by Roger plus overall naming improvements.

47. By Gustavo Niemeyer

Merged from tip.

48. By Gustavo Niemeyer

go fmt

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

*** Submitted:

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

https://codereview.appspot.com/10366044/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'decode.go'
2--- decode.go 2013-04-30 00:42:36 +0000
3+++ decode.go 2013-06-18 03:47:27 +0000
4@@ -445,7 +445,13 @@
5 continue
6 }
7 if info, ok := fieldsMap[name.String()]; ok {
8- d.unmarshal(n.children[i+1], out.Field(info.Num))
9+ var field reflect.Value
10+ if info.Inline == nil {
11+ field = out.Field(info.Num)
12+ } else {
13+ field = out.FieldByIndex(info.Inline)
14+ }
15+ d.unmarshal(n.children[i+1], field)
16 }
17 }
18 return true
19
20=== modified file 'decode_test.go'
21--- decode_test.go 2013-05-29 17:23:20 +0000
22+++ decode_test.go 2013-06-18 03:47:27 +0000
23@@ -317,6 +317,15 @@
24 B int "-"
25 }{1, 0},
26 },
27+
28+ // Struct inlining
29+ {
30+ "a: 1\nb: 2\n",
31+ &struct {
32+ A int
33+ C struct{ B int } `yaml:",inline"`
34+ }{1, struct{ B int }{2}},
35+ },
36 }
37
38 func (s *S) TestUnmarshal(c *C) {
39
40=== modified file 'encode.go'
41--- encode.go 2013-04-28 04:28:40 +0000
42+++ encode.go 2013-06-18 03:47:27 +0000
43@@ -115,7 +115,12 @@
44 }
45 e.mappingv(tag, func() {
46 for _, info := range fields.List {
47- value := in.Field(info.Num)
48+ var value reflect.Value
49+ if info.Inline == nil {
50+ value = in.Field(info.Num)
51+ } else {
52+ value = in.FieldByIndex(info.Inline)
53+ }
54 if info.OmitEmpty && isZero(value) {
55 continue
56 }
57
58=== modified file 'encode_test.go'
59--- encode_test.go 2013-04-29 15:13:04 +0000
60+++ encode_test.go 2013-06-18 03:47:27 +0000
61@@ -200,6 +200,15 @@
62 }{1, 2},
63 "a: 1\n",
64 },
65+
66+ // Struct inlining
67+ {
68+ &struct {
69+ A int
70+ C struct{ B int } `yaml:",inline"`
71+ }{1, struct{ B int }{2}},
72+ "a: 1\nb: 2\n",
73+ },
74 }
75
76 func (s *S) TestMarshal(c *C) {
77
78=== modified file 'goyaml.go'
79--- goyaml.go 2013-04-17 18:57:10 +0000
80+++ goyaml.go 2013-06-18 03:47:27 +0000
81@@ -119,6 +119,10 @@
82 // flow Marshal using a flow style (useful for structs,
83 // sequences and maps.
84 //
85+// inline Inline the struct it's applied to, so its fields
86+// are processed as if they were part of the outer
87+// struct.
88+//
89 // In addition, if the key is "-", the field is ignored.
90 //
91 // For example:
92@@ -131,7 +135,7 @@
93 // goyaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
94 //
95 func Marshal(in interface{}) (out []byte, err error) {
96- //defer handleErr(&err)
97+ defer handleErr(&err)
98 e := newEncoder()
99 defer e.destroy()
100 e.marshal("", reflect.ValueOf(in))
101@@ -146,8 +150,9 @@
102 // The code in this section was copied from gobson.
103
104 type structFields struct {
105- Map map[string]fieldInfo
106- List []fieldInfo
107+ Map map[string]fieldInfo
108+ List []fieldInfo
109+ InlineMap int
110 }
111
112 type fieldInfo struct {
113@@ -155,6 +160,7 @@
114 Num int
115 OmitEmpty bool
116 Flow bool
117+ Inline []int
118 }
119
120 var fieldMap = make(map[reflect.Type]*structFields)
121@@ -176,7 +182,8 @@
122
123 n := st.NumField()
124 fieldsMap := make(map[string]fieldInfo)
125- fieldsList := make([]fieldInfo, n)
126+ fieldsList := make([]fieldInfo, 0, n)
127+ inlineMap := -1
128 for i := 0; i != n; i++ {
129 field := st.Field(i)
130 if field.PkgPath != "" {
131@@ -193,24 +200,7 @@
132 continue
133 }
134
135- // XXX Drop this after a few releases.
136- if s := strings.Index(tag, "/"); s >= 0 {
137- recommend := tag[:s]
138- for _, c := range tag[s+1:] {
139- switch c {
140- case 'c':
141- recommend += ",omitempty"
142- case 'f':
143- recommend += ",flow"
144- default:
145- msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", string([]byte{uint8(c)}), tag, st)
146- panic(externalPanic(msg))
147- }
148- }
149- msg := fmt.Sprintf("Replace tag %q in field %s of type %s by %q", tag, field.Name, st, recommend)
150- panic(externalPanic(msg))
151- }
152-
153+ inline := false
154 fields := strings.Split(tag, ",")
155 if len(fields) > 1 {
156 for _, flag := range fields[1:] {
157@@ -219,6 +209,8 @@
158 info.OmitEmpty = true
159 case "flow":
160 info.Flow = true
161+ case "inline":
162+ inline = true
163 default:
164 msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
165 panic(externalPanic(msg))
166@@ -227,6 +219,41 @@
167 tag = fields[0]
168 }
169
170+ if inline {
171+ switch field.Type.Kind() {
172+ //case reflect.Map:
173+ // if inlineMap >= 0 {
174+ // return nil, errors.New("Multiple ,inline maps in struct " + st.String())
175+ // }
176+ // if field.Type.Key() != reflect.TypeOf("") {
177+ // return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
178+ // }
179+ // inlineMap = info.Num
180+ case reflect.Struct:
181+ sfields, err := getStructFields(field.Type)
182+ if err != nil {
183+ return nil, err
184+ }
185+ for _, finfo := range sfields.List {
186+ if _, found := fieldsMap[finfo.Key]; found {
187+ msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
188+ return nil, errors.New(msg)
189+ }
190+ if finfo.Inline == nil {
191+ finfo.Inline = []int{i, finfo.Num}
192+ } else {
193+ finfo.Inline = append([]int{i}, finfo.Inline...)
194+ }
195+ fieldsMap[finfo.Key] = finfo
196+ fieldsList = append(fieldsList, finfo)
197+ }
198+ default:
199+ //panic("Option ,inline needs a struct value or map field")
200+ panic("Option ,inline needs a struct value field")
201+ }
202+ continue
203+ }
204+
205 if tag != "" {
206 info.Key = tag
207 } else {
208@@ -238,11 +265,11 @@
209 return nil, errors.New(msg)
210 }
211
212- fieldsList[len(fieldsMap)] = info
213+ fieldsList = append(fieldsList, info)
214 fieldsMap[info.Key] = info
215 }
216
217- fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]}
218+ fields = &structFields{fieldsMap, fieldsList, inlineMap}
219
220 fieldMapMutex.Lock()
221 fieldMap[st] = fields

Subscribers

People subscribed via source and target branches