2026-02-17 16:58:00 +01:00
|
|
|
// alternative to bufio
|
|
|
|
|
// meant to be used with buffer from pool
|
|
|
|
|
package buffer
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
2026-02-22 16:30:37 +01:00
|
|
|
"io"
|
2026-02-17 16:58:00 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Pool interface {
|
|
|
|
|
Get() ([]byte, error)
|
|
|
|
|
Put([]byte)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Options struct {
|
|
|
|
|
|
|
|
|
|
// defaults to new instance created by DefaultPool()
|
|
|
|
|
Pool Pool
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 16:30:37 +01:00
|
|
|
type ContentFunc func(io.Writer) (int64, error)
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
// uninitialized reader panics
|
|
|
|
|
// reads from the underlying reader until the first error, but only returns an error when the buffer is empty
|
|
|
|
|
// once the underlying reader returned an error, it doesn't attempt to read from it anymore
|
|
|
|
|
// the pool yes, the reader does not support concurrent access
|
|
|
|
|
type Reader struct {
|
|
|
|
|
reader *reader
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 16:30:37 +01:00
|
|
|
var (
|
|
|
|
|
ErrZeroAllocation = errors.New("zero allocation")
|
|
|
|
|
ErrContentAbort = errors.New("content pipe aborted")
|
|
|
|
|
)
|
2026-02-17 16:58:00 +01:00
|
|
|
|
|
|
|
|
func DefaultPool() Pool {
|
|
|
|
|
return newPool()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NoPool(allocSize int) Pool {
|
|
|
|
|
return noPool{allocSize: allocSize}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 16:30:37 +01:00
|
|
|
func (f ContentFunc) WriteTo(w io.Writer) (int64, error) {
|
|
|
|
|
return f(w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// similar to bufio.Reader, but with pooled buffering internally
|
|
|
|
|
func BufferedReader(in io.Reader, o Options) Reader {
|
|
|
|
|
if in == nil {
|
|
|
|
|
return Reader{}
|
2026-02-17 16:58:00 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 16:30:37 +01:00
|
|
|
if o.Pool == nil {
|
|
|
|
|
o.Pool = DefaultPool()
|
2026-02-17 16:58:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Reader{reader: &reader{options: o, in: in}}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 16:30:37 +01:00
|
|
|
// similar to a pipe, but with dynamic and pooled buffering internally
|
|
|
|
|
// the individual Write calls are blocked until the reading end requests more data
|
|
|
|
|
// WriterTo instances need to be safe to call in goroutines other than they were created in
|
|
|
|
|
// if it returns with nil error, it will be interpreted as EOF on the reader side
|
|
|
|
|
// unfinished calls to the passed in io.Writer will return with ErrContentAbort when the buffer
|
|
|
|
|
// needs to be consumed
|
|
|
|
|
func BufferedContent(c io.WriterTo, o Options) Reader {
|
|
|
|
|
if c == nil {
|
|
|
|
|
return Reader{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if o.Pool == nil {
|
|
|
|
|
o.Pool = DefaultPool()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Reader{reader: &reader{options: o, in: mkcontent(c)}}
|
2026-02-17 16:58:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - it returns an error only when the underlying reader returned an error and the internal buffer is empty
|
|
|
|
|
// - if the underlying reader returns zero read length and nil error, and the buffer is empty, it returns zero
|
|
|
|
|
// read length and nil error. It's up the calling code to decide how to proceed in such cases
|
|
|
|
|
func (r Reader) Read(p []byte) (int, error) {
|
2026-02-22 16:30:37 +01:00
|
|
|
if r.reader == nil {
|
|
|
|
|
return 0, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
return r.reader.read(p)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// copy
|
|
|
|
|
// data and err when no delimiter found
|
|
|
|
|
// - when found the delimiter within max, consumes until and including the delimiter, and returns the consumed
|
|
|
|
|
// bytes, true, and nil error. If the underlying reader returned meanwhile a non-nil error, including EOF, it
|
|
|
|
|
// will be returned on subsequent reads after the internal buffer was consumed
|
|
|
|
|
// - when not found the delimiter within max, and the underlying reader didn't return an error, it returns zero
|
|
|
|
|
// length bytes, false and nil error
|
|
|
|
|
// - when not found the delimiter within max, and the underlying reader returned an error, it returns the
|
|
|
|
|
// buffered bytes, false and a nil error
|
|
|
|
|
// - when the buffer is empty, and the underlying reader previously returned an error, it returns zero length
|
|
|
|
|
// bytes, false, and the error
|
|
|
|
|
func (r Reader) ReadBytes(delimiter []byte, max int) ([]byte, bool, error) {
|
2026-02-22 16:30:37 +01:00
|
|
|
if r.reader == nil {
|
|
|
|
|
return nil, false, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
return r.reader.readBytes(delimiter, max)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// only returns an error when the underlying reader returned an error, or the used pool returned an error
|
|
|
|
|
func (r Reader) ReadUTF8(max int) ([]rune, int, error) {
|
2026-02-22 16:30:37 +01:00
|
|
|
if r.reader == nil {
|
|
|
|
|
return nil, 0, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
return r.reader.readUTF8(max)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// not a copy
|
2026-02-22 16:30:37 +01:00
|
|
|
// error only when nothing buffered
|
2026-02-17 16:58:00 +01:00
|
|
|
func (r Reader) Peek(max int) ([]byte, error) {
|
2026-02-22 16:30:37 +01:00
|
|
|
if r.reader == nil {
|
|
|
|
|
return nil, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
return r.reader.peek(max)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// not a copy
|
|
|
|
|
// can be wrong after error
|
|
|
|
|
func (r Reader) Buffered() []byte {
|
2026-02-22 16:30:37 +01:00
|
|
|
if r.reader == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
return r.reader.buffered()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// important that the writer must not modify the slice data, as defined in the io.Writer interface
|
|
|
|
|
func (r Reader) WriteTo(w io.Writer) (int64, error) {
|
2026-02-22 16:30:37 +01:00
|
|
|
if r.reader == nil {
|
|
|
|
|
return 0, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 16:58:00 +01:00
|
|
|
return r.reader.writeTo(w)
|
|
|
|
|
}
|