├── .github ├── dependabot.yml └── workflows │ └── build-test.yml ├── .gitignore ├── License ├── Readme ├── diff.go ├── diff_test.go ├── example_test.go ├── formatter.go ├── formatter_test.go ├── go.mod ├── go.sum ├── pretty.go └── zero.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: build-test 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/setup-go@v2 11 | with: 12 | go-version: 1.13.x 13 | - uses: actions/checkout@v3 14 | - name: Build 15 | run: go build . 16 | - name: Test 17 | run: go test -v . 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [568].out 2 | _go* 3 | _test* 4 | _obj 5 | /.idea 6 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright 2012 Keith Rarick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Readme: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import "github.com/kr/pretty" 4 | 5 | Package pretty provides pretty-printing for Go values. 6 | 7 | Documentation 8 | 9 | http://godoc.org/github.com/kr/pretty 10 | -------------------------------------------------------------------------------- /diff.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | ) 8 | 9 | type sbuf []string 10 | 11 | func (p *sbuf) Printf(format string, a ...interface{}) { 12 | s := fmt.Sprintf(format, a...) 13 | *p = append(*p, s) 14 | } 15 | 16 | // Diff returns a slice where each element describes 17 | // a difference between a and b. 18 | func Diff(a, b interface{}) (desc []string) { 19 | Pdiff((*sbuf)(&desc), a, b) 20 | return desc 21 | } 22 | 23 | // wprintfer calls Fprintf on w for each Printf call 24 | // with a trailing newline. 25 | type wprintfer struct{ w io.Writer } 26 | 27 | func (p *wprintfer) Printf(format string, a ...interface{}) { 28 | fmt.Fprintf(p.w, format+"\n", a...) 29 | } 30 | 31 | // Fdiff writes to w a description of the differences between a and b. 32 | func Fdiff(w io.Writer, a, b interface{}) { 33 | Pdiff(&wprintfer{w}, a, b) 34 | } 35 | 36 | type Printfer interface { 37 | Printf(format string, a ...interface{}) 38 | } 39 | 40 | // Pdiff prints to p a description of the differences between a and b. 41 | // It calls Printf once for each difference, with no trailing newline. 42 | // The standard library log.Logger is a Printfer. 43 | func Pdiff(p Printfer, a, b interface{}) { 44 | d := diffPrinter{ 45 | w: p, 46 | aVisited: make(map[visit]visit), 47 | bVisited: make(map[visit]visit), 48 | } 49 | d.diff(reflect.ValueOf(a), reflect.ValueOf(b)) 50 | } 51 | 52 | type Logfer interface { 53 | Logf(format string, a ...interface{}) 54 | } 55 | 56 | // logprintfer calls Fprintf on w for each Printf call 57 | // with a trailing newline. 58 | type logprintfer struct{ l Logfer } 59 | 60 | func (p *logprintfer) Printf(format string, a ...interface{}) { 61 | p.l.Logf(format, a...) 62 | } 63 | 64 | // Ldiff prints to l a description of the differences between a and b. 65 | // It calls Logf once for each difference, with no trailing newline. 66 | // The standard library testing.T and testing.B are Logfers. 67 | func Ldiff(l Logfer, a, b interface{}) { 68 | Pdiff(&logprintfer{l}, a, b) 69 | } 70 | 71 | type diffPrinter struct { 72 | w Printfer 73 | l string // label 74 | 75 | aVisited map[visit]visit 76 | bVisited map[visit]visit 77 | } 78 | 79 | func (w diffPrinter) printf(f string, a ...interface{}) { 80 | var l string 81 | if w.l != "" { 82 | l = w.l + ": " 83 | } 84 | w.w.Printf(l+f, a...) 85 | } 86 | 87 | func (w diffPrinter) diff(av, bv reflect.Value) { 88 | if !av.IsValid() && bv.IsValid() { 89 | w.printf("nil != %# v", formatter{v: bv, quote: true}) 90 | return 91 | } 92 | if av.IsValid() && !bv.IsValid() { 93 | w.printf("%# v != nil", formatter{v: av, quote: true}) 94 | return 95 | } 96 | if !av.IsValid() && !bv.IsValid() { 97 | return 98 | } 99 | 100 | at := av.Type() 101 | bt := bv.Type() 102 | if at != bt { 103 | w.printf("%v != %v", at, bt) 104 | return 105 | } 106 | 107 | if av.CanAddr() && bv.CanAddr() { 108 | avis := visit{av.UnsafeAddr(), at} 109 | bvis := visit{bv.UnsafeAddr(), bt} 110 | var cycle bool 111 | 112 | // Have we seen this value before? 113 | if vis, ok := w.aVisited[avis]; ok { 114 | cycle = true 115 | if vis != bvis { 116 | w.printf("%# v (previously visited) != %# v", formatter{v: av, quote: true}, formatter{v: bv, quote: true}) 117 | } 118 | } else if _, ok := w.bVisited[bvis]; ok { 119 | cycle = true 120 | w.printf("%# v != %# v (previously visited)", formatter{v: av, quote: true}, formatter{v: bv, quote: true}) 121 | } 122 | w.aVisited[avis] = bvis 123 | w.bVisited[bvis] = avis 124 | if cycle { 125 | return 126 | } 127 | } 128 | 129 | switch kind := at.Kind(); kind { 130 | case reflect.Bool: 131 | if a, b := av.Bool(), bv.Bool(); a != b { 132 | w.printf("%v != %v", a, b) 133 | } 134 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 135 | if a, b := av.Int(), bv.Int(); a != b { 136 | w.printf("%d != %d", a, b) 137 | } 138 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 139 | if a, b := av.Uint(), bv.Uint(); a != b { 140 | w.printf("%d != %d", a, b) 141 | } 142 | case reflect.Float32, reflect.Float64: 143 | if a, b := av.Float(), bv.Float(); a != b { 144 | w.printf("%v != %v", a, b) 145 | } 146 | case reflect.Complex64, reflect.Complex128: 147 | if a, b := av.Complex(), bv.Complex(); a != b { 148 | w.printf("%v != %v", a, b) 149 | } 150 | case reflect.Array: 151 | n := av.Len() 152 | for i := 0; i < n; i++ { 153 | w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i)) 154 | } 155 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 156 | if a, b := av.Pointer(), bv.Pointer(); a != b { 157 | w.printf("%#x != %#x", a, b) 158 | } 159 | case reflect.Interface: 160 | w.diff(av.Elem(), bv.Elem()) 161 | case reflect.Map: 162 | ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys()) 163 | for _, k := range ak { 164 | w := w.relabel(fmt.Sprintf("[%#v]", k)) 165 | w.printf("%q != (missing)", av.MapIndex(k)) 166 | } 167 | for _, k := range both { 168 | w := w.relabel(fmt.Sprintf("[%#v]", k)) 169 | w.diff(av.MapIndex(k), bv.MapIndex(k)) 170 | } 171 | for _, k := range bk { 172 | w := w.relabel(fmt.Sprintf("[%#v]", k)) 173 | w.printf("(missing) != %q", bv.MapIndex(k)) 174 | } 175 | case reflect.Ptr: 176 | switch { 177 | case av.IsNil() && !bv.IsNil(): 178 | w.printf("nil != %# v", formatter{v: bv, quote: true}) 179 | case !av.IsNil() && bv.IsNil(): 180 | w.printf("%# v != nil", formatter{v: av, quote: true}) 181 | case !av.IsNil() && !bv.IsNil(): 182 | w.diff(av.Elem(), bv.Elem()) 183 | } 184 | case reflect.Slice: 185 | lenA := av.Len() 186 | lenB := bv.Len() 187 | if lenA != lenB { 188 | w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB) 189 | break 190 | } 191 | for i := 0; i < lenA; i++ { 192 | w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i)) 193 | } 194 | case reflect.String: 195 | if a, b := av.String(), bv.String(); a != b { 196 | w.printf("%q != %q", a, b) 197 | } 198 | case reflect.Struct: 199 | for i := 0; i < av.NumField(); i++ { 200 | w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i)) 201 | } 202 | default: 203 | panic("unknown reflect Kind: " + kind.String()) 204 | } 205 | } 206 | 207 | func (d diffPrinter) relabel(name string) (d1 diffPrinter) { 208 | d1 = d 209 | if d.l != "" && name[0] != '[' { 210 | d1.l += "." 211 | } 212 | d1.l += name 213 | return d1 214 | } 215 | 216 | // keyEqual compares a and b for equality. 217 | // Both a and b must be valid map keys. 218 | func keyEqual(av, bv reflect.Value) bool { 219 | if !av.IsValid() && !bv.IsValid() { 220 | return true 221 | } 222 | if !av.IsValid() || !bv.IsValid() || av.Type() != bv.Type() { 223 | return false 224 | } 225 | switch kind := av.Kind(); kind { 226 | case reflect.Bool: 227 | a, b := av.Bool(), bv.Bool() 228 | return a == b 229 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 230 | a, b := av.Int(), bv.Int() 231 | return a == b 232 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 233 | a, b := av.Uint(), bv.Uint() 234 | return a == b 235 | case reflect.Float32, reflect.Float64: 236 | a, b := av.Float(), bv.Float() 237 | return a == b 238 | case reflect.Complex64, reflect.Complex128: 239 | a, b := av.Complex(), bv.Complex() 240 | return a == b 241 | case reflect.Array: 242 | for i := 0; i < av.Len(); i++ { 243 | if !keyEqual(av.Index(i), bv.Index(i)) { 244 | return false 245 | } 246 | } 247 | return true 248 | case reflect.Chan, reflect.UnsafePointer, reflect.Ptr: 249 | a, b := av.Pointer(), bv.Pointer() 250 | return a == b 251 | case reflect.Interface: 252 | return keyEqual(av.Elem(), bv.Elem()) 253 | case reflect.String: 254 | a, b := av.String(), bv.String() 255 | return a == b 256 | case reflect.Struct: 257 | for i := 0; i < av.NumField(); i++ { 258 | if !keyEqual(av.Field(i), bv.Field(i)) { 259 | return false 260 | } 261 | } 262 | return true 263 | default: 264 | panic("invalid map key type " + av.Type().String()) 265 | } 266 | } 267 | 268 | func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { 269 | for _, av := range a { 270 | inBoth := false 271 | for _, bv := range b { 272 | if keyEqual(av, bv) { 273 | inBoth = true 274 | both = append(both, av) 275 | break 276 | } 277 | } 278 | if !inBoth { 279 | ak = append(ak, av) 280 | } 281 | } 282 | for _, bv := range b { 283 | inBoth := false 284 | for _, av := range a { 285 | if keyEqual(av, bv) { 286 | inBoth = true 287 | break 288 | } 289 | } 290 | if !inBoth { 291 | bk = append(bk, bv) 292 | } 293 | } 294 | return 295 | } 296 | -------------------------------------------------------------------------------- /diff_test.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "reflect" 8 | "testing" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | _ Logfer = (*testing.T)(nil) 14 | _ Logfer = (*testing.B)(nil) 15 | _ Printfer = (*log.Logger)(nil) 16 | ) 17 | 18 | type difftest struct { 19 | a interface{} 20 | b interface{} 21 | exp []string 22 | } 23 | 24 | type S struct { 25 | A int 26 | S *S 27 | I interface{} 28 | C []int 29 | } 30 | 31 | type ( 32 | N struct{ N int } 33 | E interface{} 34 | ) 35 | 36 | var ( 37 | c0 = make(chan int) 38 | c1 = make(chan int) 39 | f0 = func() {} 40 | f1 = func() {} 41 | i0 = 0 42 | i1 = 1 43 | ) 44 | 45 | var diffs = []difftest{ 46 | {a: nil, b: nil}, 47 | {a: S{A: 1}, b: S{A: 1}}, 48 | 49 | {0, "", []string{`int != string`}}, 50 | {0, 1, []string{`0 != 1`}}, 51 | {S{}, new(S), []string{`pretty.S != *pretty.S`}}, 52 | {"a", "b", []string{`"a" != "b"`}}, 53 | {S{}, S{A: 1}, []string{`A: 0 != 1`}}, 54 | {new(S), &S{A: 1}, []string{`A: 0 != 1`}}, 55 | {S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}}, 56 | {S{}, S{I: 0}, []string{`I: nil != int(0)`}}, 57 | {S{I: 1}, S{I: "x"}, []string{`I: int != string`}}, 58 | {S{}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}}, 59 | {S{C: []int{}}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}}, 60 | {S{C: []int{1, 2, 3}}, S{C: []int{1, 2, 4}}, []string{`C[2]: 3 != 4`}}, 61 | {S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &pretty.S{}`}}, 62 | 63 | // unexported fields of every reflect.Kind (both equal and unequal) 64 | {struct{ x bool }{false}, struct{ x bool }{false}, nil}, 65 | {struct{ x bool }{false}, struct{ x bool }{true}, []string{`x: false != true`}}, 66 | {struct{ x int }{0}, struct{ x int }{0}, nil}, 67 | {struct{ x int }{0}, struct{ x int }{1}, []string{`x: 0 != 1`}}, 68 | {struct{ x int8 }{0}, struct{ x int8 }{0}, nil}, 69 | {struct{ x int8 }{0}, struct{ x int8 }{1}, []string{`x: 0 != 1`}}, 70 | {struct{ x int16 }{0}, struct{ x int16 }{0}, nil}, 71 | {struct{ x int16 }{0}, struct{ x int16 }{1}, []string{`x: 0 != 1`}}, 72 | {struct{ x int32 }{0}, struct{ x int32 }{0}, nil}, 73 | {struct{ x int32 }{0}, struct{ x int32 }{1}, []string{`x: 0 != 1`}}, 74 | {struct{ x int64 }{0}, struct{ x int64 }{0}, nil}, 75 | {struct{ x int64 }{0}, struct{ x int64 }{1}, []string{`x: 0 != 1`}}, 76 | {struct{ x uint }{0}, struct{ x uint }{0}, nil}, 77 | {struct{ x uint }{0}, struct{ x uint }{1}, []string{`x: 0 != 1`}}, 78 | {struct{ x uint8 }{0}, struct{ x uint8 }{0}, nil}, 79 | {struct{ x uint8 }{0}, struct{ x uint8 }{1}, []string{`x: 0 != 1`}}, 80 | {struct{ x uint16 }{0}, struct{ x uint16 }{0}, nil}, 81 | {struct{ x uint16 }{0}, struct{ x uint16 }{1}, []string{`x: 0 != 1`}}, 82 | {struct{ x uint32 }{0}, struct{ x uint32 }{0}, nil}, 83 | {struct{ x uint32 }{0}, struct{ x uint32 }{1}, []string{`x: 0 != 1`}}, 84 | {struct{ x uint64 }{0}, struct{ x uint64 }{0}, nil}, 85 | {struct{ x uint64 }{0}, struct{ x uint64 }{1}, []string{`x: 0 != 1`}}, 86 | {struct{ x uintptr }{0}, struct{ x uintptr }{0}, nil}, 87 | {struct{ x uintptr }{0}, struct{ x uintptr }{1}, []string{`x: 0 != 1`}}, 88 | {struct{ x float32 }{0}, struct{ x float32 }{0}, nil}, 89 | {struct{ x float32 }{0}, struct{ x float32 }{1}, []string{`x: 0 != 1`}}, 90 | {struct{ x float64 }{0}, struct{ x float64 }{0}, nil}, 91 | {struct{ x float64 }{0}, struct{ x float64 }{1}, []string{`x: 0 != 1`}}, 92 | {struct{ x complex64 }{0}, struct{ x complex64 }{0}, nil}, 93 | {struct{ x complex64 }{0}, struct{ x complex64 }{1}, []string{`x: (0+0i) != (1+0i)`}}, 94 | {struct{ x complex128 }{0}, struct{ x complex128 }{0}, nil}, 95 | {struct{ x complex128 }{0}, struct{ x complex128 }{1}, []string{`x: (0+0i) != (1+0i)`}}, 96 | {struct{ x [1]int }{[1]int{0}}, struct{ x [1]int }{[1]int{0}}, nil}, 97 | {struct{ x [1]int }{[1]int{0}}, struct{ x [1]int }{[1]int{1}}, []string{`x[0]: 0 != 1`}}, 98 | {struct{ x chan int }{c0}, struct{ x chan int }{c0}, nil}, 99 | {struct{ x chan int }{c0}, struct{ x chan int }{c1}, []string{fmt.Sprintf("x: %p != %p", c0, c1)}}, 100 | {struct{ x func() }{f0}, struct{ x func() }{f0}, nil}, 101 | {struct{ x func() }{f0}, struct{ x func() }{f1}, []string{fmt.Sprintf("x: %p != %p", f0, f1)}}, 102 | {struct{ x interface{} }{0}, struct{ x interface{} }{0}, nil}, 103 | {struct{ x interface{} }{0}, struct{ x interface{} }{1}, []string{`x: 0 != 1`}}, 104 | {struct{ x interface{} }{0}, struct{ x interface{} }{""}, []string{`x: int != string`}}, 105 | {struct{ x interface{} }{0}, struct{ x interface{} }{nil}, []string{`x: int(0) != nil`}}, 106 | {struct{ x interface{} }{nil}, struct{ x interface{} }{0}, []string{`x: nil != int(0)`}}, 107 | {struct{ x map[int]int }{map[int]int{0: 0}}, struct{ x map[int]int }{map[int]int{0: 0}}, nil}, 108 | {struct{ x map[int]int }{map[int]int{0: 0}}, struct{ x map[int]int }{map[int]int{0: 1}}, []string{`x[0]: 0 != 1`}}, 109 | {struct{ x *int }{new(int)}, struct{ x *int }{new(int)}, nil}, 110 | {struct{ x *int }{&i0}, struct{ x *int }{&i1}, []string{`x: 0 != 1`}}, 111 | {struct{ x *int }{nil}, struct{ x *int }{&i0}, []string{`x: nil != &int(0)`}}, 112 | {struct{ x *int }{&i0}, struct{ x *int }{nil}, []string{`x: &int(0) != nil`}}, 113 | {struct{ x []int }{[]int{0}}, struct{ x []int }{[]int{0}}, nil}, 114 | {struct{ x []int }{[]int{0}}, struct{ x []int }{[]int{1}}, []string{`x[0]: 0 != 1`}}, 115 | {struct{ x string }{"a"}, struct{ x string }{"a"}, nil}, 116 | {struct{ x string }{"a"}, struct{ x string }{"b"}, []string{`x: "a" != "b"`}}, 117 | {struct{ x N }{N{0}}, struct{ x N }{N{0}}, nil}, 118 | {struct{ x N }{N{0}}, struct{ x N }{N{1}}, []string{`x.N: 0 != 1`}}, 119 | { 120 | struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))}, 121 | struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))}, 122 | nil, 123 | }, 124 | { 125 | struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))}, 126 | struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(1))}, 127 | []string{`x: 0x0 != 0x1`}, 128 | }, 129 | } 130 | 131 | func TestDiff(t *testing.T) { 132 | for _, tt := range diffs { 133 | expectDiffOutput(t, tt.a, tt.b, tt.exp) 134 | } 135 | } 136 | 137 | func expectDiffOutput(t *testing.T, a, b interface{}, exp []string) { 138 | got := Diff(a, b) 139 | eq := len(got) == len(exp) 140 | if eq { 141 | for i := range got { 142 | eq = eq && got[i] == exp[i] 143 | } 144 | } 145 | if !eq { 146 | t.Errorf("diffing % #v", a) 147 | t.Errorf("with % #v", b) 148 | diffdiff(t, got, exp) 149 | } 150 | } 151 | 152 | func TestKeyEqual(t *testing.T) { 153 | var emptyInterfaceZero interface{} = 0 154 | 155 | cases := []interface{}{ 156 | new(bool), 157 | new(int), 158 | new(int8), 159 | new(int16), 160 | new(int32), 161 | new(int64), 162 | new(uint), 163 | new(uint8), 164 | new(uint16), 165 | new(uint32), 166 | new(uint64), 167 | new(uintptr), 168 | new(float32), 169 | new(float64), 170 | new(complex64), 171 | new(complex128), 172 | new([1]int), 173 | new(chan int), 174 | new(unsafe.Pointer), 175 | new(interface{}), 176 | &emptyInterfaceZero, 177 | new(*int), 178 | new(string), 179 | new(struct{ int }), 180 | } 181 | 182 | for _, test := range cases { 183 | rv := reflect.ValueOf(test).Elem() 184 | if !keyEqual(rv, rv) { 185 | t.Errorf("keyEqual(%s, %s) = false want true", rv.Type(), rv.Type()) 186 | } 187 | } 188 | } 189 | 190 | func TestFdiff(t *testing.T) { 191 | var buf bytes.Buffer 192 | Fdiff(&buf, 0, 1) 193 | want := "0 != 1\n" 194 | if got := buf.String(); got != want { 195 | t.Errorf("Fdiff(0, 1) = %q want %q", got, want) 196 | } 197 | } 198 | 199 | func TestDiffCycle(t *testing.T) { 200 | // Diff two cyclic structs 201 | a := &I{i: 1, R: nil} 202 | a.R = a 203 | b := &I{i: 2, R: nil} 204 | b.R = b 205 | expectDiffOutput(t, a, b, []string{ 206 | `i: 1 != 2`, 207 | }) 208 | 209 | // Diff two equal cyclic structs 210 | b.i = 1 211 | expectDiffOutput(t, a, b, []string{}) 212 | 213 | // Diff two structs with different cycles 214 | b2 := &I{i: 1, R: b} 215 | b.R = b2 216 | expectDiffOutput(t, a, b, []string{`R: pretty.I{ 217 | i: 1, 218 | R: &pretty.I{(CYCLIC REFERENCE)}, 219 | } (previously visited) != pretty.I{ 220 | i: 1, 221 | R: &pretty.I{ 222 | i: 1, 223 | R: &pretty.I{(CYCLIC REFERENCE)}, 224 | }, 225 | }`}) 226 | 227 | // ... and the same in the other direction 228 | expectDiffOutput(t, b, a, []string{`R: pretty.I{ 229 | i: 1, 230 | R: &pretty.I{ 231 | i: 1, 232 | R: &pretty.I{(CYCLIC REFERENCE)}, 233 | }, 234 | } != pretty.I{ 235 | i: 1, 236 | R: &pretty.I{(CYCLIC REFERENCE)}, 237 | } (previously visited)`}) 238 | } 239 | 240 | func diffdiff(t *testing.T, got, exp []string) { 241 | minus(t, "unexpected:", got, exp) 242 | minus(t, "missing:", exp, got) 243 | } 244 | 245 | func minus(t *testing.T, s string, a, b []string) { 246 | var i, j int 247 | for i = 0; i < len(a); i++ { 248 | for j = 0; j < len(b); j++ { 249 | if a[i] == b[j] { 250 | break 251 | } 252 | } 253 | if j == len(b) { 254 | t.Error(s, a[i]) 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package pretty_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kr/pretty" 6 | ) 7 | 8 | func Example() { 9 | type myType struct { 10 | a, b int 11 | } 12 | var x = []myType{{1, 2}, {3, 4}, {5, 6}} 13 | fmt.Printf("%# v", pretty.Formatter(x)) 14 | // output: 15 | // []pretty_test.myType{ 16 | // {a:1, b:2}, 17 | // {a:3, b:4}, 18 | // {a:5, b:6}, 19 | // } 20 | } 21 | -------------------------------------------------------------------------------- /formatter.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | "strconv" 8 | "text/tabwriter" 9 | 10 | "github.com/kr/text" 11 | "github.com/rogpeppe/go-internal/fmtsort" 12 | ) 13 | 14 | type formatter struct { 15 | v reflect.Value 16 | force bool 17 | quote bool 18 | } 19 | 20 | // Formatter makes a wrapper, f, that will format x as go source with line 21 | // breaks and tabs. Object f responds to the "%v" formatting verb when both the 22 | // "#" and " " (space) flags are set, for example: 23 | // 24 | // fmt.Sprintf("%# v", Formatter(x)) 25 | // 26 | // If one of these two flags is not set, or any other verb is used, f will 27 | // format x according to the usual rules of package fmt. 28 | // In particular, if x satisfies fmt.Formatter, then x.Format will be called. 29 | func Formatter(x interface{}) (f fmt.Formatter) { 30 | return formatter{v: reflect.ValueOf(x), quote: true} 31 | } 32 | 33 | func (fo formatter) String() string { 34 | return fmt.Sprint(fo.v.Interface()) // unwrap it 35 | } 36 | 37 | func (fo formatter) passThrough(f fmt.State, c rune) { 38 | s := "%" 39 | for i := 0; i < 128; i++ { 40 | if f.Flag(i) { 41 | s += string(rune(i)) 42 | } 43 | } 44 | if w, ok := f.Width(); ok { 45 | s += fmt.Sprintf("%d", w) 46 | } 47 | if p, ok := f.Precision(); ok { 48 | s += fmt.Sprintf(".%d", p) 49 | } 50 | s += string(c) 51 | fmt.Fprintf(f, s, fo.v.Interface()) 52 | } 53 | 54 | func (fo formatter) Format(f fmt.State, c rune) { 55 | if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { 56 | w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) 57 | p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} 58 | p.printValue(fo.v, true, fo.quote) 59 | w.Flush() 60 | return 61 | } 62 | fo.passThrough(f, c) 63 | } 64 | 65 | type printer struct { 66 | io.Writer 67 | tw *tabwriter.Writer 68 | visited map[visit]int 69 | depth int 70 | } 71 | 72 | func (p *printer) indent() *printer { 73 | q := *p 74 | q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) 75 | q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) 76 | return &q 77 | } 78 | 79 | func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { 80 | if showType { 81 | io.WriteString(p, v.Type().String()) 82 | fmt.Fprintf(p, "(%#v)", x) 83 | } else { 84 | fmt.Fprintf(p, "%#v", x) 85 | } 86 | } 87 | 88 | // printValue must keep track of already-printed pointer values to avoid 89 | // infinite recursion. 90 | type visit struct { 91 | v uintptr 92 | typ reflect.Type 93 | } 94 | 95 | func (p *printer) catchPanic(v reflect.Value, method string) { 96 | if r := recover(); r != nil { 97 | if v.Kind() == reflect.Ptr && v.IsNil() { 98 | writeByte(p, '(') 99 | io.WriteString(p, v.Type().String()) 100 | io.WriteString(p, ")(nil)") 101 | return 102 | } 103 | writeByte(p, '(') 104 | io.WriteString(p, v.Type().String()) 105 | io.WriteString(p, ")(PANIC=calling method ") 106 | io.WriteString(p, strconv.Quote(method)) 107 | io.WriteString(p, ": ") 108 | fmt.Fprint(p, r) 109 | writeByte(p, ')') 110 | } 111 | } 112 | 113 | func (p *printer) printValue(v reflect.Value, showType, quote bool) { 114 | if p.depth > 10 { 115 | io.WriteString(p, "!%v(DEPTH EXCEEDED)") 116 | return 117 | } 118 | 119 | if v.IsValid() && v.CanInterface() { 120 | i := v.Interface() 121 | if goStringer, ok := i.(fmt.GoStringer); ok { 122 | defer p.catchPanic(v, "GoString") 123 | io.WriteString(p, goStringer.GoString()) 124 | return 125 | } 126 | } 127 | 128 | switch v.Kind() { 129 | case reflect.Bool: 130 | p.printInline(v, v.Bool(), showType) 131 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 132 | p.printInline(v, v.Int(), showType) 133 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 134 | p.printInline(v, v.Uint(), showType) 135 | case reflect.Float32, reflect.Float64: 136 | p.printInline(v, v.Float(), showType) 137 | case reflect.Complex64, reflect.Complex128: 138 | fmt.Fprintf(p, "%#v", v.Complex()) 139 | case reflect.String: 140 | p.fmtString(v.String(), quote) 141 | case reflect.Map: 142 | t := v.Type() 143 | if showType { 144 | io.WriteString(p, t.String()) 145 | } 146 | writeByte(p, '{') 147 | if nonzero(v) { 148 | expand := !canInline(v.Type()) 149 | pp := p 150 | if expand { 151 | writeByte(p, '\n') 152 | pp = p.indent() 153 | } 154 | sm := fmtsort.Sort(v) 155 | for i := 0; i < v.Len(); i++ { 156 | k := sm.Key[i] 157 | mv := sm.Value[i] 158 | pp.printValue(k, false, true) 159 | writeByte(pp, ':') 160 | if expand { 161 | writeByte(pp, '\t') 162 | } 163 | showTypeInStruct := t.Elem().Kind() == reflect.Interface 164 | pp.printValue(mv, showTypeInStruct, true) 165 | if expand { 166 | io.WriteString(pp, ",\n") 167 | } else if i < v.Len()-1 { 168 | io.WriteString(pp, ", ") 169 | } 170 | } 171 | if expand { 172 | pp.tw.Flush() 173 | } 174 | } 175 | writeByte(p, '}') 176 | case reflect.Struct: 177 | t := v.Type() 178 | if v.CanAddr() { 179 | addr := v.UnsafeAddr() 180 | vis := visit{addr, t} 181 | if vd, ok := p.visited[vis]; ok && vd < p.depth { 182 | p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false) 183 | break // don't print v again 184 | } 185 | p.visited[vis] = p.depth 186 | } 187 | 188 | if showType { 189 | io.WriteString(p, t.String()) 190 | } 191 | writeByte(p, '{') 192 | if nonzero(v) { 193 | expand := !canInline(v.Type()) 194 | pp := p 195 | if expand { 196 | writeByte(p, '\n') 197 | pp = p.indent() 198 | } 199 | for i := 0; i < v.NumField(); i++ { 200 | showTypeInStruct := true 201 | if f := t.Field(i); f.Name != "" { 202 | io.WriteString(pp, f.Name) 203 | writeByte(pp, ':') 204 | if expand { 205 | writeByte(pp, '\t') 206 | } 207 | showTypeInStruct = labelType(f.Type) 208 | } 209 | pp.printValue(getField(v, i), showTypeInStruct, true) 210 | if expand { 211 | io.WriteString(pp, ",\n") 212 | } else if i < v.NumField()-1 { 213 | io.WriteString(pp, ", ") 214 | } 215 | } 216 | if expand { 217 | pp.tw.Flush() 218 | } 219 | } 220 | writeByte(p, '}') 221 | case reflect.Interface: 222 | switch e := v.Elem(); { 223 | case e.Kind() == reflect.Invalid: 224 | io.WriteString(p, "nil") 225 | case e.IsValid(): 226 | pp := *p 227 | pp.depth++ 228 | pp.printValue(e, showType, true) 229 | default: 230 | io.WriteString(p, v.Type().String()) 231 | io.WriteString(p, "(nil)") 232 | } 233 | case reflect.Array, reflect.Slice: 234 | t := v.Type() 235 | if showType { 236 | io.WriteString(p, t.String()) 237 | } 238 | if v.Kind() == reflect.Slice && v.IsNil() && showType { 239 | io.WriteString(p, "(nil)") 240 | break 241 | } 242 | if v.Kind() == reflect.Slice && v.IsNil() { 243 | io.WriteString(p, "nil") 244 | break 245 | } 246 | writeByte(p, '{') 247 | expand := !canInline(v.Type()) 248 | pp := p 249 | if expand { 250 | writeByte(p, '\n') 251 | pp = p.indent() 252 | } 253 | for i := 0; i < v.Len(); i++ { 254 | showTypeInSlice := t.Elem().Kind() == reflect.Interface 255 | pp.printValue(v.Index(i), showTypeInSlice, true) 256 | if expand { 257 | io.WriteString(pp, ",\n") 258 | } else if i < v.Len()-1 { 259 | io.WriteString(pp, ", ") 260 | } 261 | } 262 | if expand { 263 | pp.tw.Flush() 264 | } 265 | writeByte(p, '}') 266 | case reflect.Ptr: 267 | e := v.Elem() 268 | if !e.IsValid() { 269 | writeByte(p, '(') 270 | io.WriteString(p, v.Type().String()) 271 | io.WriteString(p, ")(nil)") 272 | } else { 273 | pp := *p 274 | pp.depth++ 275 | writeByte(pp, '&') 276 | pp.printValue(e, true, true) 277 | } 278 | case reflect.Chan: 279 | x := v.Pointer() 280 | if showType { 281 | writeByte(p, '(') 282 | io.WriteString(p, v.Type().String()) 283 | fmt.Fprintf(p, ")(%#v)", x) 284 | } else { 285 | fmt.Fprintf(p, "%#v", x) 286 | } 287 | case reflect.Func: 288 | io.WriteString(p, v.Type().String()) 289 | io.WriteString(p, " {...}") 290 | case reflect.UnsafePointer: 291 | p.printInline(v, v.Pointer(), showType) 292 | case reflect.Invalid: 293 | io.WriteString(p, "nil") 294 | } 295 | } 296 | 297 | func canInline(t reflect.Type) bool { 298 | switch t.Kind() { 299 | case reflect.Map: 300 | return !canExpand(t.Elem()) 301 | case reflect.Struct: 302 | for i := 0; i < t.NumField(); i++ { 303 | if canExpand(t.Field(i).Type) { 304 | return false 305 | } 306 | } 307 | return true 308 | case reflect.Interface: 309 | return false 310 | case reflect.Array, reflect.Slice: 311 | return !canExpand(t.Elem()) 312 | case reflect.Ptr: 313 | return false 314 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 315 | return false 316 | } 317 | return true 318 | } 319 | 320 | func canExpand(t reflect.Type) bool { 321 | switch t.Kind() { 322 | case reflect.Map, reflect.Struct, 323 | reflect.Interface, reflect.Array, reflect.Slice, 324 | reflect.Ptr: 325 | return true 326 | } 327 | return false 328 | } 329 | 330 | func labelType(t reflect.Type) bool { 331 | switch t.Kind() { 332 | case reflect.Interface, reflect.Struct: 333 | return true 334 | } 335 | return false 336 | } 337 | 338 | func (p *printer) fmtString(s string, quote bool) { 339 | if quote { 340 | s = strconv.Quote(s) 341 | } 342 | io.WriteString(p, s) 343 | } 344 | 345 | func writeByte(w io.Writer, b byte) { 346 | w.Write([]byte{b}) 347 | } 348 | 349 | func getField(v reflect.Value, i int) reflect.Value { 350 | val := v.Field(i) 351 | if val.Kind() == reflect.Interface && !val.IsNil() { 352 | val = val.Elem() 353 | } 354 | return val 355 | } 356 | -------------------------------------------------------------------------------- /formatter_test.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "testing" 8 | "time" 9 | "unsafe" 10 | ) 11 | 12 | type test struct { 13 | v interface{} 14 | s string 15 | } 16 | 17 | type passtest struct { 18 | v interface{} 19 | f, s string 20 | } 21 | 22 | type LongStructTypeName struct { 23 | longFieldName interface{} 24 | otherLongFieldName interface{} 25 | } 26 | 27 | type SA struct { 28 | t *T 29 | v T 30 | } 31 | 32 | type T struct { 33 | x, y int 34 | } 35 | 36 | type F int 37 | 38 | func (f F) Format(s fmt.State, c rune) { 39 | fmt.Fprintf(s, "F(%d)", int(f)) 40 | } 41 | 42 | type Stringer struct{ i int } 43 | 44 | func (s *Stringer) String() string { return "foo" } 45 | 46 | var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 47 | 48 | var passthrough = []passtest{ 49 | {1, "%d", "1"}, 50 | {"a", "%s", "a"}, 51 | {&Stringer{}, "%s", "foo"}, 52 | } 53 | 54 | func TestPassthrough(t *testing.T) { 55 | for _, tt := range passthrough { 56 | s := fmt.Sprintf(tt.f, Formatter(tt.v)) 57 | if tt.s != s { 58 | t.Errorf("expected %q", tt.s) 59 | t.Errorf("got %q", s) 60 | t.Errorf("expraw\n%s", tt.s) 61 | t.Errorf("gotraw\n%s", s) 62 | } 63 | } 64 | } 65 | 66 | type StructWithPrivateFields struct { 67 | A string 68 | b string 69 | } 70 | 71 | func NewStructWithPrivateFields(a string) StructWithPrivateFields { 72 | return StructWithPrivateFields{a, "fixedb"} 73 | } 74 | 75 | func (s StructWithPrivateFields) GoString() string { 76 | return fmt.Sprintf("NewStructWithPrivateFields(%q)", s.A) 77 | } 78 | 79 | var gosyntax = []test{ 80 | {nil, `nil`}, 81 | {"", `""`}, 82 | {"a", `"a"`}, 83 | {1, "int(1)"}, 84 | {1.0, "float64(1)"}, 85 | {[]int(nil), "[]int(nil)"}, 86 | {[0]int{}, "[0]int{}"}, 87 | {complex(1, 0), "(1+0i)"}, 88 | //{make(chan int), "(chan int)(0x1234)"}, 89 | {unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))}, 90 | {func(int) {}, "func(int) {...}"}, 91 | {map[string]string{"a": "a", "b": "b"}, "map[string]string{\"a\":\"a\", \"b\":\"b\"}"}, 92 | {map[int]int{1: 1}, "map[int]int{1:1}"}, 93 | {int32(1), "int32(1)"}, 94 | {io.EOF, `&errors.errorString{s:"EOF"}`}, 95 | {[]string{"a"}, `[]string{"a"}`}, 96 | { 97 | []string{long}, 98 | `[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`, 99 | }, 100 | {F(5), "pretty.F(5)"}, 101 | {NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`}, 102 | { 103 | SA{&T{1, 2}, T{3, 4}}, 104 | `pretty.SA{ 105 | t: &pretty.T{x:1, y:2}, 106 | v: pretty.T{x:3, y:4}, 107 | }`, 108 | }, 109 | { 110 | map[int][]byte{1: {}}, 111 | `map[int][]uint8{ 112 | 1: {}, 113 | }`, 114 | }, 115 | { 116 | map[int]T{1: {}}, 117 | `map[int]pretty.T{ 118 | 1: {}, 119 | }`, 120 | }, 121 | { 122 | long, 123 | `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`, 124 | }, 125 | { 126 | LongStructTypeName{ 127 | longFieldName: LongStructTypeName{}, 128 | otherLongFieldName: long, 129 | }, 130 | `pretty.LongStructTypeName{ 131 | longFieldName: pretty.LongStructTypeName{}, 132 | otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 133 | }`, 134 | }, 135 | { 136 | &LongStructTypeName{ 137 | longFieldName: &LongStructTypeName{}, 138 | otherLongFieldName: (*LongStructTypeName)(nil), 139 | }, 140 | `&pretty.LongStructTypeName{ 141 | longFieldName: &pretty.LongStructTypeName{}, 142 | otherLongFieldName: (*pretty.LongStructTypeName)(nil), 143 | }`, 144 | }, 145 | { 146 | []LongStructTypeName{ 147 | {nil, nil}, 148 | {3, 3}, 149 | {long, nil}, 150 | }, 151 | `[]pretty.LongStructTypeName{ 152 | {}, 153 | { 154 | longFieldName: int(3), 155 | otherLongFieldName: int(3), 156 | }, 157 | { 158 | longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 159 | otherLongFieldName: nil, 160 | }, 161 | }`, 162 | }, 163 | { 164 | []interface{}{ 165 | LongStructTypeName{nil, nil}, 166 | []byte{1, 2, 3}, 167 | T{3, 4}, 168 | LongStructTypeName{long, nil}, 169 | }, 170 | `[]interface {}{ 171 | pretty.LongStructTypeName{}, 172 | []uint8{0x1, 0x2, 0x3}, 173 | pretty.T{x:3, y:4}, 174 | pretty.LongStructTypeName{ 175 | longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 176 | otherLongFieldName: nil, 177 | }, 178 | }`, 179 | }, 180 | {(*time.Time)(nil), "(*time.Time)(nil)"}, 181 | {&ValueGoString{"vgs"}, `VGS vgs`}, 182 | {(*ValueGoString)(nil), `(*pretty.ValueGoString)(nil)`}, 183 | {(*VGSWrapper)(nil), `(*pretty.VGSWrapper)(nil)`}, 184 | {&PointerGoString{"pgs"}, `PGS pgs`}, 185 | {(*PointerGoString)(nil), "(*pretty.PointerGoString)(nil)"}, 186 | {&PanicGoString{"oops!"}, `(*pretty.PanicGoString)(PANIC=calling method "GoString": oops!)`}, 187 | } 188 | 189 | type ValueGoString struct { 190 | s string 191 | } 192 | 193 | func (g ValueGoString) GoString() string { 194 | return "VGS " + g.s 195 | } 196 | 197 | type VGSWrapper struct { 198 | ValueGoString 199 | } 200 | 201 | type PointerGoString struct { 202 | s string 203 | } 204 | 205 | func (g *PointerGoString) GoString() string { 206 | return "PGS " + g.s 207 | } 208 | 209 | type PanicGoString struct { 210 | s string 211 | } 212 | 213 | func (g *PanicGoString) GoString() string { 214 | panic(g.s) 215 | } 216 | 217 | func TestGoSyntax(t *testing.T) { 218 | for _, tt := range gosyntax { 219 | s := fmt.Sprintf("%# v", Formatter(tt.v)) 220 | if tt.s != s { 221 | t.Errorf("expected %q", tt.s) 222 | t.Errorf("got %q", s) 223 | t.Errorf("expraw\n%s", tt.s) 224 | t.Errorf("gotraw\n%s", s) 225 | } 226 | } 227 | } 228 | 229 | type I struct { 230 | i int 231 | R interface{} 232 | } 233 | 234 | func (i *I) I() *I { return i.R.(*I) } 235 | 236 | func TestCycle(t *testing.T) { 237 | type A struct{ *A } 238 | v := &A{} 239 | v.A = v 240 | 241 | // panics from stack overflow without cycle detection 242 | t.Logf("Example cycle:\n%# v", Formatter(v)) 243 | 244 | p := &A{} 245 | s := fmt.Sprintf("%# v", Formatter([]*A{p, p})) 246 | if strings.Contains(s, "CYCLIC") { 247 | t.Errorf("Repeated address detected as cyclic reference:\n%s", s) 248 | } 249 | 250 | type R struct { 251 | i int 252 | *R 253 | } 254 | r := &R{ 255 | i: 1, 256 | R: &R{ 257 | i: 2, 258 | R: &R{ 259 | i: 3, 260 | }, 261 | }, 262 | } 263 | r.R.R.R = r 264 | t.Logf("Example longer cycle:\n%# v", Formatter(r)) 265 | 266 | r = &R{ 267 | i: 1, 268 | R: &R{ 269 | i: 2, 270 | R: &R{ 271 | i: 3, 272 | R: &R{ 273 | i: 4, 274 | R: &R{ 275 | i: 5, 276 | R: &R{ 277 | i: 6, 278 | R: &R{ 279 | i: 7, 280 | R: &R{ 281 | i: 8, 282 | R: &R{ 283 | i: 9, 284 | R: &R{ 285 | i: 10, 286 | R: &R{ 287 | i: 11, 288 | }, 289 | }, 290 | }, 291 | }, 292 | }, 293 | }, 294 | }, 295 | }, 296 | }, 297 | }, 298 | } 299 | // here be pirates 300 | r.R.R.R.R.R.R.R.R.R.R.R = r 301 | t.Logf("Example very long cycle:\n%# v", Formatter(r)) 302 | 303 | i := &I{ 304 | i: 1, 305 | R: &I{ 306 | i: 2, 307 | R: &I{ 308 | i: 3, 309 | R: &I{ 310 | i: 4, 311 | R: &I{ 312 | i: 5, 313 | R: &I{ 314 | i: 6, 315 | R: &I{ 316 | i: 7, 317 | R: &I{ 318 | i: 8, 319 | R: &I{ 320 | i: 9, 321 | R: &I{ 322 | i: 10, 323 | R: &I{ 324 | i: 11, 325 | }, 326 | }, 327 | }, 328 | }, 329 | }, 330 | }, 331 | }, 332 | }, 333 | }, 334 | }, 335 | } 336 | iv := i.I().I().I().I().I().I().I().I().I().I() 337 | *iv = *i 338 | t.Logf("Example long interface cycle:\n%# v", Formatter(i)) 339 | } 340 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kr/pretty 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/kr/text v0.2.0 7 | github.com/rogpeppe/go-internal v1.9.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 3 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 4 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 5 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 6 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 7 | -------------------------------------------------------------------------------- /pretty.go: -------------------------------------------------------------------------------- 1 | // Package pretty provides pretty-printing for Go values. This is 2 | // useful during debugging, to avoid wrapping long output lines in 3 | // the terminal. 4 | // 5 | // It provides a function, Formatter, that can be used with any 6 | // function that accepts a format string. It also provides 7 | // convenience wrappers for functions in packages fmt and log. 8 | package pretty 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "log" 14 | "reflect" 15 | ) 16 | 17 | // Errorf is a convenience wrapper for fmt.Errorf. 18 | // 19 | // Calling Errorf(f, x, y) is equivalent to 20 | // fmt.Errorf(f, Formatter(x), Formatter(y)). 21 | func Errorf(format string, a ...interface{}) error { 22 | return fmt.Errorf(format, wrap(a, false)...) 23 | } 24 | 25 | // Fprintf is a convenience wrapper for fmt.Fprintf. 26 | // 27 | // Calling Fprintf(w, f, x, y) is equivalent to 28 | // fmt.Fprintf(w, f, Formatter(x), Formatter(y)). 29 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) { 30 | return fmt.Fprintf(w, format, wrap(a, false)...) 31 | } 32 | 33 | // Log is a convenience wrapper for log.Printf. 34 | // 35 | // Calling Log(x, y) is equivalent to 36 | // log.Print(Formatter(x), Formatter(y)), but each operand is 37 | // formatted with "%# v". 38 | func Log(a ...interface{}) { 39 | log.Print(wrap(a, true)...) 40 | } 41 | 42 | // Logf is a convenience wrapper for log.Printf. 43 | // 44 | // Calling Logf(f, x, y) is equivalent to 45 | // log.Printf(f, Formatter(x), Formatter(y)). 46 | func Logf(format string, a ...interface{}) { 47 | log.Printf(format, wrap(a, false)...) 48 | } 49 | 50 | // Logln is a convenience wrapper for log.Printf. 51 | // 52 | // Calling Logln(x, y) is equivalent to 53 | // log.Println(Formatter(x), Formatter(y)), but each operand is 54 | // formatted with "%# v". 55 | func Logln(a ...interface{}) { 56 | log.Println(wrap(a, true)...) 57 | } 58 | 59 | // Print pretty-prints its operands and writes to standard output. 60 | // 61 | // Calling Print(x, y) is equivalent to 62 | // fmt.Print(Formatter(x), Formatter(y)), but each operand is 63 | // formatted with "%# v". 64 | func Print(a ...interface{}) (n int, errno error) { 65 | return fmt.Print(wrap(a, true)...) 66 | } 67 | 68 | // Printf is a convenience wrapper for fmt.Printf. 69 | // 70 | // Calling Printf(f, x, y) is equivalent to 71 | // fmt.Printf(f, Formatter(x), Formatter(y)). 72 | func Printf(format string, a ...interface{}) (n int, errno error) { 73 | return fmt.Printf(format, wrap(a, false)...) 74 | } 75 | 76 | // Println pretty-prints its operands and writes to standard output. 77 | // 78 | // Calling Println(x, y) is equivalent to 79 | // fmt.Println(Formatter(x), Formatter(y)), but each operand is 80 | // formatted with "%# v". 81 | func Println(a ...interface{}) (n int, errno error) { 82 | return fmt.Println(wrap(a, true)...) 83 | } 84 | 85 | // Sprint is a convenience wrapper for fmt.Sprintf. 86 | // 87 | // Calling Sprint(x, y) is equivalent to 88 | // fmt.Sprint(Formatter(x), Formatter(y)), but each operand is 89 | // formatted with "%# v". 90 | func Sprint(a ...interface{}) string { 91 | return fmt.Sprint(wrap(a, true)...) 92 | } 93 | 94 | // Sprintf is a convenience wrapper for fmt.Sprintf. 95 | // 96 | // Calling Sprintf(f, x, y) is equivalent to 97 | // fmt.Sprintf(f, Formatter(x), Formatter(y)). 98 | func Sprintf(format string, a ...interface{}) string { 99 | return fmt.Sprintf(format, wrap(a, false)...) 100 | } 101 | 102 | func wrap(a []interface{}, force bool) []interface{} { 103 | w := make([]interface{}, len(a)) 104 | for i, x := range a { 105 | w[i] = formatter{v: reflect.ValueOf(x), force: force} 106 | } 107 | return w 108 | } 109 | -------------------------------------------------------------------------------- /zero.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func nonzero(v reflect.Value) bool { 8 | switch v.Kind() { 9 | case reflect.Bool: 10 | return v.Bool() 11 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 12 | return v.Int() != 0 13 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 14 | return v.Uint() != 0 15 | case reflect.Float32, reflect.Float64: 16 | return v.Float() != 0 17 | case reflect.Complex64, reflect.Complex128: 18 | return v.Complex() != complex(0, 0) 19 | case reflect.String: 20 | return v.String() != "" 21 | case reflect.Struct: 22 | for i := 0; i < v.NumField(); i++ { 23 | if nonzero(getField(v, i)) { 24 | return true 25 | } 26 | } 27 | return false 28 | case reflect.Array: 29 | for i := 0; i < v.Len(); i++ { 30 | if nonzero(v.Index(i)) { 31 | return true 32 | } 33 | } 34 | return false 35 | case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func: 36 | return !v.IsNil() 37 | case reflect.UnsafePointer: 38 | return v.Pointer() != 0 39 | } 40 | return true 41 | } 42 | --------------------------------------------------------------------------------