|
| 1 | +// Copyright 2013 Google Inc. All rights reserved. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | +// |
| 15 | +// The code in this package is copy/paste to avoid a dependency. Hence this file |
| 16 | +// carries the copyright of the original repo. |
| 17 | +// https://github.com/kylelemons/godebug/tree/v1.1.0/diff |
| 18 | +// |
| 19 | +// Package diff implements a linewise diff algorithm. |
| 20 | +package diff |
| 21 | + |
| 22 | +import ( |
| 23 | + "fmt" |
| 24 | + "strings" |
| 25 | +) |
| 26 | + |
| 27 | +// Chunk represents a piece of the diff. A chunk will not have both added and |
| 28 | +// deleted lines. Equal lines are always after any added or deleted lines. |
| 29 | +// A Chunk may or may not have any lines in it, especially for the first or last |
| 30 | +// chunk in a computation. |
| 31 | +type Chunk struct { |
| 32 | + Added []string |
| 33 | + Deleted []string |
| 34 | + Equal []string |
| 35 | +} |
| 36 | + |
| 37 | +func (c *Chunk) empty() bool { |
| 38 | + return len(c.Added) == 0 && len(c.Deleted) == 0 && len(c.Equal) == 0 |
| 39 | +} |
| 40 | + |
| 41 | +// Diff returns a string containing a line-by-line unified diff of the linewise |
| 42 | +// changes required to make A into B. Each line is prefixed with '+', '-', or |
| 43 | +// ' ' to indicate if it should be added, removed, or is correct respectively. |
| 44 | +func Diff(A, B string) string { |
| 45 | + aLines := strings.Split(A, "\n") |
| 46 | + bLines := strings.Split(B, "\n") |
| 47 | + return Render(DiffChunks(aLines, bLines)) |
| 48 | +} |
| 49 | + |
| 50 | +// Render renders the slice of chunks into a representation that prefixes |
| 51 | +// the lines with '+', '-', or ' ' depending on whether the line was added, |
| 52 | +// removed, or equal (respectively). |
| 53 | +func Render(chunks []Chunk) string { |
| 54 | + buf := new(strings.Builder) |
| 55 | + for _, c := range chunks { |
| 56 | + for _, line := range c.Added { |
| 57 | + fmt.Fprintf(buf, "+%s\n", line) |
| 58 | + } |
| 59 | + for _, line := range c.Deleted { |
| 60 | + fmt.Fprintf(buf, "-%s\n", line) |
| 61 | + } |
| 62 | + for _, line := range c.Equal { |
| 63 | + fmt.Fprintf(buf, " %s\n", line) |
| 64 | + } |
| 65 | + } |
| 66 | + return strings.TrimRight(buf.String(), "\n") |
| 67 | +} |
| 68 | + |
| 69 | +// DiffChunks uses an O(D(N+M)) shortest-edit-script algorithm |
| 70 | +// to compute the edits required from A to B and returns the |
| 71 | +// edit chunks. |
| 72 | +func DiffChunks(a, b []string) []Chunk { |
| 73 | + // algorithm: http://www.xmailserver.org/diff2.pdf |
| 74 | + |
| 75 | + // We'll need these quantities a lot. |
| 76 | + alen, blen := len(a), len(b) // M, N |
| 77 | + |
| 78 | + // At most, it will require len(a) deletions and len(b) additions |
| 79 | + // to transform a into b. |
| 80 | + maxPath := alen + blen // MAX |
| 81 | + if maxPath == 0 { |
| 82 | + // degenerate case: two empty lists are the same |
| 83 | + return nil |
| 84 | + } |
| 85 | + |
| 86 | + // Store the endpoint of the path for diagonals. |
| 87 | + // We store only the a index, because the b index on any diagonal |
| 88 | + // (which we know during the loop below) is aidx-diag. |
| 89 | + // endpoint[maxPath] represents the 0 diagonal. |
| 90 | + // |
| 91 | + // Stated differently: |
| 92 | + // endpoint[d] contains the aidx of a furthest reaching path in diagonal d |
| 93 | + endpoint := make([]int, 2*maxPath+1) // V |
| 94 | + |
| 95 | + saved := make([][]int, 0, 8) // Vs |
| 96 | + save := func() { |
| 97 | + dup := make([]int, len(endpoint)) |
| 98 | + copy(dup, endpoint) |
| 99 | + saved = append(saved, dup) |
| 100 | + } |
| 101 | + |
| 102 | + var editDistance int // D |
| 103 | +dLoop: |
| 104 | + for editDistance = 0; editDistance <= maxPath; editDistance++ { |
| 105 | + // The 0 diag(onal) represents equality of a and b. Each diagonal to |
| 106 | + // the left is numbered one lower, to the right is one higher, from |
| 107 | + // -alen to +blen. Negative diagonals favor differences from a, |
| 108 | + // positive diagonals favor differences from b. The edit distance to a |
| 109 | + // diagonal d cannot be shorter than d itself. |
| 110 | + // |
| 111 | + // The iterations of this loop cover either odds or evens, but not both, |
| 112 | + // If odd indices are inputs, even indices are outputs and vice versa. |
| 113 | + for diag := -editDistance; diag <= editDistance; diag += 2 { // k |
| 114 | + var aidx int // x |
| 115 | + switch { |
| 116 | + case diag == -editDistance: |
| 117 | + // This is a new diagonal; copy from previous iter |
| 118 | + aidx = endpoint[maxPath-editDistance+1] + 0 |
| 119 | + case diag == editDistance: |
| 120 | + // This is a new diagonal; copy from previous iter |
| 121 | + aidx = endpoint[maxPath+editDistance-1] + 1 |
| 122 | + case endpoint[maxPath+diag+1] > endpoint[maxPath+diag-1]: |
| 123 | + // diagonal d+1 was farther along, so use that |
| 124 | + aidx = endpoint[maxPath+diag+1] + 0 |
| 125 | + default: |
| 126 | + // diagonal d-1 was farther (or the same), so use that |
| 127 | + aidx = endpoint[maxPath+diag-1] + 1 |
| 128 | + } |
| 129 | + // On diagonal d, we can compute bidx from aidx. |
| 130 | + bidx := aidx - diag // y |
| 131 | + // See how far we can go on this diagonal before we find a difference. |
| 132 | + for aidx < alen && bidx < blen && a[aidx] == b[bidx] { |
| 133 | + aidx++ |
| 134 | + bidx++ |
| 135 | + } |
| 136 | + // Store the end of the current edit chain. |
| 137 | + endpoint[maxPath+diag] = aidx |
| 138 | + // If we've found the end of both inputs, we're done! |
| 139 | + if aidx >= alen && bidx >= blen { |
| 140 | + save() // save the final path |
| 141 | + break dLoop |
| 142 | + } |
| 143 | + } |
| 144 | + save() // save the current path |
| 145 | + } |
| 146 | + if editDistance == 0 { |
| 147 | + return nil |
| 148 | + } |
| 149 | + chunks := make([]Chunk, editDistance+1) |
| 150 | + |
| 151 | + x, y := alen, blen |
| 152 | + for d := editDistance; d > 0; d-- { |
| 153 | + endpoint := saved[d] |
| 154 | + diag := x - y |
| 155 | + insert := diag == -d || (diag != d && endpoint[maxPath+diag-1] < endpoint[maxPath+diag+1]) |
| 156 | + |
| 157 | + x1 := endpoint[maxPath+diag] |
| 158 | + var x0, xM, kk int |
| 159 | + if insert { |
| 160 | + kk = diag + 1 |
| 161 | + x0 = endpoint[maxPath+kk] |
| 162 | + xM = x0 |
| 163 | + } else { |
| 164 | + kk = diag - 1 |
| 165 | + x0 = endpoint[maxPath+kk] |
| 166 | + xM = x0 + 1 |
| 167 | + } |
| 168 | + y0 := x0 - kk |
| 169 | + |
| 170 | + var c Chunk |
| 171 | + if insert { |
| 172 | + c.Added = b[y0:][:1] |
| 173 | + } else { |
| 174 | + c.Deleted = a[x0:][:1] |
| 175 | + } |
| 176 | + if xM < x1 { |
| 177 | + c.Equal = a[xM:][:x1-xM] |
| 178 | + } |
| 179 | + |
| 180 | + x, y = x0, y0 |
| 181 | + chunks[d] = c |
| 182 | + } |
| 183 | + if x > 0 { |
| 184 | + chunks[0].Equal = a[:x] |
| 185 | + } |
| 186 | + if chunks[0].empty() { |
| 187 | + chunks = chunks[1:] |
| 188 | + } |
| 189 | + if len(chunks) == 0 { |
| 190 | + return nil |
| 191 | + } |
| 192 | + return chunks |
| 193 | +} |
0 commit comments