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.

149
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
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,7 +306,7 @@ 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
quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
@ -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}
```

View File

@ -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: {},
// }

345
fprint.go
View File

@ -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 ifZero(a, b int) int {
if a == 0 {
return b
}
func initialize(t *int, v int) {
if *t > 0 {
return
return a
}
*t = v
func max(a, b int) int {
if a > b {
return a
}
func max(t *int, v int) {
if *t >= v {
return
return b
}
*t = v
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 {
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.
// 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)
}
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),
}
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
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:
part = nodeLen(t, part)
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
n.parts[i] = nodeLen(t, pt)
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
}
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)
n.len += (len(pt.items) - 1) * len(pt.sep)
for _, pti := range pt.items {
n.len += pti.len
}
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)
n.len += len(fmt.Sprint(p))
}
}
max(&n.wrapLen.max, w)
max(&n.fullWrap.max, f)
w, f = 0, 0
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))
}
}
initialize(&n.wrapLen.first, w)
max(&n.wrapLen.max, w)
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
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)

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.
*/
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)

View File

@ -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,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) {
applyRef = func(n node) node { return n }
switch r.Kind() {
case reflect.Slice, reflect.Map:
case reflect.Ptr:
if r.IsNil() {
case reflect.Slice, reflect.Map, reflect.Ptr:
default:
return
}
default:
if r.IsNil() {
return
}

View File

@ -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, "}")

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) {
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)