new functions Diff and Fdiff
diff --git a/Makefile b/Makefile
index 47ac505..630aab2 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,7 @@
TARG=github.com/kr/pretty.go
GOFILES=\
+ diff.go\
formatter.go\
pretty.go\
diff --git a/diff.go b/diff.go
new file mode 100644
index 0000000..71e88de
--- /dev/null
+++ b/diff.go
@@ -0,0 +1,123 @@
+package pretty
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+)
+
+
+type sbuf []string
+
+
+func (s *sbuf) Write(b []byte) (int, os.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.Interface:
+ w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
+ default:
+ if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
+ w.printf("%#v != %#v", av.Interface(), bv.Interface())
+ }
+ }
+}
+
+
+func (d diffWriter) relabel(name string) (d1 diffWriter) {
+ d1 = d
+ if d.l != "" {
+ d1.l = d.l + "." + name
+ } else {
+ d1.l = name
+ }
+ return d1
+}
+
+
+func fieldLabel(label string, f reflect.StructField) string {
+ if label == "" {
+ return f.Name
+ }
+ return label + "." + f.Name
+}
diff --git a/diff_test.go b/diff_test.go
new file mode 100644
index 0000000..c2e5696
--- /dev/null
+++ b/diff_test.go
@@ -0,0 +1,77 @@
+package pretty
+
+import (
+ "testing"
+)
+
+type difftest struct {
+ a interface{}
+ b interface{}
+ exp []string
+}
+
+type S struct {
+ A int
+ S *S
+ I interface{}
+ C []int
+}
+
+var diffs = []difftest{
+ {a: nil, b: nil},
+ {a: S{A: 1}, b: S{A: 1}},
+
+ {0, "", []string{`int != string`}},
+ {0, 1, []string{`0 != 1`}},
+ {S{}, new(S), []string{`pretty.S != *pretty.S`}},
+ {"a", "b", []string{`"a" != "b"`}},
+ {S{}, S{A: 1}, []string{`A: 0 != 1`}},
+ {new(S), &S{A: 1}, []string{`A: 0 != 1`}},
+ {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{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) {
+ 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++ {
+ for j = 0; j < len(b); j++ {
+ if a[i] == b[j] {
+ break
+ }
+ }
+ if j == len(b) {
+ t.Error(s, a[i])
+ }
+ }
+}