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

84
core/filex/file.go Normal file
View File

@@ -0,0 +1,84 @@
package filex
import (
"io"
"os"
)
const bufSize = 1024
func FirstLine(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
return firstLine(file)
}
func LastLine(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
return lastLine(filename, file)
}
func firstLine(file *os.File) (string, error) {
var first []byte
var offset int64
for {
buf := make([]byte, bufSize)
n, err := file.ReadAt(buf, offset)
if err != nil && err != io.EOF {
return "", err
}
for i := 0; i < n; i++ {
if buf[i] == '\n' {
return string(append(first, buf[:i]...)), nil
}
}
first = append(first, buf[:n]...)
offset += bufSize
}
}
func lastLine(filename string, file *os.File) (string, error) {
info, err := os.Stat(filename)
if err != nil {
return "", err
}
var last []byte
offset := info.Size()
for {
offset -= bufSize
if offset < 0 {
offset = 0
}
buf := make([]byte, bufSize)
n, err := file.ReadAt(buf, offset)
if err != nil && err != io.EOF {
return "", err
}
if buf[n-1] == '\n' {
buf = buf[:n-1]
n -= 1
} else {
buf = buf[:n]
}
for n -= 1; n >= 0; n-- {
if buf[n] == '\n' {
return string(append(buf[n+1:], last...)), nil
}
}
last = append(buf, last...)
}
}

116
core/filex/file_test.go Normal file
View File

@@ -0,0 +1,116 @@
package filex
import (
"os"
"testing"
"zero/core/fs"
"github.com/stretchr/testify/assert"
)
const (
longLine = `Quid securi etiam tamquam eu fugiat nulla pariatur. Nec dubitamus multa iter quae et nos invenerat. Non equidem invideo, miror magis posuere velit aliquet. Integer legentibus erat a ante historiarum dapibus. Prima luce, cum quibus mons aliud consensu ab eo.Quid securi etiam tamquam eu fugiat nulla pariatur. Nec dubitamus multa iter quae et nos invenerat. Non equidem invideo, miror magis posuere velit aliquet. Integer legentibus erat a ante historiarum dapibus. Prima luce, cum quibus mons aliud consensu ab eo.Quid securi etiam tamquam eu fugiat nulla pariatur. Nec dubitamus multa iter quae et nos invenerat. Non equidem invideo, miror magis posuere velit aliquet. Integer legentibus erat a ante historiarum dapibus. Prima luce, cum quibus mons aliud consensu ab eo.Quid securi etiam tamquam eu fugiat nulla pariatur. Nec dubitamus multa iter quae et nos invenerat. Non equidem invideo, miror magis posuere velit aliquet. Integer legentibus erat a ante historiarum dapibus. Prima luce, cum quibus mons aliud consensu ab eo.Quid securi etiam tamquam eu fugiat nulla pariatur. Nec dubitamus multa iter quae et nos invenerat. Non equidem invideo, miror magis posuere velit aliquet. Integer legentibus erat a ante historiarum dapibus. Prima luce, cum quibus mons aliud consensu ab eo.`
longFirstLine = longLine + "\n" + text
text = `first line
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
` + longLine
textWithLastNewline = `first line
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
` + longLine + "\n"
shortText = `first line
second line
last line`
shortTextWithLastNewline = `first line
second line
last line
`
)
func TestFirstLine(t *testing.T) {
filename, err := fs.TempFilenameWithText(longFirstLine)
assert.Nil(t, err)
defer os.Remove(filename)
val, err := FirstLine(filename)
assert.Nil(t, err)
assert.Equal(t, longLine, val)
}
func TestFirstLineShort(t *testing.T) {
filename, err := fs.TempFilenameWithText(shortText)
assert.Nil(t, err)
defer os.Remove(filename)
val, err := FirstLine(filename)
assert.Nil(t, err)
assert.Equal(t, "first line", val)
}
func TestLastLine(t *testing.T) {
filename, err := fs.TempFilenameWithText(text)
assert.Nil(t, err)
defer os.Remove(filename)
val, err := LastLine(filename)
assert.Nil(t, err)
assert.Equal(t, longLine, val)
}
func TestLastLineWithLastNewline(t *testing.T) {
filename, err := fs.TempFilenameWithText(textWithLastNewline)
assert.Nil(t, err)
defer os.Remove(filename)
val, err := LastLine(filename)
assert.Nil(t, err)
assert.Equal(t, longLine, val)
}
func TestLastLineShort(t *testing.T) {
filename, err := fs.TempFilenameWithText(shortText)
assert.Nil(t, err)
defer os.Remove(filename)
val, err := LastLine(filename)
assert.Nil(t, err)
assert.Equal(t, "last line", val)
}
func TestLastLineWithLastNewlineShort(t *testing.T) {
filename, err := fs.TempFilenameWithText(shortTextWithLastNewline)
assert.Nil(t, err)
defer os.Remove(filename)
val, err := LastLine(filename)
assert.Nil(t, err)
assert.Equal(t, "last line", val)
}

