diff --git a/diff/diff.go b/diff/diff.go index 81aa655..cc19fe5 100644 --- a/diff/diff.go +++ b/diff/diff.go @@ -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 diff --git a/diff/parse.go b/diff/parse.go index 48eeb96..df6c140 100644 --- a/diff/parse.go +++ b/diff/parse.go @@ -1,7 +1,6 @@ package diff import ( - "bufio" "bytes" "errors" "fmt" @@ -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. @@ -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. @@ -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 @@ -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. diff --git a/diff/reader_util.go b/diff/reader_util.go index 4530025..f1c17be 100644 --- a/diff/reader_util.go +++ b/diff/reader_util.go @@ -13,6 +13,13 @@ 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 { @@ -20,13 +27,15 @@ type lineReader struct { 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 { @@ -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 } @@ -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) @@ -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)) @@ -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 { @@ -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. diff --git a/diff/reader_util_test.go b/diff/reader_util_test.go index 8dd0016..760fcb7 100644 --- a/diff/reader_util_test.go +++ b/diff/reader_util_test.go @@ -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 } @@ -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)) + } +}