123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- 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")
- }
- }
|