Skip to content

Commit a78b239

Browse files
committed
write: create package
Part of #18. I am very torn about having a single write package with a routine per format: write.Pair write.Option write.Names write.Unified write.SideBySide vs having a package per format: write.Option write.Names unified.Pair unified.Write sidebyside.Pair sidebyside.Write One motivation for separate packages is that the ideal interface for different formats is different. For unified, WriteTo is high performance, and we don't need more. For side by side, we need to be able to inspect and even modify the line before writing, so it makes more sense to have an interface specified in terms of []byte. However, we can use a bytes.Buffer internally to bridge from WriteTo to []byte, and most write options are shared between the formats. So for the moment, try out a single write package. Maybe Daniel (or someone else) will have a compelling reason to go one way or another. The adaptor API is getting ugly. It'll get fixed (mostly eliminated) soon.
1 parent b63e0fa commit a78b239

File tree

11 files changed

+146
-120
lines changed

11 files changed

+146
-120
lines changed

adapter.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@ import (
55
"fmt"
66
"io"
77
"reflect"
8+
9+
"github.com/pkg/diff/myers"
10+
"github.com/pkg/diff/write"
811
)
912

10-
// Strings returns a PairWriterTo that can diff and write a and b.
11-
func Strings(a, b []string) PairWriterTo {
13+
// DiffWrite is the union of myers.Pair and write.Pair:
14+
// It can be diffed using myers diff, and written in unified diff format.
15+
type DiffWrite interface {
16+
myers.Pair
17+
write.Pair
18+
}
19+
20+
// Strings returns a DiffWrite that can diff and write a and b.
21+
func Strings(a, b []string) DiffWrite {
1222
return &diffStrings{a: a, b: b}
1323
}
1424

@@ -22,8 +32,8 @@ func (ab *diffStrings) Equal(ai, bi int) bool { return ab.a[a
2232
func (ab *diffStrings) WriteATo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.a[i]) }
2333
func (ab *diffStrings) WriteBTo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.b[i]) }
2434

