Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ type Dumper interface {
LitterDump(w io.Writer)
}

// DumperRaw is the interface for implementing custom dumper for your types.
// Unlike with Dumper, data type won't be written into the writer.
type DumperRaw interface {
LitterDumpRaw(w io.Writer)
}

// Options represents configuration options for litter
type Options struct {
Compact bool
Expand All @@ -35,6 +41,7 @@ type Options struct {
Separator string
StrictGo bool
DumpFunc func(reflect.Value, io.Writer) bool
DumpRawFunc func(reflect.Value, io.Writer) bool

// DisablePointerReplacement, if true, disables the replacing of pointer data with variable names
// when it's safe. This is useful for diffing two structures, where pointer variables would cause
Expand Down Expand Up @@ -245,10 +252,12 @@ func (s *dumpState) dumpChan(v reflect.Value) {
s.write(res)
}

func (s *dumpState) dumpCustom(v reflect.Value, buf *bytes.Buffer) {
func (s *dumpState) dumpCustom(v reflect.Value, buf *bytes.Buffer, dumpType bool) {

// Dump the type
s.dumpType(v)
if dumpType {
// Dump the type
s.dumpType(v)
}

if s.config.Compact {
s.write(buf.Bytes())
Expand Down Expand Up @@ -339,7 +348,7 @@ func (s *dumpState) dumpVal(value reflect.Value) {
if s.config.DumpFunc != nil {
buf := new(bytes.Buffer)
if s.config.DumpFunc(v, buf) {
s.dumpCustom(v, buf)
s.dumpCustom(v, buf, true)
return
}
}
Expand All @@ -352,7 +361,29 @@ func (s *dumpState) dumpVal(value reflect.Value) {
buf := new(bytes.Buffer)
dumpFunc := v.MethodByName("LitterDump")
dumpFunc.Call([]reflect.Value{reflect.ValueOf(buf)})
s.dumpCustom(v, buf)
s.dumpCustom(v, buf, true)
})
return
}

// Try to handle with dump raw func
if s.config.DumpRawFunc != nil {
buf := new(bytes.Buffer)
if s.config.DumpRawFunc(v, buf) {
s.dumpCustom(v, buf, false)
return
}
}

// Handle custom raw dumpers
rawDumperType := reflect.TypeOf((*DumperRaw)(nil)).Elem()
if v.Type().Implements(rawDumperType) {
s.descendIntoPossiblePointer(v, func() {
// Run the custom raw dumper buffering the output
buf := new(bytes.Buffer)
dumpFunc := v.MethodByName("LitterDumpRaw")
dumpFunc.Call([]reflect.Value{reflect.ValueOf(buf)})
s.dumpCustom(v, buf, false)
})
return
}
Expand Down
46 changes: 46 additions & 0 deletions dump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ func (csld CustomSingleLineDumper) LitterDump(w io.Writer) {
_, _ = w.Write([]byte("<custom>"))
}

type CustomMultiLineDumperRaw struct {
Dummy int
}

func (cmld *CustomMultiLineDumperRaw) LitterDumpRaw(w io.Writer) {
_, _ = w.Write([]byte("{\n multi\n line\n}"))
}

type CustomSingleLineDumperRaw int

func (csld CustomSingleLineDumperRaw) LitterDumpRaw(w io.Writer) {
_, _ = w.Write([]byte("<custom>"))
}

func TestSdump_primitives(t *testing.T) {
messages := make(chan string, 3)
sends := make(chan<- int64, 1)
Expand Down Expand Up @@ -113,6 +127,22 @@ func TestSdump_customDumper(t *testing.T) {
})
}

func TestSdump_customDumperRaw(t *testing.T) {
cmld := CustomMultiLineDumperRaw{Dummy: 1}
cmld2 := CustomMultiLineDumperRaw{Dummy: 2}
csld := CustomSingleLineDumperRaw(42)
csld2 := CustomSingleLineDumperRaw(43)
runTests(t, "customDumperRaw", map[string]interface{}{
"v1": &cmld,
"v2": &cmld,
"v2x": &cmld2,
"v3": csld,
"v4": &csld,
"v5": &csld,
"v6": &csld2,
})
}

func TestSdump_pointerAliasing(t *testing.T) {
p0 := &RecursiveStruct{Ptr: nil}
p1 := &RecursiveStruct{Ptr: p0}
Expand Down Expand Up @@ -204,6 +234,22 @@ func TestSdump_config(t *testing.T) {
return false
},
}, data)
runTestWithCfg(t, "config_DumpRawFunc", &litter.Options{
DumpRawFunc: func(v reflect.Value, w io.Writer) bool {
if !v.CanInterface() {
return false
}
if b, ok := v.Interface().(bool); ok {
if b {
io.WriteString(w, `"on"`)
} else {
io.WriteString(w, `"off"`)
}
return true
}
return false
},
}, data)

basic := &BasicStruct{1, 2}
runTestWithCfg(t, "config_DisablePointerReplacement_simpleReusedStruct", &litter.Options{
Expand Down
19 changes: 19 additions & 0 deletions testdata/config_DumpFuncRaw.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[]interface {}{
litter_test.options{
Compact: "off",
StripPackageNames: "off",
HidePrivateFields: "on",
HomePackage: "",
Separator: " ",
StrictGo: "off",
},
&litter_test.BasicStruct{
Public: 1,
private: 2,
},
litter_test.Function,
&20,
&20,
litter.Dump,
func(string, int) (bool, error),
}
19 changes: 19 additions & 0 deletions testdata/config_DumpRawFunc.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[]interface {}{
litter_test.options{
Compact: "off",
StripPackageNames: "off",
HidePrivateFields: "on",
HomePackage: "",
Separator: " ",
StrictGo: "off",
},
&litter_test.BasicStruct{
Public: 1,
private: 2,
},
litter_test.Function,
&20,
&20,
litter.Dump,
func(string, int) (bool, error),
}
15 changes: 15 additions & 0 deletions testdata/customDumperRaw.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
map[string]interface {}{
"v1": { // p0
multi
line
},
"v2": p0,
"v2x": {
multi
line
},
"v3": <custom>,
"v4": <custom>, // p1
"v5": p1,
"v6": <custom>,
}