-
Notifications
You must be signed in to change notification settings - Fork 8
/
diff.go
138 lines (127 loc) · 4.52 KB
/
diff.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Package diff contains high level routines that generate a textual diff.
//
// It is implemented in terms of the other packages in this module.
// If you want fine-grained control,
// want to inspect a diff programmatically,
// want to provide a context for cancellation,
// need to diff gigantic files that don't fit in memory,
// or want to diff unusual things,
// use the lower level packages.
package diff
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"reflect"
"strings"
"github.com/pkg/diff/ctxt"
"github.com/pkg/diff/intern"
"github.com/pkg/diff/myers"
"github.com/pkg/diff/write"
)
// lines returns the lines contained in text/filename.
// text and filename are interpreted as described in the docs for Text.
func lines(m intern.Strings, filename string, text interface{}) ([]*string, error) {
var r io.Reader
switch text := text.(type) {
case nil:
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
r = f
case string:
r = strings.NewReader(text)
case []byte:
r = bytes.NewReader(text)
case io.Reader:
r = text
default:
return nil, fmt.Errorf("unexpected type %T, want string, []byte, io.Reader, or nil", text)
}
var x []*string
scan := bufio.NewScanner(r)
for scan.Scan() {
x = append(x, m.FromBytes(scan.Bytes()))
}
return x, scan.Err()
}
// addNames adds a Names write.Option using aName and bName,
// taking care to put it at the end,
// so as not to overwrite any competing option.
func addNames(aName, bName string, options []write.Option) []write.Option {
opts := make([]write.Option, len(options)+1)
opts[0] = write.Names(aName, bName)
copy(opts[1:], options)
return opts
}
// Text diffs a and b and writes the result to w.
// It treats a and b as text, and splits their contents
// into lines using bufio.ScanLines.
// aFile and bFile are filenames to use in the output.
//
// a and b each may be nil or may have type string, []byte, or io.Reader.
// If nil, the text is read from the filename.
func Text(aFile, bFile string, a, b interface{}, w io.Writer, options ...write.Option) error {
m := make(intern.Strings)
aLines, err := lines(m, aFile, a)
if err != nil {
return err
}
bLines, err := lines(m, bFile, b)
if err != nil {
return err
}
ab := &diffStrings{a: aLines, b: bLines}
s := myers.Diff(context.Background(), ab)
s = ctxt.Size(s, 3)
opts := addNames(aFile, bFile, options)
err = write.Unified(s, w, ab, opts...)
return err
}
type diffStrings struct {
a, b []*string
}
func (ab *diffStrings) LenA() int { return len(ab.a) }
func (ab *diffStrings) LenB() int { return len(ab.b) }
func (ab *diffStrings) Equal(ai, bi int) bool { return ab.a[ai] == ab.b[bi] }
func (ab *diffStrings) WriteATo(w io.Writer, i int) (int, error) { return io.WriteString(w, *ab.a[i]) }
func (ab *diffStrings) WriteBTo(w io.Writer, i int) (int, error) { return io.WriteString(w, *ab.b[i]) }
// Slices diffs slices a and b and writes the result to w.
// It uses fmt.Print to print the elements of a and b.
// It uses reflect.DeepEqual to compare elements of a and b.
// It uses aName and bName as the names of a and b in the output.
func Slices(aName, bName string, a, b interface{}, w io.Writer, options ...write.Option) error {
ab := &diffSlices{a: reflect.ValueOf(a), b: reflect.ValueOf(b)}
if err := ab.validateTypes(); err != nil {
return err
}
s := myers.Diff(context.Background(), ab)
s = ctxt.Size(s, 3)
opts := addNames(aName, bName, options)
err := write.Unified(s, w, ab, opts...)
return err
}
type diffSlices struct {
a, b reflect.Value
}
func (ab *diffSlices) LenA() int { return ab.a.Len() }
func (ab *diffSlices) LenB() int { return ab.b.Len() }
func (ab *diffSlices) atA(i int) interface{} { return ab.a.Index(i).Interface() }
func (ab *diffSlices) atB(i int) interface{} { return ab.b.Index(i).Interface() }
func (ab *diffSlices) Equal(ai, bi int) bool { return reflect.DeepEqual(ab.atA(ai), ab.atB(bi)) }
func (ab *diffSlices) WriteATo(w io.Writer, i int) (int, error) { return fmt.Fprint(w, ab.atA(i)) }
func (ab *diffSlices) WriteBTo(w io.Writer, i int) (int, error) { return fmt.Fprint(w, ab.atB(i)) }
func (ab *diffSlices) validateTypes() error {
if t := ab.a.Type(); t.Kind() != reflect.Slice {
return fmt.Errorf("a has type %v, must be a slice", t)
}
if t := ab.b.Type(); t.Kind() != reflect.Slice {
return fmt.Errorf("b has type %v, must be a slice", t)
}
return nil
}