this repo has no description
0
fork

Configure Feed

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

Document and enforce parser invariants

After considering fragment parsing, it made sense to change the
invariant estabilished in the previous commit. Specifically, parser
functions now assume they are call on the first line of their object and
return with the parser on the first line after their object. This means
code can call parse function immediately after each other without
advancing the parser in between.

It's possible this will change back later on... there seem to be
annoying edge cases with either choice, but I think making the functions
consistent is important.

+110 -78
+8 -4
gitdiff/file_header.go
··· 32 32 if err != nil { 33 33 return nil, p.Errorf(1, "git file header: %v", err) 34 34 } 35 - if end { 36 - break 37 - } 38 35 39 36 if err := p.Next(); err != nil { 40 37 if err == io.EOF { 41 38 break 42 39 } 43 40 return nil, err 41 + } 42 + 43 + if end { 44 + break 44 45 } 45 46 } 46 47 ··· 76 77 return nil, nil 77 78 } 78 79 79 - // advance past the first line so parser is at end of header 80 + // advance past the first two lines so parser is after the header 80 81 // no EOF check needed because we know there are >=3 valid lines 82 + if err := p.Next(); err != nil { 83 + return nil, err 84 + } 81 85 if err := p.Next(); err != nil { 82 86 return nil, err 83 87 }
-54
gitdiff/file_header_test.go
··· 277 277 } 278 278 } 279 279 280 - func TestParserAdvancment(t *testing.T) { 281 - tests := map[string]struct { 282 - Input string 283 - Parse func(p *parser) error 284 - NextLine string 285 - }{ 286 - "ParseGitFileHeader": { 287 - Input: `diff --git a/dir/file.txt b/dir/file.txt 288 - index 9540595..30e6333 100644 289 - --- a/dir/file.txt 290 - +++ b/dir/file.txt 291 - @@ -1,2 +1,3 @@ 292 - context line 293 - `, 294 - Parse: func(p *parser) error { 295 - _, err := p.ParseGitFileHeader() 296 - return err 297 - }, 298 - NextLine: "@@ -1,2 +1,3 @@\n", 299 - }, 300 - "ParseTraditionalFileHeader": { 301 - Input: `--- dir/file.txt 302 - +++ dir/file.txt 303 - @@ -1,2 +1,3 @@ 304 - context line 305 - `, 306 - Parse: func(p *parser) error { 307 - _, err := p.ParseTraditionalFileHeader() 308 - return err 309 - }, 310 - NextLine: "@@ -1,2 +1,3 @@\n", 311 - }, 312 - } 313 - 314 - for name, test := range tests { 315 - t.Run(name, func(t *testing.T) { 316 - p := &parser{r: bufio.NewReader(strings.NewReader(test.Input))} 317 - p.Next() 318 - 319 - if err := test.Parse(p); err != nil { 320 - t.Fatalf("unexpected error while parsing: %v", err) 321 - } 322 - 323 - if err := p.Next(); err != nil { 324 - t.Fatalf("advancing the parser after parsing returned an error: %v", err) 325 - } 326 - 327 - if test.NextLine != p.Line(0) { 328 - t.Errorf("incorrect next line after parsing\nexpected: %q\nactual: %q", test.NextLine, p.Line(0)) 329 - } 330 - }) 331 - } 332 - } 333 - 334 280 func TestCleanName(t *testing.T) { 335 281 tests := map[string]struct { 336 282 Input string
+6
gitdiff/gitdiff.go
··· 1 1 package gitdiff 2 2 3 3 import ( 4 + "fmt" 4 5 "os" 5 6 ) 6 7 ··· 35 36 NewPosition int64 36 37 NewLines int64 37 38 } 39 + 40 + // Header returns the cannonical header of this fragment. 41 + func (f *Fragment) Header() string { 42 + return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment) 43 + }
+36 -20
gitdiff/parser.go
··· 11 11 // Parse parses a patch with changes for one or more files. Any content 12 12 // preceding the first file header is ignored. If an error occurs while 13 13 // parsing, files will contain all files parsed before the error. 14 - func Parse(r io.Reader) (files []*File, err error) { 14 + func Parse(r io.Reader) ([]*File, error) { 15 15 p := &parser{r: bufio.NewReader(r)} 16 + if err := p.Next(); err != nil { 17 + if err == io.EOF { 18 + return nil, nil 19 + } 20 + return nil, err 21 + } 16 22 17 - var file *File 23 + var files []*File 18 24 for { 19 - file, err = p.ParseNextFileHeader() 25 + file, err := p.ParseNextFileHeader() 20 26 if err != nil { 21 - return 27 + return files, err 22 28 } 23 29 if file == nil { 24 30 break 25 31 } 26 32 27 - err = p.ParseFileChanges(file) 28 - if err != nil { 29 - return 33 + if err = p.ParseFragments(file); err != nil { 34 + return files, err 30 35 } 31 36 32 37 files = append(files, file) ··· 39 44 // this would enable OID validation, p-value guessing, and prefix stripping 40 45 // by allowing users to set or override defaults 41 46 47 + // parser invariants: 48 + // - methods that parse objects start on the first line of the first object 49 + // - methods that parse objects return on the first line after the last object 50 + // - if a parse method returns a nil object, the parser was not advanced 51 + // - any exported parsing methods must initialize the parser by calling Next() 52 + 42 53 type parser struct { 43 54 r *bufio.Reader 44 55 ··· 50 61 // ParseNextFileHeader finds and parses the next file header in the stream. It 51 62 // returns nil if no headers are found before the end of the stream. 52 63 func (p *parser) ParseNextFileHeader() (*File, error) { 64 + var file *File 53 65 for { 54 - if err := p.Next(); err != nil { 55 - if err == io.EOF { 56 - break 57 - } 58 - return nil, err 59 - } 60 - 61 66 // check for disconnected fragment headers (corrupt patch) 62 67 frag, err := p.ParseFragmentHeader() 63 68 if err != nil { 64 69 // not a valid header, nothing to worry about 65 - continue 70 + goto NextLine 66 71 } 67 72 if frag != nil { 68 - return nil, p.Errorf(0, "patch fragment without header: %s", p.Line(0)) 73 + return nil, p.Errorf(-1, "patch fragment without file header: %s", frag.Header()) 69 74 } 70 75 71 76 // check for a git-generated patch 72 - file, err := p.ParseGitFileHeader() 77 + file, err = p.ParseGitFileHeader() 73 78 if err != nil { 74 79 return nil, err 75 80 } ··· 85 90 if file != nil { 86 91 return file, nil 87 92 } 93 + 94 + NextLine: 95 + if err := p.Next(); err != nil { 96 + if err == io.EOF { 97 + break 98 + } 99 + return nil, err 100 + } 88 101 } 89 102 return nil, nil 90 103 } 91 104 92 - // ParseFileChanges parses file changes until the next file header or the end 93 - // of the stream and attaches them to the given file. 94 - func (p *parser) ParseFileChanges(f *File) error { 105 + // ParseFragments parses fragments until the next file header or the end of the 106 + // stream and attaches them to the given file. 107 + func (p *parser) ParseFragments(f *File) error { 95 108 panic("TODO(bkeyes): unimplemented") 96 109 } 97 110 ··· 177 190 return nil, fmt.Errorf("invalid fragment header: %v", err) 178 191 } 179 192 193 + if err := p.Next(); err != nil && err != io.EOF { 194 + return nil, err 195 + } 180 196 return f, nil 181 197 } 182 198
+60
gitdiff/parser_test.go
··· 73 73 }) 74 74 } 75 75 76 + func TestParserAdvancment(t *testing.T) { 77 + tests := map[string]struct { 78 + Input string 79 + Parse func(p *parser) error 80 + EndLine string 81 + }{ 82 + "ParseGitFileHeader": { 83 + Input: `diff --git a/dir/file.txt b/dir/file.txt 84 + index 9540595..30e6333 100644 85 + --- a/dir/file.txt 86 + +++ b/dir/file.txt 87 + @@ -1,2 +1,3 @@ 88 + context line 89 + `, 90 + Parse: func(p *parser) error { 91 + _, err := p.ParseGitFileHeader() 92 + return err 93 + }, 94 + EndLine: "@@ -1,2 +1,3 @@\n", 95 + }, 96 + "ParseTraditionalFileHeader": { 97 + Input: `--- dir/file.txt 98 + +++ dir/file.txt 99 + @@ -1,2 +1,3 @@ 100 + context line 101 + `, 102 + Parse: func(p *parser) error { 103 + _, err := p.ParseTraditionalFileHeader() 104 + return err 105 + }, 106 + EndLine: "@@ -1,2 +1,3 @@\n", 107 + }, 108 + "ParseFragmentHeader": { 109 + Input: `@@ -1,2 +1,3 @@ 110 + context line 111 + `, 112 + Parse: func(p *parser) error { 113 + _, err := p.ParseFragmentHeader() 114 + return err 115 + }, 116 + EndLine: "context line\n", 117 + }, 118 + } 119 + 120 + for name, test := range tests { 121 + t.Run(name, func(t *testing.T) { 122 + p := &parser{r: bufio.NewReader(strings.NewReader(test.Input))} 123 + p.Next() 124 + 125 + if err := test.Parse(p); err != nil { 126 + t.Fatalf("unexpected error while parsing: %v", err) 127 + } 128 + 129 + if test.EndLine != p.Line(0) { 130 + t.Errorf("incorrect position after parsing\nexpected: %q\nactual: %q", test.EndLine, p.Line(0)) 131 + } 132 + }) 133 + } 134 + } 135 + 76 136 func TestParseFragmentHeader(t *testing.T) { 77 137 tests := map[string]struct { 78 138 Input string