...
 
Commits (2)
......@@ -14,33 +14,37 @@ import (
"unsafe"
)
// "Real" names of basic kinds, used to differentiate type aliases.
var realKindName = map[reflect.Kind]string{
reflect.Bool: "bool",
reflect.Int: "int",
reflect.Int8: "int8",
reflect.Int16: "int16",
reflect.Int32: "int32",
reflect.Int64: "int64",
reflect.Uint: "uint",
reflect.Uint8: "uint8",
reflect.Uint16: "uint16",
reflect.Uint32: "uint32",
reflect.Uint64: "uint64",
reflect.Uintptr: "uintptr",
reflect.Float32: "float32",
reflect.Float64: "float64",
reflect.Complex64: "complex64",
reflect.Complex128: "complex128",
reflect.Array: "array",
reflect.Chan: "chan",
reflect.Func: "func",
reflect.Map: "map",
reflect.Slice: "slice",
reflect.String: "string",
}
var (
// "Real" names of basic kinds, used to differentiate type aliases.
realKindName = map[reflect.Kind]string{
reflect.Bool: "bool",
reflect.Int: "int",
reflect.Int8: "int8",
reflect.Int16: "int16",
reflect.Int32: "int32",
reflect.Int64: "int64",
reflect.Uint: "uint",
reflect.Uint8: "uint8",
reflect.Uint16: "uint16",
reflect.Uint32: "uint32",
reflect.Uint64: "uint64",
reflect.Uintptr: "uintptr",
reflect.Float32: "float32",
reflect.Float64: "float64",
reflect.Complex64: "complex64",
reflect.Complex128: "complex128",
reflect.Array: "array",
reflect.Chan: "chan",
reflect.Func: "func",
reflect.Map: "map",
reflect.Slice: "slice",
reflect.String: "string",
}
goStringerType = reflect.TypeOf((*fmt.GoStringer)(nil)).Elem()
var goStringerType = reflect.TypeOf((*fmt.GoStringer)(nil)).Elem()
byteSliceType = reflect.TypeOf([]byte{})
)
// Default prints to os.Stdout with two space indentation.
var Default = New(os.Stdout, Indent(" "))
......@@ -57,6 +61,7 @@ func NoIndent() Option { return Indent("") }
// OmitEmpty sets whether empty field members should be omitted from output.
func OmitEmpty(omitEmpty bool) Option { return func(o *Printer) { o.omitEmpty = omitEmpty } }
// IgnoreGoStringer disables use of the .GoString() method.
func IgnoreGoStringer() Option { return func(o *Printer) { o.ignoreGoStringer = true } }
// Hide excludes the given types from representation, instead just printing the name of the type.
......@@ -64,19 +69,22 @@ func Hide(ts ...interface{}) Option {
return func(o *Printer) {
for _, t := range ts {
rt := reflect.Indirect(reflect.ValueOf(t)).Type()
fmt.Println(rt)
o.exclude[rt] = true
}
}
}
// AlwaysIncludeType always includes explicit type information for each item.
func AlwaysIncludeType() Option { return func(o *Printer) { o.alwaysIncludeType = true } }
// Printer represents structs in a printable manner.
type Printer struct {
indent string
omitEmpty bool
ignoreGoStringer bool
exclude map[reflect.Type]bool
w io.Writer
indent string
omitEmpty bool
ignoreGoStringer bool
alwaysIncludeType bool
exclude map[reflect.Type]bool
w io.Writer
}
// New creates a new Printer on w with the given Options.
......@@ -113,7 +121,7 @@ func (p *Printer) Print(vs ...interface{}) {
if i > 0 {
fmt.Fprint(p.w, " ")
}
p.reprValue(reflect.ValueOf(v), "")
p.reprValue(map[reflect.Value]bool{}, reflect.ValueOf(v), "")
}
}
......@@ -123,21 +131,34 @@ func (p *Printer) Println(vs ...interface{}) {
if i > 0 {
fmt.Fprint(p.w, " ")
}
p.reprValue(reflect.ValueOf(v), "")
p.reprValue(map[reflect.Value]bool{}, reflect.ValueOf(v), "")
}
fmt.Fprintln(p.w)
}
func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Chan || v.Kind() == reflect.Slice || v.Kind() == reflect.Func || v.Kind() == reflect.Interface) && v.IsNil() {
func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent string) { // nolint: gocyclo
if seen[v] {
fmt.Fprint(p.w, "...")
return
}
seen[v] = true
defer delete(seen, v)
if v.Kind() == reflect.Invalid || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Chan || v.Kind() == reflect.Slice || v.Kind() == reflect.Func || v.Kind() == reflect.Interface) && v.IsNil() {
fmt.Fprint(p.w, "nil")
return
}
if p.exclude[v.Type()] {
fmt.Fprint(p.w, v.Type().Name())
fmt.Fprintf(p.w, "%s...", v.Type().Name())
return
}
t := v.Type()
if t == byteSliceType {
fmt.Fprintf(p.w, "[]byte(%q)", v.Interface())
return
}
// If we can't access a private field directly with reflection, try and do so via unsafe.
if !v.CanInterface() && v.CanAddr() {
uv := reflect.NewAt(t, unsafe.Pointer(v.UnsafeAddr())).Elem()
......@@ -146,7 +167,7 @@ func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
}
}
// Attempt to use fmt.GoStringer interface.
if t.Implements(goStringerType) {
if !p.ignoreGoStringer && t.Implements(goStringerType) {
fmt.Fprint(p.w, v.Interface().(fmt.GoStringer).GoString())
return
}
......@@ -167,7 +188,7 @@ func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
for i := 0; i < v.Len(); i++ {
e := v.Index(i)
fmt.Fprintf(p.w, "%s", ni)
p.reprValue(e, ni)
p.reprValue(seen, e, ni)
if p.indent != "" {
fmt.Fprintf(p.w, ",\n")
} else if i < v.Len()-1 {
......@@ -190,9 +211,9 @@ func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
for i, k := range v.MapKeys() {
kv := v.MapIndex(k)
fmt.Fprintf(p.w, "%s", ni)
p.reprValue(k, ni)
p.reprValue(seen, k, ni)
fmt.Fprintf(p.w, ": ")
p.reprValue(kv, ni)
p.reprValue(seen, kv, ni)
if p.indent != "" {
fmt.Fprintf(p.w, ",\n")
} else if i < v.Len()-1 {
......@@ -213,7 +234,7 @@ func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
continue
}
fmt.Fprintf(p.w, "%s%s: ", ni, t.Name)
p.reprValue(f, ni)
p.reprValue(seen, f, ni)
if p.indent != "" {
fmt.Fprintf(p.w, ",\n")
} else if i < v.NumField()-1 {
......@@ -228,10 +249,10 @@ func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
return
}
fmt.Fprintf(p.w, "&")
p.reprValue(v.Elem(), indent)
p.reprValue(seen, v.Elem(), indent)
case reflect.String:
if t.Name() != "string" {
if t.Name() != "string" || p.alwaysIncludeType {
fmt.Fprintf(p.w, "%s(%q)", t, v.String())
} else {
fmt.Fprintf(p.w, "%q", v.String())
......@@ -241,11 +262,11 @@ func (p *Printer) reprValue(v reflect.Value, indent string) { // nolint: gocyclo
if v.IsNil() {
fmt.Fprintf(p.w, "interface {}(nil)")
} else {
p.reprValue(v.Elem(), indent)
p.reprValue(seen, v.Elem(), indent)
}
default:
if t.Name() != realKindName[t.Kind()] {
if t.Name() != realKindName[t.Kind()] || p.alwaysIncludeType {
fmt.Fprintf(p.w, "%s(%v)", t, v)
} else {
fmt.Fprintf(p.w, "%v", v)
......
package repr
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
......@@ -74,7 +75,7 @@ func TestReprStructWithIndent(t *testing.T) {
func TestReprByteArray(t *testing.T) {
b := []byte{1, 2, 3}
assert.Equal(t, `[]uint8{1, 2, 3}`, String(b))
assert.Equal(t, "[]byte(\"\\x01\\x02\\x03\")", String(b))
}
type privateTestStruct struct {
......@@ -86,6 +87,18 @@ func TestReprPrivateField(t *testing.T) {
assert.Equal(t, `repr.privateTestStruct{a: "hello"}`, String(s))
}
func TestReprNilAlone(t *testing.T) {
var err error
s := String(err)
assert.Equal(t, "nil", s)
}
func TestReprNilInsideArray(t *testing.T) {
arr := []*privateTestStruct{{"hello"}, nil}
s := String(arr)
assert.Equal(t, "[]*repr.privateTestStruct{&repr.privateTestStruct{a: \"hello\"}, nil}", s)
}
type Enum int
func (e Enum) String() string {
......@@ -97,3 +110,16 @@ func TestEnum(t *testing.T) {
s := String(v)
assert.Equal(t, "repr.Enum(Value)", s)
}
func TestShowType(t *testing.T) {
a := map[string]privateTestStruct{"foo": {"bar"}}
s := String(a, AlwaysIncludeType(), Indent(" "))
t.Log(s)
assert.Equal(t, strings.TrimSpace(`
map[string]repr.privateTestStruct{
string("foo"): repr.privateTestStruct{
a: string("bar"),
},
}
`), s)
}