full diff: https://github.com/gotestyourself/gotest.tools/compare/v2.3.0...v3.0.2 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			162 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package format
 | 
						||
 | 
						||
import (
 | 
						||
	"bytes"
 | 
						||
	"fmt"
 | 
						||
	"strings"
 | 
						||
	"unicode"
 | 
						||
 | 
						||
	"gotest.tools/v3/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 '<27>'
 | 
						||
			}
 | 
						||
		}
 | 
						||
		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")
 | 
						||
	}
 | 
						||
}
 |