summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spew/config.go6
-rw-r--r--spew/dump.go56
-rw-r--r--spew/dump_test.go42
-rw-r--r--spew/format.go3
-rw-r--r--spew/format_test.go9
5 files changed, 116 insertions, 0 deletions
diff --git a/spew/config.go b/spew/config.go
index a2eab61..1faca1b 100644
--- a/spew/config.go
+++ b/spew/config.go
@@ -73,6 +73,12 @@ type ConfigState struct {
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
+
+ // SortKeys specifies map keys should be sorted before being printed. Use
+ // this to have a more deterministic, diffable output. Note that only
+ // native types (bool, int, uint, floats, uintptr and string) are supported,
+ // other key sequences will displayed in the original order.
+ SortKeys bool
}
// Config is the active configuration of the top-level functions.
diff --git a/spew/dump.go b/spew/dump.go
index 216c5b3..c06750d 100644
--- a/spew/dump.go
+++ b/spew/dump.go
@@ -24,6 +24,7 @@ import (
"os"
"reflect"
"regexp"
+ "sort"
"strconv"
"strings"
)
@@ -241,6 +242,58 @@ func (d *dumpState) dumpSlice(v reflect.Value) {
}
}
+type valuesSorter struct {
+ values []reflect.Value
+}
+
+func (s *valuesSorter) Len() int {
+ return len(s.values)
+}
+
+func (s *valuesSorter) Swap(i, j int) {
+ s.values[i], s.values[j] = s.values[j], s.values[i]
+}
+
+func (s *valuesSorter) Less(i, j int) bool {
+ switch s.values[i].Kind() {
+ case reflect.Bool:
+ return !s.values[i].Bool() && s.values[j].Bool()
+ case reflect.Float32, reflect.Float64:
+ return s.values[i].Float() < s.values[j].Float()
+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
+ return s.values[i].Int() < s.values[j].Int()
+ case reflect.String:
+ return s.values[i].String() < s.values[j].String()
+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+ return s.values[i].Uint() < s.values[j].Uint()
+ case reflect.Uintptr:
+ return s.values[i].UnsafeAddr() < s.values[j].UnsafeAddr()
+ }
+ panic("notimplemented")
+}
+
+// Generic sort function for native types: int, uint, bool, string and uintptr.
+// Other inputs are left unchanged.
+func SortValues(values []reflect.Value) {
+ if len(values) == 0 {
+ return
+ }
+ switch values[0].Kind() {
+ case reflect.Bool:
+ sort.Sort(&valuesSorter{values})
+ case reflect.Float32, reflect.Float64:
+ sort.Sort(&valuesSorter{values})
+ case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
+ sort.Sort(&valuesSorter{values})
+ case reflect.String:
+ sort.Sort(&valuesSorter{values})
+ case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+ sort.Sort(&valuesSorter{values})
+ case reflect.Uintptr:
+ sort.Sort(&valuesSorter{values})
+ }
+}
+
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
@@ -349,6 +402,9 @@ func (d *dumpState) dump(v reflect.Value) {
} else {
numEntries := v.Len()
keys := v.MapKeys()
+ if d.cs.SortKeys {
+ SortValues(keys)
+ }
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
diff --git a/spew/dump_test.go b/spew/dump_test.go
index 31b7b6b..354c71c 100644
--- a/spew/dump_test.go
+++ b/spew/dump_test.go
@@ -65,6 +65,7 @@ import (
"bytes"
"fmt"
"github.com/davecgh/go-spew/spew"
+ "reflect"
"testing"
"unsafe"
)
@@ -896,3 +897,44 @@ func TestDump(t *testing.T) {
}
}
}
+
+func TestSortValues(t *testing.T) {
+ v := reflect.ValueOf
+
+ a := v("a")
+ b := v("b")
+ c := v("c")
+ tests := []struct {
+ input []reflect.Value
+ expected []reflect.Value
+ }{
+ {[]reflect.Value{v(2), v(1), v(3)},
+ []reflect.Value{v(1), v(2), v(3)}},
+ {[]reflect.Value{v(2.), v(1.), v(3.)},
+ []reflect.Value{v(1.), v(2.), v(3.)}},
+ {[]reflect.Value{v(false), v(true), v(false)},
+ []reflect.Value{v(false), v(false), v(true)}},
+ {[]reflect.Value{b, a, c},
+ []reflect.Value{a, b, c}},
+ }
+ for _, test := range tests {
+ spew.SortValues(test.input)
+ if !reflect.DeepEqual(test.input, test.expected) {
+ t.Errorf("Sort mismatch:\n %v != %v", test.input, test.expected)
+ }
+ }
+}
+
+func TestDumpSortedKeys(t *testing.T) {
+ cfg := spew.ConfigState{SortKeys: true}
+ s := cfg.Sdump(map[int]string{1: "1", 3: "3", 2: "2"})
+ expected := `(map[int]string) {
+(int) 1: (string) "1",
+(int) 2: (string) "2",
+(int) 3: (string) "3"
+}
+`
+ if s != expected {
+ t.Errorf("Sorted keys mismatch:\n %v %v", s, expected)
+ }
+}
diff --git a/spew/format.go b/spew/format.go
index 70785fa..fc57fda 100644
--- a/spew/format.go
+++ b/spew/format.go
@@ -302,6 +302,9 @@ func (f *formatState) format(v reflect.Value) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
+ if f.cs.SortKeys {
+ SortValues(keys)
+ }
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
diff --git a/spew/format_test.go b/spew/format_test.go
index b151513..80c5ef9 100644
--- a/spew/format_test.go
+++ b/spew/format_test.go
@@ -1472,3 +1472,12 @@ func TestFormatter(t *testing.T) {
}
}
}
+
+func TestPrintSortedKeys(t *testing.T) {
+ cfg := spew.ConfigState{SortKeys: true}
+ s := cfg.Sprint(map[int]string{1: "1", 3: "3", 2: "2"})
+ expected := "map[1:1 2:2 3:3]"
+ if s != expected {
+ t.Errorf("Sorted keys mismatch:\n %v %v", s, expected)
+ }
+}