initial import

This commit is contained in:
kevin
2020-07-26 17:09:05 +08:00
commit 7e3a369a8f
647 changed files with 54754 additions and 0 deletions

34
core/iox/bufferpool.go Normal file
View 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)
}
}

View 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
View 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
View 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
View 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
View 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
View 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)
}

View 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
}

View 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)
}