From cfd0d4de1e6142603b208d8bfce0d0660954f0a5 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Thu, 27 Aug 2020 21:55:54 +0200 Subject: [PATCH] Implement omision of zeroed-fields with + flag --- example_test.go | 10 +++- formatter.go | 40 +++++++++++--- formatter_test.go | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 9 deletions(-) diff --git a/example_test.go b/example_test.go index ecf40f3..2981a33 100644 --- a/example_test.go +++ b/example_test.go @@ -10,11 +10,19 @@ func Example() { a, b int } var x = []myType{{1, 2}, {3, 4}, {5, 6}} - fmt.Printf("%# v", pretty.Formatter(x)) + fmt.Printf("%# v\n", pretty.Formatter(x)) + + var zeroedFields = []myType{{33, 0}, {a: 0, b: 34}} + // Note the '+' in the format + fmt.Printf("%# +v", pretty.Formatter(zeroedFields)) // output: // []pretty_test.myType{ // {a:1, b:2}, // {a:3, b:4}, // {a:5, b:6}, // } + // []pretty_test.myType{ + // {a:33}, + // {b:34}, + // } } diff --git a/formatter.go b/formatter.go index f3718ea..172e724 100644 --- a/formatter.go +++ b/formatter.go @@ -26,6 +26,11 @@ type formatter struct { // If one of these two flags is not set, or any other verb is used, f will // format x according to the usual rules of package fmt. // In particular, if x satisfies fmt.Formatter, then x.Format will be called. +// +// If the "+" flag is provided, zero-valued structure fields will be omitted. +// For example: +// +// fmt.Sprintf("%# +v", Formatter(x)) func Formatter(x interface{}) (f fmt.Formatter) { return formatter{v: reflect.ValueOf(x), quote: true} } @@ -55,6 +60,9 @@ func (fo formatter) Format(f fmt.State, c rune) { if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} + if f.Flag('+') { + p.skipZeroFields = true + } p.printValue(fo.v, true, fo.quote) w.Flush() return @@ -64,9 +72,10 @@ func (fo formatter) Format(f fmt.State, c rune) { type printer struct { io.Writer - tw *tabwriter.Writer - visited map[visit]int - depth int + tw *tabwriter.Writer + visited map[visit]int + depth int + skipZeroFields bool } func (p *printer) indent() *printer { @@ -169,20 +178,35 @@ func (p *printer) printValue(v reflect.Value, showType, quote bool) { writeByte(p, '\n') pp = p.indent() } + type field struct { + name string + t reflect.Type + value reflect.Value + } + fields := make([]field, 0, v.NumField()) + // Collect fields, filtering out zero fields if needed for i := 0; i < v.NumField(); i++ { + value := getField(v, i) + if p.skipZeroFields && !nonzero(value) { + continue + } + f := t.Field(i) + fields = append(fields, field{f.Name, f.Type, value}) + } + for i, field := range fields { showTypeInStruct := true - if f := t.Field(i); f.Name != "" { - io.WriteString(pp, f.Name) + if field.name != "" { + io.WriteString(pp, field.name) writeByte(pp, ':') if expand { writeByte(pp, '\t') } - showTypeInStruct = labelType(f.Type) + showTypeInStruct = labelType(field.t) } - pp.printValue(getField(v, i), showTypeInStruct, true) + pp.printValue(field.value, showTypeInStruct, true) if expand { io.WriteString(pp, ",\n") - } else if i < v.NumField()-1 { + } else if i < len(fields)-1 { io.WriteString(pp, ", ") } } diff --git a/formatter_test.go b/formatter_test.go index 10ce57a..02db129 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -164,6 +164,124 @@ var gosyntax = []test{ }, } +var gosyntaxSkipZeroFields = []test{ + {nil, `nil`}, + {"", `""`}, + {"a", `"a"`}, + {1, "int(1)"}, + {1.0, "float64(1)"}, + {[]int(nil), "[]int(nil)"}, + {[0]int{}, "[0]int{}"}, + {complex(1, 0), "(1+0i)"}, + //{make(chan int), "(chan int)(0x1234)"}, + {unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))}, + {func(int) {}, "func(int) {...}"}, + {map[string]string{"a": "a", "b": "b"}, "map[string]string{\"a\":\"a\", \"b\":\"b\"}"}, + {map[int]int{1: 1}, "map[int]int{1:1}"}, + {int32(1), "int32(1)"}, + {io.EOF, `&errors.errorString{s:"EOF"}`}, + {[]string{"a"}, `[]string{"a"}`}, + { + []string{long}, + `[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`, + }, + {F(5), "pretty.F(5)"}, + { + SA{&T{1, 2}, T{3, 4}}, + `pretty.SA{ + t: &pretty.T{x:1, y:2}, + v: pretty.T{x:3, y:4}, +}`, + }, + { + map[int][]byte{1: {}}, + `map[int][]uint8{ + 1: {}, +}`, + }, + { + map[int]T{1: {}}, + `map[int]pretty.T{ + 1: {}, +}`, + }, + { + long, + `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`, + }, + { + LongStructTypeName{ + longFieldName: LongStructTypeName{}, + otherLongFieldName: long, + }, + `pretty.LongStructTypeName{ + otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +}`, + }, + { + LongStructTypeName{ + longFieldName: long, + otherLongFieldName: "", + }, + `pretty.LongStructTypeName{ + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +}`, + }, + { + LongStructTypeName{ + longFieldName: "", + otherLongFieldName: "", + }, + `pretty.LongStructTypeName{}`, + }, + { + &LongStructTypeName{ + longFieldName: &LongStructTypeName{}, + otherLongFieldName: (*LongStructTypeName)(nil), + }, + `&pretty.LongStructTypeName{ + longFieldName: &pretty.LongStructTypeName{}, +}`, + }, + { + []LongStructTypeName{ + {nil, nil}, + {3, 3}, + {long, nil}, + }, + `[]pretty.LongStructTypeName{ + {}, + { + longFieldName: int(3), + otherLongFieldName: int(3), + }, + { + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + }, +}`, + }, + { + []interface{}{ + LongStructTypeName{nil, nil}, + []byte{1, 2, 3}, + T{3, 4}, + T{0, 4}, + T{3, 0}, + LongStructTypeName{long, nil}, + }, + `[]interface {}{ + pretty.LongStructTypeName{}, + []uint8{0x1, 0x2, 0x3}, + pretty.T{x:3, y:4}, + pretty.T{y:4}, + pretty.T{x:3}, + pretty.LongStructTypeName{ + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + }, +}`, + }, +} + func TestGoSyntax(t *testing.T) { for _, tt := range gosyntax { s := fmt.Sprintf("%# v", Formatter(tt.v)) @@ -176,6 +294,18 @@ func TestGoSyntax(t *testing.T) { } } +func TestGoSyntaxSkipZeroFields(t *testing.T) { + for _, tt := range gosyntaxSkipZeroFields { + s := fmt.Sprintf("%# +v", Formatter(tt.v)) + if tt.s != s { + t.Errorf("expected %q", tt.s) + t.Errorf("got %q", s) + t.Errorf("expraw\n%s", tt.s) + t.Errorf("gotraw\n%s", s) + } + } +} + type I struct { i int R interface{}