Skip to content

Instantly share code, notes, and snippets.

@perillo
Last active August 29, 2015 14:11
Show Gist options
  • Save perillo/2d23f738e5cbda5d4c9a to your computer and use it in GitHub Desktop.
Save perillo/2d23f738e5cbda5d4c9a to your computer and use it in GitHub Desktop.
last-line
// Read each line from a reader, and report the number of lines until EOF,
// as a negative integer, for the last N lines.
//
// Copyright Manlio Perillo (manlio.perillo@gmail.com)
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
// ring is a simple ring buffer.
type ring struct {
bs [][]byte
head, tail int
}
func newRing(size int) *ring {
// Additional space for the empty element.
bs := make([][]byte, size+1)
return &ring{bs, 0, 0}
}
// put appends b to the right.
func (r *ring) put(b []byte) {
r.bs[r.tail] = b
r.tail = (r.tail + 1) % len(r.bs)
if r.tail == r.head {
r.head = (r.head + 1) % len(r.bs)
}
}
// get pops and return b from the left.
func (r *ring) get() (b []byte) {
if r.tail == r.head {
panic("empty ring buffer")
}
b = r.bs[r.head]
r.head = (r.head + 1) % len(r.bs)
return
}
func (r *ring) len() int {
if r.tail < r.head {
return r.head - r.tail
}
return r.tail - r.head
}
type data struct {
line []byte
num int
err error
}
type reader struct {
r io.Reader
c chan *data
}
func (r *reader) read(lastn int) {
// Keep lastn lines in ring buffer.
var (
bufs = newRing(lastn)
b = bufio.NewReader(r.r)
d *data
)
// Read all the lines and send data to the channel.
for {
// TODO: use scanner when Windows must be supported.
line, err := b.ReadSlice('\n')
if err == io.EOF {
// Send last lines.
for j := 0; j < bufs.len(); j++ {
line := bufs.get()
d = &data{line, j - lastn, nil}
r.c <- d
}
// Send EOF.
d = &data{nil, 0, io.EOF}
r.c <- d
} else if err != nil {
// Send error.
d = &data{nil, 0, err}
r.c <- d
break
}
bufs.put(line)
if bufs.len() == lastn {
// Buffer full: send buffered lines.
for j := 0; j < bufs.len(); j++ {
line := bufs.get()
d = &data{line, 0, nil}
r.c <- d
}
}
}
close(r.c)
}
// NewReader return a new Reader that reports how many lines are
// available until EOF, for the lastn lines.
func NewReader(rd io.Reader, lastn int) *reader {
c := make(chan *data, 1)
r := &reader{rd, c}
go r.read(lastn)
return r
}
// ReadLine reads a line, and return it along with the number of lines
// until EOF, as a negative integer.
// The underlying array may point to data that will be overwritten by a
// subsequent call to ReadLine. It does no allocation.
func (r *reader) ReadLine() (line string, num int, err error) {
info, ok := <-r.c
if !ok {
return "", -1, io.EOF
}
line = string(info.line)
num = info.num
err = info.err
return
}
var text = `this
is some
text for
a test
volume 0
`
func main() {
var (
input = strings.NewReader(text)
output = make([]byte, 0, len(text))
r = NewReader(input, 3)
w = bytes.NewBuffer(output)
)
for {
line, num, err := r.ReadLine()
if err == io.EOF {
break
} else if err != nil {
fmt.Println("ReadLine failed:", err)
return
}
if num == -2 {
line = strings.Replace(line, "0", "1", -1)
}
w.Write([]byte(line))
}
fmt.Println(w.String())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment