1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-23 13:52:26 +08:00

Compare commits

...

17 Commits

Author SHA1 Message Date
dudaodong
6d4fc981b6 release v2.2.9 2024-02-01 10:03:26 +08:00
dudaodong
4c21fe700c refactor: update StringToBytes logic 2024-01-30 10:46:55 +08:00
pigwantacat
b7370e8ef8 refactor:refactor random function (#164)
Co-authored-by: zhuhebin <zhuhebin@fengtaisec.com>
2024-01-30 10:00:14 +08:00
dudaodong
38920e3be6 fix: fix issue #162 2024-01-29 11:00:50 +08:00
dudaodong
a630a7cda9 refactor: remove struct_internal.go 2024-01-29 10:02:36 +08:00
dudaodong
66dfd9c4fd refactor: refact Comma function 2024-01-25 16:45:17 +08:00
dudaodong
be62aaac9b refactor: update signature of WriteMapsToCsv function
g
2024-01-24 11:54:42 +08:00
dudaodong
e0c9ccbce3 doc: add DeleteRange 2024-01-23 17:27:28 +08:00
dudaodong
d2d1e5a055 feat: refact DeleteAt and add DeleteRange function 2024-01-22 15:34:35 +08:00
dudaodong
bbc58c7e46 fix: fix the map key order issue of WriteMapsToCsv 2024-01-22 11:10:02 +08:00
dudaodong
ac2ecceaec refactor: refact reduce function 2024-01-03 10:46:28 +08:00
dudaodong
a06bb8ee6a fix: fix unit test for WriteMapsToCsv 2024-01-01 18:32:41 +08:00
dudaodong
27b5702fd3 fix: fix unit test for WriteMapsToCsv 2024-01-01 17:51:32 +08:00
dudaodong
b2c3fa0ab8 fix: fix golint issue 2024-01-01 17:43:11 +08:00
dudaodong
4afc838937 doc: add doc for WriteMapsToCsv 2024-01-01 17:40:32 +08:00
colorcrow
3482f80d1c WriteCsvFile自定义分割符,增加map切片写入csv,增加追踪函数运行时间的函数 (#157)
* WriteCsvFile now support custom delimiter,Add write map slice to csv

* add trace func time
2024-01-01 16:54:04 +08:00
suacrbah
565f2893b9 feat: add support for seeking and read one line at a time from file (#158)
* feat: add support for seeking and read one line at a time from file

* feat: add support for calculating folder total size

---------

Co-authored-by: Suacrbah <5744580+Suacrbah@user.noreply.gitee.com>
2024-01-01 16:50:54 +08:00
26 changed files with 782 additions and 291 deletions

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.2.8-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.2.9-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -706,6 +706,8 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[play](https://go.dev/play/p/OExTkhGEd3_u)]
- **<big>WriteCsvFile</big>** : write content to target csv file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : write slice of map to csv file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/dAXm58Q5U1o)]
- **<big>WriteBytesToFile</big>** : write bytes to target file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteBytesToFile)]
@@ -1178,9 +1180,10 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>DifferenceWith</big>** : accepts comparator which is invoked to compare elements of slice to values.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DifferenceWith)]
[[play](https://go.dev/play/p/v2U2deugKuV)]
- **<big>DeleteAt</big>** : delete the element of slice from specific start index to end index - 1.
- **<big>DeleteAt</big>** : delete the element of slice at index.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/pJ-d6MUWcvK)]
- **<big>DeleteRange</big>** : delete the element of slice from start index to end indexexclude).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteRange)]
- **<big>Drop</big>** : drop n elements from the start of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Drop)]
[[play](https://go.dev/play/p/jnPO2yQsT8H)]
@@ -1942,22 +1945,6 @@ import "github.com/duke-git/lancet/v2/xerror"
#### [Contributing Guide](./CONTRIBUTING.en-US.md)
## Sponsor
Hello, I am a software developer and have been engaged in development work for 13 years. Love open source software. And be willing to put in the energy for it. I am the creator of project lancet. Since Lancet was released as open source two years ago, it has been used by more than 1,000 internal and external projects. lancet will always be free for all users. Your support is a powerful encouragement for me to continue my struggle. Thanks! You can use WeChat to scans the following QR code or clicks the following sponsor button to initiate sponsorship.
<div style="position: relative;display: inline-block;">
<img src="./docs/public/wechat_pay.png" width="260" height="260"/>
<a href="https://en.liberapay.com/Duke_Du/donate" target="\_blank"><img src="./docs/public/sponsor_btn.png" width="220" height="60"/></a>
</div>
<br/>
<br/>
*Donated funds will be used to maintain [lancet](https://www.golancet.cn/en/) website and pay for cloud server costs. Or just buy me a cup of ☕️ when I'm sleepy writing code.*
## Contributors
Thank you to all the people who contributed to lancet!

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.2.8-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.2.9-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -705,8 +705,10 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>ReadCsvFile</big>** : 读取 csv 文件内容到切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadCsvFile)]
[[play](https://go.dev/play/p/OExTkhGEd3_u)]
- **<big>WriteCsvFile</big>** : 向 csv 文件写入内容
- **<big>WriteCsvFile</big>** : 向csv文件写入切片数据
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : 将map切片写入csv文件中。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/dAXm58Q5U1o)]
- **<big>WriteBytesToFile</big>** : 将 bytes 写入文件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteBytesToFile)]
@@ -1177,9 +1179,10 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>DifferenceWith</big>** : 接受比较器函数,该比较器被调用以将切片的元素与值进行比较。 结果值的顺序和引用由第一个切片确定。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DifferenceWith)]
[[play](https://go.dev/play/p/v2U2deugKuV)]
- **<big>DeleteAt</big>** : 删除切片中指定开始索引到结束索引的元素。
- **<big>DeleteAt</big>** : 删除切片中指定索引到的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/pJ-d6MUWcvK)]
- **<big>DeleteRange</big>** : 删除切片中指定开始索引到结束索引的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteRange)]
- **<big>Drop</big>** : 从切片头部删除 n 个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Drop)]
[[play](https://go.dev/play/p/jnPO2yQsT8H)]
@@ -1943,23 +1946,9 @@ import "github.com/duke-git/lancet/v2/xerror"
#### [贡献代码指南](./CONTRIBUTING.zh-CN.md)
## 赞助
您好,我是一名软件开发者,从事开发工作 13 年。热爱软件开源。并愿意为此付出精力。是开源项目 lancet 的作者。Lancet 自两年前开源发布以来,已有超过 1000 个内外部项目使用。lancet 一直会对所有用户免费。您的支持是对我继续奋斗的有力鼓励。谢谢! 微信扫描以下二维码或点击以下赞助按钮发起赞助。
<div style="position: relative;display: inline-block;">
<img src="./docs/public/wechat_pay.png" width="260" height="260"/>
<a href="https://liberapay.com/Duke_Du/donate" target="\_blank"><img src="./docs/public/sponsor_btn.png" width="220" height="60"/></a>
</div>
<br/>
<br/>
_捐赠的资金将用于后续[lancet](https://www.golancet.cn/)官网的维护和云服务器的费用支付。或者当我写代码困倦时,给我买杯 ☕️。_
## 贡献者
感谢所有为 lancet 贡献过代码的人!
感谢所有为lancet贡献过代码的人
<a href="https://github.com/duke-git/lancet/graphs/contributors">
<img src="https://contrib.rocks/image?repo=duke-git/lancet" />

View File

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

View File

@@ -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>函数签名:</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 +702,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 +744,59 @@ func main() {
}
```
### <span id="WriteMapsToCsv">WriteMapsToCsv</span>
<p>将map切片写入csv文件中。</p>
<b>函数签名:</b>
```go
// filepath: CSV文件路径。
// records: 写入文件的map切片。map值必须为基本类型。会以map键的字母顺序写入。
// appendToExistingFile: 是否为追加写模式。
// delimiter: CSV文件分割符。
// headers: CSV文件表头顺序需要与map key保持一致),不指定时按字母排序。
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) 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]any{
{"Name": "Lili", "Age": "22", "Gender": "female"},
{"Name": "Jim", "Age": "21", "Gender": "male"},
}
headers := []string{"Name", "Age", "Gender"}
err := WriteMapsToCsv(csvFilePath, records, false, ';', headers)
if err != nil {
log.Fatal(err)
}
content, err := fileutil.ReadCsvFile(csvFilePath, ';')
fmt.Println(content)
// Output:
// [[Name Age Gender] [Lili 22 female] [Jim 21 male]]
}
```
### <span id="WriteBytesToFile">WriteBytesToFile</span>
<p>将bytes写入文件。</p>

View File

@@ -35,6 +35,7 @@ import (
- [DifferenceBy](#DifferenceBy)
- [DifferenceWith](#DifferenceWith)
- [DeleteAt](#DeleteAt)
- [DeleteRange](#DeleteRange)
- [Drop](#Drop)
- [DropRight](#DropRight)
- [DropWhile](#DropWhile)
@@ -516,12 +517,12 @@ func main() {
### <span id="DeleteAt">DeleteAt</span>
<p>删除切片中指定开始索引到结束索引的元素</p>
<p>删除切片中指定索引的元素(不修改原切片)。</p>
<b>函数签名:</b>
```go
func DeleteAt[T any](slice []T, start int, end ...int)
func DeleteAt[T any](slice []T, index int) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/pJ-d6MUWcvK)</span></b>
@@ -533,18 +534,66 @@ import (
)
func main() {
result1 := slice.DeleteAt([]string{"a", "b", "c"}, -1)
result2 := slice.DeleteAt([]string{"a", "b", "c"}, 0)
result3 := slice.DeleteAt([]string{"a", "b", "c"}, 0, 2)
chars := []string{"a", "b", "c", "d", "e"}
result1 := slice.DeleteAt(chars, 0)
result2 := slice.DeleteAt(chars, 4)
result3 := slice.DeleteAt(chars, 5)
result4 := slice.DeleteAt(chars, 6)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [b c]
// [c]
// [b c d e]
// [a b c d]
// [a b c d]
// [a b c d]
}
```
### <span id="DeleteRange">DeleteRange</span>
<p>删除切片中指定索引范围的元素(不修改原切片)。</p>
<b>函数签名:</b>
```go
func DeleteRange[T any](slice []T, start, end int) []T
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
chars := []string{"a", "b", "c", "d", "e"}
result1 := DeleteRange(chars, 0, 0)
result2 := DeleteRange(chars, 0, 1)
result3 := DeleteRange(chars, 0, 3)
result4 := DeleteRange(chars, 0, 4)
result5 := DeleteRange(chars, 0, 5)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// [a b c d e]
// [b c d e]
// [d e]
// [e]
// []
}
```
@@ -2475,18 +2524,18 @@ import (
func main() {
nums := []int{1, 2, 3, 4, 5}
result1 := slice.Partition(nums)
result2 := slice.Partition(nums, func(n int) bool { return n%2 == 0 })
result3 := slice.Partition(nums, func(n int) bool { return n == 1 || n == 2 }, func(n int) bool { return n == 2 || n == 3 || n == 4 })
result1 := slice.Partition(nums)
result2 := slice.Partition(nums, func(n int) bool { return n%2 == 0 })
result3 := slice.Partition(nums, func(n int) bool { return n == 1 || n == 2 }, func(n int) bool { return n == 2 || n == 3 || n == 4 })
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [[1 2 3 4 5]]
// [[2 4] [1 3 5]]
// [[1 2] [3 4] [5]]
// Output:
// [[1 2 3 4 5]]
// [[2 4] [1 3 5]]
// [[1 2] [3 4] [5]]
}
```
@@ -2510,13 +2559,13 @@ import (
)
func main() {
nums := []int{1, 2, 3, 4, 5}
nums := []int{1, 2, 3, 4, 5}
val, idx := slice.Random(nums)
if idx >= 0 && idx < len(nums) && slice.Contain(nums, val) {
fmt.Println("okk")
}
// Output:
// okk
val, idx := slice.Random(nums)
if idx >= 0 && idx < len(nums) && slice.Contain(nums, val) {
fmt.Println("okk")
}
// Output:
// okk
}
```

View File

@@ -45,6 +45,8 @@ import (
- [Sha](#Sha)
- [ReadCsvFile](#ReadCsvFile)
- [WriteCsvFile](#WriteCsvFile)
- [WriteCsvFile](#WriteCsvFile)
- [WriteMapsToCsv](#WriteMapsToCsv)
- [WriteStringToFile](#WriteStringToFile)
- [WriteBytesToFile](#WriteBytesToFile)
- [ReadFile](#ReadFile)
@@ -669,7 +671,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 +703,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 +745,59 @@ func main() {
}
```
### <span id="WriteMapsToCsv">WriteMapsToCsv</span>
<p>Write slice of map to csv file.</p>
<b>Signature:</b>
```go
// filepath: path of the CSV file.
// records: slice of maps to be written. the value of map should be basic type. The maps will be sorted by key in alphabeta order, then be written into csv file.
// appendToExistingFile: If true, data will be appended to the file if it exists.
// delimiter: Delimiter to use in the CSV file.
// headers: order of the csv column headers, needs to be consistent with the key of the map.
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) 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]any{
{"Name": "Lili", "Age": "22", "Gender": "female"},
{"Name": "Jim", "Age": "21", "Gender": "male"},
}
headers := []string{"Name", "Age", "Gender"}
err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';', headers)
if err != nil {
log.Fatal(err)
}
content, err := fileutil.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>

View File

@@ -35,6 +35,7 @@ import (
- [DifferenceBy](#DifferenceBy)
- [DifferenceWith](#DifferenceWith)
- [DeleteAt](#DeleteAt)
- [DeleteRange](#DeleteRange)
- [Drop](#Drop)
- [DropRight](#DropRight)
- [DropWhile](#DropWhile)
@@ -515,12 +516,12 @@ func main() {
### <span id="DeleteAt">DeleteAt</span>
<p>Delete the element of slice from start index to end index - 1.</p>
<p>Delete delete the element of slice at index.</p>
<b>Signature:</b>
```go
func DeleteAt[T any](slice []T, start int, end ...int)
func DeleteAt[T any](slice []T, index int)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/pJ-d6MUWcvK)</span></b>
@@ -532,18 +533,66 @@ import (
)
func main() {
result1 := slice.DeleteAt([]string{"a", "b", "c"}, -1)
result2 := slice.DeleteAt([]string{"a", "b", "c"}, 0)
result3 := slice.DeleteAt([]string{"a", "b", "c"}, 0, 2)
chars := []string{"a", "b", "c", "d", "e"}
result1 := slice.DeleteAt(chars, 0)
result2 := slice.DeleteAt(chars, 4)
result3 := slice.DeleteAt(chars, 5)
result4 := slice.DeleteAt(chars, 6)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [b c]
// [c]
// [b c d e]
// [a b c d]
// [a b c d]
// [a b c d]
}
```
### <span id="DeleteRange">DeleteRange</span>
<p>Delete the element of slice from start index to end indexexclude)</p>
<b>Signature:</b>
```go
func DeleteRange[T any](slice []T, start, end int) []T
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
chars := []string{"a", "b", "c", "d", "e"}
result1 := DeleteRange(chars, 0, 0)
result2 := DeleteRange(chars, 0, 1)
result3 := DeleteRange(chars, 0, 3)
result4 := DeleteRange(chars, 0, 4)
result5 := DeleteRange(chars, 0, 5)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// [a b c d e]
// [b c d e]
// [d e]
// [e]
// []
}
```
@@ -2473,18 +2522,18 @@ import (
func main() {
nums := []int{1, 2, 3, 4, 5}
result1 := slice.Partition(nums)
result2 := slice.Partition(nums, func(n int) bool { return n%2 == 0 })
result3 := slice.Partition(nums, func(n int) bool { return n == 1 || n == 2 }, func(n int) bool { return n == 2 || n == 3 || n == 4 })
result1 := slice.Partition(nums)
result2 := slice.Partition(nums, func(n int) bool { return n%2 == 0 })
result3 := slice.Partition(nums, func(n int) bool { return n == 1 || n == 2 }, func(n int) bool { return n == 2 || n == 3 || n == 4 })
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [[1 2 3 4 5]]
// [[2 4] [1 3 5]]
// [[1 2] [3 4] [5]]
// Output:
// [[1 2 3 4 5]]
// [[2 4] [1 3 5]]
// [[1 2] [3 4] [5]]
}
```
@@ -2507,13 +2556,13 @@ import (
)
func main() {
nums := []int{1, 2, 3, 4, 5}
nums := []int{1, 2, 3, 4, 5}
val, idx := slice.Random(nums)
if idx >= 0 && idx < len(nums) && slice.Contain(nums, val) {
fmt.Println("okk")
}
// Output:
// okk
val, idx := slice.Random(nums)
if idx >= 0 && idx < len(nums) && slice.Contain(nums, val) {
fmt.Println("okk")
}
// Output:
// okk
}
```

View File

@@ -20,11 +20,67 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"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 +564,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 +611,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 +629,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 +650,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 +668,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 +739,71 @@ 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
// filepath: Path to the CSV file.
// records: Slice of maps to be written. the value of map should be basic type.
// the maps will be sorted by key in alphabeta order, then be written into csv file.
// appendToExistingFile: If true, data will be appended to the file if it exists.
// delimiter: Delimiter to use in the CSV file.
// headers: order of the csv column headers, needs to be consistent with the key of the map.
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune,
headers ...[]string) error {
for _, record := range records {
for _, value := range record {
if !isCsvSupportedType(value) {
return errors.New("unsupported value type detected; only basic types are supported: \nbool, rune, string, int, int64, float32, float64, uint, byte, complex128, complex64, uintptr")
}
}
}
var columnHeaders []string
if len(headers) > 0 {
columnHeaders = headers[0]
} else {
for key := range records[0] {
columnHeaders = append(columnHeaders, key)
}
// sort keys in alphabeta order
sort.Strings(columnHeaders)
}
var datasToWrite [][]string
if !appendToExistingFile {
datasToWrite = append(datasToWrite, columnHeaders)
}
for _, record := range records {
var row []string
for _, h := range columnHeaders {
row = append(row, fmt.Sprintf("%v", record[h]))
}
datasToWrite = append(datasToWrite, row)
}
return WriteCsvFile(filepath, datasToWrite, appendToExistingFile, delimiter)
}
// check if the value of map which to be written into csv is basic type.
func isCsvSupportedType(v interface{}) bool {
switch v.(type) {
case bool, rune, string, int, int64, float32, float64, uint, byte, complex128, complex64, uintptr:
return true
default:
return false
}
}

View File

@@ -3,6 +3,7 @@ package fileutil
import (
"fmt"
"io"
"log"
"os"
)
@@ -331,6 +332,28 @@ func ExampleWriteCsvFile() {
// [[Lili 22 female] [Jim 21 male]]
}
func ExampleWriteMapsToCsv() {
csvFilePath := "./testdata/test3.csv"
records := []map[string]any{
{"Name": "Lili", "Age": "22", "Gender": "female"},
{"Name": "Jim", "Age": "21", "Gender": "male"},
}
headers := []string{"Name", "Age", "Gender"}
err := WriteMapsToCsv(csvFilePath, records, false, ';', headers)
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"

View File

@@ -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,36 @@ 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]any{
{"Name": "Lili", "Age": "22", "Gender": "female"},
{"Name": "Jim", "Age": "21", "Gender": "male"},
}
headers := []string{"Name", "Age", "Gender"}
err := WriteMapsToCsv(csvFilePath, records, false, ';', headers)
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])
assert.Equal("22", content[1][1])
assert.Equal("female", content[1][2])
}
func TestWriteStringToFile(t *testing.T) {
assert := internal.NewAssert(t, "TestWriteStringToFile")
@@ -476,3 +505,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)
}
}

View File

@@ -1,3 +1,5 @@
Lili,22,female
Jim,21,male
1 Lili 22 female
2 Jim 21 male
3
4
5

View File

@@ -1,3 +1,5 @@
Lili,22,female
Jim,21,male
1 Lili 22 female
2 Jim 21 male
3
4
5

3
fileutil/testdata/test3.csv vendored Normal file
View File

@@ -0,0 +1,3 @@
Name;Age;Gender
Lili;22;female
Jim;21;male
1 Name Age Gender
2 Lili 22 female
3 Jim 21 male

3
fileutil/testdata/test4.csv vendored Normal file
View File

@@ -0,0 +1,3 @@
Name;Age;Gender
Lili;22;female
Jim;21;male
1 Name Age Gender
2 Lili 22 female
3 Jim 21 male

View File

@@ -6,12 +6,11 @@ package formatter
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/strutil"
"github.com/duke-git/lancet/v2/validator"
"golang.org/x/exp/constraints"
)
@@ -20,31 +19,24 @@ import (
// Comma("12345", "$") => "$12,345", Comma(12345, "$") => "$12,345"
// Play: https://go.dev/play/p/eRD5k2vzUVX
func Comma[T constraints.Float | constraints.Integer | string](value T, symbol string) string {
if validator.IsInt(value) {
v, err := convertor.ToInt(value)
if err != nil {
return ""
}
return symbol + commaInt(v)
}
numString := convertor.ToString(value)
if validator.IsFloat(value) {
v, err := convertor.ToFloat(value)
if err != nil {
return ""
}
return symbol + commaFloat(v)
}
if strutil.IsString(value) {
v := fmt.Sprintf("%v", value)
if validator.IsNumberStr(v) {
return symbol + commaStr(v)
}
_, err := strconv.ParseFloat(numString, 64)
if err != nil {
return ""
}
return ""
index := strings.Index(numString, ".")
if index == -1 {
index = len(numString)
}
for index > 3 {
index = index - 3
numString = numString[:index] + "," + numString[index:]
}
return symbol + numString
}
// Pretty data to JSON string.

View File

@@ -1,85 +0,0 @@
package formatter
import (
"bytes"
"math"
"strconv"
"strings"
)
// see https://github.com/dustin/go-humanize/blob/master/comma.go
func commaInt(v int64) string {
sign := ""
// Min int64 can't be negated to a usable value, so it has to be special cased.
if v == math.MinInt64 {
return "-9,223,372,036,854,775,808"
}
if v < 0 {
sign = "-"
v = 0 - v
}
parts := []string{"", "", "", "", "", "", ""}
j := len(parts) - 1
for v > 999 {
parts[j] = strconv.FormatInt(v%1000, 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
v = v / 1000
j--
}
parts[j] = strconv.Itoa(int(v))
return sign + strings.Join(parts[j:], ",")
}
func commaFloat(v float64) string {
buf := &bytes.Buffer{}
if v < 0 {
buf.Write([]byte{'-'})
v = 0 - v
}
comma := []byte{','}
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}
func commaStr(s string) string {
dotIndex := strings.Index(s, ".")
if dotIndex != -1 {
return commaStrRecursive(s[:dotIndex]) + s[dotIndex:]
}
return commaStrRecursive(s)
}
func commaStrRecursive(s string) string {
if len(s) <= 3 {
return s
}
return commaStrRecursive(s[:len(s)-3]) + "," + commaStrRecursive(s[len(s)-3:])
}

View File

@@ -4,7 +4,9 @@
// Package pointer contains some util functions to operate go pointer.
package pointer
import "reflect"
import (
"reflect"
)
// Of returns a pointer to the value `v`.
// Play: https://go.dev/play/p/HFd70x4DrMj
@@ -47,5 +49,10 @@ func ExtractPointer(value any) any {
if t.Kind() != reflect.Pointer {
return value
}
return ExtractPointer(v.Elem().Interface())
if v.Elem().IsValid() {
return ExtractPointer(v.Elem().Interface())
}
return nil
}

View File

@@ -8,20 +8,25 @@ import (
crand "crypto/rand"
"fmt"
"io"
"math"
"math/rand"
"time"
"unsafe"
"github.com/duke-git/lancet/v2/mathutil"
)
const (
Numeral = "0123456789"
LowwerLetters = "abcdefghijklmnopqrstuvwxyz"
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
SymbolChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
MaximumCapacity = 1 << 31
Numeral = "0123456789"
LowwerLetters = "abcdefghijklmnopqrstuvwxyz"
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
SymbolChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?"
)
var rn = rand.NewSource(time.Now().UnixNano())
func init() {
rand.Seed(time.Now().UnixNano())
}
@@ -108,18 +113,61 @@ func RandSymbolChar(length int) string {
return random(SymbolChars, length)
}
// nearestPowerOfTwo 返回一个大于等于cap的最近的2的整数次幂参考java8的hashmap的tableSizeFor函数
func nearestPowerOfTwo(cap int) int {
n := cap - 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
if n < 0 {
return 1
} else if n >= MaximumCapacity {
return MaximumCapacity
}
return n + 1
}
// random generate a random string based on given string range.
func random(s string, length int) string {
b := make([]byte, length)
// fix: https://github.com/duke-git/lancet/issues/75
// r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := range b {
b[i] = s[rand.Int63()%int64(len(s))]
// 仿照strings.Builder
// 创建一个长度为 length 的字节切片
bytes := make([]byte, length)
strLength := len(s)
if strLength <= 0 {
return ""
} else if strLength == 1 {
for i := 0; i < length; i++ {
bytes[i] = s[0]
}
return *(*string)(unsafe.Pointer(&bytes))
}
return string(b)
// s的字符需要使用多少个比特位数才能表示完
// letterIdBits := int(math.Ceil(math.Log2(strLength))),下面比上面的代码快
letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(strLength))))
// 最大的字母id掩码
var letterIdMask int64 = 1<<letterIdBits - 1
// 可用次数的最大值
letterIdMax := 63 / letterIdBits
// 循环生成随机字符串
for i, cache, remain := length-1, rn.Int63(), letterIdMax; i >= 0; {
// 检查随机数生成器是否用尽所有随机数
if remain == 0 {
cache, remain = rn.Int63(), letterIdMax
}
// 从可用字符的字符串中随机选择一个字符
if idx := int(cache & letterIdMask); idx < len(s) {
bytes[i] = s[idx]
i--
}
// 右移比特位数,为下次选择字符做准备
cache >>= letterIdBits
remain--
}
// 仿照strings.Builder用unsafe包返回一个字符串避免拷贝
// 将字节切片转换为字符串并返回
return *(*string)(unsafe.Pointer(&bytes))
}
// UUIdV4 generate a random UUID of version 4 according to RFC 4122.

View File

@@ -498,20 +498,13 @@ func FlatMap[T any, U any](slice []T, iteratee func(index int, item T) []U) []U
// Reduce creates an slice of values by running each element of slice thru iteratee function.
// Play: https://go.dev/play/p/_RfXJJWIsIm
func Reduce[T any](slice []T, iteratee func(index int, item1, item2 T) T, initial T) T {
if len(slice) == 0 {
return initial
accumulator := initial
for i, v := range slice {
accumulator = iteratee(i, v, accumulator)
}
result := iteratee(0, initial, slice[0])
tmp := make([]T, 2)
for i := 1; i < len(slice); i++ {
tmp[0] = result
tmp[1] = slice[i]
result = iteratee(i, tmp[0], tmp[1])
}
return result
return accumulator
}
// ReduceBy produces a value from slice by accumulating the result of each element as passed through the reducer function.
@@ -625,35 +618,34 @@ func IntSlice(slice any) []int {
return result
}
// DeleteAt delete the element of slice from start index to end index - 1.
// DeleteAt delete the element of slice at index.
// Play: https://go.dev/play/p/pJ-d6MUWcvK
func DeleteAt[T any](slice []T, start int, end ...int) []T {
size := len(slice)
if start < 0 || start >= size {
return slice
func DeleteAt[T any](slice []T, index int) []T {
if index >= len(slice) {
index = len(slice) - 1
}
if len(end) > 0 {
end := end[0]
if end <= start {
return slice
}
if end > size {
end = size
}
result := make([]T, len(slice)-1)
copy(result, slice[:index])
copy(result[index:], slice[index+1:])
slice = append(slice[:start], slice[end:]...)
return slice
return result
}
// DeleteRange delete the element of slice from start index to end indexexclude).
// Play: todo
func DeleteRange[T any](slice []T, start, end int) []T {
result := make([]T, 0, len(slice)-(end-start))
for i := 0; i < start; i++ {
result = append(result, slice[i])
}
if start == size-1 {
slice = slice[:start]
} else {
slice = append(slice[:start], slice[start+1:]...)
for i := end; i < len(slice); i++ {
result = append(result, slice[i])
}
return slice
return result
}
// Drop drop n elements from the start of a slice.

View File

@@ -594,18 +594,46 @@ func ExampleIntSlice() {
}
func ExampleDeleteAt() {
result1 := DeleteAt([]string{"a", "b", "c"}, -1)
result2 := DeleteAt([]string{"a", "b", "c"}, 0)
result3 := DeleteAt([]string{"a", "b", "c"}, 0, 2)
chars := []string{"a", "b", "c", "d", "e"}
result1 := DeleteAt(chars, 0)
result2 := DeleteAt(chars, 4)
result3 := DeleteAt(chars, 5)
result4 := DeleteAt(chars, 6)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [b c]
// [c]
// [b c d e]
// [a b c d]
// [a b c d]
// [a b c d]
}
func ExampleDeleteRange() {
chars := []string{"a", "b", "c", "d", "e"}
result1 := DeleteRange(chars, 0, 0)
result2 := DeleteRange(chars, 0, 1)
result3 := DeleteRange(chars, 0, 3)
result4 := DeleteRange(chars, 0, 4)
result5 := DeleteRange(chars, 0, 5)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// [a b c d e]
// [b c d e]
// [d e]
// [e]
// []
}
func ExampleDrop() {

View File

@@ -572,19 +572,27 @@ func TestDeleteAt(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestDeleteAt")
arr := []int{1, 2, 3, 4, 5}
assert.Equal([]string{"a", "b", "c"}, DeleteAt([]string{"a", "b", "c"}, -1))
assert.Equal([]string{"a", "b", "c"}, DeleteAt([]string{"a", "b", "c"}, 3))
assert.Equal([]string{"b", "c"}, DeleteAt([]string{"a", "b", "c"}, 0))
assert.Equal([]string{"a", "c"}, DeleteAt([]string{"a", "b", "c"}, 1))
assert.Equal([]string{"a", "b"}, DeleteAt([]string{"a", "b", "c"}, 2))
assert.Equal([]int{2, 3, 4, 5}, DeleteAt(arr, 0))
assert.Equal([]int{1, 2, 3, 4}, DeleteAt(arr, 4))
assert.Equal([]string{"b", "c"}, DeleteAt([]string{"a", "b", "c"}, 0, 1))
assert.Equal([]string{"c"}, DeleteAt([]string{"a", "b", "c"}, 0, 2))
assert.Equal([]string{}, DeleteAt([]string{"a", "b", "c"}, 0, 3))
assert.Equal([]string{}, DeleteAt([]string{"a", "b", "c"}, 0, 4))
assert.Equal([]string{"a"}, DeleteAt([]string{"a", "b", "c"}, 1, 3))
assert.Equal([]string{"a"}, DeleteAt([]string{"a", "b", "c"}, 1, 4))
assert.Equal([]int{1, 2, 3, 4}, DeleteAt(arr, 5))
assert.Equal([]int{1, 2, 3, 4}, DeleteAt(arr, 6))
assert.Equal([]int{1, 2, 3, 4, 5}, arr)
}
func TestDeleteRange(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestDeleteRange")
arr := []int{1, 2, 3, 4, 5}
assert.Equal([]int{1, 2, 3, 4, 5}, DeleteRange(arr, 0, 0))
assert.Equal([]int{2, 3, 4, 5}, DeleteRange(arr, 0, 1))
assert.Equal([]int{4, 5}, DeleteRange(arr, 0, 3))
}
func TestDrop(t *testing.T) {

View File

@@ -52,6 +52,16 @@ func (f *Field) IsZero() bool {
return reflect.DeepEqual(z, v)
}
// IsNil returns true if the given field is nil value.
func (f *Field) IsNil() bool {
v := f.Value()
if v == nil || (reflect.ValueOf(v)).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil() {
return true
}
return false
}
// Name returns the name of the given field
func (f *Field) Name() string {
return f.field.Name
@@ -111,7 +121,9 @@ func (f *Field) mapValue(value any) any {
ret = v.Interface()
}
default:
ret = v.Interface()
if v.Kind().String() != "invalid" {
ret = v.Interface()
}
}
return ret
}

View File

@@ -2,6 +2,7 @@
package structs
import (
"fmt"
"reflect"
"github.com/duke-git/lancet/v2/pointer"
@@ -61,7 +62,7 @@ func New(value any, tagName ...string) *Struct {
// ToMap convert the exported fields of a struct to map.
func (s *Struct) ToMap() (map[string]any, error) {
if !s.IsStruct() {
return nil, errInvalidStruct(s)
return nil, fmt.Errorf("invalid struct %v", s)
}
result := make(map[string]any)
@@ -70,9 +71,15 @@ func (s *Struct) ToMap() (map[string]any, error) {
if !f.IsExported() || f.tag.IsEmpty() || f.tag.Name == "-" {
continue
}
if f.IsZero() && f.tag.HasOption("omitempty") {
continue
}
if f.IsNil() {
continue
}
result[f.tag.Name] = f.mapValue(f.Value())
}

View File

@@ -1,7 +0,0 @@
package structs
import "fmt"
func errInvalidStruct(v any) error {
return fmt.Errorf("invalid struct %v", v)
}

View File

@@ -65,6 +65,45 @@ func TestStruct_ToMap(t *testing.T) {
})
}
func Test_ToMap2(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestStruct_ToMap")
type M struct {
Name string `json:"name"`
}
type S struct {
ID int `json:"id"`
M *M `json:"m"`
}
s1 := &S{
ID: 1,
}
var expect1 = map[string]any{"id": 1}
map1, err := ToMap(s1)
assert.IsNil(err)
assert.Equal(expect1, map1)
s2 := &S{
ID: 1,
M: &M{
Name: "test",
},
}
var expect2 = map[string]any{
"id": 1,
"m": map[string]any{
"name": "test",
}}
map2, err := ToMap(s2)
assert.IsNil(err)
assert.Equal(expect2, map2)
}
func TestStruct_Fields(t *testing.T) {
t.Parallel()

View File

@@ -4,7 +4,6 @@
package strutil
import (
"reflect"
"regexp"
"strings"
"unicode"
@@ -380,10 +379,7 @@ func RemoveNonPrintable(str string) string {
// StringToBytes converts a string to byte slice without a memory allocation.
// Play: https://go.dev/play/p/7OyFBrf9AxA
func StringToBytes(str string) (b []byte) {
sh := *(*reflect.StringHeader)(unsafe.Pointer(&str))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
return b
return *(*[]byte)(unsafe.Pointer(&str))
}
// BytesToString converts a byte slice to string without a memory allocation.