1package testhelper
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"math"
8	"strings"
9
10	"github.com/aryann/difflib"
11)
12
13func Diff(want, got string, context int) (string, bool) {
14	records := difflib.Diff(
15		strings.Split(want, "\n"),
16		strings.Split(got, "\n"),
17	)
18
19	w := &bytes.Buffer{}
20
21	changed := checkAndPrintRecords(w, records, context)
22
23	return w.String(), changed
24}
25
26func checkAndPrintRecords(w io.Writer, records []difflib.DiffRecord, context int) bool {
27	var changed bool
28	if context >= 0 {
29		distances := calculateDistances(records)
30		omitting := false
31		for i, diff := range records {
32			if diff.Delta != difflib.Common {
33				changed = true
34			}
35			if distances[i] > context {
36				if !omitting {
37					fmt.Fprintln(w, "...")
38					omitting = true
39				}
40			} else {
41				omitting = false
42				fmt.Fprintln(w, formatRecord(diff))
43			}
44		}
45	} else {
46		for _, diff := range records {
47			if diff.Delta != difflib.Common {
48				changed = true
49			}
50			fmt.Fprintln(w, formatRecord(diff))
51		}
52	}
53	return changed
54}
55
56func formatRecord(diff difflib.DiffRecord) string {
57	var prefix string
58	switch diff.Delta {
59	case difflib.RightOnly:
60		prefix = "+ "
61	case difflib.LeftOnly:
62		prefix = "- "
63	case difflib.Common:
64		prefix = "  "
65	}
66
67	return prefix + diff.Payload
68}
69
70// Shamelessly and thankfully copied from https://github.com/databus23/helm-diff/blob/99b8474af7726ca6f57b37b0b8b8f3cd36c991e8/diff/diff.go#L116
71func calculateDistances(diffs []difflib.DiffRecord) map[int]int {
72	distances := map[int]int{}
73
74	// Iterate forwards through diffs, set 'distance' based on closest 'change' before this line
75	change := -1
76	for i, diff := range diffs {
77		if diff.Delta != difflib.Common {
78			change = i
79		}
80		distance := math.MaxInt32
81		if change != -1 {
82			distance = i - change
83		}
84		distances[i] = distance
85	}
86
87	// Iterate backwards through diffs, reduce 'distance' based on closest 'change' after this line
88	change = -1
89	for i := len(diffs) - 1; i >= 0; i-- {
90		diff := diffs[i]
91		if diff.Delta != difflib.Common {
92			change = i
93		}
94		if change != -1 {
95			distance := change - i
96			if distance < distances[i] {
97				distances[i] = distance
98			}
99		}
100	}
101
102	return distances
103}
104