fork of go-gitdiff with jj support
at v0.2.0 4.9 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 150// copyFrom writes bytes starting from offset off in src to dst stopping at the 151// end of src or at the first error. copyFrom returns the number of bytes 152// written and any error. 153func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) { 154 buf := make([]byte, 32*1024) // stolen from io.Copy 155 for { 156 nr, rerr := src.ReadAt(buf, off) 157 if nr > 0 { 158 nw, werr := dst.Write(buf[0:nr]) 159 if nw > 0 { 160 written += int64(nw) 161 } 162 if werr != nil { 163 err = werr 164 break 165 } 166 if nr != nw { 167 err = io.ErrShortWrite 168 break 169 } 170 } 171 if rerr != nil { 172 if rerr != io.EOF { 173 err = rerr 174 } 175 break 176 } 177 } 178 return written, err 179} 180 181// copyLinesFrom writes lines starting from line off in src to dst stopping at 182// the end of src or at the first error. copyLinesFrom returns the number of 183// lines written and any error. 184func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) { 185 buf := make([][]byte, 32) 186ReadLoop: 187 for { 188 nr, rerr := src.ReadLinesAt(buf, off) 189 if nr > 0 { 190 for _, line := range buf[0:nr] { 191 nw, werr := dst.Write(line) 192 if nw > 0 { 193 written++ 194 } 195 if werr != nil { 196 err = werr 197 break ReadLoop 198 } 199 if len(line) != nw { 200 err = io.ErrShortWrite 201 break ReadLoop 202 } 203 } 204 } 205 if rerr != nil { 206 if rerr != io.EOF { 207 err = rerr 208 } 209 break 210 } 211 } 212 return written, err 213}