initial import
This commit is contained in:
34
core/iox/bufferpool.go
Normal file
34
core/iox/bufferpool.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type BufferPool struct {
|
||||
capability int
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func NewBufferPool(capability int) *BufferPool {
|
||||
return &BufferPool{
|
||||
capability: capability,
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *BufferPool) Get() *bytes.Buffer {
|
||||
buf := bp.pool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
return buf
|
||||
}
|
||||
|
||||
func (bp *BufferPool) Put(buf *bytes.Buffer) {
|
||||
if buf.Cap() < bp.capability {
|
||||
bp.pool.Put(buf)
|
||||
}
|
||||
}
|
||||
15
core/iox/bufferpool_test.go
Normal file
15
core/iox/bufferpool_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBufferPool(t *testing.T) {
|
||||
capacity := 1024
|
||||
pool := NewBufferPool(capacity)
|
||||
pool.Put(bytes.NewBuffer(make([]byte, 0, 2*capacity)))
|
||||
assert.True(t, pool.Get().Cap() <= capacity)
|
||||
}
|
||||
15
core/iox/nopcloser.go
Normal file
15
core/iox/nopcloser.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package iox
|
||||
|
||||
import "io"
|
||||
|
||||
type nopCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NopCloser(w io.Writer) io.WriteCloser {
|
||||
return nopCloser{w}
|
||||
}
|
||||
103
core/iox/read.go
Normal file
103
core/iox/read.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
textReadOptions struct {
|
||||
keepSpace bool
|
||||
withoutBlanks bool
|
||||
omitPrefix string
|
||||
}
|
||||
|
||||
TextReadOption func(*textReadOptions)
|
||||
)
|
||||
|
||||
// The first returned reader needs to be read first, because the content
|
||||
// read from it will be written to the underlying buffer of the second reader.
|
||||
func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) {
|
||||
var buf bytes.Buffer
|
||||
tee := io.TeeReader(reader, &buf)
|
||||
return ioutil.NopCloser(tee), ioutil.NopCloser(&buf)
|
||||
}
|
||||
|
||||
func KeepSpace() TextReadOption {
|
||||
return func(o *textReadOptions) {
|
||||
o.keepSpace = true
|
||||
}
|
||||
}
|
||||
|
||||
// ReadBytes reads exactly the bytes with the length of len(buf)
|
||||
func ReadBytes(reader io.Reader, buf []byte) error {
|
||||
var got int
|
||||
|
||||
for got < len(buf) {
|
||||
n, err := reader.Read(buf[got:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
got += n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadText(filename string) (string, error) {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
|
||||
func ReadTextLines(filename string, opts ...TextReadOption) ([]string, error) {
|
||||
var readOpts textReadOptions
|
||||
for _, opt := range opts {
|
||||
opt(&readOpts)
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !readOpts.keepSpace {
|
||||
line = strings.TrimSpace(line)
|
||||
}
|
||||
if readOpts.withoutBlanks && len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(readOpts.omitPrefix) > 0 && strings.HasPrefix(line, readOpts.omitPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
return lines, scanner.Err()
|
||||
}
|
||||
|
||||
func WithoutBlank() TextReadOption {
|
||||
return func(o *textReadOptions) {
|
||||
o.withoutBlanks = true
|
||||
}
|
||||
}
|
||||
|
||||
func OmitWithPrefix(prefix string) TextReadOption {
|
||||
return func(o *textReadOptions) {
|
||||
o.omitPrefix = prefix
|
||||
}
|
||||
}
|
||||
142
core/iox/read_test.go
Normal file
142
core/iox/read_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"zero/core/fs"
|
||||
"zero/core/stringx"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadText(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
input: `a`,
|
||||
expect: `a`,
|
||||
}, {
|
||||
input: `a
|
||||
`,
|
||||
expect: `a`,
|
||||
}, {
|
||||
input: `a
|
||||
b`,
|
||||
expect: `a
|
||||
b`,
|
||||
}, {
|
||||
input: `a
|
||||
b
|
||||
`,
|
||||
expect: `a
|
||||
b`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
tmpfile, err := fs.TempFilenameWithText(test.input)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
|
||||
content, err := ReadText(tmpfile)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expect, content)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadTextLines(t *testing.T) {
|
||||
text := `1
|
||||
|
||||
2
|
||||
|
||||
#a
|
||||
3`
|
||||
|
||||
tmpfile, err := fs.TempFilenameWithText(text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
|
||||
tests := []struct {
|
||||
options []TextReadOption
|
||||
expectLines int
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
6,
|
||||
}, {
|
||||
[]TextReadOption{KeepSpace(), OmitWithPrefix("#")},
|
||||
6,
|
||||
}, {
|
||||
[]TextReadOption{WithoutBlank()},
|
||||
4,
|
||||
}, {
|
||||
[]TextReadOption{OmitWithPrefix("#")},
|
||||
5,
|
||||
}, {
|
||||
[]TextReadOption{WithoutBlank(), OmitWithPrefix("#")},
|
||||
3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.Rand(), func(t *testing.T) {
|
||||
lines, err := ReadTextLines(tmpfile, test.options...)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expectLines, len(lines))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDupReadCloser(t *testing.T) {
|
||||
input := "hello"
|
||||
reader := ioutil.NopCloser(bytes.NewBufferString(input))
|
||||
r1, r2 := DupReadCloser(reader)
|
||||
verify := func(r io.Reader) {
|
||||
output, err := ioutil.ReadAll(r)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, input, string(output))
|
||||
}
|
||||
|
||||
verify(r1)
|
||||
verify(r2)
|
||||
}
|
||||
|
||||
func TestReadBytes(t *testing.T) {
|
||||
reader := ioutil.NopCloser(bytes.NewBufferString("helloworld"))
|
||||
buf := make([]byte, 5)
|
||||
err := ReadBytes(reader, buf)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "hello", string(buf))
|
||||
}
|
||||
|
||||
func TestReadBytesNotEnough(t *testing.T) {
|
||||
reader := ioutil.NopCloser(bytes.NewBufferString("hell"))
|
||||
buf := make([]byte, 5)
|
||||
err := ReadBytes(reader, buf)
|
||||
assert.Equal(t, io.EOF, err)
|
||||
}
|
||||
|
||||
func TestReadBytesChunks(t *testing.T) {
|
||||
buf := make([]byte, 5)
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
writer.Write([]byte{'a'})
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
err := ReadBytes(reader, buf)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "aaaaa", string(buf))
|
||||
}
|
||||
39
core/iox/textfile.go
Normal file
39
core/iox/textfile.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const bufSize = 32 * 1024
|
||||
|
||||
func CountLines(file string) (int, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var noEol bool
|
||||
buf := make([]byte, bufSize)
|
||||
count := 0
|
||||
lineSep := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := f.Read(buf)
|
||||
count += bytes.Count(buf[:c], lineSep)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
if noEol {
|
||||
count++
|
||||
}
|
||||
return count, nil
|
||||
case err != nil:
|
||||
return count, err
|
||||
}
|
||||
|
||||
noEol = c > 0 && buf[c-1] != '\n'
|
||||
}
|
||||
}
|
||||
27
core/iox/textfile_test.go
Normal file
27
core/iox/textfile_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCountLines(t *testing.T) {
|
||||
const val = `1
|
||||
2
|
||||
3
|
||||
4`
|
||||
file, err := ioutil.TempFile(os.TempDir(), "test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
file.WriteString(val)
|
||||
file.Close()
|
||||
lines, err := CountLines(file.Name())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, lines)
|
||||
}
|
||||
42
core/iox/textlinescanner.go
Normal file
42
core/iox/textlinescanner.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TextLineScanner struct {
|
||||
reader *bufio.Reader
|
||||
hasNext bool
|
||||
line string
|
||||
err error
|
||||
}
|
||||
|
||||
func NewTextLineScanner(reader io.Reader) *TextLineScanner {
|
||||
return &TextLineScanner{
|
||||
reader: bufio.NewReader(reader),
|
||||
hasNext: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (scanner *TextLineScanner) Scan() bool {
|
||||
if !scanner.hasNext {
|
||||
return false
|
||||
}
|
||||
|
||||
line, err := scanner.reader.ReadString('\n')
|
||||
scanner.line = strings.TrimRight(line, "\n")
|
||||
if err == io.EOF {
|
||||
scanner.hasNext = false
|
||||
return true
|
||||
} else if err != nil {
|
||||
scanner.err = err
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (scanner *TextLineScanner) Line() (string, error) {
|
||||
return scanner.line, scanner.err
|
||||
}
|
||||
24
core/iox/textlinescanner_test.go
Normal file
24
core/iox/textlinescanner_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package iox
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScanner(t *testing.T) {
|
||||
const val = `1
|
||||
2
|
||||
3
|
||||
4`
|
||||
reader := strings.NewReader(val)
|
||||
scanner := NewTextLineScanner(reader)
|
||||
var lines []string
|
||||
for scanner.Scan() {
|
||||
line, err := scanner.Line()
|
||||
assert.Nil(t, err)
|
||||
lines = append(lines, line)
|
||||
}
|
||||
assert.EqualValues(t, []string{"1", "2", "3", "4"}, lines)
|
||||
}
|
||||
Reference in New Issue
Block a user