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
[![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)
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
}
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)
func stringNodeLen(n node) node {
s := strLen(n.parts[0].(str))
n.parts[0] = s
n.len = len(s.val)
if s.raw == "" {
@ -65,7 +61,7 @@ func nodeLen(t int, n node) node {
return n
}
// measure all parts:
func measureParts(t int, n node) node {
for i := range n.parts {
switch pt := n.parts[i].(type) {
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 {
switch pt := p.(type) {
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
for _, p := range n.parts {
switch pt := p.(type) {
@ -186,6 +188,20 @@ func nodeLen(t int, n node) node {
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 {
// fits:
if n.len <= c0 {
@ -319,6 +335,59 @@ func wrapNode(t, cf0, c0, c1 int, n node) node {
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) {
// handle write errors at a single place:
if w.err != nil {
@ -330,56 +399,7 @@ func fprint(w *writer, t int, n node) {
case node:
fprint(w, t, part)
case wrapper:
if len(part.items) == 0 {
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)
fprintWrapper(w, t, n.wrap, part)
default:
w.write(part)
}

View File

@ -3,6 +3,7 @@ package notation
import (
"bytes"
"errors"
"io"
"testing"
)
@ -78,129 +79,61 @@ func TestFailingWriter(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")()
n, err := Fprint(&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("Fprintw", func(t *testing.T) {
const expect = `{
o := struct{ fooBarBaz int }{42}
for _, test := range []struct {
name string
fn func(io.Writer, ...interface{}) (int, error)
expect string
}{{
name: "Fprint",
fn: Fprint,
expect: `{fooBarBaz: 42}`,
}, {
name: "Fprintw",
fn: Fprintw,
expect: `{
fooBarBaz: 42,
}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintw(&b, o)
if err != nil {
t.Fatal(err)
}
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{
}`,
}, {
name: "Fprintt",
fn: Fprintt,
expect: `struct{fooBarBaz int}{fooBarBaz: 42}`,
}, {
name: "Fprintwt",
fn: Fprintwt,
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 := Fprintwt(&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(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{
}`,
}, {
name: "Fprintv",
fn: Fprintv,
expect: `struct{fooBarBaz int}{fooBarBaz: int(42)}`,
}, {
name: "Fprintwv",
fn: Fprintwv,
expect: `struct{
fooBarBaz int
}{
fooBarBaz: int(42),
}`
}`,
}} {
t.Run(test.name, func(t *testing.T) {
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintwv(&b, o)
n, err := test.fn(&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 n != len(test.expect) {
t.Fatalf("invalid write length; expected %d, got: %d", len(test.expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
if b.String() != test.expect {
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)
}
// 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.
func Sprintw(v ...interface{}) string {
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.
func Sprintt(v ...interface{}) string {
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.
func Sprintwt(v ...interface{}) string {
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.
func Sprintv(v ...interface{}) string {
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.
func Sprintwv(v ...interface{}) string {
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) {
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("LINEWIDTH1", 80*3/2-8)
for _, ti := range tests {
for _, ti := range ts {
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
for _, test := range t {
for _, test := range ts {
if expect, doSet := expect[test.title]; doSet {
test.expect = expect
}
@ -136,8 +136,8 @@ func defaultSet() tests {
}
}
func (t tests) expectTypes() tests {
return t.expect(map[string]string{
func (ts tests) expectTypes() tests {
return ts.expect(map[string]string{
"custom false": "myBool(false)",
"custom true": "myBool(true)",
"custom int": "myInt(42)",
@ -185,8 +185,8 @@ func (t tests) expectTypes() tests {
})
}
func (t tests) expectVerboseTypes() tests {
return t.expectTypes().expect(map[string]string{
func (ts tests) expectVerboseTypes() tests {
return ts.expectTypes().expect(map[string]string{
"false": "bool(false)",
"true": "bool(true)",
"int": "int(42)",
@ -223,8 +223,8 @@ func (t tests) expectVerboseTypes() tests {
})
}
func (t tests) expectWrapAll() tests {
return t.expect(map[string]string{
func (ts tests) expectWrapAll() tests {
return ts.expect(map[string]string{
"array": `[3]{
1,
2,
@ -308,8 +308,8 @@ func (t tests) expectWrapAll() tests {
})
}
func (t tests) expectOnlyLongWrapped() tests {
return t.expect(map[string]string{
func (ts tests) expectOnlyLongWrapped() tests {
return ts.expect(map[string]string{
"long item": `[]{
"foobarbazqux",
}`,
@ -319,8 +319,8 @@ func (t tests) expectOnlyLongWrapped() tests {
})
}
func (t tests) expectWrapAllWithTypes() tests {
return t.expectTypes().expect(map[string]string{
func (ts tests) expectWrapAllWithTypes() tests {
return ts.expectTypes().expect(map[string]string{
"array": `[3]int{
1,
2,
@ -450,8 +450,8 @@ func (t tests) expectWrapAllWithTypes() tests {
})
}
func (t tests) expectOnlyLongWrappedWithTypes() tests {
return t.expectTypes().expect(map[string]string{
func (ts tests) expectOnlyLongWrappedWithTypes() tests {
return ts.expectTypes().expect(map[string]string{
"nil channel": `struct{c chan int}{
c: nil,
}`,
@ -517,8 +517,8 @@ func (t tests) expectOnlyLongWrappedWithTypes() tests {
})
}
func (t tests) expectWrapAllWithVerboseTypes() tests {
return t.expectVerboseTypes().expect(map[string]string{
func (ts tests) expectWrapAllWithVerboseTypes() tests {
return ts.expectVerboseTypes().expect(map[string]string{
"array": `[3]int{
int(1),
int(2),
@ -659,8 +659,8 @@ func (t tests) expectWrapAllWithVerboseTypes() tests {
})
}
func (t tests) expectOnlyLongWrappedWithVerboseTypes() tests {
return t.expectVerboseTypes().expect(map[string]string{
func (ts tests) expectOnlyLongWrappedWithVerboseTypes() tests {
return ts.expectVerboseTypes().expect(map[string]string{
"nil channel": `struct{c chan int}{
c: (chan int)(nil),
}`,