-74
gitdiff/io.go
-74
gitdiff/io.go
···
1
package gitdiff
2
3
import (
4
-
"bufio"
5
"errors"
6
-
"fmt"
7
"io"
8
)
9
-
10
-
// StringReader is the interface that wraps the ReadString method.
11
-
//
12
-
// ReadString reads until the first occurrence of delim in the input, returning
13
-
// a string containing the data up to and including the delimiter. If
14
-
// ReadString encounters an error before finding a delimiter, it returns the
15
-
// data read before the error and the error itself (often io.EOF). ReadString
16
-
// returns err != nil if and only if the returned data does not end in delim.
17
-
type StringReader interface {
18
-
ReadString(delim byte) (string, error)
19
-
}
20
-
21
-
type readStringReader interface {
22
-
io.Reader
23
-
StringReader
24
-
}
25
-
26
-
// LineReader is the interface that wraps the ReadLine method.
27
-
//
28
-
// ReadLine reads the next full line in the input, returing the the data
29
-
// including the line ending character(s) and the zero-indexed line number. If
30
-
// ReadLine encounters an error before reaching the end of the line, it returns
31
-
// the data read before the error, the number of the line, and the error itself
32
-
// (often io.EOF). ReadLine returns err != nil if and only if the returned data
33
-
// is not a complete line.
34
-
//
35
-
// If an implementation defines other methods for reading the same input, line
36
-
// numbers may be incorrect if calls to ReadLine are mixed with calls to other
37
-
// read methods.
38
-
type LineReader interface {
39
-
ReadLine() (string, int64, error)
40
-
}
41
-
42
-
// NewLineReader returns a LineReader starting at a specific line and using the
43
-
// newline character, \n, as a line separator. If r is a StringReader, it is
44
-
// used directly. Otherwise, it is wrapped in a way that may read extra data
45
-
// from the underlying input.
46
-
func NewLineReader(r io.Reader, lineno int64) LineReader {
47
-
sr, ok := r.(readStringReader)
48
-
if !ok {
49
-
sr = bufio.NewReader(r)
50
-
}
51
-
return &lineReader{r: sr, n: lineno}
52
-
}
53
-
54
-
type lineReader struct {
55
-
r readStringReader
56
-
n int64
57
-
}
58
-
59
-
func (lr *lineReader) ReadLine() (line string, lineno int64, err error) {
60
-
lineno = lr.n
61
-
line, err = lr.r.ReadString('\n')
62
-
if err == nil {
63
-
lr.n++
64
-
}
65
-
return
66
-
}
67
-
68
-
// unwrapLineReader returns a plain io.Reader that was converted to a
69
-
// LineReader by wrapping or casting. It should only be called from functions
70
-
// that accept an io.Reader as an argument and then convert it.
71
-
func unwrapLineReader(lr LineReader) io.Reader {
72
-
switch r := lr.(type) {
73
-
case io.Reader:
74
-
return r
75
-
case *lineReader:
76
-
return r.r
77
-
default:
78
-
panic(fmt.Sprintf("%T does not implement io.Reader and is not a gitdiff wrapper", lr))
79
-
}
80
-
}
81
82
// LineReaderAt is the interface that wraps the ReadLinesAt method.
83
//
-57
gitdiff/io_test.go
-57
gitdiff/io_test.go
···
1
-
package gitdiff
2
-
3
-
import (
4
-
"io"
5
-
"strings"
6
-
"testing"
7
-
)
8
-
9
-
func TestLineReader(t *testing.T) {
10
-
const content = "first line\nsecond line\nthird line\npartial fourth line"
11
-
12
-
t.Run("readLine", func(t *testing.T) {
13
-
r := NewLineReader(strings.NewReader(content), 0)
14
-
15
-
lines := []struct {
16
-
Data string
17
-
Err error
18
-
}{
19
-
{"first line\n", nil},
20
-
{"second line\n", nil},
21
-
{"third line\n", nil},
22
-
{"partial fourth line", io.EOF},
23
-
}
24
-
25
-
for i, line := range lines {
26
-
d, n, err := r.ReadLine()
27
-
if err != line.Err {
28
-
if line.Err == nil {
29
-
t.Fatalf("error reading line: %v", err)
30
-
} else {
31
-
t.Fatalf("expected %v while reading line, but got %v", line.Err, err)
32
-
}
33
-
}
34
-
if d != line.Data {
35
-
t.Errorf("incorrect line data: expected %q, actual %q", line.Data, d)
36
-
}
37
-
if n != int64(i) {
38
-
t.Errorf("incorrect line number: expected %d, actual %d", i, n)
39
-
}
40
-
}
41
-
})
42
-
43
-
t.Run("readLineOffset", func(t *testing.T) {
44
-
r := NewLineReader(strings.NewReader(content), 10)
45
-
46
-
d, n, err := r.ReadLine()
47
-
if err != nil {
48
-
t.Fatalf("error reading line: %v", err)
49
-
}
50
-
if d != "first line\n" {
51
-
t.Errorf("incorrect line data: expected %q, actual %q", "first line\n", d)
52
-
}
53
-
if n != 10 {
54
-
t.Errorf("incorrect line number: expected %d, actual %d", 10, n)
55
-
}
56
-
})
57
-
}
···
+10
-8
gitdiff/parser.go
+10
-8
gitdiff/parser.go
···
4
package gitdiff
5
6
import (
7
"fmt"
8
"io"
9
)
···
11
// Parse parses a patch with changes to one or more files. Any content before
12
// the first file is returned as the second value. If an error occurs while
13
// parsing, it returns all files parsed before the error.
14
-
//
15
-
// If r is a LineReader or StringReader, it is used directly. Otherwise, it is
16
-
// wrapped in a way that may read extra data from the underlying input.
17
func Parse(r io.Reader) ([]*File, string, error) {
18
p := newParser(r)
19
···
69
// - if returning an object, advance to the first line after the object
70
// - any exported parsing methods must initialize the parser by calling Next()
71
72
type parser struct {
73
-
r LineReader
74
75
eof bool
76
lineno int64
···
78
}
79
80
func newParser(r io.Reader) *parser {
81
-
if lr, ok := r.(LineReader); ok {
82
-
return &parser{r: lr}
83
}
84
-
return &parser{r: NewLineReader(r, 0)}
85
}
86
87
// Next advances the parser by one line. It returns any error encountered while
···
117
for i := 0; i < len(p.lines)-1; i++ {
118
p.lines[i] = p.lines[i+1]
119
}
120
-
p.lines[len(p.lines)-1], _, err = p.r.ReadLine()
121
return
122
}
123
···
4
package gitdiff
5
6
import (
7
+
"bufio"
8
"fmt"
9
"io"
10
)
···
12
// Parse parses a patch with changes to one or more files. Any content before
13
// the first file is returned as the second value. If an error occurs while
14
// parsing, it returns all files parsed before the error.
15
func Parse(r io.Reader) ([]*File, string, error) {
16
p := newParser(r)
17
···
67
// - if returning an object, advance to the first line after the object
68
// - any exported parsing methods must initialize the parser by calling Next()
69
70
+
type stringReader interface {
71
+
ReadString(delim byte) (string, error)
72
+
}
73
+
74
type parser struct {
75
+
r stringReader
76
77
eof bool
78
lineno int64
···
80
}
81
82
func newParser(r io.Reader) *parser {
83
+
if r, ok := r.(stringReader); ok {
84
+
return &parser{r: r}
85
}
86
+
return &parser{r: bufio.NewReader(r)}
87
}
88
89
// Next advances the parser by one line. It returns any error encountered while
···
119
for i := 0; i < len(p.lines)-1; i++ {
120
p.lines[i] = p.lines[i+1]
121
}
122
+
p.lines[len(p.lines)-1], err = p.r.ReadString('\n')
123
return
124
}
125