Skip to content

Commit

Permalink
add support for pretty printing
Browse files Browse the repository at this point in the history
  • Loading branch information
adamluzsi committed Jul 25, 2022
1 parent b162518 commit af51326
Show file tree
Hide file tree
Showing 5 changed files with 705 additions and 0 deletions.
164 changes: 164 additions & 0 deletions pp/Diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package pp

import (
"bufio"
"bytes"
"fmt"
"strings"
"text/tabwriter"
)

// Diff format the values in pp.Format and compare the results line by line in a side-by-side style.
func Diff(v1, v2 any) string {
return DiffString(Format(v1), Format(v2))
}

// DiffString compare strings line by line in a side-by-side style.
// The diff style is similar to GNU "diff -y".
func DiffString(val, oth string) string {
var (
rows []diffTableRow
valPos int
othPos int
valLines = toLines(val)
othLines = toLines(oth)
)
wrk:
for {
var (
hasVal bool
hasOth bool
valLine string
othLine string
)
if valPos < len(valLines) {
hasVal = true
valLine = valLines[valPos]
}
if othPos < len(othLines) {
hasOth = true
othLine = othLines[othPos]
}
if !hasVal && !hasOth {
break wrk
}
// only "val" has more lines, "oth" is finished
if hasVal && !hasOth {
rows = append(rows, diffTableRow{
Left: valLine,
Right: "",
Separator: "<",
})
valPos++
continue wrk
}
// only "oth" has more lines, "val" is finished
if !hasVal && hasOth {
rows = append(rows, diffTableRow{
Left: "",
Right: othLine,
Separator: ">",
})
othPos++
continue wrk
}

if valLine == othLine {
rows = append(rows, diffTableRow{
Left: valLine,
Right: othLine,
Separator: "",
})
valPos++
othPos++
continue wrk
}
///////////////////////////////////
// not equals, both line present //
///////////////////////////////////

// othLine is part of "val",
// flush out "val" lines until we reach the current "oth" line there
if contains(valLines[valPos:], othLine) {
rows = append(rows, diffTableRow{
Left: valLine,
Right: "",
Separator: "<",
})
valPos++
continue wrk
}

// "val"'s line part of other eventually
// flush out "oth" lines until we reach the current "val" line
if contains(othLines[othPos:], valLine) {
rows = append(rows, diffTableRow{
Left: "",
Right: othLine,
Separator: ">",
})
othPos++
continue wrk
}

rows = append(rows, diffTableRow{
Left: valLine,
Right: othLine,
Separator: "|",
})
valPos++
othPos++
continue wrk
}
return toTable(rows)
}

type diffTableRow struct {
Left string
Right string
Separator string
}

func contains(lines []string, str string) bool {
for _, line := range lines {
if line == str {
return true
}
}
return false
}

func toTable(rows []diffTableRow) string {
var mLen int
for _, row := range rows {
if nLen := len(row.Left); mLen < nLen {
mLen = nLen
}
}
escape := func(str string) string {
return strings.ReplaceAll(str, "\t", " ")
}
padded := func(str string) string {
//paddingLen := (mLen) / 2 / 2
padding := strings.Repeat(" ", 2)
return fmt.Sprintf("%s%s%s", padding, str, padding)
}
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, 0, 0, 0, ' ', 0)
for _, row := range rows {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", escape(row.Left), padded(row.Separator), escape(row.Right))
}
_ = w.Flush()
return buf.String()
}

func toLines(str string) []string {
scanner := bufio.NewScanner(strings.NewReader(str))
scanner.Split(bufio.ScanLines)
var lines []string
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
_ = scanner.Err()
return lines
}
133 changes: 133 additions & 0 deletions pp/Diff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package pp_test

import (
"bufio"
"strings"
"testing"

"github.com/adamluzsi/testcase/assert"
"github.com/adamluzsi/testcase/pp"
)

const DiffOutput = `
pp_test.X{ pp_test.X{
A: 1, | A: 2,
B: 2, B: 2,
} }
`

func TestDiff_smoke(t *testing.T) {
type X struct{ A, B int }
v1 := X{A: 1, B: 2}
v2 := X{A: 2, B: 2}
tr := strings.TrimSpace
assert.Equal(t, tr(DiffOutput), tr(pp.Diff(v1, v2)))
}

const DiffStringA = `
aaa
bbb
ccc
ddd
eee
fff
ggg
`

const DiffStringB = `
aaa
bbbdiff
ccc
eee
123
fff
`

const DiffStringOut = `
aaa aaa
bbb | bbbdiff
ccc ccc
ddd <
eee eee
> 123
fff fff
ggg <
`

func TestPrettyPrinter_DiffString_smoke(t *testing.T) {
t.Run("E2E", func(t *testing.T) {

tr := strings.TrimSpace
got := pp.DiffString(tr(DiffStringA), tr(DiffStringB))
t.Logf("\n%s", got)
exp := tr(DiffStringOut)
act := tr(got)
t.Logf("\n\nexpected:\n%s\n\nactual:\n%s", exp, act)
assert.Equal(t, exp, act)
})
tr := func(str string) string {
var strs []string
s := bufio.NewScanner(strings.NewReader(str))
s.Split(bufio.ScanLines)
for s.Scan() {
strs = append(strs, strings.TrimSpace(s.Text()))
}
return strings.Join(strs, "\n")
}
type TestCase struct {
Desc string
A string
B string
Diff string
}
for _, tc := range []TestCase{
{
Desc: "when only A has value",
A: "aaa",
B: "",
Diff: "aaa <",
},
{
Desc: "when only B has value",
A: "",
B: "bbb",
Diff: "> bbb",
},
{
Desc: "when A and B not equals",
A: "aaa",
B: "bbb",
Diff: "aaa | bbb",
},
{
Desc: "when A has values as B plus more in the middle",
A: "aaa\n123\nbbb",
B: "aaa\nbbb\n",
Diff: "aaa aaa\n123 <\nbbb bbb",
},
{
Desc: "when B has values as A plus more in the middle",
A: "aaa\nbbb",
B: "aaa\n123\nbbb\n",
Diff: "aaa aaa\n> 123\nbbb bbb",
},
{
Desc: "when A has values as B plus more afterwards",
A: "aaa\nbbb\n123",
B: "aaa\nbbb\n",
Diff: "aaa aaa\nbbb bbb\n123 <",
},
{
Desc: "when B has values as A plus more afterwards",
A: "aaa\nbbb\n",
B: "aaa\nbbb\n123",
Diff: "aaa aaa\nbbb bbb\n> 123",
},
} {
tc := tc
t.Run(tc.Desc, func(t *testing.T) {
diff := pp.DiffString(tr(tc.A), tr(tc.B))
assert.Equal(t, tr(tc.Diff), tr(diff))
})
}
}
Loading

0 comments on commit af51326

Please sign in to comment.