25-
// Bytes returns a PairWriterTo that can diff and write a and b.
26-
func Bytes(a, b [][]byte) PairWriterTo {
35+
// Bytes returns a DiffWrite that can diff and write a and b.
36+
func Bytes(a, b [][]byte) DiffWrite {
2737
return &diffBytes{a: a, b: b}
2838
}
2939

@@ -37,11 +47,11 @@ func (ab *diffBytes) Equal(ai, bi int) bool { return bytes.Eq
3747
func (ab *diffBytes) WriteATo(w io.Writer, i int) (int, error) { return w.Write(ab.a[i]) }
3848
func (ab *diffBytes) WriteBTo(w io.Writer, i int) (int, error) { return w.Write(ab.b[i]) }
3949

40-
// Slices returns a PairWriterTo that diffs a and b.
50+
// Slices returns a DiffWrite that diffs a and b.
4151
// It uses fmt.Print to print the elements of a and b.
4252
// It uses equal to compare elements of a and b;
4353
// if equal is nil, Slices uses reflect.DeepEqual.
44-
func Slices(a, b interface{}, equal func(x, y interface{}) bool) PairWriterTo {
54+
func Slices(a, b interface{}, equal func(x, y interface{}) bool) DiffWrite {
4555
if equal == nil {
4656
equal = reflect.DeepEqual
4757
}

cmd/pkg-diff-example/main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/pkg/diff"
1414
"github.com/pkg/diff/ctxt"
1515
"github.com/pkg/diff/myers"
16+
"github.com/pkg/diff/write"
1617
)
1718

1819
var (
@@ -77,12 +78,12 @@ func main() {
7778
}
7879
e := myers.Diff(ctx, ab)
7980
e = ctxt.Size(e, *unified) // limit amount of output context
80-
opts := []diff.WriteOpt{
81-
diff.Names(aName, bName),
81+
opts := []write.Option{
82+
write.Names(aName, bName),
8283
}
8384
if *color {
84-
opts = append(opts, diff.TerminalColor())
85+
opts = append(opts, write.TerminalColor())
8586
}
86-
_, err = diff.WriteUnified(e, os.Stdout, ab, opts...)
87+
_, err = write.Unified(e, os.Stdout, ab, opts...)
8788
check(err)
8889
}

diff.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1 @@
11
package diff
2-
3-
import (
4-
"io"
5-
6-
"github.com/pkg/diff/myers"
7-
)
8-
9-
// A WriterTo type supports writing a diff, element by element.
10-
// A is the initial state; B is the final state.
11-
type WriterTo interface {
12-
// WriteATo writes the element a[ai] to w.
13-
WriteATo(w io.Writer, ai int) (int, error)
14-
// WriteBTo writes the element b[bi] to w.
15-
WriteBTo(w io.Writer, bi int) (int, error)
16-
}
17-
18-
// PairWriterTo is the union of Pair and WriterTo.
19-
type PairWriterTo interface {
20-
myers.Pair
21-
WriterTo
22-
}

example_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/pkg/diff"
88
"github.com/pkg/diff/ctxt"
99
"github.com/pkg/diff/myers"
10+
"github.com/pkg/diff/write"
1011
)
1112

1213
// TODO: use a less heavyweight output format for Example_testHelper
@@ -20,7 +21,7 @@ func Example_testHelper() {
2021
return
2122
}
2223
e = ctxt.Size(e, 1)
23-
diff.WriteUnified(e, os.Stdout, ab)
24+
write.Unified(e, os.Stdout, ab)
2425
// Output:
2526
// --- a
2627
// +++ b
@@ -35,7 +36,7 @@ func Example_strings() {
3536
b := []string{"a", "c", "d"}
3637
ab := diff.Strings(a, b)
3738
e := myers.Diff(context.Background(), ab)
38-
diff.WriteUnified(e, os.Stdout, ab)
39+
write.Unified(e, os.Stdout, ab)
3940
// Output:
4041
// --- a
4142
// +++ b
@@ -51,7 +52,7 @@ func Example_Names() {
5152
b := []string{"a", "c", "d"}
5253
ab := diff.Strings(a, b)
5354
e := myers.Diff(context.Background(), ab)
54-
diff.WriteUnified(e, os.Stdout, ab, diff.Names("before", "after"))
55+
write.Unified(e, os.Stdout, ab, write.Names("before", "after"))
5556
// Output:
5657
// --- before
5758
// +++ after

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ module github.com/pkg/diff
22

33
go 1.13
44

5-
require github.com/sergi/go-diff v1.0.0
5+
require (
6+
github.com/sergi/go-diff v1.0.0
7+
github.com/stretchr/testify v1.4.0 // indirect
8+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
26
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
7+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
8+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
9+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
12+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

write/errwriter.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package write
2+
3+
import "io"
4+
5+
func newErrWriter(w io.Writer) *errwriter {
6+
return &errwriter{w: w}
7+
}
8+
9+
// An errwriter wraps a writer.
10+
// As soon as one write fails, it consumes all subsequent writes.
11+
// This reduces the amount of error-checking required
12+
// in write-heavy code.
13+
type errwriter struct {
14+
w io.Writer
15+
err error
16+
wrote int
17+
attempted int
18+
}
19+
20+
func (w *errwriter) Write(b []byte) (int, error) {
21+
w.attempted += len(b)
22+
if w.err != nil {
23+
return 0, w.err // TODO: use something like errors.Wrap(w.err)?
24+
}
25+
n, err := w.w.Write(b)
26+
if err != nil {
27+
w.err = err
28+
}
29+
w.wrote += n
30+
return n, err
31+
}
32+
33+
func (w *errwriter) WriteString(s string) {
34+
// TODO: use w.w's WriteString method, if it exists
35+
w.Write([]byte(s))
36+
}
37+
38+
func (w *errwriter) WriteByte(b byte) {
39+
// TODO: use w.w's WriteByte method, if it exists
40+
w.Write([]byte{b})
41+
}
42+
43+
func (w *errwriter) Error() error {
44+
return w.err
45+
}

write/option.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Package write provides routines for writing diffs.
2+
package write
3+
4+
// An Option modifies behavior when writing a diff.
5+
type Option interface {
6+
isOption()
7+
}
8+
9+
// Names provides the before/after names for writing a diff.
10+
// They are traditionally filenames.
11+
func Names(a, b string) Option {
12+
return names{a, b}
13+
}
14+
15+
type names struct {
16+
a, b string
17+
}
18+
19+
func (names) isOption() {}
20+
21+
// TerminalColor specifies that a diff intended
22+
// for a terminal should be written using colors.
23+
//
24+
// Do not use TerminalColor if TERM=dumb is set in the environment.
25+
func TerminalColor() Option {
26+
return colorOpt(true)
27+
}
28+
29+
type colorOpt bool
30+
31+
func (colorOpt) isOption() {}
32+
33+
const (
34+
ansiBold = "\u001b[1m"
35+
ansiFgRed = "\u001b[31m"
36+
ansiFgGreen = "\u001b[32m"
37+
ansiFgBlue = "\u001b[36m"
38+
ansiReset = "\u001b[0m"
39+
)

write/todo.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package write
2+
3+
// TODO: add diff writing that uses < and > (don't know what that is called)
4+
// TODO: add side by side diffs
5+
// TODO: add html diffs (?)
6+
// TODO: add intraline highlighting?
7+
// TODO: a way to specify alternative colors, like a ColorScheme write option

print.go renamed to write/unified.go

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

33
import (
44
"fmt"
@@ -7,55 +7,21 @@ import (
77
"github.com/pkg/diff/edit"
88
)
99

10-
// TODO: add diff writing that uses < and > (don't know what that is called)
11-
// TODO: add side by side diffs
12-
// TODO: add html diffs (?)
13-
// TODO: add intraline highlighting?
14-
// TODO: a way to specify alternative colors, like a ColorScheme write option
15-
16-
// A WriteOpt is used to provide options when writing a diff.
17-
type WriteOpt interface {
18-
isWriteOpt()
19-
}
20-
21-
// Names provides the before/after names for writing a diff.
22-
// They are traditionally filenames.
23-
func Names(a, b string) WriteOpt {
24-
return names{a, b}
10+
// A Pair type supports writing a unified diff, element by element.
11+
// A is the initial state; B is the final state.
12+
type Pair interface {
13+
// WriteATo writes the element a[aᵢ] to w.
14+
WriteATo(w io.Writer, ai int) (int, error)
15+
// WriteBTo writes the element b[bᵢ] to w.
16+
WriteBTo(w io.Writer, bi int) (int, error)
2517
}
2618

27-
type names struct {
28-
a, b string
29-
}
30-
31-
func (names) isWriteOpt() {}
32-
33-
// TerminalColor specifies that a diff intended for a terminal should be written
34-
// using red and green colors.
35-
//
36-
// Do not use TerminalColor if TERM=dumb is set in the environment.
37-
func TerminalColor() WriteOpt {
38-
return colorOpt(true)
39-
}
40-
41-
type colorOpt bool
42-
43-
func (colorOpt) isWriteOpt() {}
44-
45-
const (
46-
ansiBold = "\u001b[1m"
47-
ansiFgRed = "\u001b[31m"
48-
ansiFgGreen = "\u001b[32m"
49-
ansiFgBlue = "\u001b[36m"
50-
ansiReset = "\u001b[0m"
51-
)
52-
53-
// WriteUnified writes e to w using unified diff format.
19+
// Unified writes e to w using unified diff format.
5420
// ab writes the individual elements. Opts are optional write arguments.
55-
// WriteUnified returns the number of bytes written and the first error (if any) encountered.
21+
// Unified returns the number of bytes written and the first error (if any) encountered.
5622
// Before writing, edit scripts usually have their context reduced,
5723
// such as by a call to ctxt.Size.
58-
func WriteUnified(e edit.Script, w io.Writer, ab WriterTo, opts ...WriteOpt) (int, error) {
24+
func Unified(e edit.Script, w io.Writer, ab Pair, opts ...Option) (int, error) {
5925
// read opts
6026
nameA := "a"
6127
nameB := "b"
@@ -209,39 +175,3 @@ func (r lineRange) String() string {
209175
func (r lineRange) GoString() string {
210176
return fmt.Sprintf("(%d, %d)", r.first, r.last)
211177
}
212-
213-
func newErrWriter(w io.Writer) *errwriter {
214-
return &errwriter{w: w}
215-
}
216-
217-
type errwriter struct {
218-
w io.Writer
219-
err error
220-
wrote int
221-
attempted int
222-
}
223-
224-
func (w *errwriter) Write(b []byte) (int, error) {
225-
w.attempted += len(b)
226-
if w.err != nil {
227-
return 0, w.err // TODO: use something like errors.Wrap(w.err)?
228-
}
229-
n, err := w.w.Write(b)
230-
if err != nil {
231-
w.err = err
232-
}
233-
w.wrote += n
234-
return n, err
235-
}
236-
237-
func (w *errwriter) WriteString(s string) {
238-
// TODO: use w.w's WriteString method, if it exists
239-
w.Write([]byte(s))
240-
}
241-
242-
func (w *errwriter) WriteByte(b byte) {
243-
// TODO: use w.w's WriteByte method, if it exists
244-
w.Write([]byte{b})
245-
}
246-
247-
func (w *errwriter) Error() error { return w.err }

unified_test.go renamed to write/unified_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package diff_test
1+
package write_test
22

33
import (
44
"bytes"
@@ -9,13 +9,14 @@ import (
99
"github.com/pkg/diff"
1010
"github.com/pkg/diff/ctxt"
1111
"github.com/pkg/diff/myers"
12+
"github.com/pkg/diff/write"
1213
"github.com/sergi/go-diff/diffmatchpatch"
1314
)
1415

1516
var goldenTests = []struct {
1617
name string
1718
a, b string
18-
opts []diff.WriteOpt
19+
opts []write.Option
1920
want string // usually from running diff --unified and cleaning up the output
2021
}{
2122
{
@@ -59,7 +60,7 @@ var goldenTests = []struct {
5960
name: "WithTerminalColor",
6061
a: "1\n2\n2",
6162
b: "1\n3\n3",
62-
opts: []diff.WriteOpt{diff.TerminalColor()},
63+
opts: []write.Option{write.TerminalColor()},
6364
want: `
6465
`[1:] + "\u001b[1m" + `--- a
6566
+++ b
@@ -85,7 +86,7 @@ func TestGolden(t *testing.T) {
8586
e := myers.Diff(context.Background(), ab)
8687
e = ctxt.Size(e, 3)
8788
buf := new(bytes.Buffer)
88-
diff.WriteUnified(e, buf, ab, test.opts...)
89+
write.Unified(e, buf, ab, test.opts...)
8990
got := buf.String()
9091
if test.want != got {
9192
t.Logf("%q\n", test.want)

0 commit comments

Comments
 (0)