util/buffer: add new seeker implementation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
		
							
								
								
									
										78
									
								
								util/buffer/seeker_buffer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								util/buffer/seeker_buffer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | package buffer | ||||||
|  |  | ||||||
|  | import "io" | ||||||
|  |  | ||||||
|  | var _ interface { | ||||||
|  | 	io.ReadCloser | ||||||
|  | 	io.ReadSeeker | ||||||
|  | } = (*SeekerBuffer)(nil) | ||||||
|  |  | ||||||
|  | // Buffer is a ReadWriteCloser that supports seeking. It's intended to | ||||||
|  | // replicate the functionality of bytes.Buffer that I use in my projects. | ||||||
|  | // | ||||||
|  | // Note that the seeking is limited to the read marker; all writes are | ||||||
|  | // append-only. | ||||||
|  | type SeekerBuffer struct { | ||||||
|  | 	data []byte | ||||||
|  | 	pos  int64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSeekerBuffer(data []byte) *SeekerBuffer { | ||||||
|  | 	return &SeekerBuffer{ | ||||||
|  | 		data: data, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SeekerBuffer) Read(p []byte) (int, error) { | ||||||
|  | 	if b.pos >= int64(len(b.data)) { | ||||||
|  | 		return 0, io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	n := copy(p, b.data[b.pos:]) | ||||||
|  | 	b.pos += int64(n) | ||||||
|  | 	return n, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SeekerBuffer) Write(p []byte) (int, error) { | ||||||
|  | 	b.data = append(b.data, p...) | ||||||
|  | 	return len(p), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Seek sets the read pointer to pos. | ||||||
|  | func (b *SeekerBuffer) Seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	switch whence { | ||||||
|  | 	case io.SeekStart: | ||||||
|  | 		b.pos = offset | ||||||
|  | 	case io.SeekEnd: | ||||||
|  | 		b.pos = int64(len(b.data)) + offset | ||||||
|  | 	case io.SeekCurrent: | ||||||
|  | 		b.pos += offset | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return b.pos, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Rewind resets the read pointer to 0. | ||||||
|  | func (b *SeekerBuffer) Rewind() error { | ||||||
|  | 	if _, err := b.Seek(0, io.SeekStart); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close clears all the data out of the buffer and sets the read position to 0. | ||||||
|  | func (b *SeekerBuffer) Close() error { | ||||||
|  | 	b.data = nil | ||||||
|  | 	b.pos = 0 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Len returns the length of data remaining to be read. | ||||||
|  | func (b *SeekerBuffer) Len() int { | ||||||
|  | 	return len(b.data[b.pos:]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Bytes returns the underlying bytes from the current position. | ||||||
|  | func (b *SeekerBuffer) Bytes() []byte { | ||||||
|  | 	return b.data[b.pos:] | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								util/buffer/seeker_buffer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								util/buffer/seeker_buffer_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | package buffer | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func noErrorT(t *testing.T, err error) { | ||||||
|  | 	if nil != err { | ||||||
|  | 		t.Fatalf("%s", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func boolT(t *testing.T, cond bool, s ...string) { | ||||||
|  | 	if !cond { | ||||||
|  | 		what := strings.Join(s, ", ") | ||||||
|  | 		if len(what) > 0 { | ||||||
|  | 			what = ": " + what | ||||||
|  | 		} | ||||||
|  | 		t.Fatalf("assert.Bool failed%s", what) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSeeking(t *testing.T) { | ||||||
|  | 	partA := []byte("hello, ") | ||||||
|  | 	partB := []byte("world!") | ||||||
|  |  | ||||||
|  | 	buf := NewSeekerBuffer(partA) | ||||||
|  |  | ||||||
|  | 	boolT(t, buf.Len() == len(partA), fmt.Sprintf("on init: have length %d, want length %d", buf.Len(), len(partA))) | ||||||
|  |  | ||||||
|  | 	b := make([]byte, 32) | ||||||
|  |  | ||||||
|  | 	n, err := buf.Read(b) | ||||||
|  | 	noErrorT(t, err) | ||||||
|  | 	boolT(t, buf.Len() == 0, fmt.Sprintf("after reading 1: have length %d, want length 0", buf.Len())) | ||||||
|  | 	boolT(t, n == len(partA), fmt.Sprintf("after reading 2: have length %d, want length %d", n, len(partA))) | ||||||
|  |  | ||||||
|  | 	n, err = buf.Write(partB) | ||||||
|  | 	noErrorT(t, err) | ||||||
|  | 	boolT(t, n == len(partB), fmt.Sprintf("after writing: have length %d, want length %d", n, len(partB))) | ||||||
|  |  | ||||||
|  | 	n, err = buf.Read(b) | ||||||
|  | 	noErrorT(t, err) | ||||||
|  | 	boolT(t, buf.Len() == 0, fmt.Sprintf("after rereading 1: have length %d, want length 0", buf.Len())) | ||||||
|  | 	boolT(t, n == len(partB), fmt.Sprintf("after rereading 2: have length %d, want length %d", n, len(partB))) | ||||||
|  |  | ||||||
|  | 	partsLen := len(partA) + len(partB) | ||||||
|  | 	_ = buf.Rewind() | ||||||
|  | 	boolT(t, buf.Len() == partsLen, fmt.Sprintf("after rewinding: have length %d, want length %d", buf.Len(), partsLen)) | ||||||
|  |  | ||||||
|  | 	buf.Close() | ||||||
|  | 	boolT(t, buf.Len() == 0, fmt.Sprintf("after closing, have length %d, want length 0", buf.Len())) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user