New Upstream Release - golang-github-pelletier-go-buffruneio
Ready changes
Summary
Merged new upstream version: 0.3.0 (was: 0.2.0).
Resulting package
Built on 2022-03-16T08:19 (took 1m54s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-pelletier-go-buffruneio-dev
Lintian Result
Diff
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index c56069f..0000000
--- a/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.test
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 9720442..5a4bccf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,8 @@
language: go
sudo: false
go:
- - 1.3.3
- - 1.4.3
- - 1.5.3
+ - 1.6.4
+ - 1.7.6
+ - 1.8.5
+ - 1.9.2
- tip
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c93f4c5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Thomas Pelletier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index ff608b3..633c058 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,7 @@
[![Tests Status](https://travis-ci.org/pelletier/go-buffruneio.svg?branch=master)](https://travis-ci.org/pelletier/go-buffruneio)
[![GoDoc](https://godoc.org/github.com/pelletier/go-buffruneio?status.svg)](https://godoc.org/github.com/pelletier/go-buffruneio)
-Buffruneio is a wrapper around bufio to provide buffered runes access with
-unlimited unreads.
+Buffruneio provides rune-based buffered input.
```go
import "github.com/pelletier/go-buffruneio"
@@ -37,12 +36,12 @@ The documentation and additional examples are available at
## Contribute
Feel free to report bugs and patches using GitHub's pull requests system on
-[pelletier/go-toml](https://github.com/pelletier/go-buffruneio). Any feedback is
+[pelletier/go-buffruneio](https://github.com/pelletier/go-buffruneio). Any feedback is
much appreciated!
## LICENSE
-Copyright (c) 2016 Thomas Pelletier
+Copyright (c) 2016 - 2018 Thomas Pelletier
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/buffruneio.go b/buffruneio.go
index 4e6d6ea..1e6a522 100644
--- a/buffruneio.go
+++ b/buffruneio.go
@@ -1,117 +1,137 @@
-// Package buffruneio is a wrapper around bufio to provide buffered runes access with unlimited unreads.
+// Package buffruneio provides rune-based buffered input.
package buffruneio
import (
"bufio"
- "container/list"
"errors"
"io"
+ "unicode/utf8"
)
-// Rune to indicate end of file.
-const (
- EOF = -(iota + 1)
-)
+// EOF is a rune value indicating end-of-file.
+const EOF = -1
-// ErrNoRuneToUnread is returned by UnreadRune() when the read index is already at the beginning of the buffer.
+// ErrNoRuneToUnread is the error returned when UnreadRune is called with nothing to unread.
var ErrNoRuneToUnread = errors.New("no rune to unwind")
-// Reader implements runes buffering for an io.Reader object.
+// A Reader implements rune-based input for an underlying byte stream.
type Reader struct {
- buffer *list.List
- current *list.Element
+ buffer []rune
+ current int
input *bufio.Reader
}
-// NewReader returns a new Reader.
-func NewReader(rd io.Reader) *Reader {
+// NewReader returns a new Reader reading the given input.
+func NewReader(input io.Reader) *Reader {
return &Reader{
- buffer: list.New(),
- input: bufio.NewReader(rd),
+ input: bufio.NewReader(input),
}
}
-type runeWithSize struct {
- r rune
- size int
-}
+// The rune buffer stores -2 to represent RuneError of length 1 (UTF-8 decoding errors).
+const badRune = -2
+// feedBuffer adds a rune to the buffer.
+// If EOF is reached, it adds EOF to the buffer and returns nil.
+// If a different error is encountered, it returns the error without
+// adding to the buffer.
func (rd *Reader) feedBuffer() error {
+ if rd.buffer == nil {
+ rd.buffer = make([]rune, 0, 256)
+ }
r, size, err := rd.input.ReadRune()
-
if err != nil {
if err != io.EOF {
return err
}
r = EOF
}
-
- newRuneWithSize := runeWithSize{r, size}
-
- rd.buffer.PushBack(newRuneWithSize)
- if rd.current == nil {
- rd.current = rd.buffer.Back()
+ if r == utf8.RuneError && size == 1 {
+ r = badRune
}
+ rd.buffer = append(rd.buffer, r)
return nil
}
-// ReadRune reads the next rune from buffer, or from the underlying reader if needed.
+// ReadRune reads and returns the next rune from the input.
+// The rune is also saved in an internal buffer, in case UnreadRune is called.
+// To avoid unbounded buffer growth, the caller must call Forget at appropriate intervals.
+//
+// At end of file, ReadRune returns EOF, 0, nil.
+// On read errors other than io.EOF, ReadRune returns EOF, 0, err.
func (rd *Reader) ReadRune() (rune, int, error) {
- if rd.current == rd.buffer.Back() || rd.current == nil {
- err := rd.feedBuffer()
- if err != nil {
+ if rd.current >= len(rd.buffer) {
+ if err := rd.feedBuffer(); err != nil {
return EOF, 0, err
}
}
-
- runeWithSize := rd.current.Value.(runeWithSize)
- rd.current = rd.current.Next()
- return runeWithSize.r, runeWithSize.size, nil
+ r := rd.buffer[rd.current]
+ rd.current++
+ if r == badRune {
+ return utf8.RuneError, 1, nil
+ }
+ if r == EOF {
+ return EOF, 0, nil
+ }
+ return r, utf8.RuneLen(r), nil
}
-// UnreadRune pushes back the previously read rune in the buffer, extending it if needed.
+// UnreadRune rewinds the input by one rune, undoing the effect of a single ReadRune call.
+// UnreadRune may be called multiple times to rewind a sequence of ReadRune calls,
+// up to the last time Forget was called or the beginning of the input.
+//
+// If there are no ReadRune calls left to undo, UnreadRune returns ErrNoRuneToUnread.
func (rd *Reader) UnreadRune() error {
- if rd.current == rd.buffer.Front() {
+ if rd.current == 0 {
return ErrNoRuneToUnread
}
- if rd.current == nil {
- rd.current = rd.buffer.Back()
- } else {
- rd.current = rd.current.Prev()
- }
+ rd.current--
return nil
}
-// Forget removes runes stored before the current stream position index.
+// Forget discards buffered runes before the current input position.
+// Calling Forget makes it impossible to UnreadRune earlier than the current input position
+// but is necessary to avoid unbounded buffer growth.
func (rd *Reader) Forget() {
- if rd.current == nil {
- rd.current = rd.buffer.Back()
- }
- for ; rd.current != rd.buffer.Front(); rd.buffer.Remove(rd.current.Prev()) {
- }
+ n := copy(rd.buffer, rd.buffer[rd.current:])
+ rd.current = 0
+ rd.buffer = rd.buffer[:n]
}
-// PeekRune returns at most the next n runes, reading from the uderlying source if
-// needed. Does not move the current index. It includes EOF if reached.
+// PeekRunes returns the next n runes in the input,
+// without advancing the current input position.
+//
+// If the input has fewer than n runes and then returns
+// an io.EOF error, PeekRune returns a slice containing
+// the available runes followed by EOF.
+// On other hand, if the input ends early with a non-io.EOF error,
+// PeekRune returns a slice containing only the available runes,
+// with no terminating EOF.
func (rd *Reader) PeekRunes(n int) []rune {
+ for len(rd.buffer)-rd.current < n && !rd.haveEOF() {
+ if err := rd.feedBuffer(); err != nil {
+ break
+ }
+ }
+
res := make([]rune, 0, n)
- cursor := rd.current
for i := 0; i < n; i++ {
- if cursor == nil {
- err := rd.feedBuffer()
- if err != nil {
- return res
- }
- cursor = rd.buffer.Back()
+ if rd.current + i >= len(rd.buffer) {
+ // reached end of buffer before reading as much as we wanted
+ break
+ }
+ r := rd.buffer[rd.current+i]
+ if r == badRune {
+ r = utf8.RuneError
}
- if cursor != nil {
- r := cursor.Value.(runeWithSize).r
- res = append(res, r)
- if r == EOF {
- return res
- }
- cursor = cursor.Next()
+ res = append(res, r)
+ if r == EOF {
+ break
}
}
return res
}
+
+func (rd *Reader) haveEOF() bool {
+ return rd.current < len(rd.buffer) && rd.buffer[len(rd.buffer)-1] == EOF
+}
diff --git a/buffruneio_test.go b/buffruneio_test.go
index 67b0cba..e97c73a 100644
--- a/buffruneio_test.go
+++ b/buffruneio_test.go
@@ -1,9 +1,13 @@
package buffruneio
import (
+ "reflect"
"runtime/debug"
"strings"
"testing"
+ "unicode/utf8"
+ "io"
+ "fmt"
)
func assertNoError(t *testing.T, err error) {
@@ -27,14 +31,19 @@ func assumeRunesArray(t *testing.T, expected []rune, got []rune) {
func assumeRune(t *testing.T, rd *Reader, r rune) {
gotRune, size, err := rd.ReadRune()
- assertNoError(t, err)
- if gotRune != r {
- t.Fatal("got", string(gotRune),
- "(", []byte(string(gotRune)), ")",
- "expected", string(r),
- "(", []byte(string(r)), ")")
- t.Fatal("got size", size,
- "expected", len([]byte(string(r))))
+ wantSize := utf8.RuneLen(r)
+ if wantSize < 0 {
+ wantSize = 0
+ }
+ if gotRune != r || size != wantSize || err != nil {
+ t.Fatalf("ReadRune() = %q, %d, %v, wanted %q, %d, nil", gotRune, size, err, r, wantSize)
+ }
+}
+
+func assumeBadRune(t *testing.T, rd *Reader) {
+ gotRune, size, err := rd.ReadRune()
+ if gotRune != utf8.RuneError || size != 1 || err != nil {
+ t.Fatalf("ReadRune() = %q, %d, %v, wanted %q, 1, nil", gotRune, size, err, utf8.RuneError)
}
}
@@ -58,6 +67,29 @@ func TestMultipleEOF(t *testing.T) {
assumeRune(t, rd, EOF)
}
+func TestBadRunes(t *testing.T) {
+ s := "ab\xff\ufffd\xffcd"
+ rd := NewReader(strings.NewReader(s))
+
+ assumeRune(t, rd, 'a')
+ assumeRune(t, rd, 'b')
+ assumeBadRune(t, rd)
+ assumeRune(t, rd, utf8.RuneError)
+ assumeBadRune(t, rd)
+ assumeRune(t, rd, 'c')
+ assumeRune(t, rd, 'd')
+
+ for i := 0; i < 6; i++ {
+ assertNoError(t, rd.UnreadRune())
+ }
+ assumeRune(t, rd, 'b')
+ assumeBadRune(t, rd)
+ assumeRune(t, rd, utf8.RuneError)
+ assumeBadRune(t, rd)
+ assumeRune(t, rd, 'c')
+ assumeRune(t, rd, 'd')
+}
+
func TestUnread(t *testing.T) {
s := "ab"
rd := NewReader(strings.NewReader(s))
@@ -70,28 +102,57 @@ func TestUnread(t *testing.T) {
}
func TestUnreadEOF(t *testing.T) {
- s := ""
+ s := "x"
rd := NewReader(strings.NewReader(s))
_ = rd.UnreadRune()
+ assumeRune(t, rd, 'x')
assumeRune(t, rd, EOF)
assumeRune(t, rd, EOF)
assertNoError(t, rd.UnreadRune())
assumeRune(t, rd, EOF)
+ assertNoError(t, rd.UnreadRune())
+ assertNoError(t, rd.UnreadRune())
+ assumeRune(t, rd, EOF)
+ assumeRune(t, rd, EOF)
+ assertNoError(t, rd.UnreadRune())
+ assertNoError(t, rd.UnreadRune())
+ assertNoError(t, rd.UnreadRune())
+ assumeRune(t, rd, 'x')
+ assumeRune(t, rd, EOF)
+ assumeRune(t, rd, EOF)
}
func TestForget(t *testing.T) {
- s := "hello"
+ s := "helio"
rd := NewReader(strings.NewReader(s))
assumeRune(t, rd, 'h')
assumeRune(t, rd, 'e')
assumeRune(t, rd, 'l')
+ assumeRune(t, rd, 'i')
+ rd.Forget()
+ if rd.UnreadRune() != ErrNoRuneToUnread {
+ t.Fatal("no rune should be available")
+ }
+ assumeRune(t, rd, 'o')
+}
+
+func TestForgetAfterUnread(t *testing.T) {
+ s := "helio"
+ rd := NewReader(strings.NewReader(s))
+
+ assumeRune(t, rd, 'h')
+ assumeRune(t, rd, 'e')
assumeRune(t, rd, 'l')
+ assumeRune(t, rd, 'i')
+ assertNoError(t, rd.UnreadRune())
rd.Forget()
if rd.UnreadRune() != ErrNoRuneToUnread {
t.Fatal("no rune should be available")
}
+ assumeRune(t, rd, 'i')
+ assumeRune(t, rd, 'o')
}
func TestForgetEmpty(t *testing.T) {
@@ -134,12 +195,144 @@ func TestPeek(t *testing.T) {
}
func TestPeekLarge(t *testing.T) {
- s := "abcdefg"
+ s := "abcdefg☺\xff☹"
rd := NewReader(strings.NewReader(s))
runes := rd.PeekRunes(100)
- if len(runes) != len(s)+1 {
- t.Fatal("incorrect number of runes", len(runes))
+ want := []rune{'a', 'b', 'c', 'd', 'e', 'f', 'g', '☺', utf8.RuneError, '☹', EOF}
+ if !reflect.DeepEqual(runes, want) {
+ t.Fatalf("PeekRunes(100) = %q, want %q", runes, want)
+ }
+}
+
+var bigString = strings.Repeat("abcdefghi☺\xff☹", 1024) // 16 kB
+
+const bigStringRunes = 12 * 1024 // 12k runes
+
+func BenchmarkRead16K(b *testing.B) {
+ // Read 16K with no unread, no forget.
+ benchmarkRead(b, 1, false)
+}
+
+func BenchmarkReadForget16K(b *testing.B) {
+ // Read 16K, forgetting every 128 runes.
+ benchmarkRead(b, 1, true)
+}
+
+func BenchmarkReadRewind16K(b *testing.B) {
+ // Read 16K, unread all, read that 16K again.
+ benchmarkRead(b, 2, false)
+}
+
+func benchmarkRead(b *testing.B, count int, forget bool) {
+ if len(bigString) != 16*1024 {
+ b.Fatal("wrong length for bigString")
+ }
+ sr0 := strings.NewReader(bigString)
+ sr := new(strings.Reader)
+ b.SetBytes(int64(len(bigString)))
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ *sr = *sr0
+ rd := NewReader(sr)
+ for repeat := 0; repeat < count; repeat++ {
+ for j := 0; j < bigStringRunes; j++ {
+ r, _, err := rd.ReadRune()
+ if err != nil {
+ b.Fatal(err)
+ }
+ if r == EOF {
+ b.Fatal("unexpected EOF")
+ }
+ if forget && j%128 == 127 {
+ rd.Forget()
+ }
+ }
+ r, _, err := rd.ReadRune()
+ if err != nil {
+ b.Fatal(err)
+ }
+ if r != EOF {
+ b.Fatalf("missing EOF - %q", r)
+ }
+ if repeat == count-1 {
+ break
+ }
+ for rd.UnreadRune() == nil {
+ // keep unreading
+ }
+ }
+ }
+}
+
+// test reader that will fail reading after a given number of reads
+type failingReader struct {
+ r io.Reader // underlying reader
+ failAfter int // start failing after that number of reads
+ readCount int // number of reads already done
+}
+
+func newFailingReaderFromString(s string, failAfter int) *failingReader {
+ return &failingReader{
+ r: strings.NewReader(s),
+ failAfter: failAfter,
+ readCount: 0,
+ }
+}
+
+func (r *failingReader) Read(b []byte) (n int, err error) {
+ if r.readCount < r.failAfter {
+ n, err = r.r.Read(b)
+ r.readCount++
+ return
+ }
+ return 0, fmt.Errorf("expected read failure")
+}
+
+func TestReadFails(t *testing.T) {
+ size := 4097 // needs to be more than bufio.defaultBufSize, which is 4096
+ s := make([]byte, size)
+ for i := 0; i < size; i++ {
+ s[i] = 'a'
+ }
+
+ rd := NewReader(newFailingReaderFromString(string(s), 1))
+
+ runes := rd.PeekRunes(256) // first read, ok
+
+ runes = rd.PeekRunes(1) // rune already loaded, ok
+
+ runes = rd.PeekRunes(4097) // forces a new read, fails
+ if len(runes) != 4096 {
+ t.Fatalf("expected %d runes. got %d", 4096, len(runes))
+ }
+ if runes[4095] != 'a' {
+ t.Fatalf("expected last rune to be 'a'. got '%c'", runes[4095])
+ }
+
+
+ rd = NewReader(newFailingReaderFromString(string(s), 1))
+ for i := 0; i < size - 1; i++ {
+ r, size, err := rd.ReadRune() // read all the runes but last
+ if err != nil {
+ t.Fatalf("no error expeceted at that point, got %s", err)
+ }
+ if size != 1 {
+ t.Fatalf("reading runes that should have size 1, got size %d", size)
+ }
+ if r != 'a' {
+ t.Fatalf("reading a string of 'a', got %c", r)
+ }
+ }
+ // EOF, 0, err
+ r, n, err := rd.ReadRune() // should error
+ if r != EOF {
+ t.Fatalf("expected EOF, got %c", r)
+ }
+ if n != 0 {
+ t.Fatalf("expected size 0, got %d", n)
+ }
+ if err.Error() != "expected read failure" {
+ t.Fatalf("incorrect error: %s", err.Error())
}
- assumeRunesArray(t, []rune{'a', 'b', 'c', 'd', 'e', 'f', 'g', EOF}, runes)
}
diff --git a/debian/changelog b/debian/changelog
index 910a12d..afbc0fb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,12 @@
-golang-github-pelletier-go-buffruneio (0.2.0-2) UNRELEASED; urgency=medium
+golang-github-pelletier-go-buffruneio (0.3.0-1) UNRELEASED; urgency=medium
+ [ Alexandre Viau ]
* Point Vcs-* urls to salsa.debian.org.
- -- Alexandre Viau <aviau@debian.org> Mon, 02 Apr 2018 19:56:33 -0400
+ [ Debian Janitor ]
+ * New upstream release.
+
+ -- Alexandre Viau <aviau@debian.org> Wed, 16 Mar 2022 08:17:33 -0000
golang-github-pelletier-go-buffruneio (0.2.0-1) unstable; urgency=medium
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files