mirror of
https://gitlab.com/ric_harvey/MailHog.git
synced 2024-11-28 08:44:03 +00:00
478 lines
10 KiB
Go
478 lines
10 KiB
Go
|
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
package render
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
var builtinTypeMap = map[reflect.Kind]string{
|
||
|
reflect.Bool: "bool",
|
||
|
reflect.Complex128: "complex128",
|
||
|
reflect.Complex64: "complex64",
|
||
|
reflect.Float32: "float32",
|
||
|
reflect.Float64: "float64",
|
||
|
reflect.Int16: "int16",
|
||
|
reflect.Int32: "int32",
|
||
|
reflect.Int64: "int64",
|
||
|
reflect.Int8: "int8",
|
||
|
reflect.Int: "int",
|
||
|
reflect.String: "string",
|
||
|
reflect.Uint16: "uint16",
|
||
|
reflect.Uint32: "uint32",
|
||
|
reflect.Uint64: "uint64",
|
||
|
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), false)
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
// renderPointer is called to render a pointer value.
|
||
|
//
|
||
|
// This is overridable so that the test suite can have deterministic pointer
|
||
|
// values in its expectations.
|
||
|
var renderPointer = func(buf *bytes.Buffer, p uintptr) {
|
||
|
fmt.Fprintf(buf, "0x%016x", p)
|
||
|
}
|
||
|
|
||
|
// traverseState is used to note and avoid recursion as struct members are being
|
||
|
// traversed.
|
||
|
//
|
||
|
// traverseState is allowed to be nil. Specifically, the root state is nil.
|
||
|
type traverseState struct {
|
||
|
parent *traverseState
|
||
|
ptr uintptr
|
||
|
}
|
||
|
|
||
|
func (s *traverseState) forkFor(ptr uintptr) *traverseState {
|
||
|
for cur := s; cur != nil; cur = cur.parent {
|
||
|
if ptr == cur.ptr {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fs := &traverseState{
|
||
|
parent: s,
|
||
|
ptr: ptr,
|
||
|
}
|
||
|
return fs
|
||
|
}
|
||
|
|
||
|
func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value, implicit bool) {
|
||
|
if v.Kind() == reflect.Invalid {
|
||
|
buf.WriteString("nil")
|
||
|
return
|
||
|
}
|
||
|
vt := v.Type()
|
||
|
|
||
|
// If the type being rendered is a potentially recursive type (a type that
|
||
|
// can contain itself as a member), we need to avoid recursion.
|
||
|
//
|
||
|
// If we've already seen this type before, mark that this is the case and
|
||
|
// write a recursion placeholder instead of actually rendering it.
|
||
|
//
|
||
|
// If we haven't seen it before, fork our `seen` tracking so any higher-up
|
||
|
// renderers will also render it at least once, then mark that we've seen it
|
||
|
// to avoid recursing on lower layers.
|
||
|
pe := uintptr(0)
|
||
|
vk := vt.Kind()
|
||
|
switch vk {
|
||
|
case reflect.Ptr:
|
||
|
// Since structs and arrays aren't pointers, they can't directly be
|
||
|
// recursed, but they can contain pointers to themselves. Record their
|
||
|
// pointer to avoid this.
|
||
|
switch v.Elem().Kind() {
|
||
|
case reflect.Struct, reflect.Array:
|
||
|
pe = v.Pointer()
|
||
|
}
|
||
|
|
||
|
case reflect.Slice, reflect.Map:
|
||
|
pe = v.Pointer()
|
||
|
}
|
||
|
if pe != 0 {
|
||
|
s = s.forkFor(pe)
|
||
|
if s == nil {
|
||
|
buf.WriteString("<REC(")
|
||
|
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:
|
||
|
if !implicit {
|
||
|
writeType(buf, ptrs, vt)
|
||
|
}
|
||
|
structAnon := vt.Name() == ""
|
||
|
buf.WriteRune('{')
|
||
|
for i := 0; i < vt.NumField(); i++ {
|
||
|
if i > 0 {
|
||
|
buf.WriteString(", ")
|
||
|
}
|
||
|
anon := structAnon && isAnon(vt.Field(i).Type)
|
||
|
|
||
|
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() {
|
||
|
if !implicit {
|
||
|
writeType(buf, ptrs, vt)
|
||
|
buf.WriteString("(nil)")
|
||
|
} else {
|
||
|
buf.WriteString("nil")
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
fallthrough
|
||
|
|
||
|
case reflect.Array:
|
||
|
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), anon)
|
||
|
}
|
||
|
buf.WriteRune('}')
|
||
|
|
||
|
case reflect.Map:
|
||
|
if !implicit {
|
||
|
writeType(buf, ptrs, vt)
|
||
|
}
|
||
|
if v.IsNil() {
|
||
|
buf.WriteString("(nil)")
|
||
|
} else {
|
||
|
buf.WriteString("{")
|
||
|
|
||
|
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, keyAnon)
|
||
|
buf.WriteString(":")
|
||
|
s.render(buf, 0, v.MapIndex(mk), valAnon)
|
||
|
}
|
||
|
buf.WriteRune('}')
|
||
|
}
|
||
|
|
||
|
case reflect.Ptr:
|
||
|
ptrs++
|
||
|
fallthrough
|
||
|
case reflect.Interface:
|
||
|
if v.IsNil() {
|
||
|
writeType(buf, ptrs, v.Type())
|
||
|
buf.WriteString("(nil)")
|
||
|
} else {
|
||
|
s.render(buf, ptrs, v.Elem(), false)
|
||
|
}
|
||
|
|
||
|
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||
|
writeType(buf, ptrs, vt)
|
||
|
buf.WriteRune('(')
|
||
|
renderPointer(buf, v.Pointer())
|
||
|
buf.WriteRune(')')
|
||
|
|
||
|
default:
|
||
|
tstr := vt.String()
|
||
|
implicit = implicit || (ptrs == 0 && builtinTypeMap[vk] == tstr)
|
||
|
if !implicit {
|
||
|
writeType(buf, ptrs, vt)
|
||
|
buf.WriteRune('(')
|
||
|
}
|
||
|
|
||
|
switch vk {
|
||
|
case reflect.String:
|
||
|
fmt.Fprintf(buf, "%q", v.String())
|
||
|
case reflect.Bool:
|
||
|
fmt.Fprintf(buf, "%v", v.Bool())
|
||
|
|
||
|
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, reflect.Uintptr:
|
||
|
fmt.Fprintf(buf, "%d", v.Uint())
|
||
|
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
fmt.Fprintf(buf, "%g", v.Float())
|
||
|
|
||
|
case reflect.Complex64, reflect.Complex128:
|
||
|
fmt.Fprintf(buf, "%g", v.Complex())
|
||
|
}
|
||
|
|
||
|
if !implicit {
|
||
|
buf.WriteRune(')')
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func writeType(buf *bytes.Buffer, ptrs int, t reflect.Type) {
|
||
|
parens := ptrs > 0
|
||
|
switch t.Kind() {
|
||
|
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||
|
parens = true
|
||
|
}
|
||
|
|
||
|
if parens {
|
||
|
buf.WriteRune('(')
|
||
|
for i := 0; i < ptrs; i++ {
|
||
|
buf.WriteRune('*')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch t.Kind() {
|
||
|
case reflect.Ptr:
|
||
|
if ptrs == 0 {
|
||
|
// This pointer was referenced from within writeType (e.g., as part of
|
||
|
// rendering a list), and so hasn't had its pointer asterisk accounted
|
||
|
// for.
|
||
|
buf.WriteRune('*')
|
||
|
}
|
||
|
writeType(buf, 0, t.Elem())
|
||
|
|
||
|
case reflect.Interface:
|
||
|
if n := t.Name(); n != "" {
|
||
|
buf.WriteString(t.String())
|
||
|
} else {
|
||
|
buf.WriteString("interface{}")
|
||
|
}
|
||
|
|
||
|
case reflect.Array:
|
||
|
buf.WriteRune('[')
|
||
|
buf.WriteString(strconv.FormatInt(int64(t.Len()), 10))
|
||
|
buf.WriteRune(']')
|
||
|
writeType(buf, 0, t.Elem())
|
||
|
|
||
|
case reflect.Slice:
|
||
|
if t == reflect.SliceOf(t.Elem()) {
|
||
|
buf.WriteString("[]")
|
||
|
writeType(buf, 0, t.Elem())
|
||
|
} else {
|
||
|
// Custom slice type, use type name.
|
||
|
buf.WriteString(t.String())
|
||
|
}
|
||
|
|
||
|
case reflect.Map:
|
||
|
if t == reflect.MapOf(t.Key(), t.Elem()) {
|
||
|
buf.WriteString("map[")
|
||
|
writeType(buf, 0, t.Key())
|
||
|
buf.WriteRune(']')
|
||
|
writeType(buf, 0, t.Elem())
|
||
|
} else {
|
||
|
// Custom map type, use type name.
|
||
|
buf.WriteString(t.String())
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
buf.WriteString(t.String())
|
||
|
}
|
||
|
|
||
|
if parens {
|
||
|
buf.WriteRune(')')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type cmpFn func(a, b reflect.Value) int
|
||
|
|
||
|
type sortableValueSlice struct {
|
||
|
cmp cmpFn
|
||
|
elements []reflect.Value
|
||
|
}
|
||
|
|
||
|
func (s sortableValueSlice) Len() int {
|
||
|
return len(s.elements)
|
||
|
}
|
||
|
|
||
|
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 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, 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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func tryAndSortMapKeys(mt reflect.Type, k []reflect.Value) {
|
||
|
if cmp := cmpForType(mt.Key()); cmp != nil {
|
||
|
sort.Sort(sortableValueSlice{cmp, k})
|
||
|
}
|
||
|
}
|