diff --git a/dump.go b/dump.go index 03e311e..9f4fee3 100644 --- a/dump.go +++ b/dump.go @@ -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 @@ -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 @@ -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()) @@ -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 } } @@ -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 } diff --git a/dump_test.go b/dump_test.go index 68d265c..a3112f9 100644 --- a/dump_test.go +++ b/dump_test.go @@ -52,6 +52,20 @@ func (csld CustomSingleLineDumper) LitterDump(w io.Writer) { _, _ = w.Write([]byte("")) } +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("")) +} + func TestSdump_primitives(t *testing.T) { messages := make(chan string, 3) sends := make(chan<- int64, 1) @@ -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} @@ -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{ diff --git a/testdata/config_DumpFuncRaw.dump b/testdata/config_DumpFuncRaw.dump new file mode 100644 index 0000000..870b0a9 --- /dev/null +++ b/testdata/config_DumpFuncRaw.dump @@ -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), +} \ No newline at end of file diff --git a/testdata/config_DumpRawFunc.dump b/testdata/config_DumpRawFunc.dump new file mode 100644 index 0000000..870b0a9 --- /dev/null +++ b/testdata/config_DumpRawFunc.dump @@ -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), +} \ No newline at end of file diff --git a/testdata/customDumperRaw.dump b/testdata/customDumperRaw.dump new file mode 100644 index 0000000..9a10156 --- /dev/null +++ b/testdata/customDumperRaw.dump @@ -0,0 +1,15 @@ +map[string]interface {}{ + "v1": { // p0 + multi + line + }, + "v2": p0, + "v2x": { + multi + line + }, + "v3": , + "v4": , // p1 + "v5": p1, + "v6": , +} \ No newline at end of file