From a55c5ca4939845f91a72d2c3c49dcf16e9343127 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 12 Dec 2022 13:34:19 +0800 Subject: [PATCH] :sparkles: feat(dump): support skip nil field dump on map, struct by option SkipNilField close #41 --- dump/README.md | 34 ++++++++++++++++++++++++++++++++++ dump/dump.go | 14 ++++++++++++++ dump/dump_test.go | 22 ++++++++++++++++++++++ dump/dumper.go | 18 ++++++++++-------- dump/dumper_test.go | 5 ++++- dump/options_test.go | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 9 deletions(-) diff --git a/dump/README.md b/dump/README.md index fda91bb48..47cd45e31 100644 --- a/dump/README.md +++ b/dump/README.md @@ -61,6 +61,40 @@ You will see: ![](_examples/preview-nested-struct.png) +### Options for dump + +```go +// Options for dumper +type Options struct { + // Output the output writer + Output io.Writer + // NoType don't show data type TODO + NoType bool + // NoColor don't with color + NoColor bool + // IndentLen width. default is 2 + IndentLen int + // IndentChar default is one space + IndentChar byte + // MaxDepth for nested print + MaxDepth int + // ShowFlag for display caller position + ShowFlag int + // CallerSkip skip for call runtime.Caller() + CallerSkip int + // ColorTheme for print result. + ColorTheme Theme + // SkipNilField value dump on map, struct. + SkipNilField bool + // SkipPrivate field dump on struct. + SkipPrivate bool + // BytesAsString dump handle. + BytesAsString bool + // MoreLenNL array/slice elements length > MoreLenNL, will wrap new line + // MoreLenNL int +} +``` + ## Functions API ```go diff --git a/dump/dump.go b/dump/dump.go index 4894957f1..ce2a0367c 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -5,6 +5,7 @@ import ( "bytes" "io" "os" + "reflect" "github.com/gookit/color" ) @@ -130,3 +131,16 @@ func Clear(vs ...any) { func isUnexported(fieldName string) bool { return fieldName[0] < 'A' || fieldName[0] > 'Z' } + +func isNilOrInvalid(v reflect.Value) bool { + if !v.IsValid() { + return true + } + + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice: + return v.IsNil() + default: + return false + } +} diff --git a/dump/dump_test.go b/dump/dump_test.go index 5da46227f..39f088054 100644 --- a/dump/dump_test.go +++ b/dump/dump_test.go @@ -308,6 +308,28 @@ func TestStruct_ptrField(t *testing.T) { color.Infoln("\nUse fmt.Println:") fmt.Println(opt) fmt.Println("---------------------------------------------------------------") + + opt = &userOpts{ + Str: &astr, + } + + Println(opt) + /* Output: + PRINT AT github.com/gookit/goutil/dump.TestStruct_ptrField(dump_test.go:316) + &dump.userOpts { + Int: *int, + Str: &string("xyz"), #len=3 + }, + */ + d := newStd().WithOptions(SkipNilField()) + d.Println(opt) + /* Output: + PRINT AT github.com/gookit/goutil/dump.TestStruct_ptrField(dump_test.go:318) + &dump.userOpts { + Str: &string("xyz"), #len=3 + }, + + */ } func TestFormat(t *testing.T) { diff --git a/dump/dumper.go b/dump/dumper.go index a8e22b601..81d8de32e 100644 --- a/dump/dumper.go +++ b/dump/dumper.go @@ -274,16 +274,17 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { fldNum := v.NumField() for i := 0; i < fldNum; i++ { - fv := v.Field(i) - d.advance(1) - fName := t.Field(i).Name if d.SkipPrivate && isUnexported(fName) { continue } - // if d.SkipNilField { // TODO - // } + fv := v.Field(i) + if d.SkipNilField && isNilOrInvalid(fv) { + continue + } + + d.advance(1) // print field name d.indentPrint(d.ColorTheme.field(fName), ": ") @@ -303,10 +304,11 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { for _, key := range v.MapKeys() { mv := v.MapIndex(key) - d.advance(1) + if d.SkipNilField && isNilOrInvalid(mv) { + continue + } - // if d.SkipNilField { // TODO - // } + d.advance(1) // print key name if !key.CanInterface() { diff --git a/dump/dumper_test.go b/dump/dumper_test.go index 58f664c30..283b9311d 100644 --- a/dump/dumper_test.go +++ b/dump/dumper_test.go @@ -204,6 +204,9 @@ func TestDumper_AccessCantExportedField1(t *testing.T) { } Println(myS1) + + d := newStd().WithOptions(SkipPrivate()) + d.Println(myS1) } // ------------------------- map ------------------------- @@ -355,7 +358,7 @@ var ( ) func TestDump_Struct(t *testing.T) { - + P(user) } func TestStruct_WithNested(t *testing.T) { diff --git a/dump/options_test.go b/dump/options_test.go index 4601d9ea4..c76c924cf 100644 --- a/dump/options_test.go +++ b/dump/options_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/gookit/color" "github.com/gookit/goutil/testutil/assert" ) @@ -25,7 +26,38 @@ func TestSkipPrivate(t *testing.T) { assert.NotContains(t, str, "id: string(\"ab12345\")") } +// see https://github.com/gookit/goutil/issues/41 +func TestSkipNilField(t *testing.T) { + buf := newBuffer() + dumper := newStd().WithOptions(WithoutOutput(buf), WithoutPosition(), WithoutColor()) + assert.False(t, dumper.SkipNilField) + + mp := map[string]any{ + "name": "inhere", + "age": nil, + } + + dumper.Println(mp) + str := buf.String() + fmt.Print("Default: \n", str) + assert.StrContains(t, str, `"age": nil`) + buf.Reset() + + dumper.WithOptions(SkipNilField()) + assert.True(t, dumper.SkipNilField) + dumper.Println(mp) + + str = buf.String() + fmt.Print("SkipNilField: \n", str) + assert.NotContains(t, str, `"age": nil`) +} + func TestWithoutColor(t *testing.T) { + ol := color.ForceColor() + defer func() { + color.ForceSetColorLevel(ol) + }() + buf := newBuffer() dumper := newStd().WithOptions(WithoutOutput(buf), WithoutPosition(), WithCallerSkip(2))