Skip to content

Commit ccff24e

Browse files
authored
Merge pull request #306 from BurntSushi/array
Allow mixed values in arrays
2 parents 01bfc69 + 7d6f80f commit ccff24e

File tree

9 files changed

+197
-154
lines changed

9 files changed

+197
-154
lines changed

decode.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,8 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
315315
}
316316
return badtype("slice", data)
317317
}
318-
sliceLen := datav.Len()
319-
if sliceLen != rv.Len() {
320-
return e("expected array length %d; got TOML array of length %d",
321-
rv.Len(), sliceLen)
318+
if l := datav.Len(); l != rv.Len() {
319+
return e("expected array length %d; got TOML array of length %d", rv.Len(), l)
322320
}
323321
return md.unifySliceArray(datav, rv)
324322
}
@@ -340,11 +338,10 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
340338
}
341339

342340
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
343-
sliceLen := data.Len()
344-
for i := 0; i < sliceLen; i++ {
345-
v := data.Index(i).Interface()
346-
sliceval := indirect(rv.Index(i))
347-
if err := md.unify(v, sliceval); err != nil {
341+
l := data.Len()
342+
for i := 0; i < l; i++ {
343+
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
344+
if err != nil {
348345
return err
349346
}
350347
}

encode.go

Lines changed: 107 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ import (
1717
type tomlEncodeError struct{ error }
1818

1919
var (
20-
errArrayMixedElementTypes = errors.New("toml: cannot encode array with mixed element types")
21-
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
22-
errNonString = errors.New("toml: cannot encode a map with non-string key type")
23-
errAnonNonStruct = errors.New("toml: cannot encode an anonymous field that is not a struct")
24-
errArrayNoTable = errors.New("toml: TOML array element cannot contain a table")
25-
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
26-
errAnything = errors.New("") // used in testing
20+
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
21+
errNonString = errors.New("toml: cannot encode a map with non-string key type")
22+
errAnonNonStruct = errors.New("toml: cannot encode an anonymous field that is not a struct")
23+
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
24+
errAnything = errors.New("") // used in testing
2725
)
2826

2927
var quotedReplacer = strings.NewReplacer(
@@ -141,7 +139,7 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
141139
// generic structs (or whatever the underlying type of a TextMarshaler is).
142140
switch t := rv.Interface().(type) {
143141
case time.Time, encoding.TextMarshaler:
144-
enc.keyEqElement(key, rv)
142+
enc.writeKeyValue(key, rv, false)
145143
return
146144
// TODO: #76 would make this superfluous after implemented.
147145
case Primitive:
@@ -156,12 +154,12 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
156154
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
157155
reflect.Uint64,
158156
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
159-
enc.keyEqElement(key, rv)
157+
enc.writeKeyValue(key, rv, false)
160158
case reflect.Array, reflect.Slice:
161159
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
162160
enc.eArrayOfTables(key, rv)
163161
} else {
164-
enc.keyEqElement(key, rv)
162+
enc.writeKeyValue(key, rv, false)
165163
}
166164
case reflect.Interface:
167165
if rv.IsNil() {
@@ -185,31 +183,31 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) {
185183
}
186184
}
187185

188-
// eElement encodes any value that can be an array element (primitives and
189-
// arrays).
186+
// eElement encodes any value that can be an array element.
190187
func (enc *Encoder) eElement(rv reflect.Value) {
191188
switch v := rv.Interface().(type) {
192189
case time.Time:
193190
// Using TextMarshaler adds extra quotes, which we don't want.
194191
enc.wf(v.Format(time.RFC3339Nano))
195192
return
196193
case encoding.TextMarshaler:
197-
// Special case. Use text marshaler if it's available for this value.
194+
// Use text marshaler if it's available for this value.
198195
if s, err := v.MarshalText(); err != nil {
199196
encPanic(err)
200197
} else {
201198
enc.writeQuoted(string(s))
202199
}
203200
return
204201
}
202+
205203
switch rv.Kind() {
204+
case reflect.String:
205+
enc.writeQuoted(rv.String())
206206
case reflect.Bool:
207207
enc.wf(strconv.FormatBool(rv.Bool()))
208-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
209-
reflect.Int64:
208+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
210209
enc.wf(strconv.FormatInt(rv.Int(), 10))
211-
case reflect.Uint, reflect.Uint8, reflect.Uint16,
212-
reflect.Uint32, reflect.Uint64:
210+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
213211
enc.wf(strconv.FormatUint(rv.Uint(), 10))
214212
case reflect.Float32:
215213
f := rv.Float()
@@ -231,17 +229,19 @@ func (enc *Encoder) eElement(rv reflect.Value) {
231229
}
232230
case reflect.Array, reflect.Slice:
233231
enc.eArrayOrSliceElement(rv)
232+
case reflect.Struct:
233+
enc.eStruct(nil, rv, true)
234+
case reflect.Map:
235+
enc.eMap(nil, rv, true)
234236
case reflect.Interface:
235237
enc.eElement(rv.Elem())
236-
case reflect.String:
237-
enc.writeQuoted(rv.String())
238238
default:
239-
encPanic(fmt.Errorf("unexpected primitive type: %s", rv.Kind()))
239+
encPanic(fmt.Errorf("unexpected primitive type: %T", rv.Interface()))
240240
}
241241
}
242242

243-
// By the TOML spec, all floats must have a decimal with at least one
244-
// number on either side.
243+
// By the TOML spec, all floats must have a decimal with at least one number on
244+
// either side.
245245
func floatAddDecimal(fstr string) string {
246246
if !strings.Contains(fstr, ".") {
247247
return fstr + ".0"
@@ -278,7 +278,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
278278
enc.newline()
279279
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
280280
enc.newline()
281-
enc.eMapOrStruct(key, trv)
281+
enc.eMapOrStruct(key, trv, false)
282282
}
283283
}
284284

@@ -292,22 +292,22 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
292292
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
293293
enc.newline()
294294
}
295-
enc.eMapOrStruct(key, rv)
295+
enc.eMapOrStruct(key, rv, false)
296296
}
297297

298-
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
298+
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
299299
switch rv := eindirect(rv); rv.Kind() {
300300
case reflect.Map:
301-
enc.eMap(key, rv)
301+
enc.eMap(key, rv, inline)
302302
case reflect.Struct:
303-
enc.eStruct(key, rv)
303+
enc.eStruct(key, rv, inline)
304304
default:
305305
// Should never happen?
306306
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
307307
}
308308
}
309309

310-
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
310+
func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
311311
rt := rv.Type()
312312
if rt.Key().Kind() != reflect.String {
313313
encPanic(errNonString)
@@ -325,57 +325,76 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value) {
325325
}
326326
}
327327

328-
var writeMapKeys = func(mapKeys []string) {
328+
var writeMapKeys = func(mapKeys []string, trailC bool) {
329329
sort.Strings(mapKeys)
330-
for _, mapKey := range mapKeys {
331-
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
332-
if isNil(mrv) {
333-
// Don't write anything for nil fields.
330+
for i, mapKey := range mapKeys {
331+
val := rv.MapIndex(reflect.ValueOf(mapKey))
332+
if isNil(val) {
334333
continue
335334
}
336-
enc.encode(key.add(mapKey), mrv)
335+
336+
if inline {
337+
enc.writeKeyValue(Key{mapKey}, val, true)
338+
if trailC || i != len(mapKeys)-1 {
339+
enc.wf(", ")
340+
}
341+
} else {
342+
enc.encode(key.add(mapKey), val)
343+
}
337344
}
338345
}
339-
writeMapKeys(mapKeysDirect)
340-
writeMapKeys(mapKeysSub)
346+
347+
if inline {
348+
enc.wf("{")
349+
}
350+
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
351+
writeMapKeys(mapKeysSub, false)
352+
if inline {
353+
enc.wf("}")
354+
}
341355
}
342356

343-
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
357+
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
344358
// Write keys for fields directly under this key first, because if we write
345-
// a field that creates a new table, then all keys under it will be in that
359+
// a field that creates a new table then all keys under it will be in that
346360
// table (not the one we're writing here).
347-
rt := rv.Type()
348-
var fieldsDirect, fieldsSub [][]int
349-
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
361+
//
362+
// Fields is a [][]int: for fieldsDirect this always has one entry (the
363+
// struct index). For fieldsSub it contains two entries: the parent field
364+
// index from tv, and the field indexes for the fields of the sub.
365+
var (
366+
rt = rv.Type()
367+
fieldsDirect, fieldsSub [][]int
368+
addFields func(rt reflect.Type, rv reflect.Value, start []int)
369+
)
350370
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
351371
for i := 0; i < rt.NumField(); i++ {
352372
f := rt.Field(i)
353-
// skip unexported fields
354-
if f.PkgPath != "" && !f.Anonymous {
373+
if f.PkgPath != "" && !f.Anonymous { /// Skip unexported fields.
355374
continue
356375
}
376+
357377
frv := rv.Field(i)
378+
379+
// Treat anonymous struct fields with tag names as though they are
380+
// not anonymous, like encoding/json does.
381+
//
382+
// Non-struct anonymous fields use the normal encoding logic.
358383
if f.Anonymous {
359384
t := f.Type
360385
switch t.Kind() {
361386
case reflect.Struct:
362-
// Treat anonymous struct fields with
363-
// tag names as though they are not
364-
// anonymous, like encoding/json does.
365387
if getOptions(f.Tag).name == "" {
366388
addFields(t, frv, append(start, f.Index...))
367389
continue
368390
}
369391
case reflect.Ptr:
370-
if t.Elem().Kind() == reflect.Struct &&
371-
getOptions(f.Tag).name == "" {
392+
if t.Elem().Kind() == reflect.Struct && getOptions(f.Tag).name == "" {
372393
if !frv.IsNil() {
373394
addFields(t.Elem(), frv.Elem(), append(start, f.Index...))
374395
}
375396
continue
376397
}
377-
// Fall through to the normal field encoding logic below
378-
// for non-struct anonymous fields.
379398
}
380399
}
381400

@@ -388,35 +407,49 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
388407
}
389408
addFields(rt, rv, nil)
390409

391-
var writeFields = func(fields [][]int) {
410+
writeFields := func(fields [][]int) {
392411
for _, fieldIndex := range fields {
393-
sft := rt.FieldByIndex(fieldIndex)
394-
sf := rv.FieldByIndex(fieldIndex)
395-
if isNil(sf) {
396-
// Don't write anything for nil fields.
412+
fieldType := rt.FieldByIndex(fieldIndex)
413+
fieldVal := rv.FieldByIndex(fieldIndex)
414+
415+
if isNil(fieldVal) { /// Don't write anything for nil fields.
397416
continue
398417
}
399418

400-
opts := getOptions(sft.Tag)
419+
opts := getOptions(fieldType.Tag)
401420
if opts.skip {
402421
continue
403422
}
404-
keyName := sft.Name
423+
keyName := fieldType.Name
405424
if opts.name != "" {
406425
keyName = opts.name
407426
}
408-
if opts.omitempty && isEmpty(sf) {
427+
if opts.omitempty && isEmpty(fieldVal) {
409428
continue
410429
}
411-
if opts.omitzero && isZero(sf) {
430+
if opts.omitzero && isZero(fieldVal) {
412431
continue
413432
}
414433

415-
enc.encode(key.add(keyName), sf)
434+
if inline {
435+
enc.writeKeyValue(Key{keyName}, fieldVal, true)
436+
if fieldIndex[0] != len(fields)-1 {
437+
enc.wf(", ")
438+
}
439+
} else {
440+
enc.encode(key.add(keyName), fieldVal)
441+
}
416442
}
417443
}
444+
445+
if inline {
446+
enc.wf("{")
447+
}
418448
writeFields(fieldsDirect)
419449
writeFields(fieldsSub)
450+
if inline {
451+
enc.wf("}")
452+
}
420453
}
421454

422455
// tomlTypeName returns the TOML type name of the Go value's type. It is
@@ -487,31 +520,18 @@ func tomlArrayType(rv reflect.Value) tomlType {
487520
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
488521
return nil
489522
}
490-
firstType := tomlTypeOfGo(rv.Index(0))
491-
if firstType == nil {
492-
encPanic(errArrayNilElement)
493-
}
494523

524+
/// Don't allow nil.
495525
rvlen := rv.Len()
496526
for i := 1; i < rvlen; i++ {
497-
elem := rv.Index(i)
498-
switch elemType := tomlTypeOfGo(elem); {
499-
case elemType == nil:
527+
if tomlTypeOfGo(rv.Index(i)) == nil {
500528
encPanic(errArrayNilElement)
501-
case !typeEqual(firstType, elemType):
502-
encPanic(errArrayMixedElementTypes)
503529
}
504530
}
505531

506-
// If we have a nested array, then we must make sure that the nested array
507-
// contains ONLY primitives.
508-
//
509-
// This checks arbitrarily nested arrays.
510-
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
511-
nest := tomlArrayType(eindirect(rv.Index(0)))
512-
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
513-
encPanic(errArrayNoTable)
514-
}
532+
firstType := tomlTypeOfGo(rv.Index(0))
533+
if firstType == nil {
534+
encPanic(errArrayNilElement)
515535
}
516536
return firstType
517537
}
@@ -570,13 +590,20 @@ func (enc *Encoder) newline() {
570590
}
571591
}
572592

573-
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
593+
// Write a key/value pair:
594+
//
595+
// key = <any value>
596+
//
597+
// If inline is true it won't add a newline at the end.
598+
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
574599
if len(key) == 0 {
575600
encPanic(errNoKey)
576601
}
577602
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
578603
enc.eElement(val)
579-
enc.newline()
604+
if !inline {
605+
enc.newline()
606+
}
580607
}
581608

582609
func (enc *Encoder) wf(format string, v ...interface{}) {

0 commit comments

Comments
 (0)