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