this repo has no description
1package gitdiff
2
3import (
4 "errors"
5 "io"
6)
7
8// LineReaderAt is the interface that wraps the ReadLinesAt method.
9//
10// ReadLinesAt reads len(lines) into lines starting at line offset in the
11// input source. It returns number of full lines read (0 <= n <= len(lines))
12// and any error encountered. Line numbers are zero-indexed.
13//
14// If n < len(lines), ReadLinesAt returns a non-nil error explaining why more
15// lines were not returned.
16//
17// Each full line includes the line ending character(s). If the last line of
18// the input does not have a line ending character, ReadLinesAt returns the
19// content of the line and io.EOF.
20//
21// If the content of the input source changes after the first call to
22// ReadLinesAt, the behavior of future calls is undefined.
23type LineReaderAt interface {
24 ReadLinesAt(lines [][]byte, offset int64) (n int, err error)
25}
26
27type lineReaderAt struct {
28 r io.ReaderAt
29 index []int64
30 eof bool
31}
32
33func (r *lineReaderAt) ReadLinesAt(lines [][]byte, offset int64) (n int, err error) {
34 // TODO(bkeyes): revisit variable names
35 // - it's generally not clear when something is bytes vs lines
36 // - offset is a good example of this
37
38 if len(lines) == 0 {
39 return 0, nil
40 }
41
42 endLine := offset + int64(len(lines))
43 if endLine > int64(len(r.index)) && !r.eof {
44 if err := r.indexTo(endLine); err != nil {
45 return 0, err
46 }
47 }
48 if offset > int64(len(r.index)) {
49 return 0, io.EOF
50 }
51
52 size, readOffset := lookupLines(r.index, offset, int64(len(lines)))
53
54 b := make([]byte, size)
55 if _, err := r.r.ReadAt(b, readOffset); err != nil {
56 if err == io.EOF {
57 err = errors.New("ReadLinesAt: corrupt line index or changed source data")
58 }
59 return 0, err
60 }
61
62 for n = 0; n < len(lines) && offset+int64(n) < int64(len(r.index)); n++ {
63 i := offset + int64(n)
64 start, end := readOffset, r.index[i]
65 if i > 0 {
66 start = r.index[i-1]
67 }
68 lines[n] = b[start-readOffset : end-readOffset]
69 }
70
71 if n < len(lines) || b[size-1] != '\n' {
72 return n, io.EOF
73 }
74 return n, nil
75}
76
77// indexTo reads data and computes the line index until there is information
78// for line or a read returns io.EOF. It returns an error if and only if there
79// is an error reading data.
80func (r *lineReaderAt) indexTo(line int64) error {
81 var buf [1024]byte
82
83 var offset int64
84 if len(r.index) > 0 {
85 offset = r.index[len(r.index)-1]
86 }
87
88 for int64(len(r.index)) < line {
89 n, err := r.r.ReadAt(buf[:], offset)
90 if err != nil && err != io.EOF {
91 return err
92 }
93 for _, b := range buf[:n] {
94 offset++
95 if b == '\n' {
96 r.index = append(r.index, offset)
97 }
98 }
99 if err == io.EOF {
100 if n > 0 && buf[n-1] != '\n' {
101 r.index = append(r.index, offset)
102 }
103 r.eof = true
104 break
105 }
106 }
107 return nil
108}
109
110// lookupLines gets the byte offset and size of a range of lines from an index
111// where the value at n is the offset of the first byte after line number n.
112func lookupLines(index []int64, start, n int64) (size int64, offset int64) {
113 if start > int64(len(index)) {
114 offset = index[len(index)-1]
115 } else if start > 0 {
116 offset = index[start-1]
117 }
118 if n > 0 {
119 if start+n > int64(len(index)) {
120 size = index[len(index)-1] - offset
121 } else {
122 size = index[start+n-1] - offset
123 }
124 }
125 return
126}
127
128func isLen(r io.ReaderAt, n int64) (bool, error) {
129 off := n - 1
130 if off < 0 {
131 off = 0
132 }
133
134 var b [2]byte
135 nr, err := r.ReadAt(b[:], off)
136 if err == io.EOF {
137 return (n == 0 && nr == 0) || (n > 0 && nr == 1), nil
138 }
139 return false, err
140}
141
142// copyFrom writes bytes starting from offset off in src to dst stopping at the
143// end of src or at the first error. copyFrom returns the number of bytes
144// written and any error.
145func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) {
146 buf := make([]byte, 32*1024) // stolen from io.Copy
147 for {
148 nr, rerr := src.ReadAt(buf, off)
149 if nr > 0 {
150 nw, werr := dst.Write(buf[0:nr])
151 if nw > 0 {
152 written += int64(nw)
153 }
154 if werr != nil {
155 err = werr
156 break
157 }
158 if nr != nw {
159 err = io.ErrShortWrite
160 break
161 }
162 }
163 if rerr != nil {
164 if rerr != io.EOF {
165 err = rerr
166 }
167 break
168 }
169 }
170 return written, err
171}
172
173// copyLinesFrom writes lines starting from line off in src to dst stopping at
174// the end of src or at the first error. copyLinesFrom returns the number of
175// lines written and any error.
176func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) {
177 buf := make([][]byte, 32)
178ReadLoop:
179 for {
180 nr, rerr := src.ReadLinesAt(buf, off)
181 if nr > 0 {
182 for _, line := range buf[0:nr] {
183 nw, werr := dst.Write(line)
184 if nw > 0 {
185 written++
186 }
187 if werr != nil {
188 err = werr
189 break ReadLoop
190 }
191 if len(line) != nw {
192 err = io.ErrShortWrite
193 break ReadLoop
194 }
195 }
196 }
197 if rerr != nil {
198 if rerr != io.EOF {
199 err = rerr
200 }
201 break
202 }
203 }
204 return written, err
205}