pax_global_header 0000666 0000000 0000000 00000000064 13725760013 0014516 g ustar 00root root 0000000 0000000 52 comment=612b799ff231b7796194060498437d4db090da3d
go-buffer-2.0.0/ 0000775 0000000 0000000 00000000000 13725760013 0013371 5 ustar 00root root 0000000 0000000 go-buffer-2.0.0/.github/ 0000775 0000000 0000000 00000000000 13725760013 0014731 5 ustar 00root root 0000000 0000000 go-buffer-2.0.0/.github/workflows/ 0000775 0000000 0000000 00000000000 13725760013 0016766 5 ustar 00root root 0000000 0000000 go-buffer-2.0.0/.github/workflows/go.yml 0000664 0000000 0000000 00000001001 13725760013 0020106 0 ustar 00root root 0000000 0000000 name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Build
run: go build -v .
- name: Test
run: make test
go-buffer-2.0.0/.gitignore 0000664 0000000 0000000 00000000422 13725760013 0015357 0 ustar 00root root 0000000 0000000 # Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.idea
# Dependency directories (remove the comment below to include it)
# vendor/
go-buffer-2.0.0/.tool-versions 0000664 0000000 0000000 00000000014 13725760013 0016210 0 ustar 00root root 0000000 0000000 golang 1.14
go-buffer-2.0.0/LICENSE 0000664 0000000 0000000 00000002052 13725760013 0014375 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2020 Globo.com
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.
go-buffer-2.0.0/Makefile 0000664 0000000 0000000 00000000242 13725760013 0015027 0 ustar 00root root 0000000 0000000 test:
go run github.com/onsi/ginkgo/ginkgo -keepGoing -progress -timeout 1m -race --randomizeAllSpecs --randomizeSuites
bench:
go test -bench=. -run=Benchmark
go-buffer-2.0.0/README.md 0000664 0000000 0000000 00000006066 13725760013 0014660 0 ustar 00root root 0000000 0000000
# go-buffer
`go-buffer` represents a buffer that asynchronously flushes its contents. It is useful for applications that need to aggregate data before writing it to an external storage. A buffer is flushed manually, or automatically when it becomes full or after an interval has elapsed, whichever comes first.
## Installation
go get github.com/globocom/go-buffer
## Examples
### Size-triggered flush
```golang
package main
import (
"time"
"github.com/globocom/go-buffer/v2"
)
func main() {
buff := buffer.New(
// buffer can hold up to 5 items
buffer.WithSize(5),
// call this function when the buffer needs flushing
buffer.WithFlusher(func(items []interface{}) {
for _, item := range items {
println(item.(string))
}
}),
)
// ensure the buffer
defer buff.Close()
buff.Push("item 1")
buff.Push("item 2")
buff.Push("item 3")
buff.Push("item 4")
buff.Push("item 5")
// block the current goroutine
time.Sleep(3*time.Second)
println("done")
}
```
### Interval-triggered flush
```golang
package main
import (
"time"
"github.com/globocom/go-buffer/v2"
)
func main() {
buff := buffer.New(
// buffer can hold up to 5 items
buffer.WithSize(5),
// buffer will be flushed every second, regardless of
// how many items were pushed
buffer.WithFlushInterval(time.Second),
// call this function when the buffer needs flushing
buffer.WithFlusher(func(items []interface{}) {
for _, item := range items {
println(item.(string))
}
}),
)
defer buff.Close()
buff.Push("item 1")
buff.Push("item 2")
buff.Push("item 3")
// block the current goroutine
time.Sleep(3*time.Second)
println("done")
}
```
### Manual flush
```golang
package main
import (
"time"
"github.com/globocom/go-buffer/v2"
)
func main() {
buff := buffer.New(
// buffer can hold up to 5 items
buffer.WithSize(5),
// call this function when the buffer needs flushing
buffer.WithFlusher(func(items []interface{}) {
for _, item := range items {
println(item.(string))
}
}),
)
defer buff.Close()
buff.Push("item 1")
buff.Push("item 2")
buff.Push("item 3")
// block the current goroutine
time.Sleep(3*time.Second)
buff.Flush()
println("done")
}
```
## Documentation
Visit [Pkg.go.dev](https://pkg.go.dev/github.com/globocom/go-buffer) for full documentation.
## License
[MIT License](https://github.com/globocom/go-buffer/blob/master/LICENSE)
go-buffer-2.0.0/bench_test.go 0000664 0000000 0000000 00000001223 13725760013 0016034 0 ustar 00root root 0000000 0000000 package buffer_test
import (
"testing"
"github.com/globocom/go-buffer/v2"
)
func BenchmarkBuffer(b *testing.B) {
noop := buffer.FlusherFunc(func([]interface{}) {})
b.Run("push only", func(b *testing.B) {
sut := buffer.New(
buffer.WithSize(uint(b.N)+1),
buffer.WithFlusher(noop),
)
defer sut.Close()
for i := 0; i < b.N; i++ {
err := sut.Push(i)
if err != nil {
b.Fail()
}
}
})
b.Run("push and flush", func(b *testing.B) {
sut := buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(noop),
)
defer sut.Close()
for i := 0; i < b.N; i++ {
err := sut.Push(i)
if err != nil {
b.Fail()
}
}
})
}
go-buffer-2.0.0/buffer.go 0000664 0000000 0000000 00000006727 13725760013 0015205 0 ustar 00root root 0000000 0000000 package buffer
import (
"errors"
"io"
"time"
)
var (
// ErrTimeout indicates an operation has timed out.
ErrTimeout = errors.New("operation timed-out")
// ErrClosed indicates the buffer is closed and can no longer be used.
ErrClosed = errors.New("buffer is closed")
)
type (
// Buffer represents a data buffer that is asynchronously flushed, either manually or automatically.
Buffer struct {
io.Closer
dataCh chan interface{}
flushCh chan struct{}
closeCh chan struct{}
doneCh chan struct{}
options *Options
}
)
// Push appends an item to the end of the buffer.
//
// It returns an ErrTimeout if if cannot be performed in a timely fashion, and
// an ErrClosed if the buffer has been closed.
func (buffer *Buffer) Push(item interface{}) error {
if buffer.closed() {
return ErrClosed
}
select {
case buffer.dataCh <- item:
return nil
case <-time.After(buffer.options.PushTimeout):
return ErrTimeout
}
}
// Flush outputs the buffer to a permanent destination.
//
// It returns an ErrTimeout if if cannot be performed in a timely fashion, and
// an ErrClosed if the buffer has been closed.
func (buffer *Buffer) Flush() error {
if buffer.closed() {
return ErrClosed
}
select {
case buffer.flushCh <- struct{}{}:
return nil
case <-time.After(buffer.options.FlushTimeout):
return ErrTimeout
}
}
// Close flushes the buffer and prevents it from being further used.
//
// It returns an ErrTimeout if if cannot be performed in a timely fashion, and
// an ErrClosed if the buffer has already been closed.
//
// An ErrTimeout can either mean that a flush could not be triggered, or it can
// mean that a flush was triggered but it has not finished yet. In any case it is
// safe to call Close again.
func (buffer *Buffer) Close() error {
if buffer.closed() {
return ErrClosed
}
select {
case buffer.closeCh <- struct{}{}:
// noop
case <-time.After(buffer.options.CloseTimeout):
return ErrTimeout
}
select {
case <-buffer.doneCh:
close(buffer.dataCh)
close(buffer.flushCh)
close(buffer.closeCh)
return nil
case <-time.After(buffer.options.CloseTimeout):
return ErrTimeout
}
}
func (buffer Buffer) closed() bool {
select {
case <-buffer.doneCh:
return true
default:
return false
}
}
func (buffer *Buffer) consume() {
count := 0
items := make([]interface{}, buffer.options.Size)
mustFlush := false
ticker, stopTicker := newTicker(buffer.options.FlushInterval)
isOpen := true
for isOpen {
select {
case item := <-buffer.dataCh:
items[count] = item
count++
mustFlush = count >= len(items)
case <-ticker:
mustFlush = count > 0
case <-buffer.flushCh:
mustFlush = count > 0
case <-buffer.closeCh:
isOpen = false
mustFlush = count > 0
}
if mustFlush {
stopTicker()
buffer.options.Flusher.Write(items[:count])
count = 0
items = make([]interface{}, buffer.options.Size)
mustFlush = false
ticker, stopTicker = newTicker(buffer.options.FlushInterval)
}
}
stopTicker()
close(buffer.doneCh)
}
func newTicker(interval time.Duration) (<-chan time.Time, func()) {
if interval == 0 {
return nil, func() {}
}
ticker := time.NewTicker(interval)
return ticker.C, ticker.Stop
}
// New creates a new buffer instance with the provided options.
func New(opts ...Option) *Buffer {
buffer := &Buffer{
dataCh: make(chan interface{}),
flushCh: make(chan struct{}),
closeCh: make(chan struct{}),
doneCh: make(chan struct{}),
options: resolveOptions(opts...),
}
go buffer.consume()
return buffer
}
go-buffer-2.0.0/buffer_test.go 0000664 0000000 0000000 00000015342 13725760013 0016235 0 ustar 00root root 0000000 0000000 package buffer_test
import (
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/globocom/go-buffer/v2"
)
var _ = Describe("Buffer", func() {
var flusher *MockFlusher
BeforeEach(func() {
flusher = NewMockFlusher()
})
Context("Constructor", func() {
It("creates a new Buffer instance", func() {
// act
sut := buffer.New(
buffer.WithSize(10),
buffer.WithFlusher(flusher),
)
// assert
Expect(sut).NotTo(BeNil())
})
Context("invalid options", func() {
It("panics when provided an invalid size", func() {
Expect(func() {
buffer.New(
buffer.WithSize(0),
)
}).To(Panic())
})
It("panics when provided an invalid flusher", func() {
Expect(func() {
buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(nil),
)
}).To(Panic())
})
It("panics when provided an invalid flush interval", func() {
Expect(func() {
buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithFlushInterval(-1),
)
}).To(Panic())
})
It("panics when provided an invalid push timeout", func() {
Expect(func() {
buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithPushTimeout(-1),
)
}).To(Panic())
})
It("panics when provided an invalid flush timeout", func() {
Expect(func() {
buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithFlushTimeout(-1),
)
}).To(Panic())
})
It("panics when provided an invalid close timeout", func() {
Expect(func() {
buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithCloseTimeout(-1),
)
}).To(Panic())
})
})
})
Context("Pushing", func() {
It("pushes items into the buffer when Push is called", func() {
// arrange
sut := buffer.New(
buffer.WithSize(3),
buffer.WithFlusher(flusher),
)
// act
err1 := sut.Push(1)
err2 := sut.Push(2)
err3 := sut.Push(3)
// assert
Expect(err1).To(Succeed())
Expect(err2).To(Succeed())
Expect(err3).To(Succeed())
})
It("fails when Push cannot execute in a timely fashion", func() {
// arrange
flusher.Func = func() { select {} }
sut := buffer.New(
buffer.WithSize(2),
buffer.WithFlusher(flusher),
buffer.WithPushTimeout(time.Second),
)
// act
err1 := sut.Push(1)
err2 := sut.Push(2)
err3 := sut.Push(3)
// assert
Expect(err1).To(Succeed())
Expect(err2).To(Succeed())
Expect(err3).To(MatchError(buffer.ErrTimeout))
})
It("fails when the buffer is closed", func() {
// arrange
sut := buffer.New(
buffer.WithSize(2),
buffer.WithFlusher(flusher),
)
_ = sut.Close()
// act
err := sut.Push(1)
// assert
Expect(err).To(MatchError(buffer.ErrClosed))
})
})
Context("Flushing", func() {
It("flushes the buffer when it fills up", func(done Done) {
// arrange
sut := buffer.New(
buffer.WithSize(5),
buffer.WithFlusher(flusher),
)
// act
_ = sut.Push(1)
_ = sut.Push(2)
_ = sut.Push(3)
_ = sut.Push(4)
_ = sut.Push(5)
// assert
result := <-flusher.Done
Expect(result.Items).To(ConsistOf(1, 2, 3, 4, 5))
close(done)
})
It("flushes the buffer when the provided interval has elapsed", func(done Done) {
// arrange
interval := 3 * time.Second
start := time.Now()
sut := buffer.New(
buffer.WithSize(5),
buffer.WithFlusher(flusher),
buffer.WithFlushInterval(interval),
)
// act
_ = sut.Push(1)
// assert
result := <-flusher.Done
Expect(result.Items).To(ConsistOf(1))
Expect(result.Time).To(BeTemporally("~", start, interval+time.Second))
close(done)
}, 5)
It("flushes the buffer when Flush is called", func(done Done) {
// arrange
sut := buffer.New(
buffer.WithSize(3),
buffer.WithFlusher(flusher),
)
_ = sut.Push(1)
_ = sut.Push(2)
// act
err := sut.Flush()
// assert
result := <-flusher.Done
Expect(err).To(Succeed())
Expect(result.Items).To(ConsistOf(1, 2))
close(done)
})
It("fails when Flush cannot execute in a timely fashion", func() {
// arrange
flusher.Func = func() { time.Sleep(3 * time.Second) }
sut := buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithFlushTimeout(time.Second),
)
_ = sut.Push(1)
// act
err := sut.Flush()
// assert
Expect(err).To(MatchError(buffer.ErrTimeout))
})
It("fails when the buffer is closed", func() {
// arrange
sut := buffer.New(
buffer.WithSize(2),
buffer.WithFlusher(flusher),
)
_ = sut.Close()
// act
err := sut.Flush()
// assert
Expect(err).To(MatchError(buffer.ErrClosed))
})
})
Context("Closing", func() {
It("flushes the buffer and closes it when Close is called", func(done Done) {
// arrange
sut := buffer.New(
buffer.WithSize(3),
buffer.WithFlusher(flusher),
)
_ = sut.Push(1)
_ = sut.Push(2)
// act
err := sut.Close()
// assert
result := <-flusher.Done
Expect(err).To(Succeed())
Expect(result.Items).To(ConsistOf(1, 2))
close(done)
})
It("fails when Close cannot execute in a timely fashion", func() {
// arrange
flusher.Func = func() { time.Sleep(2 * time.Second) }
sut := buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithCloseTimeout(time.Second),
)
_ = sut.Push(1)
// act
err := sut.Close()
// assert
Expect(err).To(MatchError(buffer.ErrTimeout))
})
It("fails when the buffer is closed", func() {
// arrange
flusher.Func = func() { time.Sleep(2 * time.Second) }
sut := buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithCloseTimeout(time.Second),
)
_ = sut.Close()
// act
err := sut.Close()
// assert
Expect(err).To(MatchError(buffer.ErrClosed))
})
It("allows Close to be called again if it fails", func() {
// arrange
flusher.Func = func() { time.Sleep(2 * time.Second) }
sut := buffer.New(
buffer.WithSize(1),
buffer.WithFlusher(flusher),
buffer.WithCloseTimeout(time.Second),
)
_ = sut.Push(1)
// act
err1 := sut.Close()
time.Sleep(time.Second)
err2 := sut.Close()
// assert
Expect(err1).To(MatchError(buffer.ErrTimeout))
Expect(err2).To(Succeed())
})
})
})
type (
MockFlusher struct {
Done chan *WriteCall
Func func()
}
WriteCall struct {
Time time.Time
Items []interface{}
}
)
func (flusher *MockFlusher) Write(items []interface{}) {
call := &WriteCall{
Time: time.Now(),
Items: items,
}
if flusher.Func != nil {
flusher.Func()
}
flusher.Done <- call
}
func NewMockFlusher() *MockFlusher {
return &MockFlusher{
Done: make(chan *WriteCall, 1),
}
}
go-buffer-2.0.0/flusher.go 0000664 0000000 0000000 00000000433 13725760013 0015370 0 ustar 00root root 0000000 0000000 package buffer
type (
// Flusher represents a destination of buffered data.
Flusher interface {
Write(items []interface{})
}
// FlusherFunc represents a flush function.
FlusherFunc func(items []interface{})
)
func (fn FlusherFunc) Write(items []interface{}) {
fn(items)
}
go-buffer-2.0.0/go.mod 0000664 0000000 0000000 00000000176 13725760013 0014503 0 ustar 00root root 0000000 0000000 module github.com/globocom/go-buffer/v2
go 1.13
require (
github.com/onsi/ginkgo v1.13.0
github.com/onsi/gomega v1.10.1
)
go-buffer-2.0.0/go.sum 0000664 0000000 0000000 00000013140 13725760013 0014523 0 ustar 00root root 0000000 0000000 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
go-buffer-2.0.0/gopher.png 0000664 0000000 0000000 00000211631 13725760013 0015367 0 ustar 00root root 0000000 0000000 PNG
IHDR W I /n -*zTXtRaw profile type exif xڵir$9uq ( 7ΈȪ^gA4oQ(ܝ>riZ=G|_i߯_~>?h9L_~=]$?¦;ks|?~B|?|_|ҿ_ј%+(xRHc }LtA%Sp|Ji|/Ƅ~>{erOYr/>v!*ׅ,pyw1c,S#n(pcK#+ӎDZf9_c oΝ-XW~?pnMQxY\1fZ9ͫXpV||X
4͝~}.J[suϟ
L.&$VאJ[cg)
R1ȘSѵأ^KQXjjHʹ?-wbhTr)V+̚jڪ@nr+zms/{}8XFm1ƜMn4טּʫk wuӢ%&Zn 'riq%n[o;wUjj^~u\"N9M+@@G!稕ӚId8Z10
͕Z_X9ͪxnd'>]S6_XdT&7][y 0V' ZsZ-I7vPXu\a-~JW啉1ǟFt4.\}Y;C6VҮL%q!^"%ֵJ_rc{BHVXD`W,8J}Y[@} yl/Y%R { t*Yx3*IqܸXN{cD*b3qZ?3r5ZY_v҉`7%h[GIDu]U[VF*i /d
LܽJއ~1ϐC"S7{R]_樗3j?VSXOgz5.̧}ªŅ -H}+)fDe6_\=P`Yf≽
6dv_y1# eMuf"ݬf|9ۘҁ^a5%TJqJ:lY,@fy!340YVjg:d`:Q53m*4k@bxW3^֫9+jsv/UFfYPl
79\f(K ;I
O;(oDH/r%]VZf\t~ؒ pOBDbo^d$nV&