diff --git a/README.md b/README.md index 5f625c7f25ec5..2a042008f9983 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ally master branch ![Version][version-img] [![Build status][travis-img]][travis-url] changlist from Official: -- +- [#2: fmt: add flag "@" to format a verb "v" value with pretty style(indented-multi-lines) string](https://github.com/vipally/go/issues/2) - [#1: runtime, time: add API BuildTimestamp to report an application's build time](https://github.com/vipally/go/issues/1) - [func runtime.BuildTimestamp() int64](https://github.com/vipally/go/blob/ally_master/src/runtime/time.go#L21) - [func time.BuildTime() time.Time](https://github.com/vipally/go/blob/ally_master/src/time/time.go#L1517) diff --git a/src/cmd/vet/print.go b/src/cmd/vet/print.go index b4f8fc14689de..59ce9bb99ccb1 100644 --- a/src/cmd/vet/print.go +++ b/src/cmd/vet/print.go @@ -356,7 +356,7 @@ func (f *File) checkPrintf(call *ast.CallExpr, name string) { func (s *formatState) parseFlags() { for s.nbytes < len(s.format) { switch c := s.format[s.nbytes]; c { - case '#', '0', '+', '-', ' ': + case '#', '0', '+', '-', ' ', '@': s.flags = append(s.flags, c) s.nbytes++ default: diff --git a/src/fmt/doc.go b/src/fmt/doc.go index 375cdb4266db6..ae39a453b729b 100644 --- a/src/fmt/doc.go +++ b/src/fmt/doc.go @@ -13,11 +13,15 @@ The verbs: General: - %v the value in a default format - when printing structs, the plus flag (%+v) adds field names - %#v a Go-syntax representation of the value - %T a Go-syntax representation of the type of the value - %% a literal percent sign; consumes no value + %v the value in a default format + when printing structs, the plus flag (%+v) adds field names + %@v the value in a default pretty format + %#v a Go-syntax representation of the value + %@#v pretty style of Go-syntax representation of the value(an indented-multi-lines string) + %+v when printing structs, the plus flag (%+v) adds field names. + %@+v pretty style of struct-syntax representation of the value(an indented-multi-lines string) + %T a Go-syntax representation of the type of the value + %% a literal percent sign; consumes no value Boolean: %t the word true or false @@ -117,6 +121,7 @@ write e.g. U+0078 'x' if the character is printable for %U (%#U). ' ' (space) leave a space for elided sign in numbers (% d); put spaces between bytes printing strings or slices in hex (% x, % X) + @ pretty style for verb %v, which will printing as an indented-multi-lines string. 0 pad with leading zeros rather than spaces; for numbers, this moves the padding after the sign diff --git a/src/fmt/example_test.go b/src/fmt/example_test.go index c77e78809cc56..07885ad36d97b 100644 --- a/src/fmt/example_test.go +++ b/src/fmt/example_test.go @@ -27,3 +27,140 @@ func ExampleStringer() { fmt.Println(a) // Output: Gopher (2) } + +func ExamplePrintf_flagV() { + type X struct { + A int + B string + } + type Y struct { + D X + E []int + F [2]string + } + type Z struct { + G Y + H string + I []string + J map[string]int + } + var z = Z{ + G: Y{ + D: X{ + A: 123, + B: `"b" = 1`, + }, + E: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + F: [2]string{ + `aaa`, + `bbb`, + }, + }, + H: `zzz`, + I: []string{ + `c:\x\y\z`, + `d:\a\b\c`, + }, + J: map[string]int{ + `abc`: 456, + }, + } + fmt.Printf("-------\n\"%%v\":\n%v\n", z) + fmt.Printf("-------\n\"%%@v\":\n%@v\n", z) + fmt.Printf("-------\n\"%%#v\":\n%#v\n", z) + fmt.Printf("-------\n\"%%@#v\":\n%@#v\n", z) + fmt.Printf("-------\n\"%%+v\":\n%+v\n", z) + fmt.Printf("-------\n\"%%@+v\":\n%@+v\n", z) + + // Output: + // ------- + // "%v": + // {{{123 "b" = 1} [1 2 3 4 5 6 7 8 9 10 11 12] [aaa bbb]} zzz [c:\x\y\z d:\a\b\c] map[abc:456]} + // ------- + // "%@v": + // { + // { + // { + // 123 + // "b" = 1 + // } + // [ + // 1 2 3 4 5 6 7 8 9 10 + // 11 12 + // ] + // [ + // aaa + // bbb + // ] + // } + // zzz + // [ + // c:\x\y\z + // d:\a\b\c + // ] + // map[ + // abc: 456 + // ] + // } + // + // ------- + // "%#v": + // fmt_test.Z{G:fmt_test.Y{D:fmt_test.X{A:123, B:"\"b\" = 1"}, E:[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, F:[2]string{"aaa", "bbb"}}, H:"zzz", I:[]string{"c:\\x\\y\\z", "d:\\a\\b\\c"}, J:map[string]int{"abc":456}} + // ------- + // "%@#v": + // fmt_test.Z{ + // G: fmt_test.Y{ + // D: fmt_test.X{ + // A: 123, + // B: `"b" = 1`, + // }, + // E: []int{ + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + // 11, 12, + // }, + // F: [2]string{ + // `aaa`, + // `bbb`, + // }, + // }, + // H: `zzz`, + // I: []string{ + // `c:\x\y\z`, + // `d:\a\b\c`, + // }, + // J: map[string]int{ + // `abc`: 456, + // }, + // } + // + // ------- + // "%+v": + // {G:{D:{A:123 B:"b" = 1} E:[1 2 3 4 5 6 7 8 9 10 11 12] F:[aaa bbb]} H:zzz I:[c:\x\y\z d:\a\b\c] J:map[abc:456]} + // ------- + // "%@+v": + // { + // G: { + // D: { + // A: 123 + // B: "b" = 1 + // } + // E: [ + // 1 2 3 4 5 6 7 8 9 10 + // 11 12 + // ] + // F: [ + // aaa + // bbb + // ] + // } + // H: zzz + // I: [ + // c:\x\y\z + // d:\a\b\c + // ] + // J: map[ + // abc: 456 + // ] + // } + // +} diff --git a/src/fmt/fmt_test.go b/src/fmt/fmt_test.go index 08e46b4e93542..2d4ac10c21ea2 100644 --- a/src/fmt/fmt_test.go +++ b/src/fmt/fmt_test.go @@ -665,6 +665,43 @@ var fmtTests = []struct { // Stringer applies to the extracted value. {"%s", reflect.ValueOf(I(23)), `<23>`}, + //pretty style(indented-multi-line string) + {"%@v", A{1, 2, "a", []int{1, 2}}, "{\n 1\n 2\n a\n [1 2]\n}\n"}, + //{"%@v", new(byte), "0xc04200c1aPTR"}, + //{"%@v", TestFmtInterface, "0x4ffb60"}, + //{"%@v", make(chan int), "0xc0420103c0"}, + {"%@v", uint64(1<<64 - 1), "18446744073709551615"}, + {"%@v", 1000000000, "1000000000"}, + {"%@v", map[string]int{"a": 1}, "map[\n a: 1\n]\n"}, + {"%@v", map[string]B{"a": {1, 2}}, "map[\n a: {\n <1>\n 2\n }\n]\n"}, + {"%@v", []string{"a", "b"}, "[\n a\n b\n]\n"}, + {"%@v", SI{}, "{\n \n}\n"}, + {"%@v", []int(nil), "[]\n"}, + {"%@v", []int{}, "[]\n"}, + {"%@v", array, "[1 2 3 4 5]\n"}, + {"%@v", &array, "&[1 2 3 4 5]\n"}, + {"%@v", iarray, "[1 hello 2.5 ]\n"}, + {"%@v", &iarray, "&[1 hello 2.5 ]\n"}, + {"%@v", map[int]byte(nil), "map[]\n"}, + {"%@v", map[int]byte{}, "map[]\n"}, + {"%@v", "foo", "foo"}, + {"%@v", barray, "[1 2 3 4 5]\n"}, + {"%@v", bslice, "[1 2 3 4 5]\n"}, + {"%@v", []int32(nil), "[]\n"}, + {"%@v", 1.2345678, "1.2345678"}, + {"%@v", float32(1.2345678), "1.2345678"}, + // Only print []byte and []uint8 as type []byte if they appear at the top level. + {"%@v", []byte(nil), "[]"}, + {"%@v", []uint8(nil), "[]"}, + {"%@v", []byte{}, "[]"}, + {"%@v", []uint8{}, "[]"}, + {"%@v", reflect.ValueOf([]byte{}), "[]\n"}, + {"%@v", reflect.ValueOf([]uint8{}), "[]\n"}, + {"%@v", &[]byte{}, "&[]\n"}, + {"%@v", &[]uint8{}, "&[]\n"}, + {"%@v", [3]byte{}, "[0 0 0]\n"}, + {"%@v", [3]uint8{}, "[0 0 0]\n"}, + // go syntax {"%#v", A{1, 2, "a", []int{1, 2}}, `fmt_test.A{i:1, j:0x2, s:"a", x:[]int{1, 2}}`}, {"%#v", new(byte), "(*uint8)(0xPTR)"}, @@ -698,10 +735,119 @@ var fmtTests = []struct { {"%#v", reflect.ValueOf([]byte{}), "[]uint8{}"}, {"%#v", reflect.ValueOf([]uint8{}), "[]uint8{}"}, {"%#v", &[]byte{}, "&[]uint8{}"}, - {"%#v", &[]byte{}, "&[]uint8{}"}, + {"%#v", &[]uint8{}, "&[]uint8{}"}, {"%#v", [3]byte{}, "[3]uint8{0x0, 0x0, 0x0}"}, {"%#v", [3]uint8{}, "[3]uint8{0x0, 0x0, 0x0}"}, + // go syntax with pretty style(indented-multi-line string) + {"%@#v", A{1, 2, "a", []int{1, 2}}, "fmt_test.A{\n i: 1,\n j: 0x2,\n s: `a`,\n x: []int{1, 2},\n}\n"}, + {"%@#v", new(byte), "(*uint8)(0xPTR)"}, + {"%@#v", TestFmtInterface, "(func(*testing.T))(0xPTR)"}, + {"%@#v", make(chan int), "(chan int)(0xPTR)"}, + {"%@#v", uint64(1<<64 - 1), "0xffffffffffffffff"}, + {"%@#v", 1000000000, "1000000000"}, + {"%@#v", map[string]int{"a": 1}, "map[string]int{\n `a`: 1,\n}\n"}, + {"%@#v", map[string]B{"a": {1, 2}}, "map[string]fmt_test.B{\n `a`: fmt_test.B{\n I: 1,\n j: 2,\n },\n}\n"}, + {"%@#v", []string{"a", "b"}, "[]string{\n `a`,\n `b`,\n}\n"}, + {"%@#v", SI{}, "fmt_test.SI{\n I: interface {}(nil),\n}\n"}, + {"%@#v", []int(nil), `[]int(nil)`}, + {"%@#v", []int{}, "[]int{}\n"}, + {"%@#v", array, "[5]int{1, 2, 3, 4, 5}\n"}, + {"%@#v", &array, "&[5]int{1, 2, 3, 4, 5}\n"}, + {"%@#v", iarray, "[4]interface {}{1, `hello`, 2.5, interface {}(nil)}\n"}, + {"%@#v", &iarray, "&[4]interface {}{1, `hello`, 2.5, interface {}(nil)}\n"}, + {"%@#v", map[int]byte(nil), `map[int]uint8(nil)`}, + {"%@#v", map[int]byte{}, "map[int]uint8{}\n"}, + {"%@#v", "foo", "`foo`"}, + {"%@#v", barray, "[5]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}\n"}, + {"%@#v", bslice, "[]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}\n"}, + {"%@#v", []int32(nil), "[]int32(nil)"}, + {"%@#v", 1.2345678, "1.2345678"}, + {"%@#v", float32(1.2345678), "1.2345678"}, + // Only print []byte and []uint8 as type []byte if they appear at the top level. + {"%@#v", []byte(nil), "[]byte(nil)"}, + {"%@#v", []uint8(nil), "[]byte(nil)"}, + {"%@#v", []byte{}, "[]byte{}"}, + {"%@#v", []uint8{}, "[]byte{}"}, + {"%@#v", reflect.ValueOf([]byte{}), "[]uint8{}\n"}, + {"%@#v", reflect.ValueOf([]uint8{}), "[]uint8{}\n"}, + {"%@#v", &[]byte{}, "&[]uint8{}\n"}, + {"%@#v", &[]uint8{}, "&[]uint8{}\n"}, + {"%@#v", [3]byte{}, "[3]uint8{0x0, 0x0, 0x0}\n"}, + {"%@#v", [3]uint8{}, "[3]uint8{0x0, 0x0, 0x0}\n"}, + + // struct syntax + {"%+v", A{1, 2, "a", []int{1, 2}}, "{i:1 j:2 s:a x:[1 2]}"}, + //{"%+v", new(byte), "0xc0420560fPTR"}, + //{"%+v", TestFmtInterface, "0x4ffd90"}, + //{"%+v", make(chan int), "0xc042048240"}, + {"%+v", uint64(1<<64 - 1), "18446744073709551615"}, + {"%+v", 1000000000, "1000000000"}, + {"%+v", map[string]int{"a": 1}, "map[a:1]"}, + {"%+v", map[string]B{"a": {1, 2}}, "map[a:{I:<1> j:2}]"}, + {"%+v", []string{"a", "b"}, "[a b]"}, + {"%+v", SI{}, "{I:}"}, + {"%+v", []int(nil), "[]"}, + {"%+v", []int{}, "[]"}, + {"%+v", array, "[1 2 3 4 5]"}, + {"%+v", &array, "&[1 2 3 4 5]"}, + {"%+v", iarray, "[1 hello 2.5 ]"}, + {"%+v", &iarray, "&[1 hello 2.5 ]"}, + {"%+v", map[int]byte(nil), "map[]"}, + {"%+v", map[int]byte{}, "map[]"}, + {"%+v", "foo", "foo"}, + {"%+v", barray, "[1 2 3 4 5]"}, + {"%+v", bslice, "[1 2 3 4 5]"}, + {"%+v", []int32(nil), "[]"}, + {"%+v", 1.2345678, "1.2345678"}, + {"%+v", float32(1.2345678), "1.2345678"}, + {"%+v", []byte(nil), "[]"}, + {"%+v", []uint8(nil), "[]"}, + {"%+v", []byte{}, "[]"}, + {"%+v", []uint8{}, "[]"}, + {"%+v", reflect.ValueOf([]byte{}), "[]"}, + {"%+v", reflect.ValueOf([]uint8{}), "[]"}, + {"%+v", &[]byte{}, "&[]"}, + {"%+v", &[]uint8{}, "&[]"}, + {"%+v", [3]byte{}, "[0 0 0]"}, + {"%+v", [3]uint8{}, "[0 0 0]"}, + + // struct syntax with pretty style(indented-multi-line string) + {"%@+v", A{1, 2, "a", []int{1, 2}}, "{\n i: 1\n j: 2\n s: a\n x: [1 2]\n}\n"}, + //{"%@+v", new(byte), "0xc0420560fPTR"}, + //{"%@+v", TestFmtInterface, "0x4ffd90"}, + //{"%@+v", make(chan int), "0xc042048240"}, + {"%@+v", uint64(1<<64 - 1), "18446744073709551615"}, + {"%@+v", 1000000000, "1000000000"}, + {"%@+v", map[string]int{"a": 1}, "map[\n a: 1\n]\n"}, + {"%@+v", map[string]B{"a": {1, 2}}, "map[\n a: {\n I: <1>\n j: 2\n }\n]\n"}, + {"%@+v", []string{"a", "b"}, "[\n a\n b\n]\n"}, + {"%@+v", SI{}, "{\n I: \n}\n"}, + {"%@+v", []int(nil), "[]\n"}, + {"%@+v", []int{}, "[]\n"}, + {"%@+v", array, "[1 2 3 4 5]\n"}, + {"%@+v", &array, "&[1 2 3 4 5]\n"}, + {"%@+v", iarray, "[1 hello 2.5 ]\n"}, + {"%@+v", &iarray, "&[1 hello 2.5 ]\n"}, + {"%@+v", map[int]byte(nil), "map[]\n"}, + {"%@+v", map[int]byte{}, "map[]\n"}, + {"%@+v", "foo", "foo"}, + {"%@+v", barray, "[1 2 3 4 5]\n"}, + {"%@+v", bslice, "[1 2 3 4 5]\n"}, + {"%@+v", []int32(nil), "[]\n"}, + {"%@+v", 1.2345678, "1.2345678"}, + {"%@+v", float32(1.2345678), "1.2345678"}, + {"%@+v", []byte(nil), "[]"}, + {"%@+v", []uint8(nil), "[]"}, + {"%@+v", []byte{}, "[]"}, + {"%@+v", []uint8{}, "[]"}, + {"%@+v", reflect.ValueOf([]byte{}), "[]\n"}, + {"%@+v", reflect.ValueOf([]uint8{}), "[]\n"}, + {"%@+v", &[]byte{}, "&[]\n"}, + {"%@+v", &[]uint8{}, "&[]\n"}, + {"%@+v", [3]byte{}, "[0 0 0]\n"}, + {"%@+v", [3]uint8{}, "[0 0 0]\n"}, + // slices with other formats {"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`}, {"%x", []int{1, 2, 15}, `[1 2 f]`}, @@ -1083,7 +1229,7 @@ func TestSprintf(t *testing.T) { // It's too confusing to read the errors. t.Errorf("Sprintf(%q, %q) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out) } else { - t.Errorf("Sprintf(%q, %v) = %q want %q", tt.fmt, tt.val, s, tt.out) + t.Errorf("Sprintf(%q, %v) = %q},\n want %q", tt.fmt, tt.val, s, tt.out) } } } @@ -1466,8 +1612,11 @@ func TestStructPrinter(t *testing.T) { out string }{ {"%v", "{abc def 123}"}, + {"%@v", "{\n abc\n def\n 123\n}\n"}, {"%+v", "{a:abc b:def c:123}"}, {"%#v", `fmt_test.T{a:"abc", b:"def", c:123}`}, + {"%@+v", "{\n a: abc\n b: def\n c: 123\n}\n"}, + {"%@#v", "fmt_test.T{\n a: `abc`,\n b: `def`,\n c: 123,\n}\n"}, } for _, tt := range tests { out := Sprintf(tt.fmt, s) @@ -1840,3 +1989,45 @@ func TestParsenum(t *testing.T) { } } } + +func TestPrettyStyle(t *testing.T) { + type emptyStruct struct{} + type innerPretty struct { + B []string + C []string + D []string + E []int + F []int + G map[int]string + H map[int]string + I map[int]string + J emptyStruct + } + type prettyCase struct { + A innerPretty + } + var pretty prettyCase = prettyCase{ + A: innerPretty{ + C: []string{}, + D: []string{"abc", "def"}, + E: []int{}, + F: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + + H: map[int]string{}, + I: map[int]string{1: "abc"}, + }, + } + var testCases = []struct{ fmt, check string }{ + {"%@v", "{\n {\n []\n []\n [\n abc\n def\n ]\n []\n [\n 1 2 3 4 5 6 7 8 9 10\n 11 12\n ]\n map[]\n map[]\n map[\n 1: abc\n ]\n {}\n }\n}\n"}, + {"%@#v", "fmt_test.prettyCase{\n A: fmt_test.innerPretty{\n B: []string(nil),\n C: []string{},\n D: []string{\n `abc`,\n `def`,\n },\n E: []int{},\n F: []int{\n 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,\n 11, 12,\n },\n G: map[int]string(nil),\n H: map[int]string{},\n I: map[int]string{\n 1: `abc`,\n },\n J: fmt_test.emptyStruct{},\n },\n}\n"}, + {"%@+v", "{\n A: {\n B: []\n C: []\n D: [\n abc\n def\n ]\n E: []\n F: [\n 1 2 3 4 5 6 7 8 9 10\n 11 12\n ]\n G: map[]\n H: map[]\n I: map[\n 1: abc\n ]\n J: {}\n }\n}\n"}, + } + for _, testCase := range testCases { + got := Sprintf(testCase.fmt, pretty) + if got != testCase.check { + t.Errorf("%q need %q got %q", testCase.fmt, testCase.check, got) + } else { + //Printf("%q:\n%s", testCase.fmt, got) + } + } +} diff --git a/src/fmt/format.go b/src/fmt/format.go index d4b92f812148f..376c7fb742120 100644 --- a/src/fmt/format.go +++ b/src/fmt/format.go @@ -34,6 +34,10 @@ type fmtFlags struct { // different, flagless formats set at the top level. plusV bool sharpV bool + + // flag '@', print as pretty style(an indented-multi-lines style string) + // for verb 'v' only, eg: %@#v %@+v %@v + pretty bool } // A fmt is the raw formatter used by Printf etc. @@ -404,12 +408,18 @@ func (f *fmt) fmt_bx(b []byte, digits string) { f.fmt_sbx("", b, digits) } +// flag '@' (pretty) only ("%#v" "%+v" optional) +func (f *fmt) extendVflagOnly() bool { + // do not effect exists "%#v" "%+v", so do not include sharpV and plusV + return f.pretty //|| f.sharpV || f.plusV +} + // fmt_q formats a string as a double-quoted, escaped Go string constant. // If f.sharp is set a raw (backquoted) string may be returned instead // if the string does not contain any control characters other than tab. func (f *fmt) fmt_q(s string) { s = f.truncate(s) - if f.sharp && strconv.CanBackquote(s) { + if (f.extendVflagOnly() || f.sharp) && strconv.CanBackquote(s) { f.padString("`" + s + "`") return } diff --git a/src/fmt/print.go b/src/fmt/print.go index 98c156a121771..d8c7ad2413f5d 100644 --- a/src/fmt/print.go +++ b/src/fmt/print.go @@ -30,6 +30,8 @@ const ( badPrecString = "%!(BADPREC)" noVerbString = "%!(NOVERB)" invReflectString = "" + + maxArrayElemInLine = 10 //in pretty style(flag "@"), while array too long, put a new line ) // State represents the printer state passed to custom formatters. @@ -157,6 +159,8 @@ func (p *pp) Flag(b int) bool { return p.fmt.plus || p.fmt.plusV case '#': return p.fmt.sharp || p.fmt.sharpV + case '@': + return p.fmt.pretty case ' ': return p.fmt.space case '0': @@ -691,6 +695,113 @@ func (p *pp) printArg(arg interface{}, verb rune) { } } +// format a new line, for flag '@' (pretty) only +func (p *pp) newLineIfRequire(depth int, last bool) bool { + if p.fmt.pretty { + //if the last line of data set, the depth will decrease + if last { + depth-- + } + p.buf.WriteByte('\n') + for i := 0; i < depth; i++ { + p.buf.WriteString(" ") + } + return true + } + return false +} + +// check if an array format need a new line +func (p *pp) arrayRequireNewLine(elemKind reflect.Kind, index, size int) bool { + if p.fmt.pretty { + switch elemKind { + case reflect.Slice, reflect.Array, reflect.Map, reflect.Struct, + reflect.String, reflect.Ptr, reflect.Complex64, reflect.Complex128: + return true + default: + //put a new line every maxArrayElemInLine element + if index == 0 && size >= maxArrayElemInLine || index > 0 && index%maxArrayElemInLine == 0 { + return true + } + } + } + return false +} + +// flag '@' (pretty) only ("%#v" "%+v" optional) +func (p *pp) extendVflagOnly() bool { + return p.fmt.extendVflagOnly() +} + +// write head of a value set(struct, map, slice, array). +// If v is a nil map/slice, it will return false. +// Which means the hole data set is finish. +// Include the first probably new line. +func (p *pp) writeValueSetHead(v reflect.Value, newLine, empty bool, depth int) bool { + switch kind := v.Kind(); kind { + case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array: + if p.fmt.sharpV { //write type name + p.buf.WriteString(v.Type().String()) + if (kind == reflect.Map || kind == reflect.Slice) && v.IsNil() { + p.buf.WriteString(nilParenString) + return false + } + } + switch { + case kind == reflect.Struct || p.fmt.sharpV: + p.buf.WriteByte('{') + case kind == reflect.Map: + p.buf.WriteString(mapString) + default: + p.buf.WriteByte('[') + } + default: + //it will never happen except error inner usage, so use panic + panic("unknown value set:" + kind.String()) + } + if newLine && !empty { + // aways increase depth for element, + // p.newLineIfRequire will decrease it if empty + depth++ + p.newLineIfRequire(depth, empty) + } + + return true +} + +// write element separator in value sets(struct, map, slice, array). +// element separator includes the probably new line +func (p *pp) writeElemSeparator(newLine, last bool, depth int) { + if p.fmt.sharpV { + if !last || newLine && p.fmt.pretty { + p.buf.WriteByte(',') + } + } + if !(newLine && p.newLineIfRequire(depth, last)) { + if !last { + p.buf.WriteByte(' ') + } + } +} + +// write tail of a value set(struct, map, slice, array) +func (p *pp) writeValueSetTail(kind reflect.Kind, depth int) { + switch kind { + case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array: + if kind == reflect.Struct || p.fmt.sharpV { + p.buf.WriteByte('}') + } else { + p.buf.WriteByte(']') + } + default: + //it will never happen except error inner usage, so use panic + panic("unknown value set:" + kind.String()) + } + if depth == 0 { + p.newLineIfRequire(depth, true) + } +} + // printValue is similar to printArg but starts with a reflect value, not an interface{} value. // It does not handle 'p' and 'T' verbs because these should have been already handled by printArg. func (p *pp) printValue(value reflect.Value, verb rune, depth int) { @@ -733,68 +844,42 @@ func (p *pp) printValue(value reflect.Value, verb rune, depth int) { case reflect.String: p.fmtString(f.String(), verb) case reflect.Map: - if p.fmt.sharpV { - p.buf.WriteString(f.Type().String()) - if f.IsNil() { - p.buf.WriteString(nilParenString) - return - } - p.buf.WriteByte('{') - } else { - p.buf.WriteString(mapString) - } keys := f.MapKeys() + last := len(keys) - 1 + if !p.writeValueSetHead(f, true, last < 0, depth) { + return + } for i, key := range keys { - if i > 0 { - if p.fmt.sharpV { - p.buf.WriteString(commaSpaceString) - } else { - p.buf.WriteByte(' ') - } - } p.printValue(key, verb, depth+1) p.buf.WriteByte(':') + if p.extendVflagOnly() { + p.buf.WriteByte(' ') + } p.printValue(f.MapIndex(key), verb, depth+1) + p.writeElemSeparator(true, i == last, depth+1) // commaSpaceString or ' ' or newLine } - if p.fmt.sharpV { - p.buf.WriteByte('}') - } else { - p.buf.WriteByte(']') - } + p.writeValueSetTail(f.Kind(), depth) + case reflect.Struct: - if p.fmt.sharpV { - p.buf.WriteString(f.Type().String()) + numField := f.NumField() + if !p.writeValueSetHead(f, true, numField == 0, depth) { + return } - p.buf.WriteByte('{') - for i := 0; i < f.NumField(); i++ { - if i > 0 { - if p.fmt.sharpV { - p.buf.WriteString(commaSpaceString) - } else { - p.buf.WriteByte(' ') - } - } + for i, last := 0, numField-1; i < numField; i++ { if p.fmt.plusV || p.fmt.sharpV { if name := f.Type().Field(i).Name; name != "" { p.buf.WriteString(name) p.buf.WriteByte(':') + if p.extendVflagOnly() { + p.buf.WriteByte(' ') + } } } p.printValue(getField(f, i), verb, depth+1) + p.writeElemSeparator(true, i == last, depth+1) // commaSpaceString or ' ' or newLine } - p.buf.WriteByte('}') - case reflect.Interface: - value := f.Elem() - if !value.IsValid() { - if p.fmt.sharpV { - p.buf.WriteString(f.Type().String()) - p.buf.WriteString(nilParenString) - } else { - p.buf.WriteString(nilAngleString) - } - } else { - p.printValue(value, verb, depth+1) - } + p.writeValueSetTail(f.Kind(), depth) + case reflect.Array, reflect.Slice: switch verb { case 's', 'q', 'x', 'X': @@ -818,30 +903,33 @@ func (p *pp) printValue(value reflect.Value, verb rune, depth int) { p.fmtBytes(bytes, verb, t.String()) return } - } - if p.fmt.sharpV { - p.buf.WriteString(f.Type().String()) - if f.Kind() == reflect.Slice && f.IsNil() { - p.buf.WriteString(nilParenString) + fallthrough + default: + size, elemKind := f.Len(), f.Type().Elem().Kind() + firstNewLine := p.arrayRequireNewLine(elemKind, 0, size) + if !p.writeValueSetHead(f, firstNewLine, size == 0, depth) { return } - p.buf.WriteByte('{') - for i := 0; i < f.Len(); i++ { - if i > 0 { - p.buf.WriteString(commaSpaceString) - } + for i, last := 0, size-1; i < size; i++ { p.printValue(f.Index(i), verb, depth+1) + isLast := i == last + newLine := firstNewLine && isLast || p.arrayRequireNewLine(elemKind, i+1, size) + p.writeElemSeparator(newLine, isLast, depth+1) // commaSpaceString or ' ' or newLine } - p.buf.WriteByte('}') - } else { - p.buf.WriteByte('[') - for i := 0; i < f.Len(); i++ { - if i > 0 { - p.buf.WriteByte(' ') - } - p.printValue(f.Index(i), verb, depth+1) + p.writeValueSetTail(f.Kind(), depth) + } + + case reflect.Interface: + value := f.Elem() + if !value.IsValid() { + if p.fmt.sharpV { + p.buf.WriteString(f.Type().String()) + p.buf.WriteString(nilParenString) + } else { + p.buf.WriteString(nilAngleString) } - p.buf.WriteByte(']') + } else { + p.printValue(value, verb, depth+1) } case reflect.Ptr: // pointer to array or slice or struct? ok at top level @@ -850,7 +938,7 @@ func (p *pp) printValue(value reflect.Value, verb rune, depth int) { switch a := f.Elem(); a.Kind() { case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: p.buf.WriteByte('&') - p.printValue(a, verb, depth+1) + p.printValue(a, verb, depth) //pointer do not increase depth return } } @@ -983,6 +1071,8 @@ formatLoop: p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left. case '+': p.fmt.plus = true + case '@': //pretty flag + p.fmt.pretty = true case '-': p.fmt.minus = true p.fmt.zero = false // Do not pad with zeros to the right. @@ -996,6 +1086,7 @@ formatLoop: // Go syntax p.fmt.sharpV = p.fmt.sharp p.fmt.sharp = false + // Struct-field syntax p.fmt.plusV = p.fmt.plus p.fmt.plus = false @@ -1091,6 +1182,7 @@ formatLoop: // Go syntax p.fmt.sharpV = p.fmt.sharp p.fmt.sharp = false + // Struct-field syntax p.fmt.plusV = p.fmt.plus p.fmt.plus = false