// alternative to bufio // meant to be used with buffer from pool package buffer import ( "errors" "io" ) type Pool interface { Get() ([]byte, error) Put([]byte) } type Options struct { // defaults to new instance created by DefaultPool() Pool Pool } type ContentFunc func(io.Writer) (int64, error) // 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 } var ( ErrZeroAllocation = errors.New("zero allocation") ErrContentAbort = errors.New("content pipe aborted") ) func DefaultPool(allocSize int) Pool { return newPool(allocSize) } func NoPool(allocSize int) Pool { return noPool{allocSize: allocSize} } 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{} } if o.Pool == nil { o.Pool = DefaultPool(1 << 12) } return Reader{reader: &reader{options: o, in: in}} } // 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(1 << 12) } return Reader{reader: &reader{options: o, in: mkcontent(c)}} } // - 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) { if r.reader == nil { return 0, io.EOF } 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) { if r.reader == nil { return nil, false, io.EOF } 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) { if r.reader == nil { return nil, 0, io.EOF } return r.reader.readUTF8(max) } // not a copy // error only when nothing buffered func (r Reader) Peek(max int) ([]byte, error) { if r.reader == nil { return nil, io.EOF } return r.reader.peek(max) } // not a copy // can be wrong after error func (r Reader) Buffered() []byte { if r.reader == nil { return nil } 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) { if r.reader == nil { return 0, nil } return r.reader.writeTo(w) }