diff --git a/adapter.go b/adapter.go index 118f162..64a5c95 100644 --- a/adapter.go +++ b/adapter.go @@ -5,10 +5,20 @@ import ( "fmt" "io" "reflect" + + "github.com/pkg/diff/myers" + "github.com/pkg/diff/write" ) -// Strings returns a PairWriterTo that can diff and write a and b. -func Strings(a, b []string) PairWriterTo { +// DiffWrite is the union of myers.Pair and write.Pair: +// It can be diffed using myers diff, and written in unified diff format. +type DiffWrite interface { + myers.Pair + write.Pair +} + +// Strings returns a DiffWrite that can diff and write a and b. +func Strings(a, b []string) DiffWrite { return &diffStrings{a: a, b: b} } @@ -22,8 +32,8 @@ func (ab *diffStrings) Equal(ai, bi int) bool { return ab.a[a func (ab *diffStrings) WriteATo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.a[i]) } func (ab *diffStrings) WriteBTo(w io.Writer, i int) (int, error) { return io.WriteString(w, ab.b[i]) } -// Bytes returns a PairWriterTo that can diff and write a and b. -func Bytes(a, b [][]byte) PairWriterTo { +// Bytes returns a DiffWrite that can diff and write a and b. +func Bytes(a, b [][]byte) DiffWrite { return &diffBytes{a: a, b: b} } @@ -37,11 +47,11 @@ func (ab *diffBytes) Equal(ai, bi int) bool { return bytes.Eq func (ab *diffBytes) WriteATo(w io.Writer, i int) (int, error) { return w.Write(ab.a[i]) } func (ab *diffBytes) WriteBTo(w io.Writer, i int) (int, error) { return w.Write(ab.b[i]) } -// Slices returns a PairWriterTo that diffs a and b. +// Slices returns a DiffWrite that diffs a and b. // It uses fmt.Print to print the elements of a and b. // It uses equal to compare elements of a and b; // if equal is nil, Slices uses reflect.DeepEqual. -func Slices(a, b interface{}, equal func(x, y interface{}) bool) PairWriterTo { +func Slices(a, b interface{}, equal func(x, y interface{}) bool) DiffWrite { if equal == nil { equal = reflect.DeepEqual } diff --git a/cmd/pkg-diff-example/main.go b/cmd/pkg-diff-example/main.go index 56d0f83..14d4ffb 100644 --- a/cmd/pkg-diff-example/main.go +++ b/cmd/pkg-diff-example/main.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/diff" "github.com/pkg/diff/ctxt" "github.com/pkg/diff/myers" + "github.com/pkg/diff/write" ) var ( @@ -77,12 +78,12 @@ func main() { } e := myers.Diff(ctx, ab) e = ctxt.Size(e, *unified) // limit amount of output context - opts := []diff.WriteOpt{ - diff.Names(aName, bName), + opts := []write.Option{ + write.Names(aName, bName), } if *color { - opts = append(opts, diff.TerminalColor()) + opts = append(opts, write.TerminalColor()) } - _, err = diff.WriteUnified(e, os.Stdout, ab, opts...) + _, err = write.Unified(e, os.Stdout, ab, opts...) check(err) } diff --git a/diff.go b/diff.go index e956beb..f8689a2 100644 --- a/diff.go +++ b/diff.go @@ -1,22 +1 @@ package diff - -import ( - "io" - - "github.com/pkg/diff/myers" -) - -// A WriterTo type supports writing a diff, element by element. -// A is the initial state; B is the final state. -type WriterTo interface { - // WriteATo writes the element a[ai] to w. - WriteATo(w io.Writer, ai int) (int, error) - // WriteBTo writes the element b[bi] to w. - WriteBTo(w io.Writer, bi int) (int, error) -} - -// PairWriterTo is the union of Pair and WriterTo. -type PairWriterTo interface { - myers.Pair - WriterTo -} diff --git a/example_test.go b/example_test.go index 152f5e7..2197b92 100644 --- a/example_test.go +++ b/example_test.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/diff" "github.com/pkg/diff/ctxt" "github.com/pkg/diff/myers" + "github.com/pkg/diff/write" ) // TODO: use a less heavyweight output format for Example_testHelper @@ -20,7 +21,7 @@ func Example_testHelper() { return } e = ctxt.Size(e, 1) - diff.WriteUnified(e, os.Stdout, ab) + write.Unified(e, os.Stdout, ab) // Output: // --- a // +++ b @@ -35,7 +36,7 @@ func Example_strings() { b := []string{"a", "c", "d"} ab := diff.Strings(a, b) e := myers.Diff(context.Background(), ab) - diff.WriteUnified(e, os.Stdout, ab) + write.Unified(e, os.Stdout, ab) // Output: // --- a // +++ b @@ -51,7 +52,7 @@ func Example_Names() { b := []string{"a", "c", "d"} ab := diff.Strings(a, b) e := myers.Diff(context.Background(), ab) - diff.WriteUnified(e, os.Stdout, ab, diff.Names("before", "after")) + write.Unified(e, os.Stdout, ab, write.Names("before", "after")) // Output: // --- before // +++ after diff --git a/go.mod b/go.mod index a582bb9..1904a9a 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/pkg/diff go 1.13 -require github.com/sergi/go-diff v1.0.0 +require ( + github.com/sergi/go-diff v1.0.0 + github.com/stretchr/testify v1.4.0 // indirect +) diff --git a/go.sum b/go.sum index 099e74e..e130bc2 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/write/errwriter.go b/write/errwriter.go new file mode 100644 index 0000000..cb8bcad --- /dev/null +++ b/write/errwriter.go @@ -0,0 +1,45 @@ +package write + +import "io" + +func newErrWriter(w io.Writer) *errwriter { + return &errwriter{w: w} +} + +// An errwriter wraps a writer. +// As soon as one write fails, it consumes all subsequent writes. +// This reduces the amount of error-checking required +// in write-heavy code. +type errwriter struct { + w io.Writer + err error + wrote int + attempted int +} + +func (w *errwriter) Write(b []byte) (int, error) { + w.attempted += len(b) + if w.err != nil { + return 0, w.err // TODO: use something like errors.Wrap(w.err)? + } + n, err := w.w.Write(b) + if err != nil { + w.err = err + } + w.wrote += n + return n, err +} + +func (w *errwriter) WriteString(s string) { + // TODO: use w.w's WriteString method, if it exists + w.Write([]byte(s)) +} + +func (w *errwriter) WriteByte(b byte) { + // TODO: use w.w's WriteByte method, if it exists + w.Write([]byte{b}) +} + +func (w *errwriter) Error() error { + return w.err +} diff --git a/write/option.go b/write/option.go new file mode 100644 index 0000000..9e22b1a --- /dev/null +++ b/write/option.go @@ -0,0 +1,39 @@ +// Package write provides routines for writing diffs. +package write + +// An Option modifies behavior when writing a diff. +type Option interface { + isOption() +} + +// Names provides the before/after names for writing a diff. +// They are traditionally filenames. +func Names(a, b string) Option { + return names{a, b} +} + +type names struct { + a, b string +} + +func (names) isOption() {} + +// TerminalColor specifies that a diff intended +// for a terminal should be written using colors. +// +// Do not use TerminalColor if TERM=dumb is set in the environment. +func TerminalColor() Option { + return colorOpt(true) +} + +type colorOpt bool + +func (colorOpt) isOption() {} + +const ( + ansiBold = "\u001b[1m" + ansiFgRed = "\u001b[31m" + ansiFgGreen = "\u001b[32m" + ansiFgBlue = "\u001b[36m" + ansiReset = "\u001b[0m" +) diff --git a/write/todo.go b/write/todo.go new file mode 100644 index 0000000..eb6c42b --- /dev/null +++ b/write/todo.go @@ -0,0 +1,7 @@ +package write + +// TODO: add diff writing that uses < and > (don't know what that is called) +// TODO: add side by side diffs +// TODO: add html diffs (?) +// TODO: add intraline highlighting? +// TODO: a way to specify alternative colors, like a ColorScheme write option diff --git a/print.go b/write/unified.go similarity index 66% rename from print.go rename to write/unified.go index 554a56b..25340e6 100644 --- a/print.go +++ b/write/unified.go @@ -1,4 +1,4 @@ -package diff +package write import ( "fmt" @@ -7,55 +7,21 @@ import ( "github.com/pkg/diff/edit" ) -// TODO: add diff writing that uses < and > (don't know what that is called) -// TODO: add side by side diffs -// TODO: add html diffs (?) -// TODO: add intraline highlighting? -// TODO: a way to specify alternative colors, like a ColorScheme write option - -// A WriteOpt is used to provide options when writing a diff. -type WriteOpt interface { - isWriteOpt() -} - -// Names provides the before/after names for writing a diff. -// They are traditionally filenames. -func Names(a, b string) WriteOpt { - return names{a, b} +// A Pair type supports writing a unified diff, element by element. +// A is the initial state; B is the final state. +type Pair interface { + // WriteATo writes the element a[aᵢ] to w. + WriteATo(w io.Writer, ai int) (int, error) + // WriteBTo writes the element b[bᵢ] to w. + WriteBTo(w io.Writer, bi int) (int, error) } -type names struct { - a, b string -} - -func (names) isWriteOpt() {} - -// TerminalColor specifies that a diff intended for a terminal should be written -// using red and green colors. -// -// Do not use TerminalColor if TERM=dumb is set in the environment. -func TerminalColor() WriteOpt { - return colorOpt(true) -} - -type colorOpt bool - -func (colorOpt) isWriteOpt() {} - -const ( - ansiBold = "\u001b[1m" - ansiFgRed = "\u001b[31m" - ansiFgGreen = "\u001b[32m" - ansiFgBlue = "\u001b[36m" - ansiReset = "\u001b[0m" -) - -// WriteUnified writes e to w using unified diff format. +// Unified writes e to w using unified diff format. // ab writes the individual elements. Opts are optional write arguments. -// WriteUnified returns the number of bytes written and the first error (if any) encountered. +// Unified returns the number of bytes written and the first error (if any) encountered. // Before writing, edit scripts usually have their context reduced, // such as by a call to ctxt.Size. -func WriteUnified(e edit.Script, w io.Writer, ab WriterTo, opts ...WriteOpt) (int, error) { +func Unified(e edit.Script, w io.Writer, ab Pair, opts ...Option) (int, error) { // read opts nameA := "a" nameB := "b" @@ -209,39 +175,3 @@ func (r lineRange) String() string { func (r lineRange) GoString() string { return fmt.Sprintf("(%d, %d)", r.first, r.last) } - -func newErrWriter(w io.Writer) *errwriter { - return &errwriter{w: w} -} - -type errwriter struct { - w io.Writer - err error - wrote int - attempted int -} - -func (w *errwriter) Write(b []byte) (int, error) { - w.attempted += len(b) - if w.err != nil { - return 0, w.err // TODO: use something like errors.Wrap(w.err)? - } - n, err := w.w.Write(b) - if err != nil { - w.err = err - } - w.wrote += n - return n, err -} - -func (w *errwriter) WriteString(s string) { - // TODO: use w.w's WriteString method, if it exists - w.Write([]byte(s)) -} - -func (w *errwriter) WriteByte(b byte) { - // TODO: use w.w's WriteByte method, if it exists - w.Write([]byte{b}) -} - -func (w *errwriter) Error() error { return w.err } diff --git a/unified_test.go b/write/unified_test.go similarity index 92% rename from unified_test.go rename to write/unified_test.go index d693a78..b765864 100644 --- a/unified_test.go +++ b/write/unified_test.go @@ -1,4 +1,4 @@ -package diff_test +package write_test import ( "bytes" @@ -9,13 +9,14 @@ import ( "github.com/pkg/diff" "github.com/pkg/diff/ctxt" "github.com/pkg/diff/myers" + "github.com/pkg/diff/write" "github.com/sergi/go-diff/diffmatchpatch" ) var goldenTests = []struct { name string a, b string - opts []diff.WriteOpt + opts []write.Option want string // usually from running diff --unified and cleaning up the output }{ { @@ -59,7 +60,7 @@ var goldenTests = []struct { name: "WithTerminalColor", a: "1\n2\n2", b: "1\n3\n3", - opts: []diff.WriteOpt{diff.TerminalColor()}, + opts: []write.Option{write.TerminalColor()}, want: ` `[1:] + "\u001b[1m" + `--- a +++ b @@ -85,7 +86,7 @@ func TestGolden(t *testing.T) { e := myers.Diff(context.Background(), ab) e = ctxt.Size(e, 3) buf := new(bytes.Buffer) - diff.WriteUnified(e, buf, ab, test.opts...) + write.Unified(e, buf, ab, test.opts...) got := buf.String() if test.want != got { t.Logf("%q\n", test.want)