Skip to content

Commit 7cc3ed0

Browse files
committed
edit: create package
Part of #18. There are some unfortunate API changes as part of this, such as the new EditScriptWithContextSize, since edit.Script is now part of a different package. Those will get ironed out as the refactoring continues.
1 parent 5319263 commit 7cc3ed0

File tree

11 files changed

+248
-210
lines changed

11 files changed

+248
-210
lines changed

cmd/pkg-diff-example/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ func main() {
7474
defer cancel()
7575
}
7676
e := diff.Myers(ctx, ab)
77-
e = e.WithContextSize(*unified) // limit amount of output context
77+
e = diff.EditScriptWithContextSize(e, *unified) // limit amount of output context
7878
opts := []diff.WriteOpt{
7979
diff.Names(aName, bName),
8080
}
8181
if *color {
8282
opts = append(opts, diff.TerminalColor())
8383
}
84-
_, err = e.WriteUnified(os.Stdout, ab, opts...)
84+
_, err = diff.WriteUnified(e, os.Stdout, ab, opts...)
8585
check(err)
8686
}

context.go

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,93 @@
11
package diff
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
6+
"github.com/pkg/diff/edit"
7+
)
48

59
// WithContextSize returns an edit script preserving only n common elements of context for changes.
610
// The returned edit script may alias the input.
711
// If n is negative, WithContextSize panics.
812
// To generate a "unified diff", use WithContextSize and then WriteUnified the resulting edit script.
9-
func (e EditScript) WithContextSize(n int) EditScript {
13+
func EditScriptWithContextSize(e edit.Script, n int) edit.Script {
1014
if n < 0 {
1115
panic(fmt.Sprintf("EditScript.WithContextSize called with negative n: %d", n))
1216
}
1317

1418
// Handle small scripts.
15-
switch len(e.IndexRanges) {
19+
switch len(e.Ranges) {
1620
case 0:
17-
return EditScript{}
21+
return edit.Script{}
1822
case 1:
19-
if e.IndexRanges[0].IsEqual() {
23+
if e.Ranges[0].IsEqual() {
2024
// Entirely identical contents.
2125
// Unclear what to do here. For now, just bail.
2226
// TODO: something else? what does command line diff do?
23-
return EditScript{}
27+
return edit.Script{}
2428
}
25-
return scriptWithIndexRanges(e.IndexRanges[0])
29+
return edit.NewScript(e.Ranges[0])
2630
}
2731

28-
out := make([]IndexRanges, 0, len(e.IndexRanges))
29-
for i, seg := range e.IndexRanges {
32+
out := make([]edit.Range, 0, len(e.Ranges))
33+
for i, seg := range e.Ranges {
3034
if !seg.IsEqual() {
3135
out = append(out, seg)
3236
continue
3337
}
3438
if i == 0 {
35-
// Leading IndexRanges. Keep only the final n entries.
36-
if seg.len() > n {
37-
seg = indexRangesLastN(seg, n)
39+
// Leading Range. Keep only the final n entries.
40+
if seg.Len() > n {
41+
seg = rangeLastN(seg, n)
3842
}
3943
out = append(out, seg)
4044
continue
4145
}
42-
if i == len(e.IndexRanges)-1 {
43-
// Trailing IndexRanges. Keep only the first n entries.
44-
if seg.len() > n {
45-
seg = indexRangesFirstN(seg, n)
46+
if i == len(e.Ranges)-1 {
47+
// Trailing Range. Keep only the first n entries.
48+
if seg.Len() > n {
49+
seg = rangeFirstN(seg, n)
4650
}
4751
out = append(out, seg)
4852
continue
4953
}
50-
if seg.len() <= n*2 {
51-
// Small middle IndexRanges. Keep unchanged.
54+
if seg.Len() <= n*2 {
55+
// Small middle Range. Keep unchanged.
5256
out = append(out, seg)
5357
continue
5458
}
55-
// Large middle IndexRanges. Break into two disjoint parts.
56-
out = append(out, indexRangesFirstN(seg, n), indexRangesLastN(seg, n))
59+
// Large middle Range. Break into two disjoint parts.
60+
out = append(out, rangeFirstN(seg, n), rangeLastN(seg, n))
5761
}
5862

5963
// TODO: Stock macOS diff also trims common blank lines
6064
// from the beginning/end of eq IndexRangess.
6165
// Perhaps we should do that here too.
62-
// Or perhaps that should be a separate, composable EditScript method?
63-
return EditScript{IndexRanges: out}
66+
// Or perhaps that should be a separate, composable function?
67+
return edit.Script{Ranges: out}
6468
}
6569

66-
func indexRangesFirstN(seg IndexRanges, n int) IndexRanges {
70+
func rangeFirstN(seg edit.Range, n int) edit.Range {
6771
if !seg.IsEqual() {
68-
panic("indexRangesFirstN bad op")
72+
panic("rangeFirstN bad op")
6973
}
70-
if seg.len() < n {
71-
panic("indexRangesFirstN bad Len")
74+
if seg.Len() < n {
75+
panic("rangeFirstN bad Len")
7276
}
73-
return IndexRanges{
77+
return edit.Range{
7478
LowA: seg.LowA, HighA: seg.LowA + n,
7579
LowB: seg.LowB, HighB: seg.LowB + n,
7680
}
7781
}
7882

79-
func indexRangesLastN(seg IndexRanges, n int) IndexRanges {
83+
func rangeLastN(seg edit.Range, n int) edit.Range {
8084
if !seg.IsEqual() {
81-
panic("indexRangesLastN bad op")
85+
panic("rangeLastN bad op")
8286
}
83-
if seg.len() < n {
84-
panic("indexRangesLastN bad Len")
87+
if seg.Len() < n {
88+
panic("rangeLastN bad Len")
8589
}
86-
return IndexRanges{
90+
return edit.Range{
8791
LowA: seg.HighA - n, HighA: seg.HighA,
8892
LowB: seg.HighB - n, HighB: seg.HighB,
8993
}

diff.go

Lines changed: 4 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package diff
22

33
import (
4-
"bytes"
54
"fmt"
65
"io"
6+
7+
"github.com/pkg/diff/edit"
78
)
89

910
// A Pair is two things that can be diffed using the Myers diff algorithm.
@@ -48,112 +49,12 @@ type PairWriterTo interface {
4849
// If you have paid the O(n) cost to intern all strings involved in both A and B,
4950
// then string comparisons are reduced to cheap pointer comparisons.
5051

51-
// An op is a edit operation used to transform A into B.
52-
type op int8
53-
54-
//go:generate stringer -type op
55-
56-
const (
57-
del op = -1
58-
eq op = 0
59-
ins op = 1
60-
)
61-
62-
// IndexRanges represents a pair of clopen index ranges.
63-
// They represent elements A[LowA:HighA] and B[LowB:HighB].
64-
type IndexRanges struct {
65-
LowA, HighA int
66-
LowB, HighB int
67-
}
68-
69-
// IsInsert reports whether r represents an insertion in an EditScript.
70-
// If so, the inserted elements are B[LowB:HighB].
71-
func (r *IndexRanges) IsInsert() bool {
72-
return r.LowA == r.HighA
73-
}
74-
75-
// IsDelete reports whether r represents a deletion in an EditScript.
76-
// If so, the deleted elements are A[LowA:HighA].
77-
func (r *IndexRanges) IsDelete() bool {
78-
return r.LowB == r.HighB
79-
}
80-
81-
// IsEqual reports whether r represents a series of equal elements in an EditScript.
82-
// If so, the elements A[LowA:HighA] are equal to the elements B[LowB:HighB].
83-
func (r *IndexRanges) IsEqual() bool {
84-
return r.HighB-r.LowB == r.HighA-r.LowA
85-
}
86-
87-
func (r *IndexRanges) op() op {
88-
if r.IsInsert() {
89-
return ins
90-
}
91-
if r.IsDelete() {
92-
return del
93-
}
94-
if r.IsEqual() {
95-
return eq
96-
}
97-
panic("malformed IndexRanges")
98-
}
99-
100-
func (s IndexRanges) debugString() string {
52+
func rangeString(r edit.Range) string {
10153
// This output is helpful when hacking on a Myers diff.
10254
// In other contexts it is usually more natural to group LowA, HighA and LowB, HighB.
103-
return fmt.Sprintf("(%d, %d) -- %s %d --> (%d, %d)", s.LowA, s.LowB, s.op(), s.len(), s.HighA, s.HighB)
104-
}
105-
106-
func (s IndexRanges) len() int {
107-
if s.LowA == s.HighA {
108-
return s.HighB - s.LowB
109-
}
110-
return s.HighA - s.LowA
111-
}
112-
113-
// An EditScript is an edit script to alter A into B.
114-
type EditScript struct {
115-
IndexRanges []IndexRanges
116-
}
117-
118-
// IsIdentity reports whether e is the identity edit script, that is, whether A and B are identical.
119-
// See the TestHelper example.
120-
func (e EditScript) IsIdentity() bool {
121-
for _, seg := range e.IndexRanges {
122-
if !seg.IsEqual() {
123-
return false
124-
}
125-
}
126-
return true
127-
}
128-
129-
// Stat reports the number of insertions and deletions in e.
130-
func (e EditScript) Stat() (ins, del int) {
131-
for _, r := range e.IndexRanges {
132-
switch {
133-
case r.IsDelete():
134-
del += r.HighA - r.LowA
135-
case r.IsInsert():
136-
ins += r.HighB - r.LowB
137-
}
138-
}
139-
return ins, del
55+
return fmt.Sprintf("(%d, %d) -- %s %d --> (%d, %d)", r.LowA, r.LowB, r.Op(), r.Len(), r.HighA, r.HighB)
14056
}
14157

14258
// TODO: consider adding an "it just works" test helper that accepts two slices (via interface{}),
14359
// diffs them using Strings or Bytes or Slices (using reflect.DeepEqual) as appropriate,
14460
// and calls t.Errorf with a generated diff if they're not equal.
145-
146-
// scriptWithIndexRanges returns an EditScript containing s.
147-
// It is used to reduce line noise.
148-
func scriptWithIndexRanges(s ...IndexRanges) EditScript {
149-
return EditScript{IndexRanges: s}
150-
}
151-
152-
// dump formats s for debugging.
153-
func (e EditScript) dump() string {
154-
buf := new(bytes.Buffer)
155-
for _, seg := range e.IndexRanges {
156-
fmt.Fprintln(buf, seg)
157-
}
158-
return buf.String()
159-
}

edit/edit.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Package edit provides edit scripts.
2+
// Edit scripts are a core notion for diffs.
3+
// The represent a way to go from A to B by a sequence
4+
// of insertions, deletions, and equal elements.
5+
package edit
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
)
11+
12+
// A Script is an edit script to alter A into B.
13+
type Script struct {
14+
Ranges []Range
15+
}
16+
17+
// NewScript returns a Script containing the ranges r.
18+
// It is only a convenience wrapper used to reduce line noise.
19+
func NewScript(r ...Range) Script {
20+
return Script{Ranges: r}
21+
}
22+
23+
// IsIdentity reports whether s is the identity edit script,
24+
// that is, whether A and B are identical.
25+
func (s *Script) IsIdentity() bool {
26+
for _, r := range s.Ranges {
27+
if !r.IsEqual() {
28+
return false
29+
}
30+
}
31+
return true
32+
}
33+
34+
// Stat reports the number of insertions and deletions in s.
35+
func (s *Script) Stat() (ins, del int) {
36+
for _, r := range s.Ranges {
37+
switch {
38+
case r.IsDelete():
39+
del += r.HighA - r.LowA
40+
case r.IsInsert():
41+
ins += r.HighB - r.LowB
42+
}
43+
}
44+
return ins, del
45+
}
46+
47+
// dump formats s for debugging.
48+
func (s *Script) dump() string {
49+
buf := new(strings.Builder)
50+
for _, r := range s.Ranges {
51+
fmt.Fprintln(buf, r)
52+
}
53+
return buf.String()
54+
}
55+
56+
// A Range is a pair of clopen index ranges.
57+
// It represents the elements A[LowA:HighA] and B[LowB:HighB].
58+
type Range struct {
59+
LowA, HighA int
60+
LowB, HighB int
61+
}
62+
63+
// IsInsert reports whether r represents an insertion in a Script.
64+
// If so, the inserted elements are B[LowB:HighB].
65+
func (r *Range) IsInsert() bool {
66+
return r.LowA == r.HighA
67+
}
68+
69+
// IsDelete reports whether r represents a deletion in a Script.
70+
// If so, the deleted elements are A[LowA:HighA].
71+
func (r *Range) IsDelete() bool {
72+
return r.LowB == r.HighB
73+
}
74+
75+
// IsEqual reports whether r represents a series of equal elements in a Script.
76+
// If so, the elements A[LowA:HighA] are equal to the elements B[LowB:HighB].
77+
func (r *Range) IsEqual() bool {
78+
return r.HighB-r.LowB == r.HighA-r.LowA
79+
}
80+
81+
// An Op is a edit operation used to transform A into B.
82+
type Op int8
83+
84+
//go:generate stringer -type Op
85+
86+
const (
87+
Del Op = -1 // delete
88+
Eq Op = 0 // equal
89+
Ins Op = 1 // insert
90+
)
91+
92+
// Op reports what kind of operation r represents.
93+
// This can also be determined by calling r.IsInsert,
94+
// r.IsDelete, and r.IsEqual,
95+
// but this form is sometimes more convenient to use.
96+
func (r *Range) Op() Op {
97+
if r.IsInsert() {
98+
return Ins
99+
}
100+
if r.IsDelete() {
101+
return Del
102+
}
103+
if r.IsEqual() {
104+
return Eq
105+
}
106+
panic("malformed Range")
107+
}
108+
109+
// Len reports the number of elements in r.
110+
// In a deletion, it is the number of deleted elements.
111+
// In an insertion, it is the number of inserted elements.
112+
// For equal elements, it is the number of equal elements.
113+
func (r *Range) Len() int {
114+
if r.LowA == r.HighA {
115+
return r.HighB - r.LowB
116+
}
117+
return r.HighA - r.LowA
118+
}

0 commit comments

Comments
 (0)