|
@@ -12,32 +12,46 @@ import (
|
|
|
"strconv"
|
|
|
)
|
|
|
|
|
|
-var implicitTypeMap = map[reflect.Kind]string{
|
|
|
+var builtinTypeMap = map[reflect.Kind]string{
|
|
|
reflect.Bool: "bool",
|
|
|
- reflect.String: "string",
|
|
|
- reflect.Int: "int",
|
|
|
- reflect.Int8: "int8",
|
|
|
+ reflect.Complex128: "complex128",
|
|
|
+ reflect.Complex64: "complex64",
|
|
|
+ reflect.Float32: "float32",
|
|
|
+ reflect.Float64: "float64",
|
|
|
reflect.Int16: "int16",
|
|
|
reflect.Int32: "int32",
|
|
|
reflect.Int64: "int64",
|
|
|
- reflect.Uint: "uint",
|
|
|
- reflect.Uint8: "uint8",
|
|
|
+ reflect.Int8: "int8",
|
|
|
+ reflect.Int: "int",
|
|
|
+ reflect.String: "string",
|
|
|
reflect.Uint16: "uint16",
|
|
|
reflect.Uint32: "uint32",
|
|
|
reflect.Uint64: "uint64",
|
|
|
- reflect.Float32: "float32",
|
|
|
- reflect.Float64: "float64",
|
|
|
- reflect.Complex64: "complex64",
|
|
|
- reflect.Complex128: "complex128",
|
|
|
+ reflect.Uint8: "uint8",
|
|
|
+ reflect.Uint: "uint",
|
|
|
+ reflect.Uintptr: "uintptr",
|
|
|
+}
|
|
|
+
|
|
|
+var builtinTypeSet = map[string]struct{}{}
|
|
|
+
|
|
|
+func init() {
|
|
|
+ for _, v := range builtinTypeMap {
|
|
|
+ builtinTypeSet[v] = struct{}{}
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+var typeOfString = reflect.TypeOf("")
|
|
|
+var typeOfInt = reflect.TypeOf(int(1))
|
|
|
+var typeOfUint = reflect.TypeOf(uint(1))
|
|
|
+var typeOfFloat = reflect.TypeOf(10.1)
|
|
|
+
|
|
|
// Render converts a structure to a string representation. Unline the "%#v"
|
|
|
// format string, this resolves pointer types' contents in structs, maps, and
|
|
|
// slices/arrays and prints their field values.
|
|
|
func Render(v interface{}) string {
|
|
|
buf := bytes.Buffer{}
|
|
|
s := (*traverseState)(nil)
|
|
|
- s.render(&buf, 0, reflect.ValueOf(v))
|
|
|
+ s.render(&buf, 0, reflect.ValueOf(v), false)
|
|
|
return buf.String()
|
|
|
}
|
|
|
|
|
@@ -72,7 +86,7 @@ func (s *traverseState) forkFor(ptr uintptr) *traverseState {
|
|
|
return fs
|
|
|
}
|
|
|
|
|
|
-func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
|
|
|
+func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value, implicit bool) {
|
|
|
if v.Kind() == reflect.Invalid {
|
|
|
buf.WriteString("nil")
|
|
|
return
|
|
@@ -107,49 +121,80 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
|
|
|
s = s.forkFor(pe)
|
|
|
if s == nil {
|
|
|
buf.WriteString("<REC(")
|
|
|
- writeType(buf, ptrs, vt)
|
|
|
+ if !implicit {
|
|
|
+ writeType(buf, ptrs, vt)
|
|
|
+ }
|
|
|
buf.WriteString(")>")
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ isAnon := func(t reflect.Type) bool {
|
|
|
+ if t.Name() != "" {
|
|
|
+ if _, ok := builtinTypeSet[t.Name()]; !ok {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return t.Kind() != reflect.Interface
|
|
|
+ }
|
|
|
+
|
|
|
switch vk {
|
|
|
case reflect.Struct:
|
|
|
- writeType(buf, ptrs, vt)
|
|
|
+ if !implicit {
|
|
|
+ writeType(buf, ptrs, vt)
|
|
|
+ }
|
|
|
buf.WriteRune('{')
|
|
|
- for i := 0; i < vt.NumField(); i++ {
|
|
|
- if i > 0 {
|
|
|
- buf.WriteString(", ")
|
|
|
- }
|
|
|
- buf.WriteString(vt.Field(i).Name)
|
|
|
- buf.WriteRune(':')
|
|
|
+ if rendered, ok := renderTime(v); ok {
|
|
|
+ buf.WriteString(rendered)
|
|
|
+ } else {
|
|
|
+ structAnon := vt.Name() == ""
|
|
|
+ for i := 0; i < vt.NumField(); i++ {
|
|
|
+ if i > 0 {
|
|
|
+ buf.WriteString(", ")
|
|
|
+ }
|
|
|
+ anon := structAnon && isAnon(vt.Field(i).Type)
|
|
|
|
|
|
- s.render(buf, 0, v.Field(i))
|
|
|
+ if !anon {
|
|
|
+ buf.WriteString(vt.Field(i).Name)
|
|
|
+ buf.WriteRune(':')
|
|
|
+ }
|
|
|
+
|
|
|
+ s.render(buf, 0, v.Field(i), anon)
|
|
|
+ }
|
|
|
}
|
|
|
buf.WriteRune('}')
|
|
|
|
|
|
case reflect.Slice:
|
|
|
if v.IsNil() {
|
|
|
- writeType(buf, ptrs, vt)
|
|
|
- buf.WriteString("(nil)")
|
|
|
+ if !implicit {
|
|
|
+ writeType(buf, ptrs, vt)
|
|
|
+ buf.WriteString("(nil)")
|
|
|
+ } else {
|
|
|
+ buf.WriteString("nil")
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
fallthrough
|
|
|
|
|
|
case reflect.Array:
|
|
|
- writeType(buf, ptrs, vt)
|
|
|
+ if !implicit {
|
|
|
+ writeType(buf, ptrs, vt)
|
|
|
+ }
|
|
|
+ anon := vt.Name() == "" && isAnon(vt.Elem())
|
|
|
buf.WriteString("{")
|
|
|
for i := 0; i < v.Len(); i++ {
|
|
|
if i > 0 {
|
|
|
buf.WriteString(", ")
|
|
|
}
|
|
|
|
|
|
- s.render(buf, 0, v.Index(i))
|
|
|
+ s.render(buf, 0, v.Index(i), anon)
|
|
|
}
|
|
|
buf.WriteRune('}')
|
|
|
|
|
|
case reflect.Map:
|
|
|
- writeType(buf, ptrs, vt)
|
|
|
+ if !implicit {
|
|
|
+ writeType(buf, ptrs, vt)
|
|
|
+ }
|
|
|
if v.IsNil() {
|
|
|
buf.WriteString("(nil)")
|
|
|
} else {
|
|
@@ -158,14 +203,17 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
|
|
|
mkeys := v.MapKeys()
|
|
|
tryAndSortMapKeys(vt, mkeys)
|
|
|
|
|
|
+ kt := vt.Key()
|
|
|
+ keyAnon := typeOfString.ConvertibleTo(kt) || typeOfInt.ConvertibleTo(kt) || typeOfUint.ConvertibleTo(kt) || typeOfFloat.ConvertibleTo(kt)
|
|
|
+ valAnon := vt.Name() == "" && isAnon(vt.Elem())
|
|
|
for i, mk := range mkeys {
|
|
|
if i > 0 {
|
|
|
buf.WriteString(", ")
|
|
|
}
|
|
|
|
|
|
- s.render(buf, 0, mk)
|
|
|
+ s.render(buf, 0, mk, keyAnon)
|
|
|
buf.WriteString(":")
|
|
|
- s.render(buf, 0, v.MapIndex(mk))
|
|
|
+ s.render(buf, 0, v.MapIndex(mk), valAnon)
|
|
|
}
|
|
|
buf.WriteRune('}')
|
|
|
}
|
|
@@ -176,11 +224,9 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
|
|
|
case reflect.Interface:
|
|
|
if v.IsNil() {
|
|
|
writeType(buf, ptrs, v.Type())
|
|
|
- buf.WriteRune('(')
|
|
|
- fmt.Fprint(buf, "nil")
|
|
|
- buf.WriteRune(')')
|
|
|
+ buf.WriteString("(nil)")
|
|
|
} else {
|
|
|
- s.render(buf, ptrs, v.Elem())
|
|
|
+ s.render(buf, ptrs, v.Elem(), false)
|
|
|
}
|
|
|
|
|
|
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
|
@@ -191,7 +237,7 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
|
|
|
|
|
|
default:
|
|
|
tstr := vt.String()
|
|
|
- implicit := ptrs == 0 && implicitTypeMap[vk] == tstr
|
|
|
+ implicit = implicit || (ptrs == 0 && builtinTypeMap[vk] == tstr)
|
|
|
if !implicit {
|
|
|
writeType(buf, ptrs, vt)
|
|
|
buf.WriteRune('(')
|
|
@@ -206,7 +252,7 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
fmt.Fprintf(buf, "%d", v.Int())
|
|
|
|
|
|
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
|
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
|
fmt.Fprintf(buf, "%d", v.Uint())
|
|
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
@@ -288,40 +334,148 @@ func writeType(buf *bytes.Buffer, ptrs int, t reflect.Type) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+type cmpFn func(a, b reflect.Value) int
|
|
|
+
|
|
|
type sortableValueSlice struct {
|
|
|
- kind reflect.Kind
|
|
|
+ cmp cmpFn
|
|
|
elements []reflect.Value
|
|
|
}
|
|
|
|
|
|
-func (s *sortableValueSlice) Len() int {
|
|
|
+func (s sortableValueSlice) Len() int {
|
|
|
return len(s.elements)
|
|
|
}
|
|
|
|
|
|
-func (s *sortableValueSlice) Less(i, j int) bool {
|
|
|
- switch s.kind {
|
|
|
+func (s sortableValueSlice) Less(i, j int) bool {
|
|
|
+ return s.cmp(s.elements[i], s.elements[j]) < 0
|
|
|
+}
|
|
|
+
|
|
|
+func (s sortableValueSlice) Swap(i, j int) {
|
|
|
+ s.elements[i], s.elements[j] = s.elements[j], s.elements[i]
|
|
|
+}
|
|
|
+
|
|
|
+// cmpForType returns a cmpFn which sorts the data for some type t in the same
|
|
|
+// order that a go-native map key is compared for equality.
|
|
|
+func cmpForType(t reflect.Type) cmpFn {
|
|
|
+ switch t.Kind() {
|
|
|
case reflect.String:
|
|
|
- return s.elements[i].String() < s.elements[j].String()
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.String(), bv.String()
|
|
|
+ if a < b {
|
|
|
+ return -1
|
|
|
+ } else if a > b {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ case reflect.Bool:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.Bool(), bv.Bool()
|
|
|
+ if !a && b {
|
|
|
+ return -1
|
|
|
+ } else if a && !b {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
|
|
|
- case reflect.Int:
|
|
|
- return s.elements[i].Int() < s.elements[j].Int()
|
|
|
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.Int(), bv.Int()
|
|
|
+ if a < b {
|
|
|
+ return -1
|
|
|
+ } else if a > b {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
|
|
|
- default:
|
|
|
- panic(fmt.Errorf("unsupported sort kind: %s", s.kind))
|
|
|
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
|
|
+ reflect.Uint64, reflect.Uintptr, reflect.UnsafePointer:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.Uint(), bv.Uint()
|
|
|
+ if a < b {
|
|
|
+ return -1
|
|
|
+ } else if a > b {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ case reflect.Float32, reflect.Float64:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.Float(), bv.Float()
|
|
|
+ if a < b {
|
|
|
+ return -1
|
|
|
+ } else if a > b {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ case reflect.Interface:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.InterfaceData(), bv.InterfaceData()
|
|
|
+ if a[0] < b[0] {
|
|
|
+ return -1
|
|
|
+ } else if a[0] > b[0] {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ if a[1] < b[1] {
|
|
|
+ return -1
|
|
|
+ } else if a[1] > b[1] {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ case reflect.Complex64, reflect.Complex128:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.Complex(), bv.Complex()
|
|
|
+ if real(a) < real(b) {
|
|
|
+ return -1
|
|
|
+ } else if real(a) > real(b) {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ if imag(a) < imag(b) {
|
|
|
+ return -1
|
|
|
+ } else if imag(a) > imag(b) {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ case reflect.Ptr, reflect.Chan:
|
|
|
+ return func(av, bv reflect.Value) int {
|
|
|
+ a, b := av.Pointer(), bv.Pointer()
|
|
|
+ if a < b {
|
|
|
+ return -1
|
|
|
+ } else if a > b {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ case reflect.Struct:
|
|
|
+ cmpLst := make([]cmpFn, t.NumField())
|
|
|
+ for i := range cmpLst {
|
|
|
+ cmpLst[i] = cmpForType(t.Field(i).Type)
|
|
|
+ }
|
|
|
+ return func(a, b reflect.Value) int {
|
|
|
+ for i, cmp := range cmpLst {
|
|
|
+ if rslt := cmp(a.Field(i), b.Field(i)); rslt != 0 {
|
|
|
+ return rslt
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func (s *sortableValueSlice) Swap(i, j int) {
|
|
|
- s.elements[i], s.elements[j] = s.elements[j], s.elements[i]
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
func tryAndSortMapKeys(mt reflect.Type, k []reflect.Value) {
|
|
|
- // Try our stock sortable values.
|
|
|
- switch mt.Key().Kind() {
|
|
|
- case reflect.String, reflect.Int:
|
|
|
- vs := &sortableValueSlice{
|
|
|
- kind: mt.Key().Kind(),
|
|
|
- elements: k,
|
|
|
- }
|
|
|
- sort.Sort(vs)
|
|
|
+ if cmp := cmpForType(mt.Key()); cmp != nil {
|
|
|
+ sort.Sort(sortableValueSlice{cmp, k})
|
|
|
}
|
|
|
}
|