forked from mystiq/dex
133 lines
3.1 KiB
Go
133 lines
3.1 KiB
Go
// Copyright 2013 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package diff implements a linewise diff algorithm.
|
|
package diff
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Chunk represents a piece of the diff. A chunk will not have both added and
|
|
// deleted lines. Equal lines are always after any added or deleted lines.
|
|
// A Chunk may or may not have any lines in it, especially for the first or last
|
|
// chunk in a computation.
|
|
type Chunk struct {
|
|
Added []string
|
|
Deleted []string
|
|
Equal []string
|
|
}
|
|
|
|
// Diff returns a string containing a line-by-line unified diff of the linewise
|
|
// changes required to make A into B. Each line is prefixed with '+', '-', or
|
|
// ' ' to indicate if it should be added, removed, or is correct respectively.
|
|
func Diff(A, B string) string {
|
|
aLines := strings.Split(A, "\n")
|
|
bLines := strings.Split(B, "\n")
|
|
|
|
chunks := DiffChunks(aLines, bLines)
|
|
|
|
buf := new(bytes.Buffer)
|
|
for _, c := range chunks {
|
|
for _, line := range c.Added {
|
|
fmt.Fprintf(buf, "+%s\n", line)
|
|
}
|
|
for _, line := range c.Deleted {
|
|
fmt.Fprintf(buf, "-%s\n", line)
|
|
}
|
|
for _, line := range c.Equal {
|
|
fmt.Fprintf(buf, " %s\n", line)
|
|
}
|
|
}
|
|
return strings.TrimRight(buf.String(), "\n")
|
|
}
|
|
|
|
// DiffChunks uses an O(D(N+M)) shortest-edit-script algorithm
|
|
// to compute the edits required from A to B and returns the
|
|
// edit chunks.
|
|
func DiffChunks(A, B []string) []Chunk {
|
|
// algorithm: http://www.xmailserver.org/diff2.pdf
|
|
|
|
N, M := len(A), len(B)
|
|
MAX := N + M
|
|
V := make([]int, 2*MAX+1)
|
|
Vs := make([][]int, 0, 8)
|
|
|
|
var D int
|
|
dLoop:
|
|
for D = 0; D <= MAX; D++ {
|
|
for k := -D; k <= D; k += 2 {
|
|
var x int
|
|
if k == -D || (k != D && V[MAX+k-1] < V[MAX+k+1]) {
|
|
x = V[MAX+k+1]
|
|
} else {
|
|
x = V[MAX+k-1] + 1
|
|
}
|
|
y := x - k
|
|
for x < N && y < M && A[x] == B[y] {
|
|
x++
|
|
y++
|
|
}
|
|
V[MAX+k] = x
|
|
if x >= N && y >= M {
|
|
Vs = append(Vs, append(make([]int, 0, len(V)), V...))
|
|
break dLoop
|
|
}
|
|
}
|
|
Vs = append(Vs, append(make([]int, 0, len(V)), V...))
|
|
}
|
|
if D == 0 {
|
|
return nil
|
|
}
|
|
chunks := make([]Chunk, D+1)
|
|
|
|
x, y := N, M
|
|
for d := D; d > 0; d-- {
|
|
V := Vs[d]
|
|
k := x - y
|
|
insert := k == -d || (k != d && V[MAX+k-1] < V[MAX+k+1])
|
|
|
|
x1 := V[MAX+k]
|
|
var x0, xM, kk int
|
|
if insert {
|
|
kk = k + 1
|
|
x0 = V[MAX+kk]
|
|
xM = x0
|
|
} else {
|
|
kk = k - 1
|
|
x0 = V[MAX+kk]
|
|
xM = x0 + 1
|
|
}
|
|
y0 := x0 - kk
|
|
|
|
var c Chunk
|
|
if insert {
|
|
c.Added = B[y0:][:1]
|
|
} else {
|
|
c.Deleted = A[x0:][:1]
|
|
}
|
|
if xM < x1 {
|
|
c.Equal = A[xM:][:x1-xM]
|
|
}
|
|
|
|
x, y = x0, y0
|
|
chunks[d] = c
|
|
}
|
|
if x > 0 {
|
|
chunks[0].Equal = A[:x]
|
|
}
|
|
return chunks
|
|
}
|