diff --git a/readers.go b/readers.go index 66b87b5..66b9cbf 100644 --- a/readers.go +++ b/readers.go @@ -49,6 +49,7 @@ type BytesReplacingReader struct { const defaultBufSize = int(4096) // NewBytesReplacingReader creates a new `*BytesReplacingReader`. +// `search` cannot be nil/empty. `replace` can. func NewBytesReplacingReader(r io.Reader, search, replace []byte) *BytesReplacingReader { return (&BytesReplacingReader{}).Reset(r, search, replace) } @@ -60,14 +61,8 @@ func max(a, b int) int { return b } -func min(a, b int) int { - if a < b { - return a - } - return b -} - // Reset allows reuse of a previous allocated `*BytesReplacingReader` for buf allocation optimization. +// `search` cannot be nil/empty. `replace` can. func (r *BytesReplacingReader) Reset(r1 io.Reader, search1, replace1 []byte) *BytesReplacingReader { if r1 == nil { panic("io.Reader cannot be nil") @@ -75,9 +70,6 @@ func (r *BytesReplacingReader) Reset(r1 io.Reader, search1, replace1 []byte) *By if len(search1) == 0 { panic("search token cannot be nil/empty") } - if len(replace1) == 0 { - panic("replace token cannot be nil/empty") - } r.r = r1 r.search = search1 r.searchLen = len(search1) @@ -86,12 +78,19 @@ func (r *BytesReplacingReader) Reset(r1 io.Reader, search1, replace1 []byte) *By r.lenDelta = r.replaceLen - r.searchLen // could be negative r.err = nil bufSize := max(defaultBufSize, max(r.searchLen, r.replaceLen)) - if r.buf == nil || cap(r.buf) < bufSize { + if r.buf == nil || len(r.buf) < bufSize { r.buf = make([]byte, bufSize) } r.buf0 = 0 r.buf1 = 0 - r.max = min(cap(r.buf), (cap(r.buf)/r.replaceLen)*r.searchLen) + r.max = len(r.buf) + if r.searchLen < r.replaceLen { + // If len(search) < len(replace), then we have to assume the worst case: + // what's the max bound value such that if we have consecutive 'search' filling up + // the buf up to buf[:max], and all of them are placed with 'replace', and the final + // result won't end up exceed the len(buf)? + r.max = (len(r.buf) / r.replaceLen) * r.searchLen + } return r } diff --git a/readers_test.go b/readers_test.go index 1defd0a..446c88f 100644 --- a/readers_test.go +++ b/readers_test.go @@ -55,6 +55,13 @@ func TestBytesReplacingReader(t *testing.T) { replace: []byte{9}, expected: []byte{1, 9, 2, 3, 4, 5, 6, 7, 8}, }, + { + name: "strip out search, no replace", + input: []byte{1, 2, 3, 2, 2, 3, 4, 2, 3, 2, 8}, + search: []byte{2, 3, 2}, + replace: []byte{}, + expected: []byte{1, 2, 3, 4, 8}, + }, { name: "len(replace) == len(search)", input: []byte{1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5}, @@ -85,9 +92,6 @@ func TestBytesReplacingReader(t *testing.T) { assert.PanicsWithValue(t, "search token cannot be nil/empty", func() { (&BytesReplacingReader{}).Reset(strings.NewReader("test"), nil, []byte("est")) }) - assert.PanicsWithValue(t, "replace token cannot be nil/empty", func() { - NewBytesReplacingReader(strings.NewReader("test"), []byte("est"), nil) - }) } func createTestInput(length int, numTarget int) []byte { @@ -151,7 +155,7 @@ func BenchmarkBytesReplacingReader_50KBLength_1000Targets(b *testing.B) { } } -func BenchmarkRegularReader_50KBLength_100Targets(b *testing.B) { +func BenchmarkRegularReader_50KBLength_1000Targets(b *testing.B) { for i := 0; i < b.N; i++ { _, _ = ioutil.ReadAll(bytes.NewReader(testInput50KBLength1000Targets)) }