linereader

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 2 Imported by: 0

README

linereader

Simple buffered line reader for Go: read a stream line by line — or by raw bytes, or as a plain io.Reader — over one chain of reusable blocks, without ever losing a byte between styles.

lr := linereader.NewLineReader(file, 0, 0) // 0 = defaults (4096)

for {
	line, err := lr.ReadLine()
	if err == io.EOF {
		break
	}
	if err != nil {
		return err
	}
	fmt.Printf("%s (ended by %#x)\n", line, int(lr.LastEolType))
}

What it does

  • ReadLine() ([]byte, error) — the next line, terminator consumed and never included. A line ends at whatever happens first: \r, \n or EOF. The pair \r\n counts as one terminator — even when it rides a block boundary (a \r as the last buffered byte makes the reader pull another block before deciding; unless EOF got there first: a lone CR then). What ended the line lands in LastEolType: EolLf, EolCr, EolCrLf, or EolEof for a final unterminated line.
  • ReadBytes(n uint64) ([]byte, error) — the next n raw bytes, buffer first: whatever already sits in the chain is served before touching the stream. Short only at the end.
  • Read(p []byte) (int, error) — the canonical io.Reader contract: read the header of something with ReadLine, then hand the reader itself to any io.Reader consumer — it continues right where you stopped, buffered bytes first.

How it works

Blocks are grabbed from the stream (BlockSize, default 4096) and chained; lines are found across the chain. Consumed blocks are discarded whole — bytes never move. A line contained in one block is served as a sub-slice, no copy; one crossing blocks gets assembled. MaxLineLength (default 4096) caps how far a line may grow without an EOL — an error instead of unbounded memory.

Errors, EOF and sources that revive

Buffered data always comes out first: errors (io.EOF included) surface only once the chain is drained. A final unterminated line is data, not an error — it comes back with err == nil and LastEolType == EolEof; only the next call returns io.EOF.

The stream's error is kept in CachedError and served once, then cleared: the next attempt asks the stream again. A source that comes back to life after an EOF — a growing file, tail-follow style — revives with a plain poll loop, no rearming:

for {
	line, err := lr.ReadLine()
	if err == io.EOF {
		time.Sleep(interval) // the source may grow
		continue
	}
	...
}

Install

go get github.com/ot4go/linereader

License

MIT — see LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ByteBuffer

type ByteBuffer []byte

type EolType

type EolType int

EolType tells what ended a line: which terminator happened first.

const (
	EolNone EolType = 0
	EolLf   EolType = 0x000A
	EolCr   EolType = 0x000D
	EolCrLf EolType = 0x0D0A
	EolEof  EolType = 1
)

type IoLineReader added in v0.0.2

type IoLineReader interface {
	ReadLine() ([]byte, error)
}

IoLineReader is what a line consumer needs from a reader: just the line. LineReader is the reference implementation, anything else may stand in.

type LineReader

type LineReader struct {
	BlockSize     uint64
	Buffers       []*ByteBuffer
	Offset        uint64
	Reader        io.Reader
	MaxLineLength uint64
	LastEolType   EolType // what ended the last line ReadLine served
	CachedError   error   // the stream's last error (io.EOF included) — served once, then cleared
}

func NewLineReader

func NewLineReader(reader io.Reader, blockSize uint64, maxLineSize uint64) *LineReader

func (*LineReader) Read

func (lr *LineReader) Read(p []byte) (int, error)

Read implements io.Reader: fills p from the buffered chain first, then from the stream. Short reads at the end, io.EOF when nothing is left — the canonical contract.

func (*LineReader) ReadBytes

func (lr *LineReader) ReadBytes(nBytes uint64) ([]byte, error)

ReadBytes hands back the next nBytes as raw bytes — buffer first: whatever already sits in the chain is served before touching the stream, then blocks are pulled until the count is met or the stream ends. At the end it may return fewer bytes (with a nil error while something came); nothing left at all: nil and the stream's error.

func (*LineReader) ReadLine

func (lr *LineReader) ReadLine() ([]byte, error)

ReadLine returns the next line without its terminator, consuming it. A line ends at whatever happens first: '\r', '\n' or EOF; the pair \r\n counts as one terminator — even when it rides a block boundary: with the '\r' as the last buffered byte, another block is read before deciding (unless EOF got there first: a lone CR then). What ended the line lands in LastEolType: EolLf, EolCr, EolCrLf, or EolEof for a final unterminated line. At the very end: nil, io.EOF (and EolNone). A line growing past MaxLineLength without an EOL is an error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL