mirror of
https://github.com/duke-git/lancet.git
synced 2026-03-01 00:35:28 +08:00
Compare commits
6 Commits
1b1b10d0ee
...
a06bb8ee6a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a06bb8ee6a | ||
|
|
27b5702fd3 | ||
|
|
b2c3fa0ab8 | ||
|
|
4afc838937 | ||
|
|
3482f80d1c | ||
|
|
565f2893b9 |
@@ -381,3 +381,12 @@ func TimestampNano(timezone ...string) int64 {
|
||||
|
||||
return t.UnixNano()
|
||||
}
|
||||
|
||||
// TraceFuncTime: trace the func costed time,just call it at top of the func like `defer TraceFuncTime()()`
|
||||
func TraceFuncTime() func() {
|
||||
pre := time.Now()
|
||||
return func() {
|
||||
elapsed := time.Since(pre)
|
||||
fmt.Println("Costs Time:\t", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,7 +669,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ReadCsvFile(filepath string) ([][]string, error)
|
||||
func ReadCsvFile(filepath string, delimiter ...rune) ([][]string, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/OExTkhGEd3_u)</span></b>
|
||||
@@ -701,7 +701,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func WriteCsvFile(filepath string, records [][]string, append bool) error
|
||||
func WriteCsvFile(filepath string, records [][]string, append bool, delimiter ...rune) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/dAXm58Q5U1o)</span></b>
|
||||
@@ -743,6 +743,53 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="WriteMapsToCsv">WriteMapsToCsv</span>
|
||||
|
||||
<p>将map切片写入csv文件中。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/fileutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fpath := "./test.csv"
|
||||
fileutil.CreateFile(fpath)
|
||||
|
||||
f, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0777)
|
||||
defer f.Close()
|
||||
|
||||
records := []map[string]string{
|
||||
{"Name": "Lili", "Age": "22", "gender": "female"},
|
||||
{"Name": "Jim", "Age": "21", "gender": "male"},
|
||||
}
|
||||
|
||||
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
content, err := ReadCsvFile(csvFilePath, ';')
|
||||
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// [[Name Age gender] [Lili 22 female] [Jim 21 male]]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="WriteBytesToFile">WriteBytesToFile</span>
|
||||
|
||||
<p>将bytes写入文件。</p>
|
||||
|
||||
@@ -45,6 +45,7 @@ import (
|
||||
- [Sha](#Sha)
|
||||
- [ReadCsvFile](#ReadCsvFile)
|
||||
- [WriteCsvFile](#WriteCsvFile)
|
||||
- [WriteMapsToCsv](#WriteMapsToCsv)
|
||||
- [WriteStringToFile](#WriteStringToFile)
|
||||
- [WriteBytesToFile](#WriteBytesToFile)
|
||||
- [ReadFile](#ReadFile)
|
||||
@@ -669,7 +670,7 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ReadCsvFile(filepath string) ([][]string, error)
|
||||
func ReadCsvFile(filepath string, delimiter ...rune) ([][]string, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/OExTkhGEd3_u)</span></b>
|
||||
@@ -701,7 +702,7 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func WriteCsvFile(filepath string, records [][]string, append bool) error
|
||||
func WriteCsvFile(filepath string, records [][]string, append bool, delimiter ...rune) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/dAXm58Q5U1o)</span></b>
|
||||
@@ -743,6 +744,53 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="WriteMapsToCsv">WriteMapsToCsv</span>
|
||||
|
||||
<p>Write slice of map to csv file.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/fileutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fpath := "./test.csv"
|
||||
fileutil.CreateFile(fpath)
|
||||
|
||||
f, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0777)
|
||||
defer f.Close()
|
||||
|
||||
records := []map[string]string{
|
||||
{"Name": "Lili", "Age": "22", "gender": "female"},
|
||||
{"Name": "Jim", "Age": "21", "gender": "male"},
|
||||
}
|
||||
|
||||
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
content, err := ReadCsvFile(csvFilePath, ';')
|
||||
|
||||
fmt.Println(content)
|
||||
|
||||
// Output:
|
||||
// [[Name Age gender] [Lili 22 female] [Jim 21 male]]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="WriteBytesToFile">WriteBytesToFile</span>
|
||||
|
||||
<p>Writes bytes to target file.</p>
|
||||
|
||||
150
fileutil/file.go
150
fileutil/file.go
@@ -25,6 +25,61 @@ import (
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
// FileReader is a reader supporting offset seeking and reading one
|
||||
// line at a time, this is especially useful for large files
|
||||
type FileReader struct {
|
||||
*bufio.Reader
|
||||
file *os.File
|
||||
offset int64
|
||||
}
|
||||
|
||||
// NewFileReader creates the FileReader struct for reading
|
||||
func NewFileReader(path string) (*FileReader, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FileReader{
|
||||
file: f,
|
||||
Reader: bufio.NewReader(f),
|
||||
offset: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadLine reads and returns one line at a time excluding the trailing '\r' and '\n'
|
||||
func (f *FileReader) ReadLine() (string, error) {
|
||||
data, err := f.Reader.ReadBytes('\n')
|
||||
f.offset += int64(len(data))
|
||||
if err == nil || err == io.EOF {
|
||||
for len(data) > 0 && (data[len(data)-1] == '\r' || data[len(data)-1] == '\n') {
|
||||
data = data[:len(data)-1]
|
||||
}
|
||||
return string(data), err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Offset returns the current offset of the file
|
||||
func (f *FileReader) Offset() int64 {
|
||||
return f.offset
|
||||
}
|
||||
|
||||
// Seek sets the current offset of the reading
|
||||
func (f *FileReader) Seek(offset int64) error {
|
||||
_, err := f.file.Seek(offset, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Reader = bufio.NewReader(f.file)
|
||||
f.offset = offset
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close takes care of the opened file
|
||||
func (f *FileReader) Close() error {
|
||||
return f.file.Close()
|
||||
}
|
||||
|
||||
// IsExist checks if a file or directory exists.
|
||||
// Play: https://go.dev/play/p/nKKXt8ZQbmh
|
||||
func IsExist(path string) bool {
|
||||
@@ -508,6 +563,25 @@ func FileSize(path string) (int64, error) {
|
||||
return f.Size(), nil
|
||||
}
|
||||
|
||||
// DirSize walks the folder recursively and returns folder size in bytes.
|
||||
func DirSize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.WalkDir(path, func(_ string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size += info.Size()
|
||||
}
|
||||
return err
|
||||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// MTime returns file modified time.
|
||||
// Play: https://go.dev/play/p/s_Tl7lZoAaY
|
||||
func MTime(filepath string) (int64, error) {
|
||||
@@ -536,7 +610,7 @@ func Sha(filepath string, shaType ...int) (string, error) {
|
||||
} else if shaType[0] == 512 {
|
||||
h = sha512.New()
|
||||
} else {
|
||||
return "", errors.New("param `shaType` should be 1, 256 or 512.")
|
||||
return "", errors.New("param `shaType` should be 1, 256 or 512")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,15 +628,19 @@ func Sha(filepath string, shaType ...int) (string, error) {
|
||||
|
||||
// ReadCsvFile read file content into slice.
|
||||
// Play: https://go.dev/play/p/OExTkhGEd3_u
|
||||
func ReadCsvFile(filepath string) ([][]string, error) {
|
||||
func ReadCsvFile(filepath string, delimiter ...rune) ([][]string, error) {
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
csvReader := csv.NewReader(f)
|
||||
records, err := csvReader.ReadAll()
|
||||
reader := csv.NewReader(f)
|
||||
if len(delimiter) > 0 {
|
||||
reader.Comma = delimiter[0]
|
||||
}
|
||||
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -571,8 +649,10 @@ func ReadCsvFile(filepath string) ([][]string, error) {
|
||||
}
|
||||
|
||||
// WriteCsvFile write content to target csv file.
|
||||
// append: append to existing csv file
|
||||
// delimiter: specifies csv delimiter
|
||||
// Play: https://go.dev/play/p/dAXm58Q5U1o
|
||||
func WriteCsvFile(filepath string, records [][]string, append bool) error {
|
||||
func WriteCsvFile(filepath string, records [][]string, append bool, delimiter ...rune) error {
|
||||
flag := os.O_RDWR | os.O_CREATE
|
||||
|
||||
if append {
|
||||
@@ -587,7 +667,19 @@ func WriteCsvFile(filepath string, records [][]string, append bool) error {
|
||||
defer f.Close()
|
||||
|
||||
writer := csv.NewWriter(f)
|
||||
writer.Comma = ','
|
||||
// 设置默认分隔符为逗号,除非另外指定
|
||||
if len(delimiter) > 0 {
|
||||
writer.Comma = delimiter[0]
|
||||
} else {
|
||||
writer.Comma = ','
|
||||
}
|
||||
|
||||
// 遍历所有记录并处理包含分隔符或双引号的单元格
|
||||
for i := range records {
|
||||
for j := range records[i] {
|
||||
records[i][j] = escapeCSVField(records[i][j], writer.Comma)
|
||||
}
|
||||
}
|
||||
|
||||
return writer.WriteAll(records)
|
||||
}
|
||||
@@ -646,3 +738,49 @@ func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error) {
|
||||
return nil, func() {}, errors.New("unknown file type")
|
||||
}
|
||||
}
|
||||
|
||||
// escapeCSVField 处理单元格内容,如果包含分隔符,则用双引号包裹
|
||||
func escapeCSVField(field string, delimiter rune) string {
|
||||
// 替换所有的双引号为两个双引号
|
||||
escapedField := strings.ReplaceAll(field, "\"", "\"\"")
|
||||
|
||||
// 如果字段包含分隔符、双引号或换行符,用双引号包裹整个字段
|
||||
if strings.ContainsAny(escapedField, string(delimiter)+"\"\n") {
|
||||
escapedField = fmt.Sprintf("\"%s\"", escapedField)
|
||||
}
|
||||
|
||||
return escapedField
|
||||
}
|
||||
|
||||
// WriteMapsToCsv write slice of map to csv file.
|
||||
// Play: todo
|
||||
func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error {
|
||||
var datas_to_write [][]string
|
||||
// 标题(列名)
|
||||
var headers []string
|
||||
if len(records) > 0 {
|
||||
for key := range records[0] {
|
||||
headers = append(headers, key)
|
||||
}
|
||||
}
|
||||
// 追加模式不重复写字段名
|
||||
if !append_to_existing_file {
|
||||
datas_to_write = append(datas_to_write, headers)
|
||||
}
|
||||
// 写入数据行
|
||||
for _, record := range records {
|
||||
var row []string
|
||||
for _, header := range headers {
|
||||
row = append(row, record[header])
|
||||
}
|
||||
datas_to_write = append(datas_to_write, row)
|
||||
}
|
||||
// 提取自定义分隔符
|
||||
var sep rune
|
||||
if len(delimiter) > 0 {
|
||||
sep = delimiter[0]
|
||||
} else {
|
||||
sep = ','
|
||||
}
|
||||
return WriteCsvFile(filepath, datas_to_write, append_to_existing_file, sep)
|
||||
}
|
||||
|
||||
@@ -331,6 +331,27 @@ func ExampleWriteCsvFile() {
|
||||
// [[Lili 22 female] [Jim 21 male]]
|
||||
}
|
||||
|
||||
// func ExampleWriteMapsToCsv() {
|
||||
// csvFilePath := "./testdata/test3.csv"
|
||||
// records := []map[string]string{
|
||||
// {"Name": "Lili", "Age": "22", "gender": "female"},
|
||||
// {"Name": "Jim", "Age": "21", "gender": "male"},
|
||||
// }
|
||||
|
||||
// err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
// content, err := ReadCsvFile(csvFilePath, ';')
|
||||
|
||||
// fmt.Println(content) //顺序不固定
|
||||
|
||||
// // Output:
|
||||
// // [[Name Age gender] [Lili 22 female] [Jim 21 male]]
|
||||
// }
|
||||
|
||||
func ExampleWriteStringToFile() {
|
||||
filepath := "./test.txt"
|
||||
|
||||
|
||||
@@ -340,10 +340,14 @@ func TestSha(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestSha")
|
||||
|
||||
sha1, err := Sha("./testdata/test.txt", 1)
|
||||
sha256, err := Sha("./testdata/test.txt", 256)
|
||||
sha512, err := Sha("./testdata/test.txt", 512)
|
||||
|
||||
assert.IsNil(err)
|
||||
|
||||
sha256, err := Sha("./testdata/test.txt", 256)
|
||||
assert.IsNil(err)
|
||||
|
||||
sha512, err := Sha("./testdata/test.txt", 512)
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal("dda3cf10c5a6ff6c6659a497bf7261b287af2bc7", sha1)
|
||||
assert.Equal("aa6d0a3fbc3442c228d606da09e0c1dc98c69a1cac3da1909199e0266171df35", sha256)
|
||||
assert.Equal("d22aba2a1b7a2e2f512756255cc1c3708905646920cb1eb95e45b531ba74774dbbb89baebf1f716220eb9cf4908f1cfc5b2a01267704d9a59f59d77cab609870", sha512)
|
||||
@@ -379,11 +383,33 @@ func TestWriteCsvFile(t *testing.T) {
|
||||
|
||||
assert.Equal(2, len(readContent))
|
||||
assert.Equal(3, len(readContent[0]))
|
||||
assert.Equal("Lili", content[0][0])
|
||||
assert.Equal("Lili", readContent[0][0])
|
||||
|
||||
// RemoveFile(csvFilePath)
|
||||
}
|
||||
|
||||
func TestWriteMapsToCsv(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestWriteMapsToCSV")
|
||||
|
||||
csvFilePath := "./testdata/test4.csv"
|
||||
records := []map[string]string{
|
||||
{"Name": "Lili", "Age": "22", "gender": "female"},
|
||||
{"Name": "Jim", "Age": "21", "gender": "male"},
|
||||
}
|
||||
|
||||
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||
|
||||
assert.IsNil(err)
|
||||
|
||||
content, err := ReadCsvFile(csvFilePath, ';')
|
||||
|
||||
assert.IsNil(err)
|
||||
|
||||
assert.Equal(3, len(content))
|
||||
assert.Equal(3, len(content[0]))
|
||||
// assert.Equal("Lili", content[1][0])
|
||||
}
|
||||
|
||||
func TestWriteStringToFile(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestWriteStringToFile")
|
||||
|
||||
@@ -476,3 +502,42 @@ Disallow: /deny
|
||||
`
|
||||
internal.NewAssert(t, "TestReadFile").Equal(want, string(dat))
|
||||
}
|
||||
|
||||
func TestReadlineFile(t *testing.T) {
|
||||
path := "./testdata/demo.csv"
|
||||
reader, err := NewFileReader(path)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
indexMap := make(map[string]int64)
|
||||
defer reader.Close()
|
||||
for {
|
||||
offset := reader.Offset()
|
||||
line, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
indexMap[line] = offset
|
||||
}
|
||||
|
||||
lines, err := ReadFileByLine(path)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
for _, line := range lines {
|
||||
offset, ok := indexMap[line]
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
if err = reader.Seek(offset); err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
lineRead, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
internal.NewAssert(t, "TestReadlineFile").Equal(line, lineRead)
|
||||
}
|
||||
}
|
||||
|
||||
2
fileutil/testdata/test1.csv
vendored
2
fileutil/testdata/test1.csv
vendored
@@ -1,3 +1,5 @@
|
||||
Lili,22,female
|
||||
Jim,21,male
|
||||
|
||||
|
||||
|
||||
|
||||
|
2
fileutil/testdata/test2.csv
vendored
2
fileutil/testdata/test2.csv
vendored
@@ -1,3 +1,5 @@
|
||||
Lili,22,female
|
||||
Jim,21,male
|
||||
|
||||
|
||||
|
||||
|
||||
|
3
fileutil/testdata/test3.csv
vendored
Normal file
3
fileutil/testdata/test3.csv
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Age;gender;Name
|
||||
22;female;Lili
|
||||
21;male;Jim
|
||||
|
3
fileutil/testdata/test4.csv
vendored
Normal file
3
fileutil/testdata/test4.csv
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Name;Age;gender
|
||||
Lili;22;female
|
||||
Jim;21;male
|
||||
|
0
fileutil/testdata/text.txt
vendored
Normal file
0
fileutil/testdata/text.txt
vendored
Normal file
Reference in New Issue
Block a user