···11package gitdiff
2233import (
44- "bufio"
54 "errors"
66- "fmt"
75 "io"
86)
99-1010-// StringReader is the interface that wraps the ReadString method.
1111-//
1212-// ReadString reads until the first occurrence of delim in the input, returning
1313-// a string containing the data up to and including the delimiter. If
1414-// ReadString encounters an error before finding a delimiter, it returns the
1515-// data read before the error and the error itself (often io.EOF). ReadString
1616-// returns err != nil if and only if the returned data does not end in delim.
1717-type StringReader interface {
1818- ReadString(delim byte) (string, error)
1919-}
2020-2121-type readStringReader interface {
2222- io.Reader
2323- StringReader
2424-}
2525-2626-// LineReader is the interface that wraps the ReadLine method.
2727-//
2828-// ReadLine reads the next full line in the input, returing the the data
2929-// including the line ending character(s) and the zero-indexed line number. If
3030-// ReadLine encounters an error before reaching the end of the line, it returns
3131-// the data read before the error, the number of the line, and the error itself
3232-// (often io.EOF). ReadLine returns err != nil if and only if the returned data
3333-// is not a complete line.
3434-//
3535-// If an implementation defines other methods for reading the same input, line
3636-// numbers may be incorrect if calls to ReadLine are mixed with calls to other
3737-// read methods.
3838-type LineReader interface {
3939- ReadLine() (string, int64, error)
4040-}
4141-4242-// NewLineReader returns a LineReader starting at a specific line and using the
4343-// newline character, \n, as a line separator. If r is a StringReader, it is
4444-// used directly. Otherwise, it is wrapped in a way that may read extra data
4545-// from the underlying input.
4646-func NewLineReader(r io.Reader, lineno int64) LineReader {
4747- sr, ok := r.(readStringReader)
4848- if !ok {
4949- sr = bufio.NewReader(r)
5050- }
5151- return &lineReader{r: sr, n: lineno}
5252-}
5353-5454-type lineReader struct {
5555- r readStringReader
5656- n int64
5757-}
5858-5959-func (lr *lineReader) ReadLine() (line string, lineno int64, err error) {
6060- lineno = lr.n
6161- line, err = lr.r.ReadString('\n')
6262- if err == nil {
6363- lr.n++
6464- }
6565- return
6666-}
6767-6868-// unwrapLineReader returns a plain io.Reader that was converted to a
6969-// LineReader by wrapping or casting. It should only be called from functions
7070-// that accept an io.Reader as an argument and then convert it.
7171-func unwrapLineReader(lr LineReader) io.Reader {
7272- switch r := lr.(type) {
7373- case io.Reader:
7474- return r
7575- case *lineReader:
7676- return r.r
7777- default:
7878- panic(fmt.Sprintf("%T does not implement io.Reader and is not a gitdiff wrapper", lr))
7979- }
8080-}
817828// LineReaderAt is the interface that wraps the ReadLinesAt method.
839//
-57
gitdiff/io_test.go
···11-package gitdiff
22-33-import (
44- "io"
55- "strings"
66- "testing"
77-)
88-99-func TestLineReader(t *testing.T) {
1010- const content = "first line\nsecond line\nthird line\npartial fourth line"
1111-1212- t.Run("readLine", func(t *testing.T) {
1313- r := NewLineReader(strings.NewReader(content), 0)
1414-1515- lines := []struct {
1616- Data string
1717- Err error
1818- }{
1919- {"first line\n", nil},
2020- {"second line\n", nil},
2121- {"third line\n", nil},
2222- {"partial fourth line", io.EOF},
2323- }
2424-2525- for i, line := range lines {
2626- d, n, err := r.ReadLine()
2727- if err != line.Err {
2828- if line.Err == nil {
2929- t.Fatalf("error reading line: %v", err)
3030- } else {
3131- t.Fatalf("expected %v while reading line, but got %v", line.Err, err)
3232- }
3333- }
3434- if d != line.Data {
3535- t.Errorf("incorrect line data: expected %q, actual %q", line.Data, d)
3636- }
3737- if n != int64(i) {
3838- t.Errorf("incorrect line number: expected %d, actual %d", i, n)
3939- }
4040- }
4141- })
4242-4343- t.Run("readLineOffset", func(t *testing.T) {
4444- r := NewLineReader(strings.NewReader(content), 10)
4545-4646- d, n, err := r.ReadLine()
4747- if err != nil {
4848- t.Fatalf("error reading line: %v", err)
4949- }
5050- if d != "first line\n" {
5151- t.Errorf("incorrect line data: expected %q, actual %q", "first line\n", d)
5252- }
5353- if n != 10 {
5454- t.Errorf("incorrect line number: expected %d, actual %d", 10, n)
5555- }
5656- })
5757-}
+10-8
gitdiff/parser.go
···44package gitdiff
5566import (
77+ "bufio"
78 "fmt"
89 "io"
910)
···1112// Parse parses a patch with changes to one or more files. Any content before
1213// the first file is returned as the second value. If an error occurs while
1314// parsing, it returns all files parsed before the error.
1414-//
1515-// If r is a LineReader or StringReader, it is used directly. Otherwise, it is
1616-// wrapped in a way that may read extra data from the underlying input.
1715func Parse(r io.Reader) ([]*File, string, error) {
1816 p := newParser(r)
1917···6967// - if returning an object, advance to the first line after the object
7068// - any exported parsing methods must initialize the parser by calling Next()
71697070+type stringReader interface {
7171+ ReadString(delim byte) (string, error)
7272+}
7373+7274type parser struct {
7373- r LineReader
7575+ r stringReader
74767577 eof bool
7678 lineno int64
···7880}
79818082func newParser(r io.Reader) *parser {
8181- if lr, ok := r.(LineReader); ok {
8282- return &parser{r: lr}
8383+ if r, ok := r.(stringReader); ok {
8484+ return &parser{r: r}
8385 }
8484- return &parser{r: NewLineReader(r, 0)}
8686+ return &parser{r: bufio.NewReader(r)}
8587}
86888789// Next advances the parser by one line. It returns any error encountered while
···117119 for i := 0; i < len(p.lines)-1; i++ {
118120 p.lines[i] = p.lines[i+1]
119121 }
120120- p.lines[len(p.lines)-1], _, err = p.r.ReadLine()
122122+ p.lines[len(p.lines)-1], err = p.r.ReadString('\n')
121123 return
122124}
123125