this repo has no description
0
fork

Configure Feed

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

Return preamble content when parsing files

This allows callers to provide patches with commit or email headers and
then retrieve that leading content for additional parsing without
having to identify where the first file in the patch starts.

+67 -33
+28 -23
gitdiff/parser.go
··· 8 8 "strings" 9 9 ) 10 10 11 - // Parse parses a patch with changes for one or more files. Any content 12 - // preceding the first file header is ignored. If an error occurs while 13 - // parsing, files will contain all files parsed before the error. 14 - func Parse(r io.Reader) ([]*File, error) { 11 + // Parse parses a patch with changes to one or more files. Any content before 12 + // the first file is returned as the second value. If an error occurs while 13 + // parsing, it returns all files parsed before the error. 14 + func Parse(r io.Reader) ([]*File, string, error) { 15 15 p := &parser{r: bufio.NewReader(r)} 16 16 if err := p.Next(); err != nil { 17 17 if err == io.EOF { 18 - return nil, nil 18 + return nil, "", nil 19 19 } 20 - return nil, err 20 + return nil, "", err 21 21 } 22 22 23 - // TODO(bkeyes): capture non-file lines in between files 24 - 23 + var preamble string 25 24 var files []*File 26 25 for { 27 - file, err := p.ParseNextFileHeader() 26 + file, pre, err := p.ParseNextFileHeader() 28 27 if err != nil { 29 - return files, err 28 + return files, preamble, err 30 29 } 31 30 if file == nil { 32 31 break ··· 38 37 } { 39 38 n, err := fn(file) 40 39 if err != nil { 41 - return files, err 40 + return files, preamble, err 42 41 } 43 42 if n > 0 { 44 43 break 45 44 } 46 45 } 47 - // file has fragment(s) from above or the patch is empty or invalid 46 + 47 + if len(files) == 0 { 48 + preamble = pre 49 + } 48 50 files = append(files, file) 49 51 } 50 52 51 - return files, nil 53 + return files, preamble, nil 52 54 } 53 55 54 56 // TODO(bkeyes): consider exporting the parser type with configuration ··· 71 73 lines [3]string 72 74 } 73 75 74 - // ParseNextFileHeader finds and parses the next file header in the stream. It 75 - // returns nil if no headers are found before the end of the stream. 76 - func (p *parser) ParseNextFileHeader() (*File, error) { 76 + // ParseNextFileHeader finds and parses the next file header in the stream. If 77 + // a header is found, it returns a file and all input before the header. It 78 + // returns nil if no headers are found before the end of the input. 79 + func (p *parser) ParseNextFileHeader() (*File, string, error) { 80 + var preamble strings.Builder 77 81 var file *File 78 82 for { 79 83 // check for disconnected fragment headers (corrupt patch) ··· 83 87 goto NextLine 84 88 } 85 89 if frag != nil { 86 - return nil, p.Errorf(-1, "patch fragment without file header: %s", frag.Header()) 90 + return nil, "", p.Errorf(-1, "patch fragment without file header: %s", frag.Header()) 87 91 } 88 92 89 93 // check for a git-generated patch 90 94 file, err = p.ParseGitFileHeader() 91 95 if err != nil { 92 - return nil, err 96 + return nil, "", err 93 97 } 94 98 if file != nil { 95 - return file, nil 99 + return file, preamble.String(), nil 96 100 } 97 101 98 102 // check for a "traditional" patch 99 103 file, err = p.ParseTraditionalFileHeader() 100 104 if err != nil { 101 - return nil, err 105 + return nil, "", err 102 106 } 103 107 if file != nil { 104 - return file, nil 108 + return file, preamble.String(), nil 105 109 } 106 110 107 111 NextLine: 112 + preamble.WriteString(p.Line(0)) 108 113 if err := p.Next(); err != nil { 109 114 if err == io.EOF { 110 115 break 111 116 } 112 - return nil, err 117 + return nil, "", err 113 118 } 114 119 } 115 - return nil, nil 120 + return nil, "", nil 116 121 } 117 122 118 123 // ParseTextFragments parses text fragments until the next file header or the
+38 -9
gitdiff/parser_test.go
··· 133 133 } 134 134 135 135 if test.EndLine != p.Line(0) { 136 - t.Errorf("incorrect position after parsing\nexpected: %q\nactual: %q", test.EndLine, p.Line(0)) 136 + t.Errorf("incorrect position after parsing\nexpected: %q\n actual: %q", test.EndLine, p.Line(0)) 137 137 } 138 138 }) 139 139 } ··· 141 141 142 142 func TestParseNextFileHeader(t *testing.T) { 143 143 tests := map[string]struct { 144 - Input string 145 - Output *File 146 - Err bool 144 + Input string 145 + Output *File 146 + Preamble string 147 + Err bool 147 148 }{ 148 149 "gitHeader": { 149 150 Input: `commit 1acbae563cd6ef5750a82ee64e116c6eb065cb94 ··· 165 166 OldOIDPrefix: "cc34da1", 166 167 NewOIDPrefix: "1acbae5", 167 168 }, 169 + Preamble: `commit 1acbae563cd6ef5750a82ee64e116c6eb065cb94 170 + Author: Morton Haypenny <mhaypenny@example.com> 171 + Date: Tue Apr 2 22:30:00 2019 -0700 172 + 173 + This is a sample commit message. 174 + 175 + `, 168 176 }, 169 177 "traditionalHeader": { 170 178 Input: ` ··· 176 184 OldName: "file.txt", 177 185 NewName: "file.txt", 178 186 }, 187 + Preamble: "\n", 179 188 }, 180 189 "noHeaders": { 181 190 Input: ` ··· 184 193 --- could this be a header? 185 194 nope, it's just some dashes 186 195 `, 187 - Output: nil, 196 + Output: nil, 197 + Preamble: "", 188 198 }, 189 199 "detatchedFragmentLike": { 190 200 Input: ` ··· 206 216 t.Run(name, func(t *testing.T) { 207 217 p := newTestParser(test.Input, true) 208 218 209 - f, err := p.ParseNextFileHeader() 219 + f, pre, err := p.ParseNextFileHeader() 210 220 if test.Err { 211 221 if err == nil || err == io.EOF { 212 222 t.Fatalf("expected error parsing next file header, but got %v", err) ··· 217 227 t.Fatalf("unexpected error parsing next file header: %v", err) 218 228 } 219 229 230 + if test.Preamble != pre { 231 + t.Errorf("incorrect preamble\nexpected: %q\n actual: %q", test.Preamble, pre) 232 + } 220 233 if !reflect.DeepEqual(test.Output, f) { 221 - t.Errorf("incorrect file\nexpected: %+v\nactual: %+v", test.Output, f) 234 + t.Errorf("incorrect file\nexpected: %+v\n actual: %+v", test.Output, f) 222 235 } 223 236 }) 224 237 } ··· 266 279 }, 267 280 } 268 281 282 + expectedPreamble := `commit 5d9790fec7d95aa223f3d20936340bf55ff3dcbe 283 + Author: Morton Haypenny <mhaypenny@example.com> 284 + Date: Tue Apr 2 22:55:40 2019 -0700 285 + 286 + A file with multiple fragments. 287 + 288 + The content is arbitrary. 289 + 290 + ` 291 + 269 292 tests := map[string]struct { 270 293 InputFile string 271 294 Output []*File 295 + Preamble string 272 296 Err bool 273 297 }{ 274 298 "oneFile": { ··· 283 307 Fragments: expectedFragments, 284 308 }, 285 309 }, 310 + Preamble: expectedPreamble, 286 311 }, 287 312 "twoFiles": { 288 313 InputFile: "testdata/two_files.patch", ··· 304 329 Fragments: expectedFragments, 305 330 }, 306 331 }, 332 + Preamble: expectedPreamble, 307 333 }, 308 334 } 309 335 ··· 314 340 t.Fatalf("unexpected error opening input file: %v", err) 315 341 } 316 342 317 - files, err := Parse(f) 343 + files, pre, err := Parse(f) 318 344 if test.Err { 319 345 if err == nil || err == io.EOF { 320 346 t.Fatalf("expected error parsing patch, but got %v", err) ··· 328 354 if len(test.Output) != len(files) { 329 355 t.Fatalf("incorrect number of parsed files: expected %d, actual %d", len(test.Output), len(files)) 330 356 } 357 + if test.Preamble != pre { 358 + t.Errorf("incorrect preamble\nexpected: %q\n actual: %q", test.Preamble, pre) 359 + } 331 360 for i := range test.Output { 332 361 if !reflect.DeepEqual(test.Output[i], files[i]) { 333 362 exp, _ := json.MarshalIndent(test.Output[i], "", " ") 334 363 act, _ := json.MarshalIndent(files[i], "", " ") 335 - t.Errorf("incorrect file at position %d\nexpected: %s\nactual: %s", i, exp, act) 364 + t.Errorf("incorrect file at position %d\nexpected: %s\n actual: %s", i, exp, act) 336 365 } 337 366 } 338 367 })
+1 -1
gitdiff/testdata/one_file.patch
··· 2 2 Author: Morton Haypenny <mhaypenny@example.com> 3 3 Date: Tue Apr 2 22:55:40 2019 -0700 4 4 5 - A single file with multiple fragments. 5 + A file with multiple fragments. 6 6 7 7 The content is arbitrary. 8 8