159 lines
3.3 KiB
Go
159 lines
3.3 KiB
Go
|
package pretty
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
type sbuf []string
|
||
|
|
||
|
func (s *sbuf) Write(b []byte) (int, error) {
|
||
|
*s = append(*s, string(b))
|
||
|
return len(b), nil
|
||
|
}
|
||
|
|
||
|
// Diff returns a slice where each element describes
|
||
|
// a difference between a and b.
|
||
|
func Diff(a, b interface{}) (desc []string) {
|
||
|
Fdiff((*sbuf)(&desc), a, b)
|
||
|
return desc
|
||
|
}
|
||
|
|
||
|
// Fdiff writes to w a description of the differences between a and b.
|
||
|
func Fdiff(w io.Writer, a, b interface{}) {
|
||
|
diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
|
||
|
}
|
||
|
|
||
|
type diffWriter struct {
|
||
|
w io.Writer
|
||
|
l string // label
|
||
|
}
|
||
|
|
||
|
func (w diffWriter) printf(f string, a ...interface{}) {
|
||
|
var l string
|
||
|
if w.l != "" {
|
||
|
l = w.l + ": "
|
||
|
}
|
||
|
fmt.Fprintf(w.w, l+f, a...)
|
||
|
}
|
||
|
|
||
|
func (w diffWriter) diff(av, bv reflect.Value) {
|
||
|
if !av.IsValid() && bv.IsValid() {
|
||
|
w.printf("nil != %#v", bv.Interface())
|
||
|
return
|
||
|
}
|
||
|
if av.IsValid() && !bv.IsValid() {
|
||
|
w.printf("%#v != nil", av.Interface())
|
||
|
return
|
||
|
}
|
||
|
if !av.IsValid() && !bv.IsValid() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
at := av.Type()
|
||
|
bt := bv.Type()
|
||
|
if at != bt {
|
||
|
w.printf("%v != %v", at, bt)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// numeric types, including bool
|
||
|
if at.Kind() < reflect.Array {
|
||
|
a, b := av.Interface(), bv.Interface()
|
||
|
if a != b {
|
||
|
w.printf("%#v != %#v", a, b)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch at.Kind() {
|
||
|
case reflect.String:
|
||
|
a, b := av.Interface(), bv.Interface()
|
||
|
if a != b {
|
||
|
w.printf("%q != %q", a, b)
|
||
|
}
|
||
|
case reflect.Ptr:
|
||
|
switch {
|
||
|
case av.IsNil() && !bv.IsNil():
|
||
|
w.printf("nil != %v", bv.Interface())
|
||
|
case !av.IsNil() && bv.IsNil():
|
||
|
w.printf("%v != nil", av.Interface())
|
||
|
case !av.IsNil() && !bv.IsNil():
|
||
|
w.diff(av.Elem(), bv.Elem())
|
||
|
}
|
||
|
case reflect.Struct:
|
||
|
for i := 0; i < av.NumField(); i++ {
|
||
|
w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
|
||
|
}
|
||
|
case reflect.Slice:
|
||
|
lenA := av.Len()
|
||
|
lenB := bv.Len()
|
||
|
if lenA != lenB {
|
||
|
w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
|
||
|
break
|
||
|
}
|
||
|
for i := 0; i < lenA; i++ {
|
||
|
w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
|
||
|
}
|
||
|
case reflect.Map:
|
||
|
ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
|
||
|
for _, k := range ak {
|
||
|
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||
|
w.printf("%q != (missing)", av.MapIndex(k))
|
||
|
}
|
||
|
for _, k := range both {
|
||
|
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||
|
w.diff(av.MapIndex(k), bv.MapIndex(k))
|
||
|
}
|
||
|
for _, k := range bk {
|
||
|
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||
|
w.printf("(missing) != %q", bv.MapIndex(k))
|
||
|
}
|
||
|
case reflect.Interface:
|
||
|
w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
|
||
|
default:
|
||
|
if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||
|
w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface()))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d diffWriter) relabel(name string) (d1 diffWriter) {
|
||
|
d1 = d
|
||
|
if d.l != "" && name[0] != '[' {
|
||
|
d1.l += "."
|
||
|
}
|
||
|
d1.l += name
|
||
|
return d1
|
||
|
}
|
||
|
|
||
|
func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
|
||
|
for _, av := range a {
|
||
|
inBoth := false
|
||
|
for _, bv := range b {
|
||
|
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||
|
inBoth = true
|
||
|
both = append(both, av)
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !inBoth {
|
||
|
ak = append(ak, av)
|
||
|
}
|
||
|
}
|
||
|
for _, bv := range b {
|
||
|
inBoth := false
|
||
|
for _, av := range a {
|
||
|
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||
|
inBoth = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !inBoth {
|
||
|
bk = append(bk, bv)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|