105
core/filex/lookup.go Normal file
View File

@@ -0,0 +1,105 @@
package filex
import (
"io"
"os"
)
type OffsetRange struct {
File string
Start int64
Stop int64
}
func SplitLineChunks(filename string, chunks int) ([]OffsetRange, error) {
info, err := os.Stat(filename)
if err != nil {
return nil, err
}
if chunks <= 1 {
return []OffsetRange{
{
File: filename,
Start: 0,
Stop: info.Size(),
},
}, nil
}
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var ranges []OffsetRange
var offset int64
// avoid the last chunk too few bytes
preferSize := info.Size()/int64(chunks) + 1
for {
if offset+preferSize >= info.Size() {
ranges = append(ranges, OffsetRange{
File: filename,
Start: offset,
Stop: info.Size(),
})
break
}
offsetRange, err := nextRange(file, offset, offset+preferSize)
if err != nil {
return nil, err
}
ranges = append(ranges, offsetRange)
if offsetRange.Stop < info.Size() {
offset = offsetRange.Stop
} else {
break
}
}
return ranges, nil
}
func nextRange(file *os.File, start, stop int64) (OffsetRange, error) {
offset, err := skipPartialLine(file, stop)
if err != nil {
return OffsetRange{}, err
}
return OffsetRange{
File: file.Name(),
Start: start,
Stop: offset,
}, nil
}
func skipPartialLine(file *os.File, offset int64) (int64, error) {
for {
skipBuf := make([]byte, bufSize)
n, err := file.ReadAt(skipBuf, offset)
if err != nil && err != io.EOF {
return 0, err
}
if n == 0 {
return 0, io.EOF
}
for i := 0; i < n; i++ {
if skipBuf[i] != '\r' && skipBuf[i] != '\n' {
offset++
} else {
for ; i < n; i++ {
if skipBuf[i] == '\r' || skipBuf[i] == '\n' {
offset++
} else {
return offset, nil
}
}
return offset, nil
}
}
}
}

68
core/filex/lookup_test.go Normal file
View File

