-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
705 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
}) | ||
} | ||
} |
Oops, something went wrong.