Skip to content

Instantly share code, notes, and snippets.

@keegancsmith
Created April 24, 2018 11:03
Show Gist options
  • Save keegancsmith/54d2325e7a3c6eb78276c884c4208aa6 to your computer and use it in GitHub Desktop.
Save keegancsmith/54d2325e7a3c6eb78276c884c4208aa6 to your computer and use it in GitHub Desktop.
Using bufio.Scanner to get the last available line
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
)
func scanLastNonEmptyLine(data []byte, atEOF bool) (advance int, token []byte, err error) {
// Set advance to after our last line
if atEOF {
advance = len(data)
} else {
// data[advance:] now contains a possibly incomplete line
advance = bytes.LastIndexAny(data, "\n\r") + 1
}
data = data[:advance]
// Remove empty lines (strip EOL chars)
data = bytes.TrimRight(data, "\n\r")
// We have no non-empty lines, so advance but do not return a token.
if len(data) == 0 {
return advance, nil, nil
}
token = data[bytes.LastIndexAny(data, "\n\r")+1:]
return advance, token, nil
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(scanLastNonEmptyLine)
for scanner.Scan() {
fmt.Printf("%q\n", scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatalf("Invalid input: %s", err)
}
}
package main
import (
"bytes"
"testing"
)
func TestScanLastNonEmptyLine(t *testing.T) {
tests := []struct {
Name string
Data []byte
AtEOF bool
Advance int
Token []byte
}{
{
Name: "request-more",
Data: []byte("test"),
AtEOF: false,
Advance: 0,
Token: nil,
},
{
Name: "only-eol",
Data: []byte("\n"),
AtEOF: false,
Advance: 1,
Token: nil,
},
{
Name: "only-eol-multi",
Data: []byte("\n\r\r\r"),
AtEOF: false,
Advance: 4,
Token: nil,
},
{
Name: "only-eol-not-eof",
Data: []byte("\n\rworld"),
AtEOF: false,
Advance: 2, // at the w in world
Token: nil,
},
{
Name: "eof-simple",
Data: []byte("hello"),
AtEOF: true,
Advance: 5,
Token: []byte("hello"),
},
{
Name: "eof-trailing-eol-1",
Data: []byte("hello\n"),
AtEOF: true,
Advance: 6,
Token: []byte("hello"),
},
{
Name: "eof-trailing-eol-2",
Data: []byte("hello\r\n"),
AtEOF: true,
Advance: 7,
Token: []byte("hello"),
},
{
Name: "eof-trailing-eol-4",
Data: []byte("hello\r\n\r\n"),
AtEOF: true,
Advance: 9,
Token: []byte("hello"),
},
{
Name: "one-line",
Data: []byte("hello\nworld"),
AtEOF: false,
Advance: 6, // at the w in world
Token: []byte("hello"),
},
{
Name: "many-lines",
Data: []byte("one\ntwotwo\nthreethreethree\rfourfourfourfour"),
AtEOF: false,
Advance: 27, // at first f
Token: []byte("threethreethree"),
},
{
Name: "many-lines-eof",
Data: []byte("one\ntwotwo\nthreethreethree\rfourfourfourfour"),
AtEOF: true,
Advance: 43, // at end of data
Token: []byte("fourfourfourfour"),
},
{
Name: "many-lines-eol",
Data: []byte("one\ntwotwo\nthreethreethree\rfourfourfourfour\n"),
AtEOF: false,
Advance: 44, // at end of data
Token: []byte("fourfourfourfour"),
},
{
Name: "many-lines-multi-eol",
Data: []byte("one\ntwotwo\nthreethreethree\rfourfourfourfour\r\n"),
AtEOF: false,
Advance: 45, // at end of data
Token: []byte("fourfourfourfour"),
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
advance, token, err := scanLastNonEmptyLine(tt.Data, tt.AtEOF)
if err != nil {
t.Fatal(err)
}
if advance != tt.Advance {
t.Errorf("got advance %d != %d", advance, tt.Advance)
}
if !bytes.Equal(token, tt.Token) {
t.Errorf("got token %q != %q", token, tt.Token)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment