fork of go-gitdiff with jj support
at v0.5.2 5.0 kB view raw
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 if offset < 0 { 35 return 0, errors.New("ReadLinesAt: negative offset") 36 } 37 if len(lines) == 0 { 38 return 0, nil 39 } 40 41 count := len(lines) 42 startLine := offset 43 endLine := startLine + int64(count) 44 45 if endLine > int64(len(r.index)) && !r.eof { 46 if err := r.indexTo(endLine); err != nil { 47 return 0, err 48 } 49 } 50 if startLine >= int64(len(r.index)) { 51 return 0, io.EOF 52 } 53 54 buf, byteOffset, err := r.readBytes(startLine, int64(count)) 55 if err != nil { 56 return 0, err 57 } 58 59 for n = 0; n < count && startLine+int64(n) < int64(len(r.index)); n++ { 60 lineno := startLine + int64(n) 61 start, end := int64(0), r.index[lineno]-byteOffset 62 if lineno > 0 { 63 start = r.index[lineno-1] - byteOffset 64 } 65 lines[n] = buf[start:end] 66 } 67 68 if n < count || buf[len(buf)-1] != '\n' { 69 return n, io.EOF 70 } 71 return n, nil 72} 73 74// indexTo reads data and computes the line index until there is information 75// for line or a read returns io.EOF. It returns an error if and only if there 76// is an error reading data. 77func (r *lineReaderAt) indexTo(line int64) error { 78 var buf [1024]byte 79 80 var offset int64 81 if len(r.index) > 0 { 82 offset = r.index[len(r.index)-1] 83 } 84 85 for int64(len(r.index)) < line { 86 n, err := r.r.ReadAt(buf[:], offset) 87 if err != nil && err != io.EOF { 88 return err 89 } 90 for _, b := range buf[:n] { 91 offset++ 92 if b == '\n' { 93 r.index = append(r.index, offset) 94 } 95 } 96 if err == io.EOF { 97 if n > 0 && buf[n-1] != '\n' { 98 r.index = append(r.index, offset) 99 } 100 r.eof = true 101 break 102 } 103 } 104 return nil 105} 106 107// readBytes reads the bytes of the n lines starting at line and returns the 108// bytes and the offset of the first byte in the underlying source. 109func (r *lineReaderAt) readBytes(line, n int64) (b []byte, offset int64, err error) { 110 indexLen := int64(len(r.index)) 111 112 var size int64 113 if line > indexLen { 114 offset = r.index[indexLen-1] 115 } else if line > 0 { 116 offset = r.index[line-1] 117 } 118 if n > 0 { 119 if line+n > indexLen { 120 size = r.index[indexLen-1] - offset 121 } else { 122 size = r.index[line+n-1] - offset 123 } 124 } 125 126 b = make([]byte, size) 127 if _, err := r.r.ReadAt(b, offset); err != nil { 128 if err == io.EOF { 129 err = errors.New("ReadLinesAt: corrupt line index or changed source data") 130 } 131 return nil, 0, err 132 } 133 return b, offset, nil 134} 135 136func isLen(r io.ReaderAt, n int64) (bool, error) { 137 off := n - 1 138 if off < 0 { 139 off = 0 140 } 141 142 var b [2]byte 143 nr, err := r.ReadAt(b[:], off) 144 if err == io.EOF { 145 return (n == 0 && nr == 0) || (n > 0 && nr == 1), nil 146 } 147 return false, err 148} 149 150const ( 151 byteBufferSize = 32 * 1024 // from io.Copy 152 lineBufferSize = 32 153) 154 155// copyFrom writes bytes starting from offset off in src to dst stopping at the 156// end of src or at the first error. copyFrom returns the number of bytes 157// written and any error. 158func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) { 159 buf := make([]byte, byteBufferSize) 160 for { 161 nr, rerr := src.ReadAt(buf, off) 162 if nr > 0 { 163 nw, werr := dst.Write(buf[0:nr]) 164 if nw > 0 { 165 written += int64(nw) 166 } 167 if werr != nil { 168 err = werr 169 break 170 } 171 if nr != nw { 172 err = io.ErrShortWrite 173 break 174 } 175 off += int64(nr) 176 } 177 if rerr != nil { 178 if rerr != io.EOF { 179 err = rerr 180 } 181 break 182 } 183 } 184 return written, err 185} 186 187// copyLinesFrom writes lines starting from line off in src to dst stopping at 188// the end of src or at the first error. copyLinesFrom returns the number of 189// lines written and any error. 190func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) { 191 buf := make([][]byte, lineBufferSize) 192ReadLoop: 193 for { 194 nr, rerr := src.ReadLinesAt(buf, off) 195 if nr > 0 { 196 for _, line := range buf[0:nr] { 197 nw, werr := dst.Write(line) 198 if nw > 0 { 199 written++ 200 } 201 if werr != nil { 202 err = werr 203 break ReadLoop 204 } 205 if len(line) != nw { 206 err = io.ErrShortWrite 207 break ReadLoop 208 } 209 } 210 off += int64(nr) 211 } 212 if rerr != nil { 213 if rerr != io.EOF { 214 err = rerr 215 } 216 break 217 } 218 } 219 return written, err 220}