this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add initial pass at ApplyStrict functions

Text fragment application is implemented but untested and with several
TODOs, binary fragment application is unimplemented and will panic.

+142 -2
+138
gitdiff/apply.go
··· 1 + package gitdiff 2 + 3 + import ( 4 + "errors" 5 + "io" 6 + ) 7 + 8 + // ApplyStrict writes data from src to dst, modifying it as described by the 9 + // fragments in the file. For text files, each fragment, including all context 10 + // lines, must exactly match src at the expected line number. 11 + // 12 + // If the file contains no fragments, ApplyStrict is equivalent to io.Copy. 13 + func (f *File) ApplyStrict(dst io.Writer, src io.Reader) error { 14 + if f.IsBinary { 15 + if f.BinaryFragment != nil { 16 + return f.BinaryFragment.Apply(dst, src) 17 + } 18 + _, err := io.Copy(dst, src) 19 + return err 20 + } 21 + 22 + lr, ok := src.(LineReader) 23 + if !ok { 24 + lr = NewLineReader(src, 0) 25 + } 26 + 27 + for _, frag := range f.TextFragments { 28 + if err := frag.ApplyStrict(dst, lr); err != nil { 29 + return err 30 + } 31 + } 32 + 33 + _, err := io.Copy(dst, unwrapLineReader(lr)) 34 + return err 35 + } 36 + 37 + // ApplyStrict writes data from src to dst, modifying it as described by the 38 + // fragment. The fragment, including all context lines, must exactly match src 39 + // at the expected line number. 40 + // 41 + // If there is no error, the next read from src returns the line immediately 42 + // after the last line of the fragment. 43 + func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReader) error { 44 + // application code assumes fragment fields are consistent 45 + if err := f.Validate(); err != nil { 46 + // TODO(bkeyes): wrap with additional context 47 + return err 48 + } 49 + 50 + // line numbers are zero-indexed, positions are one-indexed 51 + limit := f.OldPosition - 1 52 + 53 + // an EOF is allowed here: the fragment applies to the last line of the 54 + // source but it does not have a newline character 55 + nextLine, err := copyLines(dst, src, limit) 56 + if err != nil && err != io.EOF { 57 + // TODO(bkeyes): wrap with additional context 58 + return err 59 + } 60 + 61 + for i, line := range f.Lines { 62 + fromSrc, err := applyTextLine(dst, nextLine, line) 63 + if err != nil { 64 + // TODO(bkeyes): wrap with additional context 65 + return err 66 + } 67 + 68 + if fromSrc && i < len(f.Lines)-1 { 69 + nextLine, _, err = src.ReadLine() 70 + if err != nil { 71 + if err == io.EOF { 72 + err = io.ErrUnexpectedEOF 73 + } 74 + // TODO(bkeyes): wrap with additional context 75 + return err 76 + } 77 + } 78 + } 79 + 80 + return nil 81 + } 82 + 83 + func applyTextLine(dst io.Writer, srcLine string, line Line) (fromSrc bool, err error) { 84 + switch line.Op { 85 + case OpContext, OpDelete: 86 + fromSrc = true 87 + if srcLine != line.Line { 88 + // TODO(bkeyes): use special error type here 89 + // TODO(bkeyes): include line number information, etc. 90 + return fromSrc, errors.New("apply: fragment match failed: line does not match") 91 + } 92 + } 93 + 94 + switch line.Op { 95 + case OpContext, OpAdd: 96 + // TODO(bkeyes): wrap with additional context 97 + _, err = io.WriteString(dst, line.Line) 98 + } 99 + return 100 + } 101 + 102 + // copyLines copies from src to dst until the line at limit, exclusive. The 103 + // line at limit is returned. A negative limit means the first read should 104 + // return io.EOF and no data. 105 + func copyLines(dst io.Writer, src LineReader, limit int64) (string, error) { 106 + for { 107 + line, n, err := src.ReadLine() 108 + switch { 109 + case limit < 0 && err == io.EOF && line == "": 110 + return "", nil 111 + case int64(n) == limit: 112 + return line, err 113 + case int64(n) > limit: 114 + if limit < 0 { 115 + return "", errors.New("src is not empty") 116 + } 117 + return "", errors.New("overlapping fragments") 118 + case err != nil: 119 + if err == io.EOF { 120 + err = io.ErrUnexpectedEOF 121 + } 122 + return line, err 123 + } 124 + 125 + if _, err := io.WriteString(dst, line); err != nil { 126 + return "", err 127 + } 128 + } 129 + } 130 + 131 + // Apply writes data from src to dst, modifying it as described by the 132 + // fragment. 133 + // 134 + // Unlike text fragments, binary fragments do not distinguish between strict 135 + // and non-strict application. 136 + func (f *BinaryFragment) Apply(dst io.Writer, src io.Reader) error { 137 + panic("TODO(bkeyes): unimplemented") 138 + }
+4 -2
gitdiff/io.go
··· 27 27 // ReadLine reads the next full line in the input, returing the the data 28 28 // including the line ending character(s) and the zero-indexed line number. If 29 29 // ReadLine encounters an error before reaching the end of the line, it returns 30 - // the data read before the error and the error itself (often io.EOF). ReadLine 31 - // returns err != nil if and only if the returned data is not a complete line. 30 + // the data read before the error, the number of the line, and the error itself 31 + // (often io.EOF). ReadLine returns err != nil if and only if the returned data 32 + // is not a complete line. 32 33 // 33 34 // If an implementation defines other methods for reading the same input, line 34 35 // numbers may be incorrect if calls to ReadLine are mixed with calls to other 35 36 // read methods. 36 37 type LineReader interface { 38 + // TODO(bkeyes): consider making lineno int64 to match fragment types 37 39 ReadLine() (string, int, error) 38 40 } 39 41