This commit is contained in:
Arpad Ryszka 2020-11-26 14:27:42 +01:00
parent 1becff5c5d
commit 598c0f3ff1
8 changed files with 411 additions and 304 deletions

15
LICENSE Normal file
View File

@ -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.

151
README.md
View File

@ -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 This package can be used to print (or sprint) Go objects for debugging purposes, with optional wrapping
information for debugging purposes. (indentation) and optional type information.
### Alternatives ### Alternatives
@ -11,7 +11,7 @@ Notation is similar to the following great, more established and mature packages
- [litter](https://github.com/sanity-io/litter) - [litter](https://github.com/sanity-io/litter)
- [utter](https://github.com/kortschak/utter) - [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 ### 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: Assuming to have the required types defined, if we do the following:
```go ```
b := bike{ b := bike{
frame: frame{ frame: frame{
fork: fork{}, fork: fork{},
@ -87,12 +87,17 @@ s := notation.Sprintw(b)
We get the following string: We get the following string:
```go ```
{ {
frame: { frame: {
fork: { fork: {
wheel: {size: 70, cassette: nil}, wheel: {size: 70, cassette: nil},
handlebar: {levers: []{{withShift: true}, {withShift: true}}}, handlebar: {
levers: []{
{withShift: true},
{withShift: true},
},
},
frontBrake: {discSize: 160}, frontBrake: {discSize: 160},
}, },
saddlePost: {saddle: {}}, saddlePost: {saddle: {}},
@ -111,7 +116,10 @@ We get the following string:
chain: {}, chain: {},
levers: []{{withShift: true}, {withShift: true}}, 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}}}, handlebar: {levers: []{{withShift: true}, {withShift: true}}},
saddle: {}, saddle: {},
} }
@ -119,11 +127,14 @@ We get the following string:
Using `notation.Sprintwv` instead of `notation.Sprintw`, we would get the following string: Using `notation.Sprintwv` instead of `notation.Sprintw`, we would get the following string:
```go ```
bike{ bike{
frame: frame{ frame: frame{
fork: fork{ fork: fork{
wheel: *wheel{size: float64(70), cassette: (*cassette)(nil)}, wheel: *wheel{
size: float64(70),
cassette: (*cassette)(nil),
},
handlebar: *handlebar{ handlebar: *handlebar{
levers: []*lever{ levers: []*lever{
*lever{withShift: bool(true)}, *lever{withShift: bool(true)},
@ -133,32 +144,63 @@ bike{
frontBrake: *brake{discSize: float64(160)}, frontBrake: *brake{discSize: float64(160)},
}, },
saddlePost: saddlePost{saddle: *saddle{}}, 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)}, frontDerailleur: *derailleur{gears: int(2)},
rearDerailleur: *derailleur{gears: int(11)}, rearDerailleur: *derailleur{gears: int(11)},
rearBrake: *brake{discSize: float64(140)}, rearBrake: *brake{discSize: float64(140)},
rearWheel: *wheel{ rearWheel: *wheel{
size: float64(70), size: float64(70),
cassette: *cassette{wheels: int(11), chain: *chain{}}, cassette: *cassette{
wheels: int(11),
chain: *chain{},
},
}, },
}, },
driveTrain: driveTrain{ 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{}}, 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{ derailleurs: []derailleur{
derailleur{gears: int(2)}, derailleur{
derailleur{gears: int(11)}, gears: int(2),
},
derailleur{
gears: int(11),
},
}, },
cassette: cassette{wheels: int(11), chain: *chain{}}, cassette: cassette{wheels: int(11), chain: *chain{}},
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{ wheels: []wheel{
wheel{size: float64(70), cassette: (*cassette)(nil)}, 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{}, saddle: saddle{},
} }
``` ```
@ -172,28 +214,28 @@ input objects.
##### Numbers ##### Numbers
Numbers are printed based on the `fmt` package's default formatting. When printing with moderate type 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 i := 42
notation.Printlnt(i) notation.Printlnt(i)
``` ```
Output: Output:
```go ```
42 42
``` ```
##### Strings ##### Strings
When printing strings, by default they are escaped using the `strconv.Quote` function. However, when wrapping 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 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 '`'. as a raw string literal, delimited by backquotes.
Short string: Short string:
```go ```
s := `foobar s := `foobar
baz` baz`
notation.Printlnw(s) notation.Printlnw(s)
@ -201,13 +243,13 @@ notation.Printlnw(s)
Output: Output:
```go ```
"foobar\nbaz" "foobar\nbaz"
``` ```
Long string: Long string:
```go ```
s := `The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The 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 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 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: Output:
```go ```
`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 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 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 Slices are are printed by printing their elements between braces, prefixed either by '[]' or the type of the
slice. Example: slice. Example:
```go ```
l := []int{1, 2, 3} l := []int{1, 2, 3}
notation.Println(l) notation.Println(l)
``` ```
Output: Output:
```go ```
[]{1, 2, 3} []{1, 2, 3}
``` ```
To differentiate arrays from slices, arrays are always prefixed with their type or square brackets containing To differentiate arrays from slices, arrays are always prefixed with their type or square brackets containing
the length of the array: the length of the array:
```go ```
a := [...]{1, 2, 3} a := [...]{1, 2, 3}
notation.Println(a) notation.Println(a)
``` ```
Output: Output:
```go ```
[3]{1, 2, 3} [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 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: representation of its bytes:
```go ```
b := []byte( 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 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 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 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: Output:
```go ```
[]byte{ []byte{
54 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 66 6f 78 20 6a 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 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: 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} m := map[string]int{"b": 1, "c": 2, "a": 3}
notation.Println(m) notation.Println(m)
``` ```
Output: Output:
```go ```
map{"a": 3, "b": 1, "c": 2} map{"a": 3, "b": 1, "c": 2}
``` ```
@ -330,42 +372,42 @@ disabled via the `MAPSORT=0` environment variable.
##### Hidden values: channels, functions ##### 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: function body. When printing with types, the signature of these objects is printed:
```go ```
f := func(int) int { return 42 } f := func(int) int { return 42 }
notation.Println(f) notation.Println(f)
``` ```
Output: Output:
```go ```
func() func()
``` ```
With types: With types:
```go ```
f := func(int) int { return 42 } f := func(int) int { return 42 }
notation.Printlnt(f) notation.Printlnt(f)
``` ```
Output: Output:
```go ```
func(int) int func(int) int
``` ```
##### Wrapping ##### 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 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, 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. 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 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 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 default line width. E.g. if somebody uses two-character wide tabs in their console, they can use the package
like this: like this:
@ -374,7 +416,7 @@ like this:
TABWIDTH=2 go test -v -count 1 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 TABWIDTH=0 LINEWIDTH=0 LINEWIDTH1=0 go test -v -count 1
@ -388,7 +430,7 @@ type. The package path is not printed.
Named type: Named type:
```go ```
type t struct{foo int} type t struct{foo int}
v := t{42} v := t{42}
notation.Printlnt(v) notation.Printlnt(v)
@ -396,43 +438,43 @@ notation.Printlnt(v)
Output: Output:
```go ```
t{foo: 42} t{foo: 42}
``` ```
Unnamed type: Unnamed type:
```go ```
v := struct{foo int}{42} v := struct{foo int}{42}
notation.Printlnt(v) notation.Printlnt(v)
``` ```
Output: Output:
```go ```
struct{foo int}{foo: 42} struct{foo int}{foo: 42}
``` ```
Using the 't' suffixed variants of the printing functions, displaying only moderately verbose type information, 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: the types of certain values is omitted, where it can be inferred from the context:
```go ```
v := []struct{foo int}{{42}, {84}} v := []struct{foo int}{{42}, {84}}
notation.Printlnt(os.Stdout, v) notation.Printlnt(os.Stdout, v)
``` ```
Output: Output:
```go ```
[]struct{foo int}{{foo: 42}, {foo: 84}} []struct{foo int}{{foo: 42}, {foo: 84}}
``` ```
##### Cyclic references ##### Cyclic references
Cyclic references are detected based on an approach similar to the one in the stdlib's reflect.DeepEqual 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 := []interface{}{"foo"}
l[0] = l l[0] = l
notation.Fprint(os.Stdout, l) notation.Fprint(os.Stdout, l)
@ -440,6 +482,7 @@ notation.Fprint(os.Stdout, l)
Output: Output:
```go ```
r0=[]{r0} r0=[]{r0}
``` ```

View File

@ -100,7 +100,12 @@ func Example() {
// frame: { // frame: {
// fork: { // fork: {
// wheel: {size: 70, cassette: nil}, // wheel: {size: 70, cassette: nil},
// handlebar: {levers: []{{withShift: true}, {withShift: true}}}, // handlebar: {
// levers: []{
// {withShift: true},
// {withShift: true},
// },
// },
// frontBrake: {discSize: 160}, // frontBrake: {discSize: 160},
// }, // },
// saddlePost: {saddle: {}}, // saddlePost: {saddle: {}},
@ -119,7 +124,10 @@ func Example() {
// chain: {}, // chain: {},
// levers: []{{withShift: true}, {withShift: true}}, // 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}}}, // handlebar: {levers: []{{withShift: true}, {withShift: true}}},
// saddle: {}, // saddle: {},
// } // }

349
fprint.go
View File

@ -1,183 +1,222 @@
package notation package notation
import "strings" import (
"fmt"
"strings"
)
func unwrappable(n node) bool { func ifZero(a, b int) int {
return n.len == n.wrapLen.max && if a == 0 {
n.len == n.fullWrap.max return b
}
func initialize(t *int, v int) {
if *t > 0 {
return
} }
*t = v return a
} }
func max(t *int, v int) { func max(a, b int) int {
if *t >= v { if a > b {
return return a
} }
*t = v return b
}
func strLen(s str) str {
l := strings.Split(s.raw, "\n")
for j, li := range l {
if j == 0 {
s.rawLen.first = len(li)
}
if len(li) > s.rawLen.max {
s.rawLen.max = len(li)
}
if j == len(l)-1 {
s.rawLen.last = len(li)
}
}
return s
} }
func nodeLen(t int, n node) node { func nodeLen(t int, n node) node {
var w, f int // We assume here that an str is always contained
for i, p := range n.parts { // by a node that has only a single str.
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 if s, ok := n.parts[0].(str); ok {
// additional cases. If this doesn't change anytime soon, then we can s = strLen(s)
// refactor this part. n.parts[0] = s
// n.len = len(s.val)
n.len = len(part.val) if s.raw == "" {
if part.raw == "" { wl := wrapLen{
w = len(part.val) first: len(s.val),
f = len(part.val) max: len(s.val),
} else { last: len(s.val),
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)
}
} }
part.rawLen.last = len(lines[len(lines)-1]) n.wrapLen = wl
n.parts[i] = part n.fullWrap = wl
n.wrapLen.first = part.rawLen.first return n
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
} }
n.wrapLen = s.rawLen
n.fullWrap = s.rawLen
return n
}
for i := range n.parts {
switch pt := n.parts[i].(type) {
case node: case node:
part = nodeLen(t, part) n.parts[i] = nodeLen(t, pt)
n.parts[i] = part
n.len += part.len
if unwrappable(part) {
w += part.len
f += part.len
continue
}
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: case wrapper:
if len(part.items) == 0 { 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 continue
} }
initialize(&n.wrapLen.first, w) n.len += (len(pt.items) - 1) * len(pt.sep)
max(&n.wrapLen.max, w) for _, pti := range pt.items {
initialize(&n.fullWrap.first, f) n.len += pti.len
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: default:
wj := t + item.len + len(part.suffix) n.len += len(fmt.Sprint(p))
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) var w, f int
max(&n.fullWrap.max, f) for _, p := range n.parts {
w, f = 0, 0 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))
} }
} }
initialize(&n.wrapLen.first, w) n.wrapLen.max = max(n.wrapLen.max, w)
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 n.wrapLen.last = w
initialize(&n.fullWrap.first, f) n.fullWrap.first = ifZero(n.fullWrap.first, f)
max(&n.fullWrap.max, f) n.fullWrap.max = max(n.fullWrap.max, f)
n.fullWrap.last = f n.fullWrap.last = f
return n return n
} }
func wrapNode(t, cf0, c0, c1 int, n node) node { func wrapNode(t, cf0, c0, c1 int, n node) node {
// fits:
if n.len <= c0 { if n.len <= c0 {
return n return n
} }
// we don't want to make it longer:
if n.wrapLen.max >= n.len && n.fullWrap.max >= n.len { if n.wrapLen.max >= n.len && n.fullWrap.max >= n.len {
return n 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 return n
} }
n.wrap = true 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 cc0, cc1 := c0, c1
lastWrapperIndex := -1 lastWrapperIndex := -1
var trackBack bool var trackBack bool
for i := 0; i < len(n.parts); i++ { for i := 0; i < len(n.parts); i++ {
p := n.parts[i] p := n.parts[i]
switch part := p.(type) { 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: case node:
part = wrapNode(t, cf0, cc0, cc1, part) part = wrapNode(t, cf0, cc0, cc1, part)
n.parts[i] = part n.parts[i] = part
if part.wrap { 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. // here, but usually those are the same.
//
cc0 -= part.wrapLen.first cc0 -= part.wrapLen.first
cc1 -= part.wrapLen.first cc1 -= part.wrapLen.first
} else { } else {
@ -185,12 +224,26 @@ func wrapNode(t, cf0, c0, c1 int, n node) node {
cc1 -= part.len cc1 -= part.len
} }
if !trackBack && cc1 < 0 { if cc1 >= 0 {
cc0 = 0 if part.wrap {
cc1 = 0 cc0 = c0 - part.wrapLen.last
cc1 = c1 - part.wrapLen.last
}
continue
}
if trackBack {
continue
}
// trackback from after the last wrapper:
i = lastWrapperIndex i = lastWrapperIndex
trackBack = true trackBack = true
}
// force wrapping during trackback:
cc0 = 0
cc1 = 0
case wrapper: case wrapper:
if len(part.items) == 0 { if len(part.items) == 0 {
continue continue
@ -201,37 +254,50 @@ func wrapNode(t, cf0, c0, c1 int, n node) node {
lastWrapperIndex = i lastWrapperIndex = i
switch part.mode { switch part.mode {
case line: case line:
// we only set the line endings. We use
// the full column width:
//
cl := cf0 - t cl := cf0 - t
var w int var w int
for j, ni := range part.items { for j, nj := range part.items {
if w > 0 && w+len(part.sep)+ni.len > cl { if w > 0 && w+len(part.sep)+nj.len > cl {
part.lineEnds = append(part.lineEnds, j)
w = 0 w = 0
part.lineEnds = append(
part.lineEnds,
j,
)
} }
if w > 0 { if w > 0 {
w += len(part.sep) w += len(part.sep)
} }
w += ni.len w += nj.len
} }
part.lineEnds = append(part.lineEnds, len(part.items)) part.lineEnds = append(part.lineEnds, len(part.items))
n.parts[i] = part n.parts[i] = part
default: default:
for j := range part.items { for j := range part.items {
part.items[j] = wrapNode( part.items[j] = wrapNode(t, cf0, c0-t, c1-t, part.items[j])
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 { for _, line := range lines {
w.blankLine() w.line(1)
w.tabs(1)
for i, ni := range line { for i, ni := range line {
if i > 0 { if i > 0 {
w.write(part.sep) w.write(part.sep)

View File

@ -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. optional type information.
*/ */
package notation package notation
@ -40,18 +40,18 @@ type pending struct {
} }
type node struct { type node struct {
parts []interface{}
len int len int
wrapLen wrapLen wrapLen wrapLen
fullWrap wrapLen fullWrap wrapLen
wrap bool wrap bool
parts []interface{}
} }
type str struct { type str struct {
val string val string
raw string raw string
useRaw bool
rawLen wrapLen rawLen wrapLen
useRaw bool
} }
type wrapMode int type wrapMode int
@ -76,6 +76,11 @@ type writer struct {
var stderr io.Writer = os.Stderr var stderr io.Writer = os.Stderr
func nodeOf(parts ...interface{}) node {
return node{parts: parts}
}
// used only for debugging
func (n node) String() string { func (n node) String() string {
var b bytes.Buffer var b bytes.Buffer
w := &writer{w: &b} w := &writer{w: &b}
@ -116,10 +121,6 @@ func (w *writer) line(t int) {
w.tabs(t) w.tabs(t)
} }
func nodeOf(parts ...interface{}) node {
return node{parts: parts}
}
func config(name string, dflt int) int { func config(name string, dflt int) int {
s := os.Getenv(name) s := os.Getenv(name)
if s == "" { if s == "" {
@ -166,12 +167,8 @@ func fprintValues(w io.Writer, o opts, v []interface{}) (int, error) {
continue continue
} }
n := reflectValue( p := &pending{values: make(map[uintptr]nodeRef)}
o, n := reflectValue(o, p, reflect.ValueOf(vi))
&pending{values: make(map[uintptr]nodeRef)},
reflect.ValueOf(vi),
)
if o&wrap != 0 { if o&wrap != 0 {
n = nodeLen(tab, n) n = nodeLen(tab, n)
n = wrapNode(tab, cols0, cols0, cols1, 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) 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. // When multiple objects are printed, they'll be separated by a newline.
func Fprintw(w io.Writer, v ...interface{}) (int, error) { func Fprintw(w io.Writer, v ...interface{}) (int, error) {
return fprintValues(w, wrap, v) return fprintValues(w, wrap, v)
@ -221,7 +218,7 @@ func Fprintt(w io.Writer, v ...interface{}) (int, error) {
return fprintValues(w, types, v) 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. // 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) { func Fprintwt(w io.Writer, v ...interface{}) (int, error) {
return fprintValues(w, wrap|types, v) return fprintValues(w, wrap|types, v)
@ -233,7 +230,7 @@ func Fprintv(w io.Writer, v ...interface{}) (int, error) {
return fprintValues(w, allTypes, v) 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. // 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) { func Fprintwv(w io.Writer, v ...interface{}) (int, error) {
return fprintValues(w, wrap|allTypes, v) return fprintValues(w, wrap|allTypes, v)
@ -245,7 +242,7 @@ func Print(v ...interface{}) (int, error) {
return printValues(none, v) 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. // objects are printed, they'll be separated by a newline.
func Printw(v ...interface{}) (int, error) { func Printw(v ...interface{}) (int, error) {
return printValues(wrap, v) return printValues(wrap, v)
@ -257,7 +254,7 @@ func Printt(v ...interface{}) (int, error) {
return printValues(types, v) 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. // moderate type information. When multiple objects are printed, they'll be separated by a newline.
func Printwt(v ...interface{}) (int, error) { func Printwt(v ...interface{}) (int, error) {
return printValues(wrap|types, v) return printValues(wrap|types, v)
@ -269,7 +266,7 @@ func Printv(v ...interface{}) (int, error) {
return printValues(allTypes, v) 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. // verbose type information. When multiple objects are printed, they'll be separated by a newline.
func Printwv(v ...interface{}) (int, error) { func Printwv(v ...interface{}) (int, error) {
return printValues(wrap|allTypes, v) return printValues(wrap|allTypes, v)
@ -281,7 +278,7 @@ func Println(v ...interface{}) (int, error) {
return printlnValues(none, v) 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. // necessary. When multiple objects are printed, they'll be separated by a newline.
func Printlnw(v ...interface{}) (int, error) { func Printlnw(v ...interface{}) (int, error) {
return printlnValues(wrap, v) return printlnValues(wrap, v)
@ -293,7 +290,7 @@ func Printlnt(v ...interface{}) (int, error) {
return printlnValues(types, v) 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 // necessary, and with moderate type information. When multiple objects are printed, they'll be separated by a
// newline. // newline.
func Printlnwt(v ...interface{}) (int, error) { func Printlnwt(v ...interface{}) (int, error) {
@ -306,7 +303,7 @@ func Printlnv(v ...interface{}) (int, error) {
return printlnValues(allTypes, v) 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 // necessary, and with verbose type information. When multiple objects are printed, they'll be separated by a
// newline. // newline.
func Printlnwv(v ...interface{}) (int, error) { func Printlnwv(v ...interface{}) (int, error) {
@ -319,7 +316,7 @@ func Sprint(v ...interface{}) string {
return sprintValues(none, v) 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. // When multiple objects are provided, they'll be seprated by a newline.
func Sprintw(v ...interface{}) string { func Sprintw(v ...interface{}) string {
return sprintValues(wrap, v) return sprintValues(wrap, v)
@ -331,7 +328,7 @@ func Sprintt(v ...interface{}) string {
return sprintValues(types, v) 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. // and with moderate type information. When multiple objects are provided, they'll be seprated by a newline.
func Sprintwt(v ...interface{}) string { func Sprintwt(v ...interface{}) string {
return sprintValues(wrap|types, v) return sprintValues(wrap|types, v)
@ -343,7 +340,7 @@ func Sprintv(v ...interface{}) string {
return sprintValues(allTypes, v) 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. // and with verbose type information. When multiple objects are provided, they'll be seprated by a newline.
func Sprintwv(v ...interface{}) string { func Sprintwv(v ...interface{}) string {
return sprintValues(wrap|allTypes, v) return sprintValues(wrap|allTypes, v)

View File

@ -38,11 +38,10 @@ func reflectPrimitive(o opts, r reflect.Value, v interface{}, suppressType ...st
} }
for _, suppress := range suppressType { for _, suppress := range suppressType {
if tn.parts[0] != suppress { if tn.parts[0] == suppress {
continue return nodeOf(s)
} }
return nodeOf(s)
} }
return nodeOf(tn, "(", 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 { func reflectItems(o opts, p *pending, prefix string, r reflect.Value) node {
typ := r.Type() typ := r.Type()
var items wrapper var w wrapper
if typ.Elem().Name() == "uint8" { if typ.Elem().Kind() == reflect.Uint8 {
items = wrapper{sep: " ", mode: line} w.sep = " "
w.mode = line
for i := 0; i < r.Len(); i++ { for i := 0; i < r.Len(); i++ {
items.items = append( w.items = append(
items.items, w.items,
nodeOf(fmt.Sprintf("%02x", r.Index(i).Uint())), nodeOf(fmt.Sprintf("%02x", r.Index(i).Uint())),
) )
} }
} else { } else {
items = wrapper{sep: ", ", suffix: ","} w.sep = ", "
w.suffix = ","
itemOpts := o | skipTypes itemOpts := o | skipTypes
for i := 0; i < r.Len(); i++ { for i := 0; i < r.Len(); i++ {
items.items = append( w.items = append(
items.items, w.items,
reflectValue(itemOpts, p, r.Index(i)), reflectValue(itemOpts, p, r.Index(i)),
) )
} }
} }
if _, t, _ := withType(o); !t { if _, t, _ := withType(o); t {
return nodeOf(prefix, "{", items, "}") return nodeOf(reflectType(typ), "{", w, "}")
} }
return nodeOf(reflectType(typ), "{", items, "}") return nodeOf(prefix, "{", w, "}")
} }
func reflectHidden(o opts, hidden string, r reflect.Value) node { 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) return reflectNil(o, true, r)
} }
if _, t, _ := withType(o); !t { if _, t, _ := withType(o); t {
return nodeOf(hidden) return reflectType(r.Type())
} }
return reflectType(r.Type()) return nodeOf(hidden)
} }
func reflectArray(o opts, p *pending, r reflect.Value) node { 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 var skeys []string
items := wrapper{sep: ", ", suffix: ","}
itemOpts := o | skipTypes itemOpts := o | skipTypes
keys := r.MapKeys()
sv := make(map[string]reflect.Value) sv := make(map[string]reflect.Value)
sn := make(map[string]node) sn := make(map[string]node)
for _, key := range keys { for _, key := range r.MapKeys() {
kn := reflectValue(itemOpts, p, key)
var b bytes.Buffer var b bytes.Buffer
nk := reflectValue(itemOpts, p, key)
wr := writer{w: &b} wr := writer{w: &b}
fprint(&wr, 0, nk) fprint(&wr, 0, kn)
skey := b.String() skey := b.String()
skeys = append(skeys, skey) skeys = append(skeys, skey)
sv[skey] = key sv[skey] = key
sn[skey] = nk sn[skey] = kn
} }
if o&randomMaps == 0 { if o&randomMaps == 0 {
sort.Strings(skeys) sort.Strings(skeys)
} }
w := wrapper{sep: ", ", suffix: ","}
for _, skey := range skeys { for _, skey := range skeys {
items.items = append( vn := reflectValue(itemOpts, p, r.MapIndex(sv[skey]))
items.items, w.items = append(
nodeOf( w.items,
sn[skey], nodeOf(sn[skey], ": ", vn),
": ",
reflectValue(
itemOpts,
p,
r.MapIndex(sv[skey]),
),
),
) )
} }
if _, t, _ := withType(o); !t { 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 { 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() rt := r.Type()
for i := 0; i < r.NumField(); i++ { for i := 0; i < r.NumField(); i++ {
name := rt.Field(i).Name name := rt.Field(i).Name
fv := reflectValue(fieldOpts, p, r.FieldByName(name))
wr.items = append( wr.items = append(
wr.items, wr.items,
nodeOf( nodeOf(name, ": ", fv),
name,
": ",
reflectValue(
fieldOpts,
p,
r.FieldByName(name),
),
),
) )
} }
@ -265,12 +251,12 @@ func reflectUnsafePointer(o opts, r reflect.Value) node {
func checkPending(p *pending, r reflect.Value) (applyRef func(node) node, ref node, isPending bool) { func checkPending(p *pending, r reflect.Value) (applyRef func(node) node, ref node, isPending bool) {
applyRef = func(n node) node { return n } applyRef = func(n node) node { return n }
switch r.Kind() { switch r.Kind() {
case reflect.Slice, reflect.Map: case reflect.Slice, reflect.Map, reflect.Ptr:
case reflect.Ptr: default:
if r.IsNil() {
return return
} }
default:
if r.IsNil() {
return return
} }

View File

@ -64,13 +64,8 @@ func reflectInterfaceType(t reflect.Type) node {
wr := wrapper{sep: "; "} wr := wrapper{sep: "; "}
for i := 0; i < t.NumMethod(); i++ { for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i) method := t.Method(i)
wr.items = append( mn := nodeOf(method.Name, reflectFuncBaseType(method.Type))
wr.items, wr.items = append(wr.items, mn)
nodeOf(
method.Name,
reflectFuncBaseType(method.Type),
),
)
} }
return nodeOf("interface{", wr, "}") return nodeOf("interface{", wr, "}")
@ -92,14 +87,8 @@ func reflectStructType(t reflect.Type) node {
wr := wrapper{sep: "; "} wr := wrapper{sep: "; "}
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
fi := t.Field(i) fi := t.Field(i)
wr.items = append( fn := nodeOf(fi.Name, " ", reflectType(fi.Type))
wr.items, wr.items = append(wr.items, fn)
nodeOf(
fi.Name,
" ",
reflectType(fi.Type),
),
)
} }
return nodeOf("struct{", wr, "}") return nodeOf("struct{", wr, "}")

View File

@ -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) { t.Run("no line breaks", func(t *testing.T) {
const expect = `"foobarbazquxquuxquzquuz"` const expect = `"foobarbazquxquuxquzquuz"`
defer withEnv(t, "TABWIDTH=2", "LINEWIDTH=9", "LINEWIDTH1=12")() 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) { 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 } type typ struct{ f0, f1, f2 *typ }
v0 := new(typ) v0 := new(typ)
v1 := new(typ) v1 := new(typ)