aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/gotest.tools/internal/format/diff.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gotest.tools/internal/format/diff.go')
-rw-r--r--vendor/gotest.tools/internal/format/diff.go161
1 files changed, 161 insertions, 0 deletions
diff --git a/vendor/gotest.tools/internal/format/diff.go b/vendor/gotest.tools/internal/format/diff.go
new file mode 100644
index 00000000..c938c97b
--- /dev/null
+++ b/vendor/gotest.tools/internal/format/diff.go
@@ -0,0 +1,161 @@
+package format
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "unicode"
+
+ "gotest.tools/internal/difflib"
+)
+
+const (
+ contextLines = 2
+)
+
+// DiffConfig for a unified diff
+type DiffConfig struct {
+ A string
+ B string
+ From string
+ To string
+}
+
+// UnifiedDiff is a modified version of difflib.WriteUnifiedDiff with better
+// support for showing the whitespace differences.
+func UnifiedDiff(conf DiffConfig) string {
+ a := strings.SplitAfter(conf.A, "\n")
+ b := strings.SplitAfter(conf.B, "\n")
+ groups := difflib.NewMatcher(a, b).GetGroupedOpCodes(contextLines)
+ if len(groups) == 0 {
+ return ""
+ }
+
+ buf := new(bytes.Buffer)
+ writeFormat := func(format string, args ...interface{}) {
+ buf.WriteString(fmt.Sprintf(format, args...))
+ }
+ writeLine := func(prefix string, s string) {
+ buf.WriteString(prefix + s)
+ }
+ if hasWhitespaceDiffLines(groups, a, b) {
+ writeLine = visibleWhitespaceLine(writeLine)
+ }
+ formatHeader(writeFormat, conf)
+ for _, group := range groups {
+ formatRangeLine(writeFormat, group)
+ for _, opCode := range group {
+ in, out := a[opCode.I1:opCode.I2], b[opCode.J1:opCode.J2]
+ switch opCode.Tag {
+ case 'e':
+ formatLines(writeLine, " ", in)
+ case 'r':
+ formatLines(writeLine, "-", in)
+ formatLines(writeLine, "+", out)
+ case 'd':
+ formatLines(writeLine, "-", in)
+ case 'i':
+ formatLines(writeLine, "+", out)
+ }
+ }
+ }
+ return buf.String()
+}
+
+// hasWhitespaceDiffLines returns true if any diff groups is only different
+// because of whitespace characters.
+func hasWhitespaceDiffLines(groups [][]difflib.OpCode, a, b []string) bool {
+ for _, group := range groups {
+ in, out := new(bytes.Buffer), new(bytes.Buffer)
+ for _, opCode := range group {
+ if opCode.Tag == 'e' {
+ continue
+ }
+ for _, line := range a[opCode.I1:opCode.I2] {
+ in.WriteString(line)
+ }
+ for _, line := range b[opCode.J1:opCode.J2] {
+ out.WriteString(line)
+ }
+ }
+ if removeWhitespace(in.String()) == removeWhitespace(out.String()) {
+ return true
+ }
+ }
+ return false
+}
+
+func removeWhitespace(s string) string {
+ var result []rune
+ for _, r := range s {
+ if !unicode.IsSpace(r) {
+ result = append(result, r)
+ }
+ }
+ return string(result)
+}
+
+func visibleWhitespaceLine(ws func(string, string)) func(string, string) {
+ mapToVisibleSpace := func(r rune) rune {
+ switch r {
+ case '\n':
+ case ' ':
+ return '·'
+ case '\t':
+ return '▷'
+ case '\v':
+ return '▽'
+ case '\r':
+ return '↵'
+ case '\f':
+ return '↓'
+ default:
+ if unicode.IsSpace(r) {
+ return '�'
+ }
+ }
+ return r
+ }
+ return func(prefix, s string) {
+ ws(prefix, strings.Map(mapToVisibleSpace, s))
+ }
+}
+
+func formatHeader(wf func(string, ...interface{}), conf DiffConfig) {
+ if conf.From != "" || conf.To != "" {
+ wf("--- %s\n", conf.From)
+ wf("+++ %s\n", conf.To)
+ }
+}
+
+func formatRangeLine(wf func(string, ...interface{}), group []difflib.OpCode) {
+ first, last := group[0], group[len(group)-1]
+ range1 := formatRangeUnified(first.I1, last.I2)
+ range2 := formatRangeUnified(first.J1, last.J2)
+ wf("@@ -%s +%s @@\n", range1, range2)
+}
+
+// Convert range to the "ed" format
+func formatRangeUnified(start, stop int) string {
+ // Per the diff spec at http://www.unix.org/single_unix_specification/
+ beginning := start + 1 // lines start numbering with one
+ length := stop - start
+ if length == 1 {
+ return fmt.Sprintf("%d", beginning)
+ }
+ if length == 0 {
+ beginning-- // empty ranges begin at line just before the range
+ }
+ return fmt.Sprintf("%d,%d", beginning, length)
+}
+
+func formatLines(writeLine func(string, string), prefix string, lines []string) {
+ for _, line := range lines {
+ writeLine(prefix, line)
+ }
+ // Add a newline if the last line is missing one so that the diff displays
+ // properly.
+ if !strings.HasSuffix(lines[len(lines)-1], "\n") {
+ writeLine("", "\n")
+ }
+}