@@ -0,0 +1,68 @@
package filex
import (
"os"
"testing"
"zero/core/fs"
"github.com/stretchr/testify/assert"
)
func TestSplitLineChunks(t *testing.T) {
const text = `first line
second line
third line
fourth line
fifth line
sixth line
seventh line
`
fp, err := fs.TempFileWithText(text)
assert.Nil(t, err)
defer func() {
fp.Close()
os.Remove(fp.Name())
}()
offsets, err := SplitLineChunks(fp.Name(), 3)
assert.Nil(t, err)
body := make([]byte, 512)
for _, offset := range offsets {
reader := NewRangeReader(fp, offset.Start, offset.Stop)
n, err := reader.Read(body)
assert.Nil(t, err)
assert.Equal(t, uint8('\n'), body[n-1])
}
}
func TestSplitLineChunksNoFile(t *testing.T) {
_, err := SplitLineChunks("nosuchfile", 2)
assert.NotNil(t, err)
}
func TestSplitLineChunksFull(t *testing.T) {
const text = `first line
second line
third line
fourth line
fifth line
sixth line
`
fp, err := fs.TempFileWithText(text)
assert.Nil(t, err)
defer func() {
fp.Close()
os.Remove(fp.Name())
}()
offsets, err := SplitLineChunks(fp.Name(), 1)
assert.Nil(t, err)
body := make([]byte, 512)
for _, offset := range offsets {
reader := NewRangeReader(fp, offset.Start, offset.Stop)
n, err := reader.Read(body)
assert.Nil(t, err)
assert.Equal(t, []byte(text), body[:n])
}
}

View File

@@ -0,0 +1,28 @@
package filex
import "gopkg.in/cheggaaa/pb.v1"
type (
Scanner interface {
Scan() bool
Text() string
}
progressScanner struct {
Scanner
bar *pb.ProgressBar
}
)
func NewProgressScanner(scanner Scanner, bar *pb.ProgressBar) Scanner {
return &progressScanner{
Scanner: scanner,
bar: bar,
}
}
func (ps *progressScanner) Text() string {
s := ps.Scanner.Text()
ps.bar.Add64(int64(len(s)) + 1) // take newlines into account
return s
}

View File

@@ -0,0 +1,31 @@
package filex
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/cheggaaa/pb.v1"
)
func TestProgressScanner(t *testing.T) {
const text = "hello, world"
bar := pb.New(100)
var builder strings.Builder
builder.WriteString(text)
scanner := NewProgressScanner(&mockedScanner{builder: &builder}, bar)
assert.True(t, scanner.Scan())
assert.Equal(t, text, scanner.Text())
}
type mockedScanner struct {
builder *strings.Builder
}
func (s *mockedScanner) Scan() bool {
return s.builder.Len() > 0
}
func (s *mockedScanner) Text() string {
return s.builder.String()
}

43
core/filex/rangereader.go Normal file
View File

@@ -0,0 +1,43 @@
package filex
import (
"errors"
"os"
)
type RangeReader struct {
file *os.File
start int64
stop int64
}
func NewRangeReader(file *os.File, start, stop int64) *RangeReader {
return &RangeReader{
file: file,
start: start,
stop: stop,
}
}
func (rr *RangeReader) Read(p []byte) (n int, err error) {
stat, err := rr.file.Stat()
if err != nil {
return 0, err
}
if rr.stop < rr.start || rr.start >= stat.Size() {
return 0, errors.New("exceed file size")
}
if rr.stop-rr.start < int64(len(p)) {
p = p[:rr.stop-rr.start]
}
n, err = rr.file.ReadAt(p, rr.start)
if err != nil {
return n, err
}
rr.start += int64(n)
return
}

View File

@@ -0,0 +1,45 @@
package filex
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"zero/core/fs"
)
func TestRangeReader(t *testing.T) {
const text = `hello
world`
file, err := fs.TempFileWithText(text)
assert.Nil(t, err)
defer func() {
file.Close()
os.Remove(file.Name())
}()
reader := NewRangeReader(file, 5, 8)
buf := make([]byte, 10)
n, err := reader.Read(buf)
assert.Nil(t, err)
assert.Equal(t, 3, n)
assert.Equal(t, `
wo`, string(buf[:n]))
}
func TestRangeReader_OutOfRange(t *testing.T) {
const text = `hello
world`
file, err := fs.TempFileWithText(text)
assert.Nil(t, err)
defer func() {
file.Close()
os.Remove(file.Name())
}()
reader := NewRangeReader(file, 50, 8)
buf := make([]byte, 10)
_, err = reader.Read(buf)
assert.NotNil(t, err)
}