Skip to content

Commit fe34798

Browse files
committed
diff: initial commit
Simple diff for use primarily in tests. Extracted from cmd/go/script_test.go.
0 parents  commit fe34798

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

LICENSE

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2009 The Go Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions are
5+
met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above
10+
copyright notice, this list of conditions and the following disclaimer
11+
in the documentation and/or other materials provided with the
12+
distribution.
13+
* Neither the name of Google Inc. nor the names of its
14+
contributors may be used to endorse or promote products derived from
15+
this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

diff.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package diff provides basic text comparison (like Unix's diff(1)).
6+
package diff
7+
8+
import (
9+
"fmt"
10+
"strings"
11+
)
12+
13+
// Format returns a formatted diff of the two texts,
14+
// showing the entire text and the minimum line-level
15+
// additions and removals to turn text1 into text2.
16+
// (That is, lines only in text1 appear with a leading -,
17+
// and lines only in text2 appear with a leading +.)
18+
func Format(text1, text2 string) string {
19+
if text1 != "" && !strings.HasSuffix(text1, "\n") {
20+
text1 += "(missing final newline)"
21+
}
22+
lines1 := strings.Split(text1, "\n")
23+
lines1 = lines1[:len(lines1)-1] // remove empty string after final line
24+
if text2 != "" && !strings.HasSuffix(text2, "\n") {
25+
text2 += "(missing final newline)"
26+
}
27+
lines2 := strings.Split(text2, "\n")
28+
lines2 = lines2[:len(lines2)-1] // remove empty string after final line
29+
30+
// Naive dynamic programming algorithm for edit distance.
31+
// https://en.wikipedia.org/wiki/Wagner–Fischer_algorithm
32+
// dist[i][j] = edit distance between lines1[:len(lines1)-i] and lines2[:len(lines2)-j]
33+
// (The reversed indices make following the minimum cost path
34+
// visit lines in the same order as in the text.)
35+
dist := make([][]int, len(lines1)+1)
36+
for i := range dist {
37+
dist[i] = make([]int, len(lines2)+1)
38+
if i == 0 {
39+
for j := range dist[0] {
40+
dist[0][j] = j
41+
}
42+
continue
43+
}
44+
for j := range dist[i] {
45+
if j == 0 {
46+
dist[i][0] = i
47+
continue
48+
}
49+
cost := dist[i][j-1] + 1
50+
if cost > dist[i-1][j]+1 {
51+
cost = dist[i-1][j] + 1
52+
}
53+
if lines1[len(lines1)-i] == lines2[len(lines2)-j] {
54+
if cost > dist[i-1][j-1] {
55+
cost = dist[i-1][j-1]
56+
}
57+
}
58+
dist[i][j] = cost
59+
}
60+
}
61+
62+
var buf strings.Builder
63+
i, j := len(lines1), len(lines2)
64+
for i > 0 || j > 0 {
65+
cost := dist[i][j]
66+
if i > 0 && j > 0 && cost == dist[i-1][j-1] && lines1[len(lines1)-i] == lines2[len(lines2)-j] {
67+
fmt.Fprintf(&buf, " %s\n", lines1[len(lines1)-i])
68+
i--
69+
j--
70+
} else if i > 0 && cost == dist[i-1][j]+1 {
71+
fmt.Fprintf(&buf, "-%s\n", lines1[len(lines1)-i])
72+
i--
73+
} else {
74+
fmt.Fprintf(&buf, "+%s\n", lines2[len(lines2)-j])
75+
j--
76+
}
77+
}
78+
return buf.String()
79+
}

diff_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package diff
6+
7+
import (
8+
"strings"
9+
"testing"
10+
)
11+
12+
var formatTests = []struct {
13+
text1 string
14+
text2 string
15+
diff string
16+
}{
17+
{"a b c", "a b d e f", "a b -c +d +e +f"},
18+
{"", "a b c", "+a +b +c"},
19+
{"a b c", "", "-a -b -c"},
20+
{"a b c", "d e f", "-a -b -c +d +e +f"},
21+
{"a b c d e f", "a b d e f", "a b -c d e f"},
22+
{"a b c e f", "a b c d e f", "a b c +d e f"},
23+
}
24+
25+
func TestFormat(t *testing.T) {
26+
for _, tt := range formatTests {
27+
// Turn spaces into \n.
28+
text1 := strings.ReplaceAll(tt.text1, " ", "\n")
29+
if text1 != "" {
30+
text1 += "\n"
31+
}
32+
text2 := strings.ReplaceAll(tt.text2, " ", "\n")
33+
if text2 != "" {
34+
text2 += "\n"
35+
}
36+
out := Format(text1, text2)
37+
// Cut final \n, cut spaces, turn remaining \n into spaces.
38+
out = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSuffix(out, "\n"), " ", ""), "\n", " ")
39+
if out != tt.diff {
40+
t.Errorf("diff(%q, %q) = %q, want %q", text1, text2, out, tt.diff)
41+
}
42+
}
43+
}

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module rsc.io/diff
2+
3+
go 1.13

0 commit comments

Comments
 (0)