Merge pull request #13 from pmezard/add-option-to-sort-map-keys
config: add SortKeys option to sort native map keys before display
This commit is contained in:
commit
f13f098c7a
|
@ -73,6 +73,13 @@ type ConfigState struct {
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
ContinueOnMethod bool
|
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 types will be sort according to the reflect.Value.String() output
|
||||||
|
// which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
// Config is the active configuration of the top-level functions.
|
||||||
|
|
44
spew/dump.go
44
spew/dump.go
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -241,6 +242,46 @@ 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()
|
||||||
|
}
|
||||||
|
return s.values[i].String() < s.values[j].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic sort function for native types: int, uint, bool, string and uintptr.
|
||||||
|
// Other inputs are sort according to their Value.String() value to ensure
|
||||||
|
// display stability.
|
||||||
|
func SortValues(values []reflect.Value) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
}
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
// 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
|
// 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
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
@ -349,6 +390,9 @@ func (d *dumpState) dump(v reflect.Value) {
|
||||||
} else {
|
} else {
|
||||||
numEntries := v.Len()
|
numEntries := v.Len()
|
||||||
keys := v.MapKeys()
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
SortValues(keys)
|
||||||
|
}
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
d.dump(d.unpackValue(key))
|
d.dump(d.unpackValue(key))
|
||||||
d.w.Write(colonSpaceBytes)
|
d.w.Write(colonSpaceBytes)
|
||||||
|
|
|
@ -65,6 +65,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -302,6 +302,9 @@ func (f *formatState) format(v reflect.Value) {
|
||||||
f.fs.Write(maxShortBytes)
|
f.fs.Write(maxShortBytes)
|
||||||
} else {
|
} else {
|
||||||
keys := v.MapKeys()
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
SortValues(keys)
|
||||||
|
}
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
f.fs.Write(spaceBytes)
|
f.fs.Write(spaceBytes)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue