Skip to content

Commit bd33d36

Browse files
authored
Add unified diff format (#2)
1 parent d379ee2 commit bd33d36

File tree

7 files changed

+1023
-123
lines changed

7 files changed

+1023
-123
lines changed

README.md

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,78 @@
66

77
Go implementation of the Patience Diff algorithm.
88

9+
This library generates line-oriented diffs between source and destination inputs, using the Patience Diff algorithm.
10+
11+
## Features
12+
13+
Supports both plain format and [Unified format](https://en.wikipedia.org/wiki/Diff#Unified_format) (unidiff).
14+
15+
Plain format:
16+
```diff
17+
the
18+
quick
19+
brown
20+
-chicken
21+
+fox
22+
jumps
23+
over
24+
the
25+
+lazy
26+
dog
27+
```
28+
29+
Unified format (unidiff):
30+
31+
```diff
32+
--- a.txt
33+
+++ b.txt
34+
@@ -3,3 +3,3 @@
35+
brown
36+
-chicken
37+
+fox
38+
jumps
39+
@@ -7,2 +7,3 @@
40+
the
41+
+lazy
42+
dog
43+
```
44+
45+
## Installation
46+
47+
```sh
48+
go get github.com/peter-evans/patience
49+
```
50+
51+
## Usage
52+
53+
```go
54+
a := strings.Split(textA, "\n")
55+
b := strings.Split(textB, "\n")
56+
57+
diffs := patience.Diff(a, b)
58+
59+
// Combined diff
60+
diff := patience.DiffText(diffs)
61+
62+
// Split diffs
63+
diffA := patience.DiffTextA(diffs)
64+
diffB := patience.DiffTextB(diffs)
65+
66+
// Unified diff
67+
unidiff := patience.UnifiedDiffText(diffs)
68+
69+
// Unified diff with options
70+
unidiffopts := patience.UnifiedDiffTextWithOptions(
71+
diffs,
72+
UnifiedDiffOptions{
73+
Precontext: 2,
74+
Postcontext: 2,
75+
SrcHeader: "a.txt",
76+
DstHeader: "b.txt",
77+
},
78+
)
79+
```
80+
981
## About
1082

1183
Patience Diff is an algorithm credited to [Bram Cohen](https://bramcohen.livejournal.com/73318.html) that produces diffs tending to be more human-readable than the common diff algorithm.
@@ -16,8 +88,6 @@ While the diffs generated by this algorithm are efficient, in many cases they te
1688

1789
Patience Diff, while also relying on computing the longest common subsequence, takes a different approach. It only computes the longest common subsequence of the *unique*, *common* elements of both texts. This means that lines that are frequently non-unique, such as those containing a single brace or new line character, are ignored. The result is that distinctive lines, such as function declarations, become the anchor points of commonality between the two texts.
1890

19-
## Example
20-
2191
This is an example comparing Patience Diff to the common diff algorithm (Myers).
2292

2393
Patience Diff
@@ -100,28 +170,6 @@ Common diff (Myers)
100170
}
101171
```
102172

103-
## Installation
104-
105-
```sh
106-
go get github.com/peter-evans/patience
107-
```
108-
109-
## Usage
110-
111-
```go
112-
a := strings.Split(textA, "\n")
113-
b := strings.Split(textB, "\n")
114-
115-
diffs := patience.Diff(a, b)
116-
117-
// Combined diff
118-
diff := patience.DiffText(diffs)
119-
120-
// Split diffs
121-
diffA := patience.DiffTextA(diffs)
122-
diffB := patience.DiffTextB(diffs)
123-
```
124-
125173
## References
126174

127175
- [Patience Diff Advantages](https://bramcohen.livejournal.com/73318.html) by Bram Cohen

format.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,46 @@ func DiffTextB(diffs []DiffLine) string {
6363
}
6464
return strings.Join(s, "\n")
6565
}
66+
67+
// UnifiedDiffOptions represents the options for UnifiedDiffTextWithOptions.
68+
type UnifiedDiffOptions struct {
69+
// Precontext is the number of lines of context before each change in a hunk.
70+
Precontext int
71+
// Postcontext is the number of lines of context after each change in a hunk.
72+
Postcontext int
73+
// SrcHeader is the header for the source file.
74+
SrcHeader string
75+
// DstHeader is the header for the destination file.
76+
DstHeader string
77+
}
78+
79+
// UnifiedDiffTextWithOptions returns the diff text in unidiff format.
80+
func UnifiedDiffTextWithOptions(diffs []DiffLine, opts UnifiedDiffOptions) string {
81+
hunks := makeHunks(diffs, opts.Precontext, opts.Postcontext)
82+
s := []string{}
83+
if len(opts.SrcHeader) > 0 {
84+
s = append(s, fmt.Sprintf("--- %s", opts.SrcHeader))
85+
}
86+
if len(opts.DstHeader) > 0 {
87+
s = append(s, fmt.Sprintf("+++ %s", opts.DstHeader))
88+
}
89+
for _, h := range hunks {
90+
s = append(s, fmt.Sprintf("@@ -%d,%d +%d,%d @@", h.SrcStart, h.SrcLines, h.DstStart, h.DstLines))
91+
for _, l := range h.Diffs {
92+
if l.Type == Equal && len(l.Text) == 0 {
93+
s = append(s, "")
94+
} else {
95+
s = append(s, fmt.Sprintf("%s%s", typeSymbol(l.Type), l.Text))
96+
}
97+
}
98+
}
99+
return strings.Join(s, "\n")
100+
}
101+
102+
// UnifiedDiffText returns the diff text in unidiff format with a context of 3 lines.
103+
func UnifiedDiffText(diffs []DiffLine) string {
104+
return UnifiedDiffTextWithOptions(
105+
diffs,
106+
UnifiedDiffOptions{Precontext: 3, Postcontext: 3},
107+
)
108+
}

format_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,65 @@ func TestDiffTextB(t *testing.T) {
153153
})
154154
}
155155
}
156+
157+
func TestUnifiedDiffTextWithOptions(t *testing.T) {
158+
type args struct {
159+
diffs []DiffLine
160+
opts UnifiedDiffOptions
161+
}
162+
tests := []struct {
163+
name string
164+
args args
165+
want string
166+
}{
167+
{
168+
name: "Test multiple hunks with context",
169+
args: args{
170+
diffs: []DiffLine{
171+
{Type: Equal, Text: "a"},
172+
{Type: Equal, Text: "b"},
173+
{Type: Insert, Text: "c"},
174+
{Type: Equal, Text: "d"},
175+
{Type: Equal, Text: "e"},
176+
{Type: Equal, Text: "f"},
177+
{Type: Delete, Text: "g"},
178+
{Type: Insert, Text: "h"},
179+
{Type: Equal, Text: "i"},
180+
{Type: Insert, Text: "j"},
181+
{Type: Equal, Text: "k"},
182+
{Type: Equal, Text: "l"},
183+
},
184+
opts: UnifiedDiffOptions{
185+
Precontext: 1,
186+
Postcontext: 1,
187+
},
188+
},
189+
want: "@@ -2,2 +2,3 @@\n b\n+c\n d\n@@ -5,4 +6,5 @@\n f\n-g\n+h\n i\n+j\n k",
190+
},
191+
{
192+
name: "Test source and destination file headers",
193+
args: args{
194+
diffs: []DiffLine{
195+
{Type: Equal, Text: "a"},
196+
{Type: Equal, Text: "b"},
197+
{Type: Insert, Text: "c"},
198+
{Type: Equal, Text: ""},
199+
},
200+
opts: UnifiedDiffOptions{
201+
Precontext: 1,
202+
Postcontext: 1,
203+
SrcHeader: "a.txt",
204+
DstHeader: "b.txt",
205+
},
206+
},
207+
want: "--- a.txt\n+++ b.txt\n@@ -2,2 +2,3 @@\n b\n+c\n",
208+
},
209+
}
210+
for _, tt := range tests {
211+
t.Run(tt.name, func(t *testing.T) {
212+
if got := UnifiedDiffTextWithOptions(tt.args.diffs, tt.args.opts); got != tt.want {
213+
t.Errorf("UnifiedDiffTextWithOptions() = %v, want %v", got, tt.want)
214+
}
215+
})
216+
}
217+
}

0 commit comments

Comments
 (0)