From 598c0f3ff122c30d789d702f3a6209fb38365cc8 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Thu, 26 Nov 2020 14:27:42 +0100 Subject: [PATCH] refactor --- LICENSE | 15 ++ README.md | 151 ++++++++++++------- example_test.go | 12 +- fprint.go | 377 ++++++++++++++++++++++++++++-------------------- notation.go | 47 +++--- reflect.go | 86 +++++------ reflecttype.go | 19 +-- sprint_test.go | 8 +- 8 files changed, 411 insertions(+), 304 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2d6964d --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +Copyright 2020 Arpad Ryszka + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of +the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index ba5c552..b6a12b4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Notation - Print Go objects +# Notation - print Go objects -This package can be used to print (or sprint) Go objects with optional wrapping (indentation) and optional type -information for debugging purposes. +This package can be used to print (or sprint) Go objects for debugging purposes, with optional wrapping +(indentation) and optional type information. ### Alternatives @@ -11,7 +11,7 @@ Notation is similar to the following great, more established and mature packages - [litter](https://github.com/sanity-io/litter) - [utter](https://github.com/kortschak/utter) -Notation differs from these primarily in the 'flavour' of printing and the package interface. +Notation differs from these primarily in the 'flavor' of printing and the package interface. ### Installation @@ -47,7 +47,7 @@ For the available functions, see also the [godoc](https://godoc.org/github.com/a Assuming to have the required types defined, if we do the following: -```go +``` b := bike{ frame: frame{ fork: fork{}, @@ -87,12 +87,17 @@ s := notation.Sprintw(b) We get the following string: -```go +``` { frame: { fork: { wheel: {size: 70, cassette: nil}, - handlebar: {levers: []{{withShift: true}, {withShift: true}}}, + handlebar: { + levers: []{ + {withShift: true}, + {withShift: true}, + }, + }, frontBrake: {discSize: 160}, }, saddlePost: {saddle: {}}, @@ -111,7 +116,10 @@ We get the following string: chain: {}, levers: []{{withShift: true}, {withShift: true}}, }, - wheels: []{{size: 70, cassette: nil}, {size: 70, cassette: {wheels: 11, chain: {}}}}, + wheels: []{ + {size: 70, cassette: nil}, + {size: 70, cassette: {wheels: 11, chain: {}}}, + }, handlebar: {levers: []{{withShift: true}, {withShift: true}}}, saddle: {}, } @@ -119,11 +127,14 @@ We get the following string: Using `notation.Sprintwv` instead of `notation.Sprintw`, we would get the following string: -```go +``` bike{ frame: frame{ fork: fork{ - wheel: *wheel{size: float64(70), cassette: (*cassette)(nil)}, + wheel: *wheel{ + size: float64(70), + cassette: (*cassette)(nil), + }, handlebar: *handlebar{ levers: []*lever{ *lever{withShift: bool(true)}, @@ -133,32 +144,63 @@ bike{ frontBrake: *brake{discSize: float64(160)}, }, saddlePost: saddlePost{saddle: *saddle{}}, - bottomBracket: *bracket{crank: *crank{wheels: int(2), chain: *chain{}}}, + bottomBracket: *bracket{ + crank: *crank{ + wheels: int(2), + chain: *chain{}, + }, + }, frontDerailleur: *derailleur{gears: int(2)}, rearDerailleur: *derailleur{gears: int(11)}, rearBrake: *brake{discSize: float64(140)}, rearWheel: *wheel{ size: float64(70), - cassette: *cassette{wheels: int(11), chain: *chain{}}, + cassette: *cassette{ + wheels: int(11), + chain: *chain{}, + }, }, }, driveTrain: driveTrain{ - bottomBracket: bracket{crank: *crank{wheels: int(2), chain: *chain{}}}, + bottomBracket: bracket{ + crank: *crank{ + wheels: int(2), + chain: *chain{}, + }, + }, crank: crank{wheels: int(2), chain: *chain{}}, - brakes: []brake{brake{discSize: float64(160)}, brake{discSize: float64(140)}}, + brakes: []brake{ + brake{discSize: float64(160)}, + brake{discSize: float64(140)}, + }, derailleurs: []derailleur{ - derailleur{gears: int(2)}, - derailleur{gears: int(11)}, + derailleur{ + gears: int(2), + }, + derailleur{ + gears: int(11), + }, }, cassette: cassette{wheels: int(11), chain: *chain{}}, chain: chain{}, - levers: []lever{lever{withShift: bool(true)}, lever{withShift: bool(true)}}, + levers: []lever{ + lever{withShift: bool(true)}, + lever{withShift: bool(true)}, + }, }, wheels: []wheel{ wheel{size: float64(70), cassette: (*cassette)(nil)}, - wheel{size: float64(70), cassette: *cassette{wheels: int(11), chain: *chain{}}}, + wheel{ + size: float64(70), + cassette: *cassette{wheels: int(11), chain: *chain{}}, + }, + }, + handlebar: handlebar{ + levers: []*lever{ + *lever{withShift: bool(true)}, + *lever{withShift: bool(true)}, + }, }, - handlebar: handlebar{levers: []*lever{*lever{withShift: bool(true)}, *lever{withShift: bool(true)}}}, saddle: saddle{}, } ``` @@ -172,28 +214,28 @@ input objects. ##### Numbers Numbers are printed based on the `fmt` package's default formatting. When printing with moderate type -information, the type for the `int`, default witdth signed integers, will be omitted. +information, the type for the `int`, default width signed integers, will be omitted. -```go +``` i := 42 notation.Printlnt(i) ``` Output: -```go +``` 42 ``` ##### Strings When printing strings, by default they are escaped using the `strconv.Quote` function. However, when wrapping -long strings, and the string contains a newline and doesn't contain a backquote '`', then the string is printed -as a raw string literal, delimited by '`'. +long strings, and the string contains a newline and doesn't contain a backquote, then the string is printed +as a raw string literal, delimited by backquotes. Short string: -```go +``` s := `foobar baz` notation.Printlnw(s) @@ -201,13 +243,13 @@ notation.Printlnw(s) Output: -```go +``` "foobar\nbaz" ``` Long string: -```go +``` s := `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps @@ -220,7 +262,7 @@ notation.Println(s) Output: -```go +``` `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps @@ -234,28 +276,28 @@ quick brown fox jumps over the lazy dog.` Slices are are printed by printing their elements between braces, prefixed either by '[]' or the type of the slice. Example: -```go +``` l := []int{1, 2, 3} notation.Println(l) ``` Output: -```go +``` []{1, 2, 3} ``` To differentiate arrays from slices, arrays are always prefixed with their type or square brackets containing the length of the array: -```go +``` a := [...]{1, 2, 3} notation.Println(a) ``` Output: -```go +``` [3]{1, 2, 3} ``` @@ -264,9 +306,9 @@ Output: When the type of a slice is `uint8`, or an alias of it, e.g. `byte`, then it is printed as []byte, with the hexa representation of its bytes: -```go +``` b := []byte( -`The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The + `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy @@ -279,7 +321,7 @@ notation.Printlnwt(b) Output: -```go +``` []byte{ 54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a 75 6d 70 73 20 6f 76 65 72 20 74 68 65 20 6c 61 7a 79 20 64 6f @@ -314,14 +356,14 @@ Output: Maps are printed with their entries sorted by the string representation of their keys: -```go +``` m := map[string]int{"b": 1, "c": 2, "a": 3} notation.Println(m) ``` Output: -```go +``` map{"a": 3, "b": 1, "c": 2} ``` @@ -330,42 +372,42 @@ disabled via the `MAPSORT=0` environment variable. ##### Hidden values: channels, functions -Certain values, like channels and functions are printed without expanding their internals, e.g. channee state or +Certain values, like channels and functions are printed without expanding their internals, e.g. channel state or function body. When printing with types, the signature of these objects is printed: -```go +``` f := func(int) int { return 42 } notation.Println(f) ``` Output: -```go +``` func() ``` With types: -```go +``` f := func(int) int { return 42 } notation.Printlnt(f) ``` Output: -```go +``` func(int) int ``` ##### Wrapping -Whe 'w' variant of the printing functions wraps the output with Go style indentation where the lines would be +The 'w' variant of the printing functions wraps the output with Go style indentation where the lines would be too long otherwise. The wrapping is not eager, it only aims for fitting the lines on 72 columns. To measure the indentation, it assumes 8 character width tabs. In certain cases, it tolerates longer lines up to 112 columns, when the output would probably more readable that way. Of course, readability is subjective. As a hidden feature, when it's really necessary, it is possible to change the above control values via -environmetn variables. TABWIDTH controls the measuring of the indentation. LINEWIDTH sets the aimed column width +environment variables. TABWIDTH controls the measuring of the indentation. LINEWIDTH sets the aimed column width of the printed lines. LINEWIDTH1 sets the tolerated threshold for those lines that are allowed to exceed the default line width. E.g. if somebody uses two-character wide tabs in their console, they can use the package like this: @@ -374,7 +416,7 @@ like this: TABWIDTH=2 go test -v -count 1 ``` -As a consequence, it is also possible to wrap all lines: +As a consequence, it is also possible to forcibly wrap all lines: ``` TABWIDTH=0 LINEWIDTH=0 LINEWIDTH1=0 go test -v -count 1 @@ -388,7 +430,7 @@ type. The package path is not printed. Named type: -```go +``` type t struct{foo int} v := t{42} notation.Printlnt(v) @@ -396,43 +438,43 @@ notation.Printlnt(v) Output: -```go +``` t{foo: 42} ``` Unnamed type: -```go +``` v := struct{foo int}{42} notation.Printlnt(v) ``` Output: -```go +``` struct{foo int}{foo: 42} ``` Using the 't' suffixed variants of the printing functions, displaying only moderately verbose type information, the types of certain values is omitted, where it can be inferred from the context: -```go +``` v := []struct{foo int}{{42}, {84}} notation.Printlnt(os.Stdout, v) ``` Output: -```go +``` []struct{foo int}{{foo: 42}, {foo: 84}} ``` ##### Cyclic references Cyclic references are detected based on an approach similar to the one in the stdlib's reflect.DeepEqual -function. Such occurences are displayed in the output with references: +function. Such occurrences are displayed in the output with references: -```go +``` l := []interface{}{"foo"} l[0] = l notation.Fprint(os.Stdout, l) @@ -440,6 +482,7 @@ notation.Fprint(os.Stdout, l) Output: -```go +``` r0=[]{r0} ``` + diff --git a/example_test.go b/example_test.go index e46b908..6365fe7 100644 --- a/example_test.go +++ b/example_test.go @@ -100,7 +100,12 @@ func Example() { // frame: { // fork: { // wheel: {size: 70, cassette: nil}, - // handlebar: {levers: []{{withShift: true}, {withShift: true}}}, + // handlebar: { + // levers: []{ + // {withShift: true}, + // {withShift: true}, + // }, + // }, // frontBrake: {discSize: 160}, // }, // saddlePost: {saddle: {}}, @@ -119,7 +124,10 @@ func Example() { // chain: {}, // levers: []{{withShift: true}, {withShift: true}}, // }, - // wheels: []{{size: 70, cassette: nil}, {size: 70, cassette: {wheels: 11, chain: {}}}}, + // wheels: []{ + // {size: 70, cassette: nil}, + // {size: 70, cassette: {wheels: 11, chain: {}}}, + // }, // handlebar: {levers: []{{withShift: true}, {withShift: true}}}, // saddle: {}, // } diff --git a/fprint.go b/fprint.go index 8c4b9b9..c6dabbd 100644 --- a/fprint.go +++ b/fprint.go @@ -1,183 +1,222 @@ package notation -import "strings" +import ( + "fmt" + "strings" +) -func unwrappable(n node) bool { - return n.len == n.wrapLen.max && - n.len == n.fullWrap.max -} - -func initialize(t *int, v int) { - if *t > 0 { - return +func ifZero(a, b int) int { + if a == 0 { + return b } - *t = v + return a } -func max(t *int, v int) { - if *t >= v { - return +func max(a, b int) int { + if a > b { + return a } - *t = v + return b } -func nodeLen(t int, n node) node { - var w, f int - for i, p := range n.parts { - switch part := p.(type) { - case string: - n.len += len(part) - w += len(part) - f += len(part) - case str: - // We assume here that an str is always contained by a node that has only a - // single str. - // - // If this changes in the future, then we need to provide tests for the - // additional cases. If this doesn't change anytime soon, then we can - // refactor this part. - // - n.len = len(part.val) - if part.raw == "" { - w = len(part.val) - f = len(part.val) - } else { - lines := strings.Split(part.raw, "\n") - part.rawLen.first = len(lines[0]) - for _, line := range lines { - if len(line) > part.rawLen.max { - part.rawLen.max = len(line) - } - } +func strLen(s str) str { + l := strings.Split(s.raw, "\n") + for j, li := range l { + if j == 0 { + s.rawLen.first = len(li) + } - part.rawLen.last = len(lines[len(lines)-1]) - n.parts[i] = part - n.wrapLen.first = part.rawLen.first - n.fullWrap.first = part.rawLen.first - n.wrapLen.max = part.rawLen.max - n.fullWrap.max = part.rawLen.max - w = part.rawLen.last - f = part.rawLen.last - } - case node: - part = nodeLen(t, part) - n.parts[i] = part - n.len += part.len - if unwrappable(part) { - w += part.len - f += part.len - continue - } + if len(li) > s.rawLen.max { + s.rawLen.max = len(li) + } - if part.len == part.wrapLen.max { - w += part.len - } else { - w += part.wrapLen.first - initialize(&n.wrapLen.first, w) - max(&n.wrapLen.max, w) - w = part.wrapLen.last - } - - f += part.fullWrap.first - initialize(&n.fullWrap.first, f) - max(&n.fullWrap.max, f) - f = part.fullWrap.last - case wrapper: - if len(part.items) == 0 { - continue - } - - initialize(&n.wrapLen.first, w) - max(&n.wrapLen.max, w) - initialize(&n.fullWrap.first, f) - max(&n.fullWrap.max, f) - w, f = 0, 0 - n.len += (len(part.items) - 1) * len(part.sep) - if part.mode == line { - w += (len(part.items) - 1) * len(part.sep) - } - - for j, item := range part.items { - item = nodeLen(t, item) - part.items[j] = item - n.len += item.len - switch part.mode { - case line: - w += item.len - max(&f, item.len) - default: - wj := t + item.len + len(part.suffix) - max(&w, wj) - fj := t + item.fullWrap.max - max(&f, fj) - fj = t + item.fullWrap.last + len(part.suffix) - max(&f, fj) - } - } - - max(&n.wrapLen.max, w) - max(&n.fullWrap.max, f) - w, f = 0, 0 + if j == len(l)-1 { + s.rawLen.last = len(li) } } - initialize(&n.wrapLen.first, w) - max(&n.wrapLen.max, w) + return s +} + +func nodeLen(t int, n node) node { + // We assume here that an str is always contained + // by a node that has only a single str. + // + if s, ok := n.parts[0].(str); ok { + s = strLen(s) + n.parts[0] = s + n.len = len(s.val) + if s.raw == "" { + wl := wrapLen{ + first: len(s.val), + max: len(s.val), + last: len(s.val), + } + + n.wrapLen = wl + n.fullWrap = wl + return n + } + + n.wrapLen = s.rawLen + n.fullWrap = s.rawLen + return n + } + + for i := range n.parts { + switch pt := n.parts[i].(type) { + case node: + n.parts[i] = nodeLen(t, pt) + case wrapper: + for j := range pt.items { + pt.items[j] = nodeLen(t, pt.items[j]) + } + } + } + + for _, p := range n.parts { + switch pt := p.(type) { + case node: + n.len += pt.len + case wrapper: + if len(pt.items) == 0 { + continue + } + + n.len += (len(pt.items) - 1) * len(pt.sep) + for _, pti := range pt.items { + n.len += pti.len + } + default: + n.len += len(fmt.Sprint(p)) + } + } + + var w, f int + for _, p := range n.parts { + switch pt := p.(type) { + case node: + w += pt.wrapLen.first + if pt.len != pt.wrapLen.first { + n.wrapLen.first = ifZero(n.wrapLen.first, w) + n.wrapLen.max = max(n.wrapLen.max, w) + n.wrapLen.max = max(n.wrapLen.max, pt.wrapLen.max) + w = pt.wrapLen.last + } + + f += pt.fullWrap.first + if pt.len != pt.fullWrap.first { + n.fullWrap.first = ifZero(n.fullWrap.first, f) + n.fullWrap.max = max(n.fullWrap.max, f) + n.fullWrap.max = max(n.fullWrap.max, pt.fullWrap.max) + f = pt.fullWrap.last + } + case wrapper: + if len(pt.items) == 0 { + continue + } + + n.wrapLen.first = ifZero(n.wrapLen.first, w) + n.wrapLen.max = max(n.wrapLen.max, w) + n.fullWrap.first = ifZero(n.fullWrap.first, f) + n.fullWrap.max = max(n.fullWrap.max, f) + w = 0 + f = 0 + switch pt.mode { + case line: + // line wrapping is flexible, here + // we measure the longest case + // + w = (len(pt.items) - 1) * len(pt.sep) + for _, pti := range pt.items { + w += pti.len + } + + // here me measure the shortest + // possible case + // + for _, pti := range pt.items { + f = max(f, pti.fullWrap.max) + } + default: + // for non-full wrap, we measure the full + // length of the items + // + for _, pti := range pt.items { + w = max(w, t+pti.len+len(pt.suffix)) + } + + // for full wrap, we measure the fully + // wrapped length of the items + // + for _, pti := range pt.items { + f = max(f, t+pti.fullWrap.max) + f = max(f, t+pti.fullWrap.last+len(pt.suffix)) + } + } + + n.wrapLen.max = max(n.wrapLen.max, w) + n.fullWrap.max = max(n.fullWrap.max, f) + w = 0 + f = 0 + default: + w += len(fmt.Sprint(p)) + f += len(fmt.Sprint(p)) + } + } + + n.wrapLen.first = ifZero(n.wrapLen.first, w) + n.wrapLen.max = max(n.wrapLen.max, w) n.wrapLen.last = w - initialize(&n.fullWrap.first, f) - max(&n.fullWrap.max, f) + n.fullWrap.first = ifZero(n.fullWrap.first, f) + n.fullWrap.max = max(n.fullWrap.max, f) n.fullWrap.last = f return n } func wrapNode(t, cf0, c0, c1 int, n node) node { + // fits: if n.len <= c0 { return n } + // we don't want to make it longer: if n.wrapLen.max >= n.len && n.fullWrap.max >= n.len { return n } - if n.len <= c1 && n.len-c0 <= n.wrapLen.max { + // tolerate below c1 when it's not worth wrapping: + if n.len <= c1 && n.len-c0 <= c0-n.wrapLen.max { return n } n.wrap = true + + // We assume here that an str is always contained + // by a node that has only a single str. + // + if s, ok := n.parts[0].(str); ok { + s.useRaw = s.raw != "" + n.parts[0] = s + return n + } + cc0, cc1 := c0, c1 lastWrapperIndex := -1 var trackBack bool for i := 0; i < len(n.parts); i++ { p := n.parts[i] switch part := p.(type) { - case string: - cc0 -= len(part) - cc1 -= len(part) - if !trackBack && cc1 < 0 { - cc0 = 0 - cc1 = 0 - i = lastWrapperIndex - trackBack = true - } - case str: - // We assume here that an str is always contained by a node that has only a - // single str. Therefore we don't need to trackback to here, because the - // decision on wrapping was already made for the node. - // - // If this changes in the future, then we need to provide tests for the - // additional cases. If this doesn't change anytime soon, then we can - // refactor this part. - // - part.useRaw = part.raw != "" - n.parts[i] = part case node: part = wrapNode(t, cf0, cc0, cc1, part) n.parts[i] = part if part.wrap { - // This is an approximation: sometimes part.fullWrap.first should be applied + // This is an approximation: sometimes + // part.fullWrap.last should be applied // here, but usually those are the same. + // cc0 -= part.wrapLen.first cc1 -= part.wrapLen.first } else { @@ -185,12 +224,26 @@ func wrapNode(t, cf0, c0, c1 int, n node) node { cc1 -= part.len } - if !trackBack && cc1 < 0 { - cc0 = 0 - cc1 = 0 - i = lastWrapperIndex - trackBack = true + if cc1 >= 0 { + if part.wrap { + cc0 = c0 - part.wrapLen.last + cc1 = c1 - part.wrapLen.last + } + + continue } + + if trackBack { + continue + } + + // trackback from after the last wrapper: + i = lastWrapperIndex + trackBack = true + + // force wrapping during trackback: + cc0 = 0 + cc1 = 0 case wrapper: if len(part.items) == 0 { continue @@ -201,37 +254,50 @@ func wrapNode(t, cf0, c0, c1 int, n node) node { lastWrapperIndex = i switch part.mode { case line: + // we only set the line endings. We use + // the full column width: + // cl := cf0 - t var w int - for j, ni := range part.items { - if w > 0 && w+len(part.sep)+ni.len > cl { + for j, nj := range part.items { + if w > 0 && w+len(part.sep)+nj.len > cl { + part.lineEnds = append(part.lineEnds, j) w = 0 - part.lineEnds = append( - part.lineEnds, - j, - ) } if w > 0 { w += len(part.sep) } - w += ni.len + w += nj.len } part.lineEnds = append(part.lineEnds, len(part.items)) n.parts[i] = part default: for j := range part.items { - part.items[j] = wrapNode( - t, - cf0, - c0-t, - c1-t, - part.items[j], - ) + part.items[j] = wrapNode(t, cf0, c0-t, c1-t, part.items[j]) } } + default: + s := fmt.Sprint(part) + cc0 -= len(s) + cc1 -= len(s) + if cc1 >= 0 { + continue + } + + if trackBack { + continue + } + + // trackback from after the last wrapper: + i = lastWrapperIndex + trackBack = true + + // force wrapping during trackback: + cc0 = 0 + cc1 = 0 } } @@ -277,8 +343,7 @@ func fprint(w *writer, t int, n node) { } for _, line := range lines { - w.blankLine() - w.tabs(1) + w.line(1) for i, ni := range line { if i > 0 { w.write(part.sep) diff --git a/notation.go b/notation.go index 2eb59b7..eb88b20 100644 --- a/notation.go +++ b/notation.go @@ -1,5 +1,5 @@ /* -Package notation can be used to print (or sprint) Go objects with optional wrapping (pretty printing) and +Package notation can be used to print (or sprint) Go objects with optional wrapping (and indentation) and optional type information. */ package notation @@ -40,18 +40,18 @@ type pending struct { } type node struct { + parts []interface{} len int wrapLen wrapLen fullWrap wrapLen wrap bool - parts []interface{} } type str struct { val string raw string - useRaw bool rawLen wrapLen + useRaw bool } type wrapMode int @@ -76,6 +76,11 @@ type writer struct { var stderr io.Writer = os.Stderr +func nodeOf(parts ...interface{}) node { + return node{parts: parts} +} + +// used only for debugging func (n node) String() string { var b bytes.Buffer w := &writer{w: &b} @@ -116,10 +121,6 @@ func (w *writer) line(t int) { w.tabs(t) } -func nodeOf(parts ...interface{}) node { - return node{parts: parts} -} - func config(name string, dflt int) int { s := os.Getenv(name) if s == "" { @@ -166,12 +167,8 @@ func fprintValues(w io.Writer, o opts, v []interface{}) (int, error) { continue } - n := reflectValue( - o, - &pending{values: make(map[uintptr]nodeRef)}, - reflect.ValueOf(vi), - ) - + p := &pending{values: make(map[uintptr]nodeRef)} + n := reflectValue(o, p, reflect.ValueOf(vi)) if o&wrap != 0 { n = nodeLen(tab, n) n = wrapNode(tab, cols0, cols0, cols1, n) @@ -209,7 +206,7 @@ func Fprint(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, none, v) } -// Fprintw prints the provided objects to the provided writer, with wrapping (pretty-printing) where necessary. +// Fprintw prints the provided objects to the provided writer, with wrapping (and indentation) where necessary. // When multiple objects are printed, they'll be separated by a newline. func Fprintw(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, wrap, v) @@ -221,7 +218,7 @@ func Fprintt(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, types, v) } -// Fprintwt prints the provided objects to the provided writer, with wrapping (pretty-printing) where necessary, +// Fprintwt prints the provided objects to the provided writer, with wrapping (and indentation) where necessary, // and with moderate type information. When multiple objects are printed, they'll be separated by a newline. func Fprintwt(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, wrap|types, v) @@ -233,7 +230,7 @@ func Fprintv(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, allTypes, v) } -// Fprintwv prints the provided objects to the provided writer, with wrapping (pretty-printing) where necessary, +// Fprintwv prints the provided objects to the provided writer, with wrapping (and indentation) where necessary, // and with verbose type information. When multiple objects are printed, they'll be separated by a newline. func Fprintwv(w io.Writer, v ...interface{}) (int, error) { return fprintValues(w, wrap|allTypes, v) @@ -245,7 +242,7 @@ func Print(v ...interface{}) (int, error) { return printValues(none, v) } -// Printw prints the provided objects to stderr, with wrapping (pretty-printing) where necessary. When multiple +// Printw prints the provided objects to stderr, with wrapping (and indentation) where necessary. When multiple // objects are printed, they'll be separated by a newline. func Printw(v ...interface{}) (int, error) { return printValues(wrap, v) @@ -257,7 +254,7 @@ func Printt(v ...interface{}) (int, error) { return printValues(types, v) } -// Printwt prints the provided objects to stderr, with wrapping (pretty-printing) where necessary, and with +// Printwt prints the provided objects to stderr, with wrapping (and indentation) where necessary, and with // moderate type information. When multiple objects are printed, they'll be separated by a newline. func Printwt(v ...interface{}) (int, error) { return printValues(wrap|types, v) @@ -269,7 +266,7 @@ func Printv(v ...interface{}) (int, error) { return printValues(allTypes, v) } -// Printwv prints the provided objects to stderr, with wrapping (pretty-printing) where necessary, and with +// Printwv prints the provided objects to stderr, with wrapping (and indentation) where necessary, and with // verbose type information. When multiple objects are printed, they'll be separated by a newline. func Printwv(v ...interface{}) (int, error) { return printValues(wrap|allTypes, v) @@ -281,7 +278,7 @@ func Println(v ...interface{}) (int, error) { return printlnValues(none, v) } -// Printlnw prints the provided objects to stderr with a closing newline, with wrapping (pretty-printing) where +// Printlnw prints the provided objects to stderr with a closing newline, with wrapping (and indentation) where // necessary. When multiple objects are printed, they'll be separated by a newline. func Printlnw(v ...interface{}) (int, error) { return printlnValues(wrap, v) @@ -293,7 +290,7 @@ func Printlnt(v ...interface{}) (int, error) { return printlnValues(types, v) } -// Printlnwt prints the provided objects to stderr with a closing newline, with wrapping (pretty-printing) where +// Printlnwt prints the provided objects to stderr with a closing newline, with wrapping (and indentation) where // necessary, and with moderate type information. When multiple objects are printed, they'll be separated by a // newline. func Printlnwt(v ...interface{}) (int, error) { @@ -306,7 +303,7 @@ func Printlnv(v ...interface{}) (int, error) { return printlnValues(allTypes, v) } -// Printlnwv prints the provided objects to stderr with a closing newline, with wrapping (pretty-printing) where +// Printlnwv prints the provided objects to stderr with a closing newline, with wrapping (and indentation) where // necessary, and with verbose type information. When multiple objects are printed, they'll be separated by a // newline. func Printlnwv(v ...interface{}) (int, error) { @@ -319,7 +316,7 @@ func Sprint(v ...interface{}) string { return sprintValues(none, v) } -// Sprint returns the string representation of the Go objects, with wrapping (pretty-printing) where necessary. +// Sprint returns the string representation of the Go objects, with wrapping (and indentation) where necessary. // When multiple objects are provided, they'll be seprated by a newline. func Sprintw(v ...interface{}) string { return sprintValues(wrap, v) @@ -331,7 +328,7 @@ func Sprintt(v ...interface{}) string { return sprintValues(types, v) } -// Sprint returns the string representation of the Go objects, with wrapping (pretty-printing) where necessary, +// Sprint returns the string representation of the Go objects, with wrapping (and indentation) where necessary, // and with moderate type information. When multiple objects are provided, they'll be seprated by a newline. func Sprintwt(v ...interface{}) string { return sprintValues(wrap|types, v) @@ -343,7 +340,7 @@ func Sprintv(v ...interface{}) string { return sprintValues(allTypes, v) } -// Sprint returns the string representation of the Go objects, with wrapping (pretty-printing) where necessary, +// Sprint returns the string representation of the Go objects, with wrapping (and indentation) where necessary, // and with verbose type information. When multiple objects are provided, they'll be seprated by a newline. func Sprintwv(v ...interface{}) string { return sprintValues(wrap|allTypes, v) diff --git a/reflect.go b/reflect.go index 4dc74b3..478dacb 100644 --- a/reflect.go +++ b/reflect.go @@ -38,11 +38,10 @@ func reflectPrimitive(o opts, r reflect.Value, v interface{}, suppressType ...st } for _, suppress := range suppressType { - if tn.parts[0] != suppress { - continue + if tn.parts[0] == suppress { + return nodeOf(s) } - return nodeOf(s) } return nodeOf(tn, "(", s, ")") @@ -63,31 +62,33 @@ func reflectNil(o opts, groupUnnamedType bool, r reflect.Value) node { func reflectItems(o opts, p *pending, prefix string, r reflect.Value) node { typ := r.Type() - var items wrapper - if typ.Elem().Name() == "uint8" { - items = wrapper{sep: " ", mode: line} + var w wrapper + if typ.Elem().Kind() == reflect.Uint8 { + w.sep = " " + w.mode = line for i := 0; i < r.Len(); i++ { - items.items = append( - items.items, + w.items = append( + w.items, nodeOf(fmt.Sprintf("%02x", r.Index(i).Uint())), ) } } else { - items = wrapper{sep: ", ", suffix: ","} + w.sep = ", " + w.suffix = "," itemOpts := o | skipTypes for i := 0; i < r.Len(); i++ { - items.items = append( - items.items, + w.items = append( + w.items, reflectValue(itemOpts, p, r.Index(i)), ) } } - if _, t, _ := withType(o); !t { - return nodeOf(prefix, "{", items, "}") + if _, t, _ := withType(o); t { + return nodeOf(reflectType(typ), "{", w, "}") } - return nodeOf(reflectType(typ), "{", items, "}") + return nodeOf(prefix, "{", w, "}") } func reflectHidden(o opts, hidden string, r reflect.Value) node { @@ -95,11 +96,11 @@ func reflectHidden(o opts, hidden string, r reflect.Value) node { return reflectNil(o, true, r) } - if _, t, _ := withType(o); !t { - return nodeOf(hidden) + if _, t, _ := withType(o); t { + return reflectType(r.Type()) } - return reflectType(r.Type()) + return nodeOf(hidden) } func reflectArray(o opts, p *pending, r reflect.Value) node { @@ -138,46 +139,38 @@ func reflectMap(o opts, p *pending, r reflect.Value) node { } var skeys []string - items := wrapper{sep: ", ", suffix: ","} itemOpts := o | skipTypes - keys := r.MapKeys() sv := make(map[string]reflect.Value) sn := make(map[string]node) - for _, key := range keys { + for _, key := range r.MapKeys() { + kn := reflectValue(itemOpts, p, key) var b bytes.Buffer - nk := reflectValue(itemOpts, p, key) wr := writer{w: &b} - fprint(&wr, 0, nk) + fprint(&wr, 0, kn) skey := b.String() skeys = append(skeys, skey) sv[skey] = key - sn[skey] = nk + sn[skey] = kn } if o&randomMaps == 0 { sort.Strings(skeys) } + w := wrapper{sep: ", ", suffix: ","} for _, skey := range skeys { - items.items = append( - items.items, - nodeOf( - sn[skey], - ": ", - reflectValue( - itemOpts, - p, - r.MapIndex(sv[skey]), - ), - ), + vn := reflectValue(itemOpts, p, r.MapIndex(sv[skey])) + w.items = append( + w.items, + nodeOf(sn[skey], ": ", vn), ) } if _, t, _ := withType(o); !t { - return nodeOf("map{", items, "}") + return nodeOf("map{", w, "}") } - return nodeOf(reflectType(r.Type()), "{", items, "}") + return nodeOf(reflectType(r.Type()), "{", w, "}") } func reflectPointer(o opts, p *pending, r reflect.Value) node { @@ -229,17 +222,10 @@ func reflectStruct(o opts, p *pending, r reflect.Value) node { rt := r.Type() for i := 0; i < r.NumField(); i++ { name := rt.Field(i).Name + fv := reflectValue(fieldOpts, p, r.FieldByName(name)) wr.items = append( wr.items, - nodeOf( - name, - ": ", - reflectValue( - fieldOpts, - p, - r.FieldByName(name), - ), - ), + nodeOf(name, ": ", fv), ) } @@ -265,15 +251,15 @@ func reflectUnsafePointer(o opts, r reflect.Value) node { func checkPending(p *pending, r reflect.Value) (applyRef func(node) node, ref node, isPending bool) { applyRef = func(n node) node { return n } switch r.Kind() { - case reflect.Slice, reflect.Map: - case reflect.Ptr: - if r.IsNil() { - return - } + case reflect.Slice, reflect.Map, reflect.Ptr: default: return } + if r.IsNil() { + return + } + var nr nodeRef key := r.Pointer() nr, isPending = p.values[key] diff --git a/reflecttype.go b/reflecttype.go index f798512..fafe234 100644 --- a/reflecttype.go +++ b/reflecttype.go @@ -64,13 +64,8 @@ func reflectInterfaceType(t reflect.Type) node { wr := wrapper{sep: "; "} for i := 0; i < t.NumMethod(); i++ { method := t.Method(i) - wr.items = append( - wr.items, - nodeOf( - method.Name, - reflectFuncBaseType(method.Type), - ), - ) + mn := nodeOf(method.Name, reflectFuncBaseType(method.Type)) + wr.items = append(wr.items, mn) } return nodeOf("interface{", wr, "}") @@ -92,14 +87,8 @@ func reflectStructType(t reflect.Type) node { wr := wrapper{sep: "; "} for i := 0; i < t.NumField(); i++ { fi := t.Field(i) - wr.items = append( - wr.items, - nodeOf( - fi.Name, - " ", - reflectType(fi.Type), - ), - ) + fn := nodeOf(fi.Name, " ", reflectType(fi.Type)) + wr.items = append(wr.items, fn) } return nodeOf("struct{", wr, "}") diff --git a/sprint_test.go b/sprint_test.go index b3be827..65a6aed 100644 --- a/sprint_test.go +++ b/sprint_test.go @@ -973,7 +973,7 @@ func TestLongNonWrapperNodes(t *testing.T) { } } -func TestSingleLongString(t *testing.T) { +func TestLongString(t *testing.T) { t.Run("no line breaks", func(t *testing.T) { const expect = `"foobarbazquxquuxquzquuz"` defer withEnv(t, "TABWIDTH=2", "LINEWIDTH=9", "LINEWIDTH1=12")() @@ -1034,7 +1034,11 @@ func TestCyclicReferences(t *testing.T) { }) t.Run("multiple refs", func(t *testing.T) { - const expect = `{f0: r1={f0: r2={f0: nil, f1: r1, f2: r2}, f1: nil, f2: nil}, f1: nil, f2: nil}` + const expect = `{ + f0: r1={f0: r2={f0: nil, f1: r1, f2: r2}, f1: nil, f2: nil}, + f1: nil, + f2: nil, +}` type typ struct{ f0, f1, f2 *typ } v0 := new(typ) v1 := new(typ)