refresh!
diff --git a/Readme b/Readme
new file mode 100644
index 0000000..c589fc6
--- /dev/null
+++ b/Readme
@@ -0,0 +1,9 @@
+package pretty
+
+ import "github.com/kr/pretty"
+
+ Package pretty provides pretty-printing for Go values.
+
+Documentation
+
+ http://godoc.org/github.com/kr/pretty
diff --git a/Readme.md b/Readme.md
deleted file mode 100644
index e8ec9b4..0000000
--- a/Readme.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# pretty
-
-Package pretty provides pretty-printing for Go values. This is useful during
-debugging, to avoid wrapping long output lines in the terminal.
-
-It provides a function, Formatter, that can be used with any function that
-accepts a format string. For example,
-
- type LongTypeName struct {
- longFieldName, otherLongFieldName int
- }
- func TestFoo(t *testing.T) {
- var x []LongTypeName{{1, 2}, {3, 4}, {5, 6}}
- t.Errorf("%# v", Formatter(x))
- }
-
-This package also provides a convenience wrapper for each function in
-package fmt that takes a format string.
-
-
-## Documentation
-
-See [GoDoc](http://godoc.org/github.com/kr/pretty) for automatic documentation.
-
-
-## Installation
-
- $ go get github.com/kr/pretty
-
-then
-
- import "github.com/kr/pretty"
diff --git a/diff_test.go b/diff_test.go
index c2e5696..02d1953 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -30,38 +30,34 @@
{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
{S{}, S{I: 0}, []string{`I: nil != 0`}},
{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
- {S{}, S{C: []int{1}}, []string{`C: []int{} != []int{1}`}},
+ {S{}, S{C: []int{1}}, []string{`C: []int(nil) != []int{1}`}},
+ {S{C: []int{}}, S{C: []int{1}}, []string{`C: []int{} != []int{1}`}},
{S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 <nil> <nil> []}`}},
}
-
func TestDiff(t *testing.T) {
for _, tt := range diffs {
got := Diff(tt.a, tt.b)
- if len(got) != len(tt.exp) {
+ eq := len(got) == len(tt.exp)
+ if eq {
+ for i := range got {
+ eq = eq && got[i] == tt.exp[i]
+ }
+ }
+ if !eq {
t.Errorf("diffing % #v", tt.a)
t.Errorf("with % #v", tt.b)
diffdiff(t, got, tt.exp)
continue
}
- for i := range got {
- if got[i] != tt.exp[i] {
- t.Errorf("diffing % #v", tt.a)
- t.Errorf("with % #v", tt.b)
- diffdiff(t, got, tt.exp)
- break
- }
- }
}
}
-
func diffdiff(t *testing.T, got, exp []string) {
minus(t, "unexpected:", got, exp)
minus(t, "missing:", exp, got)
}
-
func minus(t *testing.T, s string, a, b []string) {
var i, j int
for i = 0; i < len(a); i++ {
diff --git a/example_test.go b/example_test.go
new file mode 100644
index 0000000..ecf40f3
--- /dev/null
+++ b/example_test.go
@@ -0,0 +1,20 @@
+package pretty_test
+
+import (
+ "fmt"
+ "github.com/kr/pretty"
+)
+
+func Example() {
+ type myType struct {
+ a, b int
+ }
+ var x = []myType{{1, 2}, {3, 4}, {5, 6}}
+ fmt.Printf("%# v", pretty.Formatter(x))
+ // output:
+ // []pretty_test.myType{
+ // {a:1, b:2},
+ // {a:3, b:4},
+ // {a:5, b:6},
+ // }
+}
diff --git a/formatter.go b/formatter.go
index 23111ad..77a9f18 100644
--- a/formatter.go
+++ b/formatter.go
@@ -2,26 +2,19 @@
import (
"fmt"
+ "github.com/kr/text"
"io"
"reflect"
+ "strconv"
+ "text/tabwriter"
)
const (
limit = 50
)
-var (
- commaLFBytes = []byte(",\n")
- curlyBytes = []byte("{}")
- openCurlyLFBytes = []byte("{\n")
- spacePlusLFBytes = []byte(" +\n")
-)
-
type formatter struct {
- d int
x interface{}
-
- omit bool
}
// Formatter makes a wrapper, f, that will format x as go source with line
@@ -60,136 +53,231 @@
func (fo formatter) Format(f fmt.State, c rune) {
if c == 'v' && f.Flag('#') && f.Flag(' ') {
- fo.format(f)
+ w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
+ p := &printer{tw: w, Writer: w}
+ p.printValue(reflect.ValueOf(fo.x), true)
+ w.Flush()
return
}
fo.passThrough(f, c)
}
-func (fo formatter) format(w io.Writer) {
- v := reflect.ValueOf(fo.x)
+type printer struct {
+ io.Writer
+ tw *tabwriter.Writer
+}
+
+func (p *printer) indent() *printer {
+ q := *p
+ q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
+ q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
+ return &q
+}
+
+func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
+ if showType {
+ io.WriteString(p, v.Type().String())
+ fmt.Fprintf(p, "(%#v)", x)
+ } else {
+ fmt.Fprintf(p, "%#v", x)
+ }
+}
+
+func (p *printer) printValue(v reflect.Value, showType bool) {
switch v.Kind() {
+ case reflect.Bool:
+ p.printInline(v, v.Bool(), showType)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ p.printInline(v, v.Int(), showType)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ p.printInline(v, v.Uint(), showType)
+ case reflect.Float32, reflect.Float64:
+ p.printInline(v, v.Float(), showType)
+ case reflect.Complex64, reflect.Complex128:
+ fmt.Fprintf(p, "%#v", v.Complex())
case reflect.String:
- lim := limit - 8*fo.d
- s := v.String()
- z := len(s)
- n := (z + lim - 1) / lim
- if n < 1 {
- n = 1 // empty string still produces output
+ p.fmtString(v.String())
+ case reflect.Map:
+ t := v.Type()
+ if showType {
+ io.WriteString(p, t.String())
}
- sep := append(spacePlusLFBytes, make([]byte, fo.d)...)
- for j := 0; j < fo.d; j++ {
- sep[3+j] = '\t'
- }
- for i := 0; i < n; i++ {
- if i > 0 {
- w.Write(sep)
+ writeByte(p, '{')
+ if nonzero(v) {
+ expand := !canInline(v.Type())
+ pp := p
+ if expand {
+ writeByte(p, '\n')
+ pp = p.indent()
}
- l, h := i*lim, (i+1)*lim
- if h > z {
- h = z
+ keys := v.MapKeys()
+ for i := 0; i < v.Len(); i++ {
+ showTypeInStruct := true
+ k := keys[i]
+ mv := v.MapIndex(k)
+ pp.printValue(k, false)
+ writeByte(pp, ':')
+ if expand {
+ writeByte(pp, '\t')
+ }
+ showTypeInStruct = t.Elem().Kind() == reflect.Interface
+ pp.printValue(mv, showTypeInStruct)
+ if expand {
+ io.WriteString(pp, ",\n")
+ } else if i < v.Len()-1 {
+ io.WriteString(pp, ", ")
+ }
}
- fmt.Fprintf(w, "%#v", s[l:h])
+ if expand {
+ pp.tw.Flush()
+ }
}
- return
+ writeByte(p, '}')
+ case reflect.Struct:
+ t := v.Type()
+ if showType {
+ io.WriteString(p, t.String())
+ }
+ writeByte(p, '{')
+ if nonzero(v) {
+ expand := !canInline(v.Type())
+ pp := p
+ if expand {
+ writeByte(p, '\n')
+ pp = p.indent()
+ }
+ for i := 0; i < v.NumField(); i++ {
+ showTypeInStruct := true
+ if f := t.Field(i); f.Name != "" {
+ io.WriteString(pp, f.Name)
+ writeByte(pp, ':')
+ if expand {
+ writeByte(pp, '\t')
+ }
+ showTypeInStruct = f.Type.Kind() == reflect.Interface
+ }
+ pp.printValue(getField(v, i), showTypeInStruct)
+ if expand {
+ io.WriteString(pp, ",\n")
+ } else if i < v.NumField()-1 {
+ io.WriteString(pp, ", ")
+ }
+ }
+ if expand {
+ pp.tw.Flush()
+ }
+ }
+ writeByte(p, '}')
+ case reflect.Interface:
+ switch e := v.Elem(); {
+ case e.Kind() == reflect.Invalid:
+ io.WriteString(p, "nil")
+ case e.IsValid():
+ p.printValue(e, showType)
+ default:
+ io.WriteString(p, v.Type().String())
+ io.WriteString(p, "(nil)")
+ }
+ case reflect.Array, reflect.Slice:
+ t := v.Type()
+ io.WriteString(p, t.String())
+ writeByte(p, '{')
+ expand := !canInline(v.Type())
+ pp := p
+ if expand {
+ writeByte(p, '\n')
+ pp = p.indent()
+ }
+ for i := 0; i < v.Len(); i++ {
+ showTypeInSlice := t.Elem().Kind() == reflect.Interface
+ pp.printValue(v.Index(i), showTypeInSlice)
+ if expand {
+ io.WriteString(pp, ",\n")
+ } else if i < v.Len()-1 {
+ io.WriteString(pp, ", ")
+ }
+ }
+ if expand {
+ pp.tw.Flush()
+ }
+ writeByte(p, '}')
case reflect.Ptr:
e := v.Elem()
if !e.IsValid() {
- fmt.Fprintf(w, "%#v", fo.x)
+ writeByte(p, '(')
+ io.WriteString(p, v.Type().String())
+ io.WriteString(p, ")(nil)")
} else {
- writeByte(w, '&')
- if e.CanInterface() {
- fmt.Fprintf(w, "%# v", formatter{d: fo.d, x: e.Interface()})
- } else {
- fmt.Fprint(w, e.String())
- }
+ writeByte(p, '&')
+ p.printValue(e, true)
}
- case reflect.Slice:
- s := fmt.Sprintf("%#v", fo.x)
- if len(s) < limit {
- io.WriteString(w, s)
- return
+ case reflect.Chan:
+ x := v.Pointer()
+ if showType {
+ writeByte(p, '(')
+ io.WriteString(p, v.Type().String())
+ fmt.Fprintf(p, ")(%#v)", x)
+ } else {
+ fmt.Fprintf(p, "%#v", x)
}
-
- t := v.Type()
-
- io.WriteString(w, reflect.TypeOf(fo.x).String())
- w.Write(openCurlyLFBytes)
- for i := 0; i < v.Len(); i++ {
- for j := 0; j < fo.d+1; j++ {
- writeByte(w, '\t')
- }
- if v.Index(i).CanInterface() {
- inner := formatter{d: fo.d + 1, x: v.Index(i).Interface(), omit: t.Elem().Kind() != reflect.Interface}
- fmt.Fprintf(w, "%# v", inner)
- } else {
- fmt.Fprint(w, v.Index(i).String())
- }
- w.Write(commaLFBytes)
- }
- for j := 0; j < fo.d; j++ {
- writeByte(w, '\t')
- }
- writeByte(w, '}')
- case reflect.Struct:
- t := v.Type()
- if tryDeepEqual(reflect.Zero(t).Interface(), fo.x) {
- if !fo.omit {
- io.WriteString(w, t.String())
- }
- w.Write(curlyBytes)
- return
- }
-
- s := fmt.Sprintf("%#v", fo.x)
- if fo.omit {
- s = s[len(t.String()):]
- }
- if len(s) < limit {
- io.WriteString(w, s)
- return
- }
-
- if !fo.omit {
- io.WriteString(w, t.String())
- }
- w.Write(openCurlyLFBytes)
- var max int
- for i := 0; i < v.NumField(); i++ {
- if v := t.Field(i); v.Name != "" {
- if len(v.Name)+2 > max {
- max = len(v.Name) + 2
- }
- }
- }
- for i := 0; i < v.NumField(); i++ {
- if f := t.Field(i); f.Name != "" {
- for j := 0; j < fo.d+1; j++ {
- writeByte(w, '\t')
- }
- io.WriteString(w, f.Name)
- writeByte(w, ':')
- for j := len(f.Name) + 1; j < max; j++ {
- writeByte(w, ' ')
- }
- if v.Field(i).CanInterface() {
- inner := formatter{d: fo.d + 1, x: v.Field(i).Interface()}
- fmt.Fprintf(w, "%# v", inner)
- } else {
- io.WriteString(w, v.Field(i).String())
- }
- w.Write(commaLFBytes)
- }
- }
- for j := 0; j < fo.d; j++ {
- writeByte(w, '\t')
- }
- writeByte(w, '}')
+ case reflect.Func:
+ io.WriteString(p, v.Type().String())
+ io.WriteString(p, " {...}")
+ case reflect.UnsafePointer:
+ p.printInline(v, v.Pointer(), showType)
default:
- fmt.Fprintf(w, "%#v", fo.x)
+ io.WriteString(p, "(UNKNOWN)")
}
}
+func canInline(t reflect.Type) bool {
+ switch t.Kind() {
+ case reflect.Map:
+ return !canExpand(t.Elem())
+ case reflect.Struct:
+ for i := 0; i < t.NumField(); i++ {
+ if canExpand(t.Field(i).Type) {
+ return false
+ }
+ }
+ return true
+ case reflect.Interface:
+ return false
+ case reflect.Array, reflect.Slice:
+ return !canExpand(t.Elem())
+ case reflect.Ptr:
+ return false
+ case reflect.Chan, reflect.Func, reflect.UnsafePointer:
+ return false
+ }
+ return true
+}
+
+func canExpand(t reflect.Type) bool {
+ switch t.Kind() {
+ case reflect.String, reflect.Map, reflect.Struct,
+ reflect.Interface, reflect.Array, reflect.Slice,
+ reflect.Ptr:
+ return true
+ }
+ return false
+}
+
+func (p *printer) fmtString(s string) {
+ const segSize = 30
+ y, c := 0, 0
+ for i := range s {
+ if c > segSize {
+ q := strconv.Quote(s[y:][:c])
+ io.WriteString(p, q)
+ io.WriteString(p, " +\n\t")
+ y, c = i, 0
+ }
+ c++
+ }
+ fmt.Fprintf(p, "%#v", s[y:][:c])
+}
+
func tryDeepEqual(a, b interface{}) bool {
defer func() { recover() }()
return reflect.DeepEqual(a, b)
@@ -198,3 +286,17 @@
func writeByte(w io.Writer, b byte) {
w.Write([]byte{b})
}
+
+func getField(v reflect.Value, i int) reflect.Value {
+ val := v.Field(i)
+ if val.Kind() == reflect.Interface && !val.IsNil() {
+ val = val.Elem()
+ }
+ return val
+}
+
+func nonzero(v reflect.Value) bool {
+ defer func() { recover() }()
+ z := reflect.Zero(v.Type())
+ return !reflect.DeepEqual(z.Interface(), v.Interface())
+}
diff --git a/formatter_test.go b/formatter_test.go
index 52c9eca..561b5a6 100644
--- a/formatter_test.go
+++ b/formatter_test.go
@@ -3,6 +3,7 @@
import (
"fmt"
"testing"
+ "unsafe"
)
type test struct {
@@ -21,7 +22,6 @@
type F int
-
func (f F) Format(s fmt.State, c int) {
fmt.Fprintf(s, "F(%d)", int(f))
}
@@ -31,18 +31,35 @@
var gosyntax = []test{
{"", `""`},
{"a", `"a"`},
- {1, "1"},
- {F(5), "F(5)"},
- {long, `"` + long[:50] + "\" +\n\"" + long[50:] + `"`},
+ {1, "int(1)"},
+ {1.0, "float64(1)"},
+ {complex(1, 0), "(1+0i)"},
+ //{make(chan int), "(chan int)(0x1234)"},
+ {unsafe.Pointer(uintptr(1)), "unsafe.Pointer(0x1)"},
+ {func(int) {}, "func(int) {...}"},
+ {map[int]int{1: 1}, "map[int]int{1:1}"},
+ {int32(1), "int32(1)"},
+ {F(5), "pretty.F(5)"},
+ {
+ map[int]T{1: T{}},
+ `map[int]pretty.T{
+ 1: {},
+}`,
+ },
+ {
+ long,
+ `"abcdefghijklmnopqrstuvwxyzABCDE" +
+ "FGHIJKLMNOPQRSTUVWXYZ0123456789"`,
+ },
{
LongStructTypeName{
longFieldName: LongStructTypeName{},
otherLongFieldName: long,
},
`pretty.LongStructTypeName{
- longFieldName: pretty.LongStructTypeName{},
- otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" +
- "QRSTUVWXYZ0123456789",
+ longFieldName: pretty.LongStructTypeName{},
+ otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDE" +
+ "FGHIJKLMNOPQRSTUVWXYZ0123456789",
}`,
},
{
@@ -51,8 +68,8 @@
otherLongFieldName: (*LongStructTypeName)(nil),
},
`&pretty.LongStructTypeName{
- longFieldName: &pretty.LongStructTypeName{},
- otherLongFieldName: (*pretty.LongStructTypeName)(nil),
+ longFieldName: &pretty.LongStructTypeName{},
+ otherLongFieldName: (*pretty.LongStructTypeName)(nil),
}`,
},
{
@@ -62,13 +79,16 @@
{long, nil},
},
`[]pretty.LongStructTypeName{
- {},
- {longFieldName:3, otherLongFieldName:3},
- {
- longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGH" +
- "IJKLMNOPQRSTUVWXYZ0123456789",
- otherLongFieldName: <nil>,
- },
+ {},
+ {
+ longFieldName: int(3),
+ otherLongFieldName: int(3),
+ },
+ {
+ longFieldName: "abcdefghijklmnopqrstuvwxyzABCDE" +
+ "FGHIJKLMNOPQRSTUVWXYZ0123456789",
+ otherLongFieldName: nil,
+ },
}`,
},
{
@@ -78,28 +98,27 @@
T{3, 4},
LongStructTypeName{long, nil},
},
- `[]interface { }{
- pretty.LongStructTypeName{},
- []byte{0x1, 0x2, 0x3},
- pretty.T{x:3, y:4},
- pretty.LongStructTypeName{
- longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGH" +
- "IJKLMNOPQRSTUVWXYZ0123456789",
- otherLongFieldName: <nil>,
- },
+ `[]interface {}{
+ pretty.LongStructTypeName{},
+ []uint8{0x1, 0x2, 0x3},
+ pretty.T{x:3, y:4},
+ pretty.LongStructTypeName{
+ longFieldName: "abcdefghijklmnopqrstuvwxyzABCDE" +
+ "FGHIJKLMNOPQRSTUVWXYZ0123456789",
+ otherLongFieldName: nil,
+ },
}`,
},
}
-
func TestGoSyntax(t *testing.T) {
for _, tt := range gosyntax {
s := fmt.Sprintf("%# v", Formatter(tt.v))
if tt.s != s {
- t.Errorf("expected %q\n", tt.s)
- t.Errorf("got %q\n", s)
- t.Errorf("expraw %s\n", tt.s)
- t.Errorf("gotraw %s\n", s)
+ t.Errorf("expected %q", tt.s)
+ t.Errorf("got %q", s)
+ t.Errorf("expraw\n%s", tt.s)
+ t.Errorf("gotraw\n%s", s)
}
}
}
diff --git a/pretty.go b/pretty.go
index 84b69c4..733424a 100644
--- a/pretty.go
+++ b/pretty.go
@@ -1,19 +1,11 @@
-// Package pretty provides pretty-printing for go values. This is useful during
-// debugging, to avoid wrapping long output lines in the terminal.
+// Package pretty provides pretty-printing for Go values. This is
+// useful during debugging, to avoid wrapping long output lines in
+// the terminal.
//
-// It provides a function, Formatter, that can be used with any function that
-// accepts a format string. For example,
-//
-// type LongTypeName struct {
-// longFieldName, otherLongFieldName int
-// }
-// func TestFoo(t *testing.T) {
-// var x []LongTypeName{{1, 2}, {3, 4}, {5, 6}}
-// t.Errorf("%# v", Formatter(x))
-// }
-//
-// This package also provides a convenience wrapper for each function in
-// package fmt that takes a format string.
+// It provides a function, Formatter, that can be used with any
+// function that accepts a format string. It also provides a
+// convenience wrapper for each function in package fmt that takes
+// a format string.
package pretty
import (