Skip to content

Commit

Permalink
write: create package
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
josharian committed Dec 30, 2019
1 parent b63e0fa commit a78b239
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 120 deletions.
22 changes: 16 additions & 6 deletions adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}

Expand All @@ -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}
}

Expand All @@ -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
}
Expand Down
9 changes: 5 additions & 4 deletions cmd/pkg-diff-example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
}
21 changes: 0 additions & 21 deletions diff.go
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 4 additions & 3 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
45 changes: 45 additions & 0 deletions write/errwriter.go
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 39 additions & 0 deletions write/option.go
Original file line number Diff line number Diff line change
@@ -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"
)
7 changes: 7 additions & 0 deletions write/todo.go
Original file line number Diff line number Diff line change
@@ -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
92 changes: 11 additions & 81 deletions print.go → write/unified.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package diff
package write

import (
"fmt"
Expand All @@ -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"
Expand Down Expand Up @@ -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 }
9 changes: 5 additions & 4 deletions unified_test.go → write/unified_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package diff_test
package write_test

import (
"bytes"
Expand All @@ -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
}{
{
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit a78b239

Please sign in to comment.