this repo has no description
0
fork

Configure Feed

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

Change parser interface to be more iterator-like

Call Next() to advance the parser state until it returns a non-nil
error, then check if the error is io.EOF. This makes EOF handling easier
and also means that Line() and PeekLine() can be called multiple times
without changing state.

The next step is to update parse functions to return an internal marker
error if they are called on an invalid line. This should improve
correctness and remove the duplication of testing a condition and then
calling a parse function, which checks the same condition.

+80 -77
+5 -9
gitdiff/file_header.go
··· 16 16 return p.Errorf(0, "git file header: %v", err) 17 17 } 18 18 19 - for { 20 - line, err := p.PeekLine() 21 - if err == io.EOF { 22 - break 23 - } else if err != nil { 24 - return err 25 - } 26 - 27 - end, err := parseGitHeaderData(f, line, defaultName) 19 + for err = p.Next(); err == nil; err = p.Next() { 20 + end, err := parseGitHeaderData(f, p.Line(), defaultName) 28 21 if err != nil { 29 22 return p.Errorf(1, "git file header: %v", err) 30 23 } ··· 32 25 break 33 26 } 34 27 p.Line() 28 + } 29 + if err != nil && err != io.EOF { 30 + return err 35 31 } 36 32 37 33 if f.OldName == "" && f.NewName == "" {
+2 -2
gitdiff/file_header_test.go
··· 144 144 for name, test := range tests { 145 145 t.Run(name, func(t *testing.T) { 146 146 p := &parser{r: bufio.NewReader(strings.NewReader(test.Input))} 147 - header, _ := p.Line() 147 + p.Next() 148 148 149 149 var f File 150 - err := p.ParseGitFileHeader(&f, header) 150 + err := p.ParseGitFileHeader(&f, p.Line()) 151 151 if test.Err { 152 152 if err == nil { 153 153 t.Fatalf("expected error parsing git file header, got nil")
+55 -43
gitdiff/parser.go
··· 40 40 // by allowing users to set or override defaults 41 41 42 42 type parser struct { 43 - r *bufio.Reader 44 - lineno int64 45 - nextLine string 43 + r *bufio.Reader 44 + 45 + eof bool 46 + lineno int64 47 + lines [2]string 46 48 } 47 49 48 50 const ( ··· 60 62 func (p *parser) ParseNextFileHeader() (file *File, err error) { 61 63 // based on find_header() in git/apply.c 62 64 63 - defer func() { 64 - if err == io.EOF && file == nil { 65 - err = nil 66 - } 67 - }() 68 - 69 - for { 70 - line, err := p.Line() 71 - if err != io.EOF { 72 - return nil, err 73 - } 65 + for err = p.Next(); err == nil; err = p.Next() { 66 + line := p.Line() 74 67 75 68 // check for disconnected fragment headers (corrupt patch) 76 69 if isMaybeFragmentHeader(line) { ··· 91 84 return file, nil 92 85 } 93 86 94 - next, err := p.PeekLine() 95 - if err != nil { 96 - return nil, err 97 - } 98 - 99 87 // check for a "traditional" patch 100 - if strings.HasPrefix(line, oldFilePrefix) && strings.HasPrefix(next, newFilePrefix) { 101 - oldFileLine := line 102 - newFileLine, _ := p.Line() 103 - 104 - next, err := p.PeekLine() 105 - if err != nil { 88 + if strings.HasPrefix(line, oldFilePrefix) && strings.HasPrefix(p.PeekLine(), newFilePrefix) { 89 + if err = p.Next(); err != nil { 106 90 return nil, err 107 91 } 92 + 93 + oldFileLine := line 94 + newFileLine := p.Line() 108 95 109 96 // only a file header if followed by a (probable) unified fragment header 110 - if !isMaybeFragmentHeader(next) { 97 + if !isMaybeFragmentHeader(p.PeekLine()) { 111 98 continue 112 99 } 113 100 ··· 118 105 return file, nil 119 106 } 120 107 } 108 + 109 + if err != nil && err != io.EOF { 110 + return nil, err 111 + } 112 + return file, nil 121 113 } 122 114 123 115 // ParseFileChanges parses file changes until the next file header or the end ··· 126 118 panic("TODO(bkeyes): unimplemented") 127 119 } 128 120 129 - // Line reads and returns the next line. The first call to Line after a call to 130 - // PeekLine will never retrun an error. 131 - func (p *parser) Line() (line string, err error) { 132 - if p.nextLine != "" { 133 - line = p.nextLine 134 - p.nextLine = "" 135 - } else { 136 - line, err = p.r.ReadString('\n') 121 + // Next advances the parser by one line. It returns any error encountered while 122 + // reading the line, including io.EOF when the end of stream is reached. 123 + func (p *parser) Next() error { 124 + if p.eof { 125 + p.lines[0] = "" 126 + return io.EOF 127 + } 128 + 129 + if p.lineno == 0 { 130 + // on the first call, need extra shift to initialize both slots 131 + if err := p.shiftLines(); err != nil && err != io.EOF { 132 + return err 133 + } 137 134 } 135 + 136 + err := p.shiftLines() 137 + if err == io.EOF { 138 + p.eof = p.lines[1] == "" 139 + } else if err != nil { 140 + return err 141 + } 142 + 138 143 p.lineno++ 139 - return 144 + return nil 140 145 } 141 146 142 - // PeekLine reads and returns the next line without advancing the parser. 143 - func (p *parser) PeekLine() (line string, err error) { 144 - if p.nextLine != "" { 145 - line = p.nextLine 146 - } else { 147 - line, err = p.r.ReadString('\n') 148 - } 149 - p.nextLine = line 147 + func (p *parser) shiftLines() (err error) { 148 + p.lines[0] = p.lines[1] 149 + p.lines[1], err = p.r.ReadString('\n') 150 150 return 151 + } 152 + 153 + // Line returns the current line or an empty string if Next has returned io.EOF. 154 + func (p *parser) Line() string { 155 + return p.lines[0] 156 + } 157 + 158 + // PeekLine returns the line following the current line or an empty string if 159 + // the current line is the final line. If PeekLine returns an empty string, 160 + // Next will return io.EOF on the next call. 161 + func (p *parser) PeekLine() string { 162 + return p.lines[1] 151 163 } 152 164 153 165 // Errorf generates an error and appends the current line information.
+18 -23
gitdiff/parser_test.go
··· 17 17 t.Run("readLine", func(t *testing.T) { 18 18 p := newParser() 19 19 20 - line, err := p.Line() 21 - if err != nil { 22 - t.Fatalf("error reading first line: %v", err) 20 + if err := p.Next(); err != nil { 21 + t.Fatalf("error advancing parser: %v", err) 23 22 } 23 + 24 + line := p.Line() 24 25 if line != "the first line\n" { 25 26 t.Fatalf("incorrect first line: %s", line) 26 27 } 27 28 28 - line, err = p.Line() 29 - if err != nil { 30 - t.Fatalf("error reading second line: %v", err) 29 + if err := p.Next(); err != nil { 30 + t.Fatalf("error advancing parser: %v", err) 31 31 } 32 - if line != "the second line\n" { 32 + 33 + line = p.Line() 34 + if p.Line() != "the second line\n" { 33 35 t.Fatalf("incorrect second line: %s", line) 34 36 } 35 37 }) ··· 37 39 t.Run("peekLine", func(t *testing.T) { 38 40 p := newParser() 39 41 40 - line, err := p.PeekLine() 41 - if err != nil { 42 - t.Fatalf("error peeking line: %v", err) 43 - } 44 - if line != "the first line\n" { 45 - t.Fatalf("incorrect peek line: %s", line) 42 + if err := p.Next(); err != nil { 43 + t.Fatalf("error advancing parser: %v", err) 46 44 } 47 45 48 - // test that a second peek returns the same value 49 - line, err = p.PeekLine() 50 - if err != nil { 51 - t.Fatalf("error peeking line: %v", err) 52 - } 53 - if line != "the first line\n" { 46 + line := p.PeekLine() 47 + if line != "the second line\n" { 54 48 t.Fatalf("incorrect peek line: %s", line) 55 49 } 56 50 57 51 // test that reading the line returns the same value 58 - line, err = p.Line() 59 - if err != nil { 60 - t.Fatalf("error reading line: %v", err) 52 + if err := p.Next(); err != nil { 53 + t.Fatalf("error advancing parser: %v", err) 61 54 } 62 - if line != "the first line\n" { 55 + 56 + line = p.Line() 57 + if line != "the second line\n" { 63 58 t.Fatalf("incorrect line: %s", line) 64 59 } 65 60 })