This commit is contained in:
Arpad Ryszka 2020-11-26 18:05:14 +01:00
parent 364c01ab6f
commit 64ec6c34e4
5 changed files with 168 additions and 214 deletions

View File

@ -1,5 +1,6 @@
# Notation - print Go objects # Notation - print Go objects
[![Go Report Card](https://goreportcard.com/badge/github.com/aryszka/notation)](https://goreportcard.com/report/github.com/aryszka/notation)
[![codecov](https://codecov.io/gh/aryszka/notation/branch/master/graph/badge.svg?token=7M18MEAVQW)](https://codecov.io/gh/aryszka/notation) [![codecov](https://codecov.io/gh/aryszka/notation/branch/master/graph/badge.svg?token=7M18MEAVQW)](https://codecov.io/gh/aryszka/notation)
This package can be used to print (or sprint) Go objects for debugging purposes, with optional wrapping This package can be used to print (or sprint) Go objects for debugging purposes, with optional wrapping

138
fprint.go
View File

@ -40,12 +40,8 @@ func strLen(s str) str {
return s return s
} }
func nodeLen(t int, n node) node { func stringNodeLen(n node) node {
// We assume here that an str is always contained s := strLen(n.parts[0].(str))
// 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.parts[0] = s
n.len = len(s.val) n.len = len(s.val)
if s.raw == "" { if s.raw == "" {
@ -65,7 +61,7 @@ func nodeLen(t int, n node) node {
return n return n
} }
// measure all parts: func measureParts(t int, n node) node {
for i := range n.parts { for i := range n.parts {
switch pt := n.parts[i].(type) { switch pt := n.parts[i].(type) {
case node: case node:
@ -77,7 +73,10 @@ func nodeLen(t int, n node) node {
} }
} }
// measure the unwrapped length: return n
}
func measureUnwrapped(n node) node {
for _, p := range n.parts { for _, p := range n.parts {
switch pt := p.(type) { switch pt := p.(type) {
case node: case node:
@ -96,7 +95,10 @@ func nodeLen(t int, n node) node {
} }
} }
// measure the wrapped and the fully wrapped length: return n
}
func measureWrapped(t int, n node) node {
var w, f int var w, f int
for _, p := range n.parts { for _, p := range n.parts {
switch pt := p.(type) { switch pt := p.(type) {
@ -186,6 +188,20 @@ func nodeLen(t int, n node) node {
return n return n
} }
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 _, ok := n.parts[0].(str); ok {
return stringNodeLen(n)
}
n = measureParts(t, n)
n = measureUnwrapped(n)
n = measureWrapped(t, n)
return n
}
func wrapNode(t, cf0, c0, c1 int, n node) node { func wrapNode(t, cf0, c0, c1 int, n node) node {
// fits: // fits:
if n.len <= c0 { if n.len <= c0 {
@ -319,6 +335,59 @@ func wrapNode(t, cf0, c0, c1 int, n node) node {
return n return n
} }
func fprintWrapper(w *writer, t int, wrap bool, wr wrapper) {
if len(wr.items) == 0 {
return
}
if !wrap {
for i, ni := range wr.items {
if i > 0 {
w.write(wr.sep)
}
fprint(w, t, ni)
}
return
}
switch wr.mode {
case line:
var (
lines [][]node
last int
)
for _, i := range wr.lineEnds {
lines = append(lines, wr.items[last:i])
last = i
}
for _, line := range lines {
w.line(1)
for i, ni := range line {
if i > 0 {
w.write(wr.sep)
}
fprint(w, 0, ni)
}
}
default:
t++
for _, ni := range wr.items {
w.line(t)
fprint(w, t, ni)
w.write(wr.suffix)
}
t--
}
w.line(t)
}
func fprint(w *writer, t int, n node) { func fprint(w *writer, t int, n node) {
// handle write errors at a single place: // handle write errors at a single place:
if w.err != nil { if w.err != nil {
@ -330,56 +399,7 @@ func fprint(w *writer, t int, n node) {
case node: case node:
fprint(w, t, part) fprint(w, t, part)
case wrapper: case wrapper:
if len(part.items) == 0 { fprintWrapper(w, t, n.wrap, part)
continue
}
if !n.wrap {
for i, ni := range part.items {
if i > 0 {
w.write(part.sep)
}
fprint(w, t, ni)
}
continue
}
switch part.mode {
case line:
var (
lines [][]node
last int
)
for _, i := range part.lineEnds {
lines = append(lines, part.items[last:i])
last = i
}
for _, line := range lines {
w.line(1)
for i, ni := range line {
if i > 0 {
w.write(part.sep)
}
fprint(w, 0, ni)
}
}
default:
t++
for _, ni := range part.items {
w.line(t)
fprint(w, t, ni)
w.write(part.suffix)
}
t--
}
w.line(t)
default: default:
w.write(part) w.write(part)
} }

View File

@ -3,6 +3,7 @@ package notation
import ( import (
"bytes" "bytes"
"errors" "errors"
"io"
"testing" "testing"
) )
@ -78,129 +79,61 @@ func TestFailingWriter(t *testing.T) {
} }
func TestFprint(t *testing.T) { func TestFprint(t *testing.T) {
t.Run("Fprint", func(t *testing.T) {
const expect = `{fooBarBaz: 42}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")() defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprint(&b, o) o := struct{ fooBarBaz int }{42}
if err != nil { for _, test := range []struct {
t.Fatal(err) name string
} fn func(io.Writer, ...interface{}) (int, error)
expect string
if n != len(expect) { }{{
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n) name: "Fprint",
} fn: Fprint,
expect: `{fooBarBaz: 42}`,
if b.String() != expect { }, {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String()) name: "Fprintw",
} fn: Fprintw,
}) expect: `{
t.Run("Fprintw", func(t *testing.T) {
const expect = `{
fooBarBaz: 42, fooBarBaz: 42,
}` }`,
}, {
var b bytes.Buffer name: "Fprintt",
o := struct{ fooBarBaz int }{42} fn: Fprintt,
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")() expect: `struct{fooBarBaz int}{fooBarBaz: 42}`,
n, err := Fprintw(&b, o) }, {
if err != nil { name: "Fprintwt",
t.Fatal(err) fn: Fprintwt,
} expect: `struct{
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
})
t.Run("Fprintt", func(t *testing.T) {
const expect = `struct{fooBarBaz int}{fooBarBaz: 42}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintt(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintwt", func(t *testing.T) {
const expect = `struct{
fooBarBaz int fooBarBaz int
}{ }{
fooBarBaz: 42, fooBarBaz: 42,
}` }`,
}, {
var b bytes.Buffer name: "Fprintv",
o := struct{ fooBarBaz int }{42} fn: Fprintv,
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")() expect: `struct{fooBarBaz int}{fooBarBaz: int(42)}`,
n, err := Fprintwt(&b, o) }, {
if err != nil { name: "Fprintwv",
t.Fatal(err) fn: Fprintwv,
} expect: `struct{
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintv", func(t *testing.T) {
const expect = `struct{fooBarBaz int}{fooBarBaz: int(42)}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintv(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintv", func(t *testing.T) {
const expect = `struct{
fooBarBaz int fooBarBaz int
}{ }{
fooBarBaz: int(42), fooBarBaz: int(42),
}` }`,
}} {
t.Run(test.name, func(t *testing.T) {
var b bytes.Buffer var b bytes.Buffer
o := struct{ fooBarBaz int }{42} n, err := test.fn(&b, o)
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintwv(&b, o)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n != len(expect) { if n != len(test.expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n) t.Fatalf("invalid write length; expected %d, got: %d", len(test.expect), n)
} }
if b.String() != expect { if b.String() != test.expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String()) t.Fatalf("invalid output; expected: %s, got: %s", test.expect, b.String())
} }
}) })
} }
}

View File

@ -316,31 +316,31 @@ func Sprint(v ...interface{}) string {
return sprintValues(none, v) return sprintValues(none, v)
} }
// Sprint returns the string representation of the Go objects, with wrapping (and indentation) where necessary. // Sprintw 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)
} }
// Sprint returns the string representation of the Go objects, with moderate type information. When multiple // Sprintt returns the string representation of the Go objects, with moderate type information. When multiple
// objects are provided, they'll be seprated by a space. // objects are provided, they'll be seprated by a space.
func Sprintt(v ...interface{}) string { func Sprintt(v ...interface{}) string {
return sprintValues(types, v) return sprintValues(types, v)
} }
// Sprint returns the string representation of the Go objects, with wrapping (and indentation) where necessary, // Sprintwt 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)
} }
// Sprint returns the string representation of the Go objects, with verbose type information. When multiple // Sprintv returns the string representation of the Go objects, with verbose type information. When multiple
// objects are provided, they'll be seprated by a space. // objects are provided, they'll be seprated by a space.
func Sprintv(v ...interface{}) string { func Sprintv(v ...interface{}) string {
return sprintValues(allTypes, v) return sprintValues(allTypes, v)
} }
// Sprint returns the string representation of the Go objects, with wrapping (and indentation) where necessary, // Sprintwv 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

@ -40,7 +40,7 @@ func (test test) run(t *testing.T, sprint func(...interface{}) string) {
}) })
} }
func (tests tests) run(t *testing.T, sprint func(...interface{}) string) { func (ts tests) run(t *testing.T, sprint func(...interface{}) string) {
logEnv := func(name string, dflt int) { logEnv := func(name string, dflt int) {
t.Logf("%s=%d", name, config(name, dflt)) t.Logf("%s=%d", name, config(name, dflt))
} }
@ -49,14 +49,14 @@ func (tests tests) run(t *testing.T, sprint func(...interface{}) string) {
logEnv("LINEWIDTH", 80-8) logEnv("LINEWIDTH", 80-8)
logEnv("LINEWIDTH1", 80*3/2-8) logEnv("LINEWIDTH1", 80*3/2-8)
for _, ti := range tests { for _, ti := range ts {
ti.run(t, sprint) ti.run(t, sprint)
} }
} }
func (t tests) expect(expect map[string]string) tests { func (ts tests) expect(expect map[string]string) tests {
var set tests var set tests
for _, test := range t { for _, test := range ts {
if expect, doSet := expect[test.title]; doSet { if expect, doSet := expect[test.title]; doSet {
test.expect = expect test.expect = expect
} }
@ -136,8 +136,8 @@ func defaultSet() tests {
} }
} }
func (t tests) expectTypes() tests { func (ts tests) expectTypes() tests {
return t.expect(map[string]string{ return ts.expect(map[string]string{
"custom false": "myBool(false)", "custom false": "myBool(false)",
"custom true": "myBool(true)", "custom true": "myBool(true)",
"custom int": "myInt(42)", "custom int": "myInt(42)",
@ -185,8 +185,8 @@ func (t tests) expectTypes() tests {
}) })
} }
func (t tests) expectVerboseTypes() tests { func (ts tests) expectVerboseTypes() tests {
return t.expectTypes().expect(map[string]string{ return ts.expectTypes().expect(map[string]string{
"false": "bool(false)", "false": "bool(false)",
"true": "bool(true)", "true": "bool(true)",
"int": "int(42)", "int": "int(42)",
@ -223,8 +223,8 @@ func (t tests) expectVerboseTypes() tests {
}) })
} }
func (t tests) expectWrapAll() tests { func (ts tests) expectWrapAll() tests {
return t.expect(map[string]string{ return ts.expect(map[string]string{
"array": `[3]{ "array": `[3]{
1, 1,
2, 2,
@ -308,8 +308,8 @@ func (t tests) expectWrapAll() tests {
}) })
} }
func (t tests) expectOnlyLongWrapped() tests { func (ts tests) expectOnlyLongWrapped() tests {
return t.expect(map[string]string{ return ts.expect(map[string]string{
"long item": `[]{ "long item": `[]{
"foobarbazqux", "foobarbazqux",
}`, }`,
@ -319,8 +319,8 @@ func (t tests) expectOnlyLongWrapped() tests {
}) })
} }
func (t tests) expectWrapAllWithTypes() tests { func (ts tests) expectWrapAllWithTypes() tests {
return t.expectTypes().expect(map[string]string{ return ts.expectTypes().expect(map[string]string{
"array": `[3]int{ "array": `[3]int{
1, 1,
2, 2,
@ -450,8 +450,8 @@ func (t tests) expectWrapAllWithTypes() tests {
}) })
} }
func (t tests) expectOnlyLongWrappedWithTypes() tests { func (ts tests) expectOnlyLongWrappedWithTypes() tests {
return t.expectTypes().expect(map[string]string{ return ts.expectTypes().expect(map[string]string{
"nil channel": `struct{c chan int}{ "nil channel": `struct{c chan int}{
c: nil, c: nil,
}`, }`,
@ -517,8 +517,8 @@ func (t tests) expectOnlyLongWrappedWithTypes() tests {
}) })
} }
func (t tests) expectWrapAllWithVerboseTypes() tests { func (ts tests) expectWrapAllWithVerboseTypes() tests {
return t.expectVerboseTypes().expect(map[string]string{ return ts.expectVerboseTypes().expect(map[string]string{
"array": `[3]int{ "array": `[3]int{
int(1), int(1),
int(2), int(2),
@ -659,8 +659,8 @@ func (t tests) expectWrapAllWithVerboseTypes() tests {
}) })
} }
func (t tests) expectOnlyLongWrappedWithVerboseTypes() tests { func (ts tests) expectOnlyLongWrappedWithVerboseTypes() tests {
return t.expectVerboseTypes().expect(map[string]string{ return ts.expectVerboseTypes().expect(map[string]string{
"nil channel": `struct{c chan int}{ "nil channel": `struct{c chan int}{
c: (chan int)(nil), c: (chan int)(nil),
}`, }`,