feat(pkg/csv2lp): add SkipHeaderLinesReader

pull/17764/head
Pavel Zavora 2020-04-29 09:24:56 +02:00
parent bfba3480fb
commit 8dcbd15df2
2 changed files with 136 additions and 0 deletions

View File

@ -0,0 +1,54 @@
package csv2lp
import (
"io"
)
// skipFirstLines is an io.Reader that skips first lines
type skipFirstLines struct {
reader io.Reader
skipLines int
// line is a mutable variable that increases until skipLines is reached
line int
}
// Read implements io.Reader
func (state *skipFirstLines) Read(p []byte) (n int, err error) {
skipHeaderLines:
for state.line < state.skipLines {
n, err := state.reader.Read(p)
if n == 0 {
return n, err
}
for i := 0; i < n; i++ {
if p[i] == '\n' {
state.line++
if state.line == state.skipLines {
// modify the buffer and return
if i == n-1 {
if err != nil {
return 0, err
}
// continue with the next chunk
break skipHeaderLines
} else {
// copy all bytes after the newline
for j := i + 1; j < n; j++ {
p[j-i-1] = p[j]
}
return n - i - 1, err
}
}
}
}
}
return state.reader.Read(p)
}
// SkipHeaderLinesReader wraps a reader to skip the first skipLines lines
func SkipHeaderLinesReader(skipLines int, reader io.Reader) io.Reader {
return &skipFirstLines{
skipLines: skipLines,
reader: reader,
}
}

View File

@ -0,0 +1,82 @@
package csv2lp
import (
"io"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
// simulates the reader that returns all data together with EOF
type readOnceWithEOF struct {
reader io.Reader
}
func (r *readOnceWithEOF) Read(p []byte) (n int, err error) {
n, _ = r.reader.Read(p)
return n, io.EOF
}
// Test_SkipHeaderLines checks that first lines are skipped
func Test_SkipHeaderLines(t *testing.T) {
input := "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n"
var tests = []struct {
skipCount int
result string
}{
{
10,
"",
},
{
0,
"1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n",
},
{
1,
"2\n3\n4\n5\n6\n7\n8\n9\n0\n",
},
{
5,
"6\n7\n8\n9\n0\n",
},
{
20,
"",
},
}
bufferSizes := []int{1, 2, 7, 0, len(input), len(input) + 1}
for i, test := range tests {
for _, bufferSize := range bufferSizes {
t.Run(strconv.Itoa(i)+"_"+strconv.Itoa(bufferSize), func(t *testing.T) {
var reader io.Reader
if bufferSize == 0 {
// emulate a reader that returns EOF together with data
bufferSize = len(input)
reader = SkipHeaderLinesReader(test.skipCount, &readOnceWithEOF{strings.NewReader(input)})
} else {
reader = SkipHeaderLinesReader(test.skipCount, strings.NewReader(input))
}
buffer := make([]byte, bufferSize)
result := make([]byte, 0, 100)
for {
n, err := reader.Read(buffer)
if n > 0 {
result = append(result, buffer[:n]...)
}
if err != nil {
if err != io.EOF {
require.Nil(t, err.Error())
}
break
}
}
require.Equal(t, test.result, string(result))
})
}
}
}