Browse Source

bump(github.com/BurntSushi/toml): da57f3b4c85ec56cf139d7dc05396fa98a040773

Ben Johnson 12 years ago
parent
commit
9ebac0b9fd

+ 27 - 15
third_party/github.com/BurntSushi/toml/decode.go

@@ -151,29 +151,41 @@ func unifyStruct(mapping interface{}, rv reflect.Value) error {
 		return mismatch(rv, "map", mapping)
 	}
 
-	rt := rv.Type()
-	for i := 0; i < rt.NumField(); i++ {
-		// A little tricky. We want to use the special `toml` name in the
-		// struct tag if it exists. In particular, we need to make sure that
-		// this struct field is in the current map before trying to unify it.
-		sft := rt.Field(i)
-		kname := sft.Tag.Get("toml")
-		if len(kname) == 0 {
-			kname = sft.Name
+	for key, datum := range tmap {
+		var f *field
+		fields := cachedTypeFields(rv.Type())
+		for i := range fields {
+			ff := &fields[i]
+			if ff.name == key {
+				f = ff
+				break
+			}
+			if f == nil && strings.EqualFold(ff.name, key) {
+				f = ff
+			}
 		}
-		if datum, ok := insensitiveGet(tmap, kname); ok {
-			sf := indirect(rv.Field(i))
+		if f != nil {
+			subv := rv
+			for _, i := range f.index {
+				if subv.Kind() == reflect.Ptr {
+					if subv.IsNil() {
+						subv.Set(reflect.New(subv.Type().Elem()))
+					}
+					subv = subv.Elem()
+				}
+				subv = subv.Field(i)
+			}
+			sf := indirect(subv)
 
-			// Don't try to mess with unexported types and other such things.
 			if sf.CanSet() {
 				if err := unify(datum, sf); err != nil {
 					return e("Type mismatch for '%s.%s': %s",
-						rt.String(), sft.Name, err)
+						rv.Type().String(), f.name, err)
 				}
-			} else if len(sft.Tag.Get("toml")) > 0 {
+			} else if f.name != "" {
 				// Bad user! No soup for you!
 				return e("Field '%s.%s' is unexported, and therefore cannot "+
-					"be loaded with reflection.", rt.String(), sft.Name)
+					"be loaded with reflection.", rv.Type().String(), f.name)
 			}
 		}
 	}

+ 49 - 5
third_party/github.com/BurntSushi/toml/decode_test.go

@@ -1,6 +1,7 @@
 package toml
 
 import (
+	"encoding/json"
 	"fmt"
 	"log"
 	"reflect"
@@ -63,6 +64,53 @@ func TestDecode(t *testing.T) {
 	testf("%v\n", val)
 }
 
+func TestDecodeEmbedded(t *testing.T) {
+	type Dog struct{ Name string }
+
+	tests := map[string]struct {
+		input       string
+		decodeInto  interface{}
+		wantDecoded interface{}
+	}{
+		"embedded struct": {
+			input:       `Name = "milton"`,
+			decodeInto:  &struct{ Dog }{},
+			wantDecoded: &struct{ Dog }{Dog{"milton"}},
+		},
+		"embedded non-nil pointer to struct": {
+			input:       `Name = "milton"`,
+			decodeInto:  &struct{ *Dog }{},
+			wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
+		},
+		"embedded nil pointer to struct": {
+			input:       ``,
+			decodeInto:  &struct{ *Dog }{},
+			wantDecoded: &struct{ *Dog }{nil},
+		},
+	}
+
+	for label, test := range tests {
+		_, err := Decode(test.input, test.decodeInto)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		want, got := jsonstr(test.wantDecoded), jsonstr(test.decodeInto)
+		if want != got {
+			t.Errorf("%s: want decoded == %+v, got %+v", label, want, got)
+		}
+	}
+}
+
+// jsonstr allows comparison of deeply nested structs with pointer members.
+func jsonstr(o interface{}) string {
+	s, err := json.MarshalIndent(o, "", "  ")
+	if err != nil {
+		panic(err.Error())
+	}
+	return string(s)
+}
+
 var tomlTableArrays = `
 [[albums]]
 name = "Born to Run"
@@ -124,8 +172,6 @@ tOpdate = 2006-01-02T15:04:05Z
 tOparray = [ "array" ]
 Match = "i should be in Match only"
 MatcH = "i should be in MatcH only"
-Field = "neat"
-FielD = "messy"
 once = "just once"
 [nEst.eD]
 nEstedString = "another string"
@@ -140,7 +186,6 @@ type Insensitive struct {
 	TopArray  []string
 	Match     string
 	MatcH     string
-	Field     string
 	Once      string
 	OncE      string
 	Nest      InsensitiveNest
@@ -168,9 +213,8 @@ func TestCase(t *testing.T) {
 		TopArray:  []string{"array"},
 		MatcH:     "i should be in MatcH only",
 		Match:     "i should be in Match only",
-		Field:     "neat", // encoding/json would store "messy" here
 		Once:      "just once",
-		OncE:      "just once", // wait, what?
+		OncE:      "",
 		Nest: InsensitiveNest{
 			Ed: InsensitiveEd{NestedString: "another string"},
 		},

+ 241 - 0
third_party/github.com/BurntSushi/toml/type_fields.go

@@ -0,0 +1,241 @@
+package toml
+
+// Struct field handling is adapted from code in encoding/json:
+//
+// Copyright 2010 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import (
+	"reflect"
+	"sort"
+	"sync"
+)
+
+// A field represents a single field found in a struct.
+type field struct {
+	name  string
+	tag   bool
+	index []int
+	typ   reflect.Type
+}
+
+// byName sorts field by name, breaking ties with depth,
+// then breaking ties with "name came from toml tag", then
+// breaking ties with index sequence.
+type byName []field
+
+func (x byName) Len() int { return len(x) }
+
+func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+
+func (x byName) Less(i, j int) bool {
+	if x[i].name != x[j].name {
+		return x[i].name < x[j].name
+	}
+	if len(x[i].index) != len(x[j].index) {
+		return len(x[i].index) < len(x[j].index)
+	}
+	if x[i].tag != x[j].tag {
+		return x[i].tag
+	}
+	return byIndex(x).Less(i, j)
+}
+
+// byIndex sorts field by index sequence.
+type byIndex []field
+
+func (x byIndex) Len() int { return len(x) }
+
+func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+
+func (x byIndex) Less(i, j int) bool {
+	for k, xik := range x[i].index {
+		if k >= len(x[j].index) {
+			return false
+		}
+		if xik != x[j].index[k] {
+			return xik < x[j].index[k]
+		}
+	}
+	return len(x[i].index) < len(x[j].index)
+}
+
+// typeFields returns a list of fields that TOML should recognize for the given
+// type. The algorithm is breadth-first search over the set of structs to
+// include - the top struct and then any reachable anonymous structs.
+func typeFields(t reflect.Type) []field {
+	// Anonymous fields to explore at the current level and the next.
+	current := []field{}
+	next := []field{{typ: t}}
+
+	// Count of queued names for current level and the next.
+	count := map[reflect.Type]int{}
+	nextCount := map[reflect.Type]int{}
+
+	// Types already visited at an earlier level.
+	visited := map[reflect.Type]bool{}
+
+	// Fields found.
+	var fields []field
+
+	for len(next) > 0 {
+		current, next = next, current[:0]
+		count, nextCount = nextCount, map[reflect.Type]int{}
+
+		for _, f := range current {
+			if visited[f.typ] {
+				continue
+			}
+			visited[f.typ] = true
+
+			// Scan f.typ for fields to include.
+			for i := 0; i < f.typ.NumField(); i++ {
+				sf := f.typ.Field(i)
+				if sf.PkgPath != "" { // unexported
+					continue
+				}
+				name := sf.Tag.Get("toml")
+				if name == "-" {
+					continue
+				}
+				index := make([]int, len(f.index)+1)
+				copy(index, f.index)
+				index[len(f.index)] = i
+
+				ft := sf.Type
+				if ft.Name() == "" && ft.Kind() == reflect.Ptr {
+					// Follow pointer.
+					ft = ft.Elem()
+				}
+
+				// Record found field and index sequence.
+				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
+					tagged := name != ""
+					if name == "" {
+						name = sf.Name
+					}
+					fields = append(fields, field{name, tagged, index, ft})
+					if count[f.typ] > 1 {
+						// If there were multiple instances, add a second,
+						// so that the annihilation code will see a duplicate.
+						// It only cares about the distinction between 1 or 2,
+						// so don't bother generating any more copies.
+						fields = append(fields, fields[len(fields)-1])
+					}
+					continue
+				}
+
+				// Record new anonymous struct to explore in next round.
+				nextCount[ft]++
+				if nextCount[ft] == 1 {
+					f := field{name: ft.Name(), index: index, typ: ft}
+					next = append(next, f)
+				}
+			}
+		}
+	}
+
+	sort.Sort(byName(fields))
+
+	// Delete all fields that are hidden by the Go rules for embedded fields,
+	// except that fields with TOML tags are promoted.
+
+	// The fields are sorted in primary order of name, secondary order
+	// of field index length. Loop over names; for each name, delete
+	// hidden fields by choosing the one dominant field that survives.
+	out := fields[:0]
+	for advance, i := 0, 0; i < len(fields); i += advance {
+		// One iteration per name.
+		// Find the sequence of fields with the name of this first field.
+		fi := fields[i]
+		name := fi.name
+		for advance = 1; i+advance < len(fields); advance++ {
+			fj := fields[i+advance]
+			if fj.name != name {
+				break
+			}
+		}
+		if advance == 1 { // Only one field with this name
+			out = append(out, fi)
+			continue
+		}
+		dominant, ok := dominantField(fields[i : i+advance])
+		if ok {
+			out = append(out, dominant)
+		}
+	}
+
+	fields = out
+	sort.Sort(byIndex(fields))
+
+	return fields
+}
+
+// dominantField looks through the fields, all of which are known to
+// have the same name, to find the single field that dominates the
+// others using Go's embedding rules, modified by the presence of
+// TOML tags. If there are multiple top-level fields, the boolean
+// will be false: This condition is an error in Go and we skip all
+// the fields.
+func dominantField(fields []field) (field, bool) {
+	// The fields are sorted in increasing index-length order. The winner
+	// must therefore be one with the shortest index length. Drop all
+	// longer entries, which is easy: just truncate the slice.
+	length := len(fields[0].index)
+	tagged := -1 // Index of first tagged field.
+	for i, f := range fields {
+		if len(f.index) > length {
+			fields = fields[:i]
+			break
+		}
+		if f.tag {
+			if tagged >= 0 {
+				// Multiple tagged fields at the same level: conflict.
+				// Return no field.
+				return field{}, false
+			}
+			tagged = i
+		}
+	}
+	if tagged >= 0 {
+		return fields[tagged], true
+	}
+	// All remaining fields have the same length. If there's more than one,
+	// we have a conflict (two fields named "X" at the same level) and we
+	// return no field.
+	if len(fields) > 1 {
+		return field{}, false
+	}
+	return fields[0], true
+}
+
+var fieldCache struct {
+	sync.RWMutex
+	m map[reflect.Type][]field
+}
+
+// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
+func cachedTypeFields(t reflect.Type) []field {
+	fieldCache.RLock()
+	f := fieldCache.m[t]
+	fieldCache.RUnlock()
+	if f != nil {
+		return f
+	}
+
+	// Compute fields without lock.
+	// Might duplicate effort but won't hold other computations back.
+	f = typeFields(t)
+	if f == nil {
+		f = []field{}
+	}
+
+	fieldCache.Lock()
+	if fieldCache.m == nil {
+		fieldCache.m = map[reflect.Type][]field{}
+	}
+	fieldCache.m[t] = f
+	fieldCache.Unlock()
+	return f
+}