Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import (
"time"
)

// ParseOptions specifies options for parsing diffs.
type ParseOptions struct {
// KeepCR specifies whether to keep trailing carriage return characters (\r) in lines.
KeepCR bool
}

// A FileDiff represents a unified diff for a single file.
//
// A file unified diff has a header that resembles the following:
//
// --- oldname 2009-10-11 15:12:20.000000000 -0700
// +++ newname 2009-10-11 15:12:30.000000000 -0700
// --- oldname 2009-10-11 15:12:20.000000000 -0700
// +++ newname 2009-10-11 15:12:30.000000000 -0700
type FileDiff struct {
// the original name of the file
OrigName string
Expand Down
46 changes: 39 additions & 7 deletions diff/parse.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package diff

import (
"bufio"
"bytes"
"errors"
"fmt"
Expand All @@ -17,13 +16,24 @@ import (
// case of per-file errors. If it cannot detect when the diff of the next file
// begins, the hunks are added to the FileDiff of the previous file.
func ParseMultiFileDiff(diff []byte) ([]*FileDiff, error) {
return NewMultiFileDiffReader(bytes.NewReader(diff)).ReadAllFiles()
return ParseMultiFileDiffOptions(diff, ParseOptions{})
}

// ParseMultiFileDiffOptions parses a multi-file unified diff with the given options.
func ParseMultiFileDiffOptions(diff []byte, opts ParseOptions) ([]*FileDiff, error) {
return NewMultiFileDiffReaderOptions(bytes.NewReader(diff), opts).ReadAllFiles()
}

// NewMultiFileDiffReader returns a new MultiFileDiffReader that reads
// a multi-file unified diff from r.
func NewMultiFileDiffReader(r io.Reader) *MultiFileDiffReader {
return &MultiFileDiffReader{reader: newLineReader(r)}
return NewMultiFileDiffReaderOptions(r, ParseOptions{})
}

// NewMultiFileDiffReaderOptions returns a new MultiFileDiffReader that reads
// a multi-file unified diff from r with the given options.
func NewMultiFileDiffReaderOptions(r io.Reader, opts ParseOptions) *MultiFileDiffReader {
return &MultiFileDiffReader{reader: newLineReaderOptions(r, opts)}
}

// MultiFileDiffReader reads a multi-file unified diff.
Expand Down Expand Up @@ -153,13 +163,24 @@ func (r *MultiFileDiffReader) ReadAllFiles() ([]*FileDiff, error) {

// ParseFileDiff parses a file unified diff.
func ParseFileDiff(diff []byte) (*FileDiff, error) {
return NewFileDiffReader(bytes.NewReader(diff)).Read()
return ParseFileDiffOptions(diff, ParseOptions{})
}

// ParseFileDiffOptions parses a file unified diff with the given options.
func ParseFileDiffOptions(diff []byte, opts ParseOptions) (*FileDiff, error) {
return NewFileDiffReaderOptions(bytes.NewReader(diff), opts).Read()
}

// NewFileDiffReader returns a new FileDiffReader that reads a file
// unified diff.
func NewFileDiffReader(r io.Reader) *FileDiffReader {
return &FileDiffReader{reader: &lineReader{reader: bufio.NewReader(r)}}
return NewFileDiffReaderOptions(r, ParseOptions{})
}

// NewFileDiffReaderOptions returns a new FileDiffReader that reads a file
// unified diff with the given options.
func NewFileDiffReaderOptions(r io.Reader, opts ParseOptions) *FileDiffReader {
return &FileDiffReader{reader: newLineReaderOptions(r, opts)}
}

// FileDiffReader reads a unified file diff.
Expand Down Expand Up @@ -586,7 +607,12 @@ var (
// only of hunks and not include a file header; if it has a file
// header, use ParseFileDiff.
func ParseHunks(diff []byte) ([]*Hunk, error) {
r := NewHunksReader(bytes.NewReader(diff))
return ParseHunksOptions(diff, ParseOptions{})
}

// ParseHunksOptions parses hunks from a unified diff with the given options.
func ParseHunksOptions(diff []byte, opts ParseOptions) ([]*Hunk, error) {
r := NewHunksReaderOptions(bytes.NewReader(diff), opts)
hunks, err := r.ReadAllHunks()
if err != nil {
return nil, err
Expand All @@ -597,7 +623,13 @@ func ParseHunks(diff []byte) ([]*Hunk, error) {
// NewHunksReader returns a new HunksReader that reads unified diff hunks
// from r.
func NewHunksReader(r io.Reader) *HunksReader {
return &HunksReader{reader: &lineReader{reader: bufio.NewReader(r)}}
return NewHunksReaderOptions(r, ParseOptions{})
}

// NewHunksReaderOptions returns a new HunksReader that reads unified diff hunks
// from r with the given options.
func NewHunksReaderOptions(r io.Reader, opts ParseOptions) *HunksReader {
return &HunksReader{reader: newLineReaderOptions(r, opts)}
}

// A HunksReader reads hunks from a unified diff.
Expand Down
27 changes: 21 additions & 6 deletions diff/reader_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,29 @@ func newLineReader(r io.Reader) *lineReader {
return &lineReader{reader: bufio.NewReader(r)}
}

func newLineReaderOptions(r io.Reader, opts ParseOptions) *lineReader {
return &lineReader{
reader: bufio.NewReader(r),
keepCR: opts.KeepCR,
}
}

// lineReader is a wrapper around a bufio.Reader that caches the next line to
// provide lookahead functionality for the next two lines.
type lineReader struct {
reader *bufio.Reader

cachedNextLine []byte
cachedNextLineErr error

keepCR bool
}

// readLine returns the next unconsumed line and advances the internal cache of
// the lineReader.
func (l *lineReader) readLine() ([]byte, error) {
if l.cachedNextLine == nil && l.cachedNextLineErr == nil {
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader)
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader, l.keepCR)
}

if l.cachedNextLineErr != nil {
Expand All @@ -35,7 +44,7 @@ func (l *lineReader) readLine() ([]byte, error) {

next := l.cachedNextLine

l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader)
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader, l.keepCR)

return next, nil
}
Expand All @@ -47,7 +56,7 @@ func (l *lineReader) readLine() ([]byte, error) {
// be used when at the end of the file.
func (l *lineReader) nextLineStartsWith(prefix string) (bool, error) {
if l.cachedNextLine == nil && l.cachedNextLineErr == nil {
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader)
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader, l.keepCR)
}

return l.lineHasPrefix(l.cachedNextLine, prefix, l.cachedNextLineErr)
Expand All @@ -64,7 +73,7 @@ func (l *lineReader) nextLineStartsWith(prefix string) (bool, error) {
// returned.
func (l *lineReader) nextNextLineStartsWith(prefix string) (bool, error) {
if l.cachedNextLine == nil && l.cachedNextLineErr == nil {
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader)
l.cachedNextLine, l.cachedNextLineErr = readLine(l.reader, l.keepCR)
}

next, err := l.reader.Peek(len(prefix))
Expand Down Expand Up @@ -93,7 +102,7 @@ func (l *lineReader) lineHasPrefix(line []byte, prefix string, readErr error) (b
// the next line in the Reader with the trailing newline stripped. It will return an
// io.EOF error when there is nothing left to read (at the start of the function call). It
// will return any other errors it receives from the underlying call to ReadBytes.
func readLine(r *bufio.Reader) ([]byte, error) {
func readLine(r *bufio.Reader, keepCR bool) ([]byte, error) {
line_, err := r.ReadBytes('\n')
if err == io.EOF {
if len(line_) == 0 {
Expand All @@ -103,12 +112,18 @@ func readLine(r *bufio.Reader) ([]byte, error) {
// ReadBytes returned io.EOF, because it didn't find another newline, but there is
// still the remainder of the file to return as a line.
line := line_
if !keepCR {
return dropCR(line), nil
}
return line, nil
} else if err != nil {
return nil, err
}
line := line_[0 : len(line_)-1]
return dropCR(line), nil
if !keepCR {
return dropCR(line), nil
}
return line, nil
}

// dropCR drops a terminal \r from the data.
Expand Down
23 changes: 22 additions & 1 deletion diff/reader_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ index 0000000..3be2928`,
in := bufio.NewReader(strings.NewReader(test.input))
out := []string{}
for {
l, err := readLine(in)
l, err := readLine(in, false)
if err == io.EOF {
break
}
Expand Down Expand Up @@ -207,3 +207,24 @@ ccc rest of line`

}
}

func TestReadLine_KeepCR(t *testing.T) {
input := "line1\r\nline2\r\n"
in := bufio.NewReader(strings.NewReader(input))

l, err := readLine(in, true)
if err != nil {
t.Fatal(err)
}
if string(l) != "line1\r" {
t.Errorf("expected line1\\r, got %q", string(l))
}

l, err = readLine(in, true)
if err != nil {
t.Fatal(err)
}
if string(l) != "line2\r" {
t.Errorf("expected line2\\r, got %q", string(l))
}
}