1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-03-01 00:35:28 +08:00

Compare commits

...

21 Commits

Author SHA1 Message Date
dudaodong
4cc1722f81 feat: fix AesEcbEncrypt and AesEcbDecrypt 2023-02-14 16:42:25 +08:00
dudaodong
d0260b2841 feat: add XError to support more contextual error handling 2023-02-14 16:32:57 +08:00
dudaodong
57bd64cae7 feat: add stack 2023-02-14 10:21:38 +08:00
dudaodong
c383719496 doc: add doc for DeepClone function 2023-02-12 19:24:56 +08:00
dudaodong
4b196a72b1 feat: add DeepClone 2023-02-10 15:50:28 +08:00
dudaodong
888381a06c doc: add drop and sort related function doc 2023-02-10 10:53:41 +08:00
dudaodong
a554eb7ef4 doc: add doc for sort function 2023-02-10 10:42:38 +08:00
dudaodong
26ff90122b doc: add doc for Drop function 2023-02-10 10:31:02 +08:00
dudaodong
75b27c6540 feat: add IsSortedByKey 2023-02-10 10:10:57 +08:00
dudaodong
a7e77fa98d feat: add IsSorted 2023-02-09 19:30:11 +08:00
dudaodong
abf392117a feat: add IsAscending, Isdescending 2023-02-09 19:25:58 +08:00
dudaodong
d231d9f572 feat: add DropRightWhile 2023-02-09 17:53:26 +08:00
dudaodong
97447d058e test: add test and example for DropWhile 2023-02-09 17:46:40 +08:00
dudaodong
040e112aa6 feat: add DropRight and DropWhile 2023-02-09 17:38:31 +08:00
dudaodong
afb021b4b5 feat: add Max, Min for stream 2023-02-08 15:20:29 +08:00
dudaodong
5075774000 feat: add Sorted for stream 2023-02-08 15:07:09 +08:00
dudaodong
6bba44dc50 feat: add Concat for stream 2023-02-07 10:47:28 +08:00
dudaodong
422022c74d feat: add Range for stream 2023-02-07 10:36:07 +08:00
dudaodong
87fcf97e2d feat: add FindFirst, Reverse for stream 2023-02-07 10:24:19 +08:00
dudaodong
05d1f348d4 refactor: update constant names 2023-02-06 19:39:31 +08:00
dudaodong
6f2f1f3004 fix: IsZeroValue support pointer 2023-02-06 19:36:36 +08:00
23 changed files with 2216 additions and 119 deletions

View File

@@ -246,6 +246,8 @@ import "github.com/duke-git/lancet/v2/convertor"
- **<big>DecodeByte</big>** : decode byte slice data to target object.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/convertor.md#DecodeByte)]
[[play](https://go.dev/play/p/zI6xsmuQRbn)]
- **<big>DeepClone</big>** : creates a deep copy of passed item, can't clone unexported field of struct.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/convertor.md#DeepClone)]
### 5. Cryptor package is for data encryption and decryption.
@@ -832,9 +834,15 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>DeleteAt</big>** : delete the element of slice from specific start index to end index - 1.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/pJ-d6MUWcvK)]
- **<big>Drop</big>** : creates a slice with `n` elements dropped from the beginning when n > 0, or `n` elements dropped from the ending when n < 0.
- **<big>Drop</big>** : drop n elements from the start of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#Drop)]
[[play](https://go.dev/play/p/pJ-d6MUWcvK)]
- **<big>DropRight</big>** : drop n elements from the end of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#DropRight)]
- **<big>DropWhile</big>** : drop n elements from the start of a slice while predicate function returns true.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#DropWhile)]
- **<big>DropRightWhile</big>** : drop n elements from the end of a slice while predicate function returns true.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#DropRightWhile)]
- **<big>Equal</big>** : checks if two slices are equal: the same length and all elements' order and value are equal.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#Equal)]
[[play](https://go.dev/play/p/WcRQJ37ifPa)]
@@ -910,6 +918,14 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Shuffle</big>** : shuffle the slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#Shuffle)]
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
- **<big>IsAscending</big>** : Checks if a slice is ascending order.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#IsAscending)]
- **<big>IsDescending</big>** : Checks if a slice is descending order.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#IsDescending)]
- **<big>IsSorted</big>** : Checks if a slice is sorted (ascending or descending).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#IsSorted)]
- **<big>IsSortedByKey</big>** : Checks if a slice is sorted by iteratee function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#IsSortedByKey)]
- **<big>Sort</big>** : sorts a slice of any ordered type(number or string).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice.md#Sort)]
[[play](https://go.dev/play/p/V9AVjzf_4Fk)]

View File

@@ -245,6 +245,9 @@ import "github.com/duke-git/lancet/v2/convertor"
- **<big>DecodeByte</big>** : 解码字节切片到目标对象,目标对象需要传入一个指针实例。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/convertor_zh-CN.md#DecodeByte)]
[[play](https://go.dev/play/p/zI6xsmuQRbn)]
- **<big>DeepClone</big>** : 创建一个传入值的深拷贝, 无法克隆结构体的非导出字段。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/convertor_zh-CN.md#DeepClone)]
### 5. cryptor 加密包支持数据加密和解密,获取 md5hash 值。支持 base64, md5, hmac, aes, des, rsa。
@@ -839,9 +842,15 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>DeleteAt</big>** : 删除切片中指定开始索引到结束索引的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#DeleteAt)]
[[play](https://go.dev/play/p/pJ-d6MUWcvK)]
- **<big>Drop</big>** : 创建一个切片当n > 0时从开头删除n个元素或者当n < 0时从结尾删除n个元素。
- **<big>Drop</big>** : 从切片头部删除n个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#Drop)]
[[play](https://go.dev/play/p/pJ-d6MUWcvK)]
- **<big>DropRight</big>** : 从切片尾部删除n个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#DropRight)]
- **<big>DropWhile</big>** : 从切片的头部删除n个元素这个n个元素满足predicate函数返回true。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#DropWhile)]
- **<big>DropRightWhile</big>** : 从切片的尾部删除n个元素这个n个元素满足predicate函数返回true。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#DropRightWhile)]
- **<big>Equal</big>** : 检查两个切片是否相等,相等条件:切片长度相同,元素顺序和值都相同。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#Equal)]
[[play](https://go.dev/play/p/WcRQJ37ifPa)]
@@ -917,6 +926,14 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Shuffle</big>** : 随机打乱切片中的元素顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#Shuffle)]
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
- **<big>IsAscending</big>** : 检查切片元素是否按升序排列。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#IsAscending)]
- **<big>IsDescending</big>** : 检查切片元素是否按降序排列。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#IsDescending)]
- **<big>IsSorted</big>** : 检查切片元素是否是有序的(升序或降序)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#IsSorted)]
- **<big>IsSortedByKey</big>** : 通过iteratee函数检查切片元素是否是有序的。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#IsSortedByKey)]
- **<big>Sort</big>** : 对任何有序类型(数字或字符串)的切片进行排序,使用快速排序算法。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/slice_zh-CN.md#Sort)]
[[play](https://go.dev/play/p/V9AVjzf_4Fk)]

View File

@@ -324,3 +324,19 @@ func DecodeByte(data []byte, target any) error {
decoder := gob.NewDecoder(buffer)
return decoder.Decode(target)
}
// DeepClone creates a deep copy of passed item.
// can't clone unexported field of struct
// Play: todo
func DeepClone[T any](src T) T {
c := cloner{
ptrs: map[reflect.Type]map[uintptr]reflect.Value{},
}
result := c.clone(reflect.ValueOf(src))
if result.Kind() == reflect.Invalid {
var zeroValue T
return zeroValue
}
return result.Interface().(T)
}

View File

@@ -252,3 +252,46 @@ func ExampleDecodeByte() {
// Output:
// abc
}
func ExampleDeepClone() {
type Struct struct {
Str string
Int int
Float float64
Bool bool
Nil interface{}
unexported string
}
cases := []interface{}{
true,
1,
0.1,
map[string]int{
"a": 1,
"b": 2,
},
&Struct{
Str: "test",
Int: 1,
Float: 0.1,
Bool: true,
Nil: nil,
// unexported: "can't be cloned",
},
}
for _, item := range cases {
cloned := DeepClone(item)
isPointerEqual := &cloned == &item
fmt.Println(cloned, isPointerEqual)
}
// Output:
// true false
// 1 false
// 0.1 false
// map[a:1 b:2] false
// &{test 1 0.1 true <nil> } false
}

View File

@@ -0,0 +1,216 @@
// Copyright 2023 dudaodong@gmail.com. All rights reserved.
// Use of this source code is governed by MIT license
// Package convertor implements some functions to convert data.
package convertor
import "reflect"
type cloner struct {
ptrs map[reflect.Type]map[uintptr]reflect.Value
}
// clone return a duplicate of passed item.
func (c *cloner) clone(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Invalid:
return reflect.ValueOf(nil)
// bool
case reflect.Bool:
return reflect.ValueOf(v.Bool())
//int
case reflect.Int:
return reflect.ValueOf(int(v.Int()))
case reflect.Int8:
return reflect.ValueOf(int8(v.Int()))
case reflect.Int16:
return reflect.ValueOf(int16(v.Int()))
case reflect.Int32:
return reflect.ValueOf(int32(v.Int()))
case reflect.Int64:
return reflect.ValueOf(v.Int())
// uint
case reflect.Uint:
return reflect.ValueOf(uint(v.Uint()))
case reflect.Uint8:
return reflect.ValueOf(uint8(v.Uint()))
case reflect.Uint16:
return reflect.ValueOf(uint16(v.Uint()))
case reflect.Uint32:
return reflect.ValueOf(uint32(v.Uint()))
case reflect.Uint64:
return reflect.ValueOf(v.Uint())
// float
case reflect.Float32:
return reflect.ValueOf(float32(v.Float()))
case reflect.Float64:
return reflect.ValueOf(v.Float())
// complex
case reflect.Complex64:
return reflect.ValueOf(complex64(v.Complex()))
case reflect.Complex128:
return reflect.ValueOf(v.Complex())
// string
case reflect.String:
return reflect.ValueOf(v.String())
// array
case reflect.Array, reflect.Slice:
return c.cloneArray(v)
// map
case reflect.Map:
return c.cloneMap(v)
// Ptr
case reflect.Ptr:
return c.clonePtr(v)
// struct
case reflect.Struct:
return c.cloneStruct(v)
// func
case reflect.Func:
return v
// interface
case reflect.Interface:
return c.clone(v.Elem())
}
return reflect.Zero(v.Type())
}
func (c *cloner) cloneArray(v reflect.Value) reflect.Value {
if v.IsNil() {
return reflect.Zero(v.Type())
}
arr := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
val := c.clone(v.Index(i))
if val.IsValid() {
continue
}
item := arr.Index(i)
if !item.CanSet() {
continue
}
item.Set(val.Convert(item.Type()))
}
return arr
}
func (c *cloner) cloneMap(v reflect.Value) reflect.Value {
if v.IsNil() {
return reflect.Zero(v.Type())
}
clonedMap := reflect.MakeMap(v.Type())
for _, key := range v.MapKeys() {
value := v.MapIndex(key)
clonedKey := c.clone(key)
clonedValue := c.clone(value)
if !isNillable(clonedKey) || !clonedKey.IsNil() {
clonedKey = clonedKey.Convert(key.Type())
}
if (!isNillable(clonedValue) || !clonedValue.IsNil()) && clonedValue.IsValid() {
clonedValue = clonedValue.Convert(value.Type())
}
if !clonedValue.IsValid() {
clonedValue = reflect.Zero(clonedMap.Type().Elem())
}
clonedMap.SetMapIndex(clonedKey, clonedValue)
}
return clonedMap
}
func isNillable(v reflect.Value) bool {
switch v.Kind() {
case reflect.Chan, reflect.Interface, reflect.Ptr, reflect.Func:
return true
}
return false
}
func (c *cloner) clonePtr(v reflect.Value) reflect.Value {
if v.IsNil() {
return reflect.Zero(v.Type())
}
var newVal reflect.Value
if v.Elem().CanAddr() {
ptrs, exists := c.ptrs[v.Type()]
if exists {
if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists {
return newVal
}
}
}
newVal = c.clone(v.Elem())
if v.Elem().CanAddr() {
ptrs, exists := c.ptrs[v.Type()]
if exists {
if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists {
return newVal
}
}
}
clonedPtr := reflect.New(newVal.Type())
clonedPtr.Elem().Set(newVal)
return clonedPtr
}
func (c *cloner) cloneStruct(v reflect.Value) reflect.Value {
clonedStructPtr := reflect.New(v.Type())
clonedStruct := clonedStructPtr.Elem()
if v.CanAddr() {
ptrs := c.ptrs[clonedStructPtr.Type()]
if ptrs == nil {
ptrs = make(map[uintptr]reflect.Value)
c.ptrs[clonedStructPtr.Type()] = ptrs
}
ptrs[v.UnsafeAddr()] = clonedStructPtr
}
for i := 0; i < v.NumField(); i++ {
newStructValue := clonedStruct.Field(i)
if !newStructValue.CanSet() {
continue
}
clonedVal := c.clone(v.Field(i))
if !clonedVal.IsValid() {
continue
}
newStructValue.Set(clonedVal.Convert(newStructValue.Type()))
}
return clonedStruct
}

View File

@@ -2,6 +2,7 @@ package convertor
import (
"fmt"
"reflect"
"strconv"
"testing"
@@ -253,3 +254,47 @@ func TestDecodeByte(t *testing.T) {
assert.IsNil(err)
assert.Equal("abc", obj)
}
func TestDeepClone(t *testing.T) {
// assert := internal.NewAssert(t, "TestDeepClone")
type Struct struct {
Str string
Int int
Float float64
Bool bool
Nil interface{}
unexported string
}
cases := []interface{}{
true,
1,
0.1,
map[string]int{
"a": 1,
"b": 2,
},
&Struct{
Str: "test",
Int: 1,
Float: 0.1,
Bool: true,
Nil: nil,
// unexported: "can't be cloned",
},
}
for i, item := range cases {
cloned := DeepClone(item)
t.Log(cloned)
if &cloned == &item {
t.Fatalf("[TestDeepClone case #%d failed]: equal pointer", i)
}
if !reflect.DeepEqual(item, cloned) {
t.Fatalf("[TestDeepClone case #%d failed] unequal objects", i)
}
}
}

View File

@@ -2,11 +2,11 @@ package cryptor
import "bytes"
func generateAesKey(key []byte) []byte {
genKey := make([]byte, 16)
func generateAesKey(key []byte, size int) []byte {
genKey := make([]byte, size)
copy(genKey, key)
for i := 16; i < len(key); {
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
for i := size; i < len(key); {
for j := 0; j < size && i < len(key); j, i = j+1, i+1 {
genKey[j] ^= key[i]
}
}

View File

@@ -23,6 +23,11 @@ import (
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/jT5irszHx-j
func AesEcbEncrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
}
length := (len(data) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
@@ -34,7 +39,7 @@ func AesEcbEncrypt(data, key []byte) []byte {
}
encrypted := make([]byte, len(plain))
cipher, _ := aes.NewCipher(generateAesKey(key))
cipher, _ := aes.NewCipher(generateAesKey(key, size))
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
@@ -47,7 +52,11 @@ func AesEcbEncrypt(data, key []byte) []byte {
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/jT5irszHx-j
func AesEcbDecrypt(encrypted, key []byte) []byte {
cipher, _ := aes.NewCipher(generateAesKey(key))
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
}
cipher, _ := aes.NewCipher(generateAesKey(key, size))
decrypted := make([]byte, len(encrypted))
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {

View File

@@ -1,15 +1,17 @@
# Convertor
Package convertor contains some functions for data type convertion.
<div STYLE="page-break-after: always;"></div>
## Source:
- [https://github.com/duke-git/lancet/blob/main/convertor/convertor.go](https://github.com/duke-git/lancet/blob/main/convertor/convertor.go)
- [https://github.com/duke-git/lancet/blob/main/convertor/convertor.go](https://github.com/duke-git/lancet/blob/main/convertor/convertor.go)
<div STYLE="page-break-after: always;"></div>
## Usage:
```go
import (
"github.com/duke-git/lancet/v2/convertor"
@@ -19,28 +21,31 @@ import (
<div STYLE="page-break-after: always;"></div>
## Index
- [ColorHexToRGB](#ColorHexToRGB)
- [ColorRGBToHex](#ColorRGBToHex)
- [ToBool](#ToBool)
- [ToBytes](#ToBytes)
- [ToChar](#ToChar)
- [ToChannel](#ToChannel)
- [ToFloat](#ToFloat)
- [ToInt](#ToInt)
- [ToJson](#ToJson)
- [ToMap](#ToMap)
- [ToPointer](#ToPointer)
- [ToString](#ToString)
- [StructToMap](#StructToMap)
- [MapToSlice](#MapToSlice)
- [EncodeByte](#EncodeByte)
- [DecodeByte](#DecodeByte)
- [ColorHexToRGB](#ColorHexToRGB)
- [ColorRGBToHex](#ColorRGBToHex)
- [ToBool](#ToBool)
- [ToBytes](#ToBytes)
- [ToChar](#ToChar)
- [ToChannel](#ToChannel)
- [ToFloat](#ToFloat)
- [ToInt](#ToInt)
- [ToJson](#ToJson)
- [ToMap](#ToMap)
- [ToPointer](#ToPointer)
- [ToString](#ToString)
- [StructToMap](#StructToMap)
- [MapToSlice](#MapToSlice)
- [EncodeByte](#EncodeByte)
- [DecodeByte](#DecodeByte)
- [DeepClone](#DeepClone)
<div STYLE="page-break-after: always;"></div>
## Documentation
### <span id="ColorHexToRGB">ColorHexToRGB</span>
<p>Convert color hex to color rgb.</p>
<b>Signature:</b>
@@ -48,6 +53,7 @@ import (
```go
func ColorHexToRGB(colorHex string) (red, green, blue int)
```
<b>Example:</b>
```go
@@ -78,6 +84,7 @@ func main() {
```go
func ColorRGBToHex(red, green, blue int) string
```
<b>Example:</b>
```go
@@ -110,6 +117,7 @@ func main() {
```go
func ToBool(s string) (bool, error)
```
<b>Example:</b>
```go
@@ -150,6 +158,7 @@ func main() {
```go
func ToBytes(data any) ([]byte, error)
```
<b>Example:</b>
```go
@@ -182,6 +191,7 @@ func main() {
```go
func ToChar(s string) []string
```
<b>Example:</b>
```go
@@ -217,6 +227,7 @@ func main() {
```go
func ToChannel[T any](array []T) <-chan T
```
<b>Example:</b>
```go
@@ -253,6 +264,7 @@ func main() {
```go
func ToFloat(value any) (float64, error)
```
<b>Example:</b>
```go
@@ -297,6 +309,7 @@ func main() {
```go
func ToInt(value any) (int64, error)
```
<b>Example:</b>
```go
@@ -338,6 +351,7 @@ func main() {
```go
func ToJson(value any) (string, error)
```
<b>Example:</b>
```go
@@ -372,6 +386,7 @@ func main() {
```go
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V
```
<b>Example:</b>
```go
@@ -412,6 +427,7 @@ func main() {
```go
func ToPointer[T any](value T) *T
```
<b>Example:</b>
```go
@@ -424,8 +440,8 @@ import (
func main() {
result := convertor.ToPointer(123)
fmt.Println(*result)
fmt.Println(*result)
// Output:
// 123
}
@@ -440,6 +456,7 @@ func main() {
```go
func ToString(value any) string
```
<b>Example:</b>
```go
@@ -487,6 +504,7 @@ func main() {
```go
func StructToMap(value any) (map[string]any, error)
```
<b>Example:</b>
```go
@@ -524,6 +542,7 @@ func main() {
```go
func MapToSlice[T any, K comparable, V any](aMap map[K]V, iteratee func(K, V) T) []T
```
<b>Example:</b>
```go
@@ -544,7 +563,6 @@ func main() {
}
```
### <span id="EncodeByte">EncodeByte</span>
<p>Encode data to byte slice.</p>
@@ -554,6 +572,7 @@ func main() {
```go
func EncodeByte(data any) ([]byte, error)
```
<b>Example:</b>
```go
@@ -582,6 +601,7 @@ func main() {
```go
func DecodeByte(data []byte, target any) error
```
<b>Example:</b>
```go
@@ -595,15 +615,80 @@ import (
func main() {
var result string
byteData := []byte{6, 12, 0, 3, 97, 98, 99}
err := convertor.DecodeByte(byteData, &result)
if err != nil {
return
}
fmt.Println(result)
// Output:
// abc
}
```
### <span id="DeepClone">DeepClone</span>
<p>Creates a deep copy of passed item, can't clone unexported field of struct.</p>
<b>Signature:</b>
```go
func DeepClone[T any](src T) T
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
type Struct struct {
Str string
Int int
Float float64
Bool bool
Nil interface{}
unexported string
}
cases := []interface{}{
true,
1,
0.1,
map[string]int{
"a": 1,
"b": 2,
},
&Struct{
Str: "test",
Int: 1,
Float: 0.1,
Bool: true,
Nil: nil,
// unexported: "can't be cloned",
},
}
for _, item := range cases {
cloned := convertor.DeepClone(item)
isPointerEqual := &cloned == &item
fmt.Println(cloned, isPointerEqual)
}
// Output:
// true false
// 1 false
// 0.1 false
// map[a:1 b:2] false
// &{test 1 0.1 true <nil> } false
}
```

View File

@@ -1,11 +1,12 @@
# Convertor
convertor转换器包支持一些常见的数据类型转换
convertor 转换器包支持一些常见的数据类型转换
<div STYLE="page-break-after: always;"></div>
## 源码:
- [https://github.com/duke-git/lancet/blob/main/convertor/convertor.go](https://github.com/duke-git/lancet/blob/main/convertor/convertor.go)
- [https://github.com/duke-git/lancet/blob/main/convertor/convertor.go](https://github.com/duke-git/lancet/blob/main/convertor/convertor.go)
<div STYLE="page-break-after: always;"></div>
@@ -21,29 +22,30 @@ import (
## 目录
- [ColorHexToRGB](#ColorHexToRGB)
- [ColorRGBToHex](#ColorRGBToHex)
- [ToBool](#ToBool)
- [ToBytes](#ToBytes)
- [ToChar](#ToChar)
- [ToChannel](#ToChannel)
- [ToFloat](#ToFloat)
- [ToInt](#ToInt)
- [ToJson](#ToJson)
- [ToMap](#ToMap)
- [ToPointer](#ToPointer)
- [ToString](#ToString)
- [StructToMap](#StructToMap)
- [MapToSlice](#MapToSlice)
- [EncodeByte](#EncodeByte)
- [DecodeByte](#DecodeByte)
- [ColorHexToRGB](#ColorHexToRGB)
- [ColorRGBToHex](#ColorRGBToHex)
- [ToBool](#ToBool)
- [ToBytes](#ToBytes)
- [ToChar](#ToChar)
- [ToChannel](#ToChannel)
- [ToFloat](#ToFloat)
- [ToInt](#ToInt)
- [ToJson](#ToJson)
- [ToMap](#ToMap)
- [ToPointer](#ToPointer)
- [ToString](#ToString)
- [StructToMap](#StructToMap)
- [MapToSlice](#MapToSlice)
- [EncodeByte](#EncodeByte)
- [DecodeByte](#DecodeByte)
- [DeepClone](#DeepClone)
<div STYLE="page-break-after: always;"></div>
## 文档
### <span id="ColorHexToRGB">ColorHexToRGB</span>
<p>颜色值十六进制转rgb。</p>
<b>函数签名:</b>
@@ -51,6 +53,7 @@ import (
```go
func ColorHexToRGB(colorHex string) (red, green, blue int)
```
<b>示例:</b>
```go
@@ -81,6 +84,7 @@ func main() {
```go
func ColorRGBToHex(red, green, blue int) string
```
<b>示例:</b>
```go
@@ -113,6 +117,7 @@ func main() {
```go
func ToBool(s string) (bool, error)
```
<b>示例:</b>
```go
@@ -153,6 +158,7 @@ func main() {
```go
func ToBytes(data any) ([]byte, error)
```
<b>示例:</b>
```go
@@ -185,6 +191,7 @@ func main() {
```go
func ToChar(s string) []string
```
<b>示例:</b>
```go
@@ -220,6 +227,7 @@ func main() {
```go
func ToChannel[T any](array []T) <-chan T
```
<b>示例:</b>
```go
@@ -256,6 +264,7 @@ func main() {
```go
func ToFloat(value any) (float64, error)
```
<b>示例:</b>
```go
@@ -300,6 +309,7 @@ func main() {
```go
func ToInt(value any) (int64, error)
```
<b>示例:</b>
```go
@@ -341,6 +351,7 @@ func main() {
```go
func ToJson(value any) (string, error)
```
<b>示例:</b>
```go
@@ -375,6 +386,7 @@ func main() {
```go
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V
```
<b>示例:</b>
```go
@@ -415,6 +427,7 @@ func main() {
```go
func ToPointer[T any](value T) *T
```
<b>示例:</b>
```go
@@ -427,8 +440,8 @@ import (
func main() {
result := convertor.ToPointer(123)
fmt.Println(*result)
fmt.Println(*result)
// Output:
// 123
}
@@ -443,6 +456,7 @@ func main() {
```go
func ToString(value any) string
```
<b>示例:</b>
```go
@@ -490,6 +504,7 @@ func main() {
```go
func StructToMap(value any) (map[string]any, error)
```
<b>示例:</b>
```go
@@ -527,6 +542,7 @@ func main() {
```go
func MapToSlice[T any, K comparable, V any](aMap map[K]V, iteratee func(K, V) T) []T
```
<b>示例:</b>
```go
@@ -556,6 +572,7 @@ func main() {
```go
func EncodeByte(data any) ([]byte, error)
```
<b>示例:</b>
```go
@@ -584,6 +601,7 @@ func main() {
```go
func DecodeByte(data []byte, target any) error
```
<b>示例:</b>
```go
@@ -597,15 +615,79 @@ import (
func main() {
var result string
byteData := []byte{6, 12, 0, 3, 97, 98, 99}
err := convertor.DecodeByte(byteData, &result)
if err != nil {
return
}
fmt.Println(result)
// Output:
// abc
}
```
### <span id="DeepClone">DeepClone</span>
<p>创建一个传入值的深拷贝, 无法克隆结构体的非导出字段。</p>
<b>函数签名:</b>
```go
func DeepClone[T any](src T) T
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
type Struct struct {
Str string
Int int
Float float64
Bool bool
Nil interface{}
unexported string
}
cases := []interface{}{
true,
1,
0.1,
map[string]int{
"a": 1,
"b": 2,
},
&Struct{
Str: "test",
Int: 1,
Float: 0.1,
Bool: true,
Nil: nil,
// unexported: "can't be cloned",
},
}
for _, item := range cases {
cloned := convertor.DeepClone(item)
isPointerEqual := &cloned == &item
fmt.Println(cloned, isPointerEqual)
}
// Output:
// true false
// 1 false
// 0.1 false
// map[a:1 b:2] false
// &{test 1 0.1 true <nil> } false
}
```

View File

@@ -35,6 +35,9 @@ import (
- [DifferenceWith](#DifferenceWith)
- [DeleteAt](#DeleteAt)
- [Drop](#Drop)
- [DropRight](#DropRight)
- [DropWhile](#DropWhile)
- [DropRightWhile](#DropRightWhile)
- [Equal](#Equal)
- [EqualWith](#EqualWith)
- [Every](#Every)
@@ -60,6 +63,10 @@ import (
- [ReplaceAll](#ReplaceAll)
- [Repeat](#Repeat)
- [Shuffle](#Shuffle)
- [IsAscending](#IsAscending)
- [IsDescending](#IsDescending)
- [IsSorted](#IsSorted)
- [IsSortedByKey](#IsSortedByKey)
- [Sort](#Sort)
- [SortBy](#SortBy)
- [SortByField<sup>deprecated</sup>](#SortByField)
@@ -487,7 +494,7 @@ func main() {
### <span id="Drop">Drop</span>
<p>Creates a slice with `n` elements dropped from the beginning when n > 0, or `n` elements dropped from the ending when n < 0.</p>
<p>Drop n elements from the start of a slice.</p>
<b>Signature:</b>
@@ -505,20 +512,139 @@ import (
func main() {
result1 := slice.Drop([]string{"a", "b", "c"}, 0)
result2 := slice.Drop([]string{"a", "b", "c"}, 1)
result3 := slice.Drop([]string{"a", "b", "c"}, -1)
result4 := slice.Drop([]string{"a", "b", "c"}, 4)
result2 := slice.Drop([]string{"a", "b", "c"}, 1)
result3 := slice.Drop([]string{"a", "b", "c"}, -1)
result4 := slice.Drop([]string{"a", "b", "c"}, 4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [b c]
// [a b]
// []
// Output:
// [a b c]
// [b c]
// [a b c]
// []
}
```
### <span id="DropRight">DropRight</span>
<p>Drop n elements from the end of a slice.</p>
<b>Signature:</b>
```go
func DropRight[T any](slice []T, n int) []T
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.DropRight([]string{"a", "b", "c"}, 0)
result2 := slice.DropRight([]string{"a", "b", "c"}, 1)
result3 := slice.DropRight([]string{"a", "b", "c"}, -1)
result4 := slice.DropRight([]string{"a", "b", "c"}, 4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [a b]
// [a b c]
// []
}
```
### <span id="DropWhile">DropWhile</span>
<p>Drop n elements from the start of a slice while predicate function returns true.</p>
<b>Signature:</b>
```go
func DropWhile[T any](slice []T, predicate func(item T) bool) []T
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.DropWhile(numbers, func(n int) bool {
return n != 2
})
result2 := slice.DropWhile(numbers, func(n int) bool {
return true
})
result3 := slice.DropWhile(numbers, func(n int) bool {
return n == 0
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [2 3 4 5]
// []
// [1 2 3 4 5]
}
```
### <span id="DropRightWhile">DropRightWhile</span>
<p>Drop n elements from the end of a slice while predicate function returns true.</p>
<b>Signature:</b>
```go
func DropRightWhile[T any](slice []T, predicate func(item T) bool) []T
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
result1 := slice.DropRightWhile(numbers, func(n int) bool {
return n != 2
})
result2 := slice.DropRightWhile(numbers, func(n int) bool {
return true
})
result3 := slice.DropRightWhile(numbers, func(n int) bool {
return n == 0
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [1 2]
// []
// [1 2 3 4 5]
}
```
@@ -1348,6 +1474,148 @@ func main() {
}
```
### <span id="IsAscending">IsAscending</span>
<p>Checks if a slice is ascending order.</p>
<b>Signature:</b>
```go
func IsAscending[T constraints.Ordered](slice []T) bool
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsAscending([]int{1, 2, 3, 4, 5})
result2 := slice.IsAscending([]int{5, 4, 3, 2, 1})
result3 := slice.IsAscending([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// false
// false
}
```
### <span id="IsDescending">IsDescending</span>
<p>Checks if a slice is descending order.</p>
<b>Signature:</b>
```go
func IsDescending[T constraints.Ordered](slice []T) bool
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsDescending([]int{5, 4, 3, 2, 1})
result2 := slice.IsDescending([]int{1, 2, 3, 4, 5})
result3 := slice.IsDescending([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// false
// false
}
```
### <span id="IsSorted">IsSorted</span>
<p>Checks if a slice is sorted (ascending or descending).</p>
<b>Signature:</b>
```go
func IsSorted[T constraints.Ordered](slice []T) bool
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsSorted([]int{5, 4, 3, 2, 1})
result2 := slice.IsSorted([]int{1, 2, 3, 4, 5})
result3 := slice.IsSorted([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// true
// false
}
```
### <span id="IsSortedByKey">IsSortedByKey</span>
<p>Checks if a slice is sorted by iteratee function.</p>
<b>Signature:</b>
```go
func IsSortedByKey[T any, K constraints.Ordered](slice []T, iteratee func(item T) K) bool
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsSortedByKey([]string{"a", "ab", "abc"}, func(s string) int {
return len(s)
})
result2 := slice.IsSortedByKey([]string{"abc", "ab", "a"}, func(s string) int {
return len(s)
})
result3 := slice.IsSortedByKey([]string{"abc", "a", "ab"}, func(s string) int {
return len(s)
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// true
// false
}
```
### <span id="Sort">Sort</span>
<p>Sorts a slice of any ordered type(number or string), use quick sort algrithm. Default sort order is ascending (asc), if want descending order, set param `sortOrder` to `desc`. Ordered type: number(all ints uints floats) or string.

View File

@@ -35,6 +35,9 @@ import (
- [DifferenceWith](#DifferenceWith)
- [DeleteAt](#DeleteAt)
- [Drop](#Drop)
- [DropRight](#DropRight)
- [DropWhile](#DropWhile)
- [DropRightWhile](#DropRightWhile)
- [Every](#Every)
- [Equal](#Equal)
- [EqualWith](#EqualWith)
@@ -60,6 +63,10 @@ import (
- [ReplaceAll](#ReplaceAll)
- [Repeat](#Repeat)
- [Shuffle](#Shuffle)
- [IsAscending](#IsAscending)
- [IsDescending](#IsDescending)
- [IsSorted](#IsSorted)
- [IsSortedByKey](#IsSortedByKey)
- [Sort](#Sort)
- [SortBy](#SortBy)
- [SortByField<sup>deprecated</sup>](#SortByField)
@@ -486,9 +493,10 @@ func main() {
}
```
### <span id="Drop">Drop</span>
<p>创建一个切片当n > 0时从开头删除n个元素或者当n < 0时从结尾删除n个元素</p>
<p>从切片的头部删除n个元素。</p>
<b>函数签名:</b>
@@ -506,20 +514,139 @@ import (
func main() {
result1 := slice.Drop([]string{"a", "b", "c"}, 0)
result2 := slice.Drop([]string{"a", "b", "c"}, 1)
result3 := slice.Drop([]string{"a", "b", "c"}, -1)
result4 := slice.Drop([]string{"a", "b", "c"}, 4)
result2 := slice.Drop([]string{"a", "b", "c"}, 1)
result3 := slice.Drop([]string{"a", "b", "c"}, -1)
result4 := slice.Drop([]string{"a", "b", "c"}, 4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [b c]
// [a b]
// []
// Output:
// [a b c]
// [b c]
// [a b c]
// []
}
```
### <span id="DropRight">DropRight</span>
<p>从切片的尾部删除n个元素。</p>
<b>函数签名:</b>
```go
func DropRight[T any](slice []T, n int) []T
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.DropRight([]string{"a", "b", "c"}, 0)
result2 := slice.DropRight([]string{"a", "b", "c"}, 1)
result3 := slice.DropRight([]string{"a", "b", "c"}, -1)
result4 := slice.DropRight([]string{"a", "b", "c"}, 4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [a b]
// [a b c]
// []
}
```
### <span id="DropWhile">DropWhile</span>
<p>从切片的头部删除n个元素这个n个元素满足predicate函数返回true。</p>
<b>函数签名:</b>
```go
func DropWhile[T any](slice []T, predicate func(item T) bool) []T
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.DropWhile(numbers, func(n int) bool {
return n != 2
})
result2 := slice.DropWhile(numbers, func(n int) bool {
return true
})
result3 := slice.DropWhile(numbers, func(n int) bool {
return n == 0
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [2 3 4 5]
// []
// [1 2 3 4 5]
}
```
### <span id="DropRightWhile">DropRightWhile</span>
<p>从切片的尾部删除n个元素这个n个元素满足predicate函数返回true。</p>
<b>函数签名:</b>
```go
func DropRightWhile[T any](slice []T, predicate func(item T) bool) []T
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
result1 := slice.DropRightWhile(numbers, func(n int) bool {
return n != 2
})
result2 := slice.DropRightWhile(numbers, func(n int) bool {
return true
})
result3 := slice.DropRightWhile(numbers, func(n int) bool {
return n == 0
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [1 2]
// []
// [1 2 3 4 5]
}
```
@@ -1349,6 +1476,148 @@ func main() {
}
```
### <span id="IsAscending">IsAscending</span>
<p>检查切片元素是否按升序排列。</p>
<b>函数签名:</b>
```go
func IsAscending[T constraints.Ordered](slice []T) bool
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsAscending([]int{1, 2, 3, 4, 5})
result2 := slice.IsAscending([]int{5, 4, 3, 2, 1})
result3 := slice.IsAscending([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// false
// false
}
```
### <span id="IsDescending">IsDescending</span>
<p>检查切片元素是否按降序排列。</p>
<b>函数签名:</b>
```go
func IsDescending[T constraints.Ordered](slice []T) bool
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsDescending([]int{5, 4, 3, 2, 1})
result2 := slice.IsDescending([]int{1, 2, 3, 4, 5})
result3 := slice.IsDescending([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// false
// false
}
```
### <span id="IsSorted">IsSorted</span>
<p>检查切片元素是否是有序的(升序或降序)。</p>
<b>函数签名:</b>
```go
func IsSorted[T constraints.Ordered](slice []T) bool
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsSorted([]int{5, 4, 3, 2, 1})
result2 := slice.IsSorted([]int{1, 2, 3, 4, 5})
result3 := slice.IsSorted([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// true
// false
}
```
### <span id="IsSortedByKey">IsSortedByKey</span>
<p>通过iteratee函数检查切片元素是否是有序的。</p>
<b>函数签名:</b>
```go
func IsSortedByKey[T any, K constraints.Ordered](slice []T, iteratee func(item T) K) bool
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.IsSortedByKey([]string{"a", "ab", "abc"}, func(s string) int {
return len(s)
})
result2 := slice.IsSortedByKey([]string{"abc", "ab", "a"}, func(s string) int {
return len(s)
})
result3 := slice.IsSortedByKey([]string{"abc", "a", "ab"}, func(s string) int {
return len(s)
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// true
// false
}
```
### <span id="Sort">Sort</span>
<p>对任何有序类型(数字或字符串)的切片进行排序,使用快速排序算法。 默认排序顺序为升序 (asc),如果需要降序,请将参数 `sortOrder` 设置为 `desc`。 Ordered类型数字所有整数浮点数或字符串。</p>

View File

@@ -13,10 +13,10 @@ import (
)
const (
NUMERAL = "0123456789"
LOWER_LETTERS = "abcdefghijklmnopqrstuvwxyz"
UPPER_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Numeral = "0123456789"
LowwerLetters = "abcdefghijklmnopqrstuvwxyz"
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
// RandInt generate random int between min and max, maybe min, not be max.
@@ -51,31 +51,31 @@ func RandBytes(length int) []byte {
// RandString generate random string of specified length.
// Play: https://go.dev/play/p/W2xvRUXA7Mi
func RandString(length int) string {
return random(LETTERS, length)
return random(Letters, length)
}
// RandUpper generate a random upper case string.
// Play: https://go.dev/play/p/29QfOh0DVuh
func RandUpper(length int) string {
return random(UPPER_LETTERS, length)
return random(UpperLetters, length)
}
// RandLower generate a random lower case string.
// Play: https://go.dev/play/p/XJtZ471cmtI
func RandLower(length int) string {
return random(LOWER_LETTERS, length)
return random(LowwerLetters, length)
}
// RandNumeral generate a random numeral string of specified length.
// Play: https://go.dev/play/p/g4JWVpHsJcf
func RandNumeral(length int) string {
return random(NUMERAL, length)
return random(Numeral, length)
}
// RandNumeralOrLetter generate a random numeral or letter string.
// Play: https://go.dev/play/p/19CEQvpx2jD
func RandNumeralOrLetter(length int) string {
return random(NUMERAL+LETTERS, length)
return random(Numeral+Letters, length)
}
// random generate a random string based on given string range.

View File

@@ -6,7 +6,6 @@ package slice
import (
"fmt"
"math"
"math/rand"
"reflect"
"sort"
@@ -559,24 +558,72 @@ func DeleteAt[T any](slice []T, start int, end ...int) []T {
return slice
}
// Drop creates a slice with `n` elements dropped from the beginning when n > 0, or `n` elements dropped from the ending when n < 0.
// Drop drop n elements from the start of a slice.
// Play: https://go.dev/play/p/pJ-d6MUWcvK
func Drop[T any](slice []T, n int) []T {
size := len(slice)
if size == 0 || n == 0 {
return slice
}
if math.Abs(float64(n)) >= float64(size) {
if size <= n {
return []T{}
}
if n < 0 {
return slice[0 : size+n]
if n <= 0 {
return slice
}
return slice[n:size]
result := make([]T, 0, size-n)
return append(result, slice[n:]...)
}
// DropRight drop n elements from the end of a slice.
// Play: todo
func DropRight[T any](slice []T, n int) []T {
size := len(slice)
if size <= n {
return []T{}
}
if n <= 0 {
return slice
}
result := make([]T, 0, size-n)
return append(result, slice[:size-n]...)
}
// DropWhile drop n elements from the start of a slice while predicate function returns true.
// Play: todo
func DropWhile[T any](slice []T, predicate func(item T) bool) []T {
i := 0
for ; i < len(slice); i++ {
if !predicate(slice[i]) {
break
}
}
result := make([]T, 0, len(slice)-i)
return append(result, slice[i:]...)
}
// DropRightWhile drop n elements from the end of a slice while predicate function returns true.
// Play: todo
func DropRightWhile[T any](slice []T, predicate func(item T) bool) []T {
i := len(slice) - 1
for ; i >= 0; i-- {
if !predicate(slice[i]) {
break
}
}
result := make([]T, 0, i+1)
return append(result, slice[:i+1]...)
}
// InsertAt insert the value or other slice into slice at index.
@@ -783,6 +830,64 @@ func Shuffle[T any](slice []T) []T {
return slice
}
// IsAscending checks if a slice is ascending order.
// Play: todo
func IsAscending[T constraints.Ordered](slice []T) bool {
for i := 1; i < len(slice); i++ {
if slice[i-1] > slice[i] {
return false
}
}
return true
}
// IsDescending checks if a slice is descending order.
// Play: todo
func IsDescending[T constraints.Ordered](slice []T) bool {
for i := 1; i < len(slice); i++ {
if slice[i-1] < slice[i] {
return false
}
}
return true
}
// IsSorted checks if a slice is sorted(ascending or descending).
// Play: todo
func IsSorted[T constraints.Ordered](slice []T) bool {
return IsAscending(slice) || IsDescending(slice)
}
// IsSortedByKey checks if a slice is sorted by iteratee function.
// Play: todo
func IsSortedByKey[T any, K constraints.Ordered](slice []T, iteratee func(item T) K) bool {
size := len(slice)
isAscending := func(data []T) bool {
for i := 0; i < size-1; i++ {
if iteratee(data[i]) > iteratee(data[i+1]) {
return false
}
}
return true
}
isDescending := func(data []T) bool {
for i := 0; i < size-1; i++ {
if iteratee(data[i]) < iteratee(data[i+1]) {
return false
}
}
return true
}
return isAscending(slice) || isDescending(slice)
}
// Sort sorts a slice of any ordered type(number or string), use quick sort algrithm.
// default sort order is ascending (asc), if want descending order, set param `sortOrder` to `desc`.
// Play: https://go.dev/play/p/V9AVjzf_4Fk

View File

@@ -482,10 +482,74 @@ func ExampleDrop() {
// Output:
// [a b c]
// [b c]
// [a b]
// [a b c]
// []
}
func ExampleDropRight() {
result1 := DropRight([]string{"a", "b", "c"}, 0)
result2 := DropRight([]string{"a", "b", "c"}, 1)
result3 := DropRight([]string{"a", "b", "c"}, -1)
result4 := DropRight([]string{"a", "b", "c"}, 4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// [a b c]
// [a b]
// [a b c]
// []
}
func ExampleDropWhile() {
numbers := []int{1, 2, 3, 4, 5}
result1 := DropWhile(numbers, func(n int) bool {
return n != 2
})
result2 := DropWhile(numbers, func(n int) bool {
return true
})
result3 := DropWhile(numbers, func(n int) bool {
return n == 0
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [2 3 4 5]
// []
// [1 2 3 4 5]
}
func ExampleDropRightWhile() {
numbers := []int{1, 2, 3, 4, 5}
result1 := DropRightWhile(numbers, func(n int) bool {
return n != 2
})
result2 := DropRightWhile(numbers, func(n int) bool {
return true
})
result3 := DropRightWhile(numbers, func(n int) bool {
return n == 0
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// [1 2]
// []
// [1 2 3 4 5]
}
func ExampleInsertAt() {
result1 := InsertAt([]string{"a", "b", "c"}, 0, "1")
result2 := InsertAt([]string{"a", "b", "c"}, 1, "1")
@@ -621,6 +685,75 @@ func ExampleReverse() {
// [d c b a]
}
func ExampleIsAscending() {
result1 := IsAscending([]int{1, 2, 3, 4, 5})
result2 := IsAscending([]int{5, 4, 3, 2, 1})
result3 := IsAscending([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// false
// false
}
func ExampleIsDescending() {
result1 := IsDescending([]int{5, 4, 3, 2, 1})
result2 := IsDescending([]int{1, 2, 3, 4, 5})
result3 := IsDescending([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// false
// false
}
func ExampleIsSorted() {
result1 := IsSorted([]int{1, 2, 3, 4, 5})
result2 := IsSorted([]int{5, 4, 3, 2, 1})
result3 := IsSorted([]int{2, 1, 3, 4, 5})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// true
// false
}
func ExampleIsSortedByKey() {
result1 := IsSortedByKey([]string{"a", "ab", "abc"}, func(s string) int {
return len(s)
})
result2 := IsSortedByKey([]string{"abc", "ab", "a"}, func(s string) int {
return len(s)
})
result3 := IsSortedByKey([]string{"abc", "a", "ab"}, func(s string) int {
return len(s)
})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// true
// true
// false
}
func ExampleSort() {
nums := []int{1, 4, 3, 2, 5}

View File

@@ -377,7 +377,7 @@ func TestDeleteAt(t *testing.T) {
}
func TestDrop(t *testing.T) {
assert := internal.NewAssert(t, "TestInterfaceSlice")
assert := internal.NewAssert(t, "TestDrop")
assert.Equal([]int{}, Drop([]int{}, 0))
assert.Equal([]int{}, Drop([]int{}, 1))
@@ -388,9 +388,64 @@ func TestDrop(t *testing.T) {
assert.Equal([]int{}, Drop([]int{1, 2, 3, 4, 5}, 5))
assert.Equal([]int{}, Drop([]int{1, 2, 3, 4, 5}, 6))
assert.Equal([]int{1, 2, 3, 4}, Drop([]int{1, 2, 3, 4, 5}, -1))
assert.Equal([]int{}, Drop([]int{1, 2, 3, 4, 5}, -6))
assert.Equal([]int{}, Drop([]int{1, 2, 3, 4, 5}, -6))
assert.Equal([]int{1, 2, 3, 4, 5}, Drop([]int{1, 2, 3, 4, 5}, -1))
}
func TestDropRight(t *testing.T) {
assert := internal.NewAssert(t, "TestDropRight")
assert.Equal([]int{}, DropRight([]int{}, 0))
assert.Equal([]int{}, DropRight([]int{}, 1))
assert.Equal([]int{}, DropRight([]int{}, -1))
assert.Equal([]int{1, 2, 3, 4, 5}, DropRight([]int{1, 2, 3, 4, 5}, 0))
assert.Equal([]int{1, 2, 3, 4}, DropRight([]int{1, 2, 3, 4, 5}, 1))
assert.Equal([]int{}, DropRight([]int{1, 2, 3, 4, 5}, 5))
assert.Equal([]int{}, DropRight([]int{1, 2, 3, 4, 5}, 6))
assert.Equal([]int{1, 2, 3, 4, 5}, DropRight([]int{1, 2, 3, 4, 5}, -1))
}
func TestDropWhile(t *testing.T) {
assert := internal.NewAssert(t, "TestDropWhile")
numbers := []int{1, 2, 3, 4, 5}
r1 := DropWhile(numbers, func(n int) bool {
return n != 2
})
assert.Equal([]int{2, 3, 4, 5}, r1)
r2 := DropWhile(numbers, func(n int) bool {
return true
})
assert.Equal([]int{}, r2)
r3 := DropWhile(numbers, func(n int) bool {
return n == 0
})
assert.Equal([]int{1, 2, 3, 4, 5}, r3)
}
func TestDropRightWhile(t *testing.T) {
assert := internal.NewAssert(t, "TestDropRightWhile")
numbers := []int{1, 2, 3, 4, 5}
r1 := DropRightWhile(numbers, func(n int) bool {
return n != 2
})
assert.Equal([]int{1, 2}, r1)
r2 := DropRightWhile(numbers, func(n int) bool {
return true
})
assert.Equal([]int{}, r2)
r3 := DropRightWhile(numbers, func(n int) bool {
return n == 0
})
assert.Equal([]int{1, 2, 3, 4, 5}, r3)
}
func TestInsertAt(t *testing.T) {
@@ -548,6 +603,46 @@ func TestDifferenceBy(t *testing.T) {
assert.Equal([]int{1, 2}, DifferenceBy(s1, s2, addOne))
}
func TestIsAscending(t *testing.T) {
assert := internal.NewAssert(t, "TestIsAscending")
assert.Equal(true, IsAscending([]int{1, 2, 3, 4, 5}))
assert.Equal(false, IsAscending([]int{5, 4, 3, 2, 1}))
assert.Equal(false, IsAscending([]int{2, 1, 3, 4, 5}))
}
func TestIsDescending(t *testing.T) {
assert := internal.NewAssert(t, "TestIsDescending")
assert.Equal(true, IsDescending([]int{5, 4, 3, 2, 1}))
assert.Equal(false, IsDescending([]int{1, 2, 3, 4, 5}))
assert.Equal(false, IsDescending([]int{2, 1, 3, 4, 5}))
}
func TestIsSorted(t *testing.T) {
assert := internal.NewAssert(t, "TestIsSorted")
assert.Equal(true, IsSorted([]int{5, 4, 3, 2, 1}))
assert.Equal(true, IsSorted([]int{1, 2, 3, 4, 5}))
assert.Equal(false, IsSorted([]int{2, 1, 3, 4, 5}))
}
func TestIsSortedByKey(t *testing.T) {
assert := internal.NewAssert(t, "TestIsSortedByKey")
assert.Equal(true, IsSortedByKey([]string{"a", "ab", "abc"}, func(s string) int {
return len(s)
}))
assert.Equal(true, IsSortedByKey([]string{"abc", "ab", "a"}, func(s string) int {
return len(s)
}))
assert.Equal(false, IsSortedByKey([]string{"abc", "a", "ab"}, func(s string) int {
return len(s)
}))
}
func TestSort(t *testing.T) {
assert := internal.NewAssert(t, "TestSort")

View File

@@ -10,6 +10,7 @@ import (
"bytes"
"encoding/gob"
"github.com/duke-git/lancet/v2/slice"
"golang.org/x/exp/constraints"
)
@@ -23,7 +24,7 @@ import (
// Map(mapper func(item T) T) StreamI[T]
// Peek(consumer func(item T)) StreamI[T]
// Sort(less func(a, b T) bool) StreamI[T]
// Sorted(less func(a, b T) bool) StreamI[T]
// Max(less func(a, b T) bool) (T, bool)
// Min(less func(a, b T) bool) (T, bool)
@@ -43,7 +44,7 @@ import (
// // part of methods custom extension
// Reverse() StreamI[T]
// Range(start, end int64) StreamI[T]
// Range(start, end int) StreamI[T]
// Concat(streams ...StreamI[T]) StreamI[T]
// }
@@ -72,12 +73,12 @@ func Generate[T any](generator func() func() (item T, ok bool)) stream[T] {
return FromSlice(source)
}
// FromSlice create stream from slice.
// FromSlice creates stream from slice.
func FromSlice[T any](source []T) stream[T] {
return stream[T]{source: source}
}
// FromChannel create stream from channel.
// FromChannel creates stream from channel.
func FromChannel[T any](source <-chan T) stream[T] {
s := make([]T, 0)
@@ -88,7 +89,7 @@ func FromChannel[T any](source <-chan T) stream[T] {
return FromSlice(s)
}
// FromRange create a number stream from start to end. both start and end are included. [start, end]
// FromRange creates a number stream from start to end. both start and end are included. [start, end]
func FromRange[T constraints.Integer | constraints.Float](start, end, step T) stream[T] {
if end < start {
panic("stream.FromRange: param start should be before param end")
@@ -106,6 +107,16 @@ func FromRange[T constraints.Integer | constraints.Float](start, end, step T) st
return FromSlice(source)
}
// Concat creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream.
func Concat[T any](a, b stream[T]) stream[T] {
source := make([]T, 0)
source = append(source, a.source...)
source = append(source, b.source...)
return FromSlice(source)
}
// Distinct returns a stream that removes the duplicated items.
func (s stream[T]) Distinct() stream[T] {
source := make([]T, 0)
@@ -255,6 +266,98 @@ func (s stream[T]) Count() int {
return len(s.source)
}
// FindFirst returns the first element of this stream and true, or zero value and false if the stream is empty.
func (s stream[T]) FindFirst() (T, bool) {
var result T
if s.source == nil || len(s.source) == 0 {
return result, false
}
return s.source[0], true
}
// Reverse returns a stream whose elements are reverse order of given stream.
func (s stream[T]) Reverse() stream[T] {
l := len(s.source)
source := make([]T, l)
for i := 0; i < l; i++ {
source[i] = s.source[l-1-i]
}
return FromSlice(source)
}
// Range returns a stream whose elements are in the range from start(included) to end(excluded) original stream.
func (s stream[T]) Range(start, end int) stream[T] {
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}
if start >= end {
return FromSlice([]T{})
}
source := make([]T, 0)
if end > len(s.source) {
end = len(s.source)
}
for i := start; i < end; i++ {
source = append(source, s.source[i])
}
return FromSlice(source)
}
// Sorted returns a stream consisting of the elements of this stream, sorted according to the provided less function.
func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] {
source := []T{}
source = append(source, s.source...)
slice.SortBy(source, less)
return FromSlice(source)
}
// Max returns the maximum element of this stream according to the provided less function.
// less: a > b
func (s stream[T]) Max(less func(a, b T) bool) (T, bool) {
var max T
if len(s.source) == 0 {
return max, false
}
for i, v := range s.source {
if less(v, max) || i == 0 {
max = v
}
}
return max, true
}
// Min returns the minimum element of this stream according to the provided less function.
// less: a < b
func (s stream[T]) Min(less func(a, b T) bool) (T, bool) {
var min T
if len(s.source) == 0 {
return min, false
}
for i, v := range s.source {
if less(v, min) || i == 0 {
min = v
}
}
return min, true
}
// ToSlice return the elements in the stream.
func (s stream[T]) ToSlice() []T {
return s.source

View File

@@ -247,3 +247,92 @@ func TestStream_Reduce(t *testing.T) {
assert.Equal(6, result)
}
func TestStream_FindFirst(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_FindFirst")
stream := FromSlice([]int{1, 2, 3})
result, ok := stream.FindFirst()
assert.Equal(1, result)
assert.Equal(true, ok)
}
func TestStream_Reverse(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_Reverse")
s := FromSlice([]int{1, 2, 3})
rs := s.Reverse()
assert.Equal([]int{3, 2, 1}, rs.ToSlice())
}
func TestStream_Range(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_Range")
s := FromSlice([]int{1, 2, 3})
s1 := s.Range(-1, 0)
assert.Equal([]int{}, s1.ToSlice())
s2 := s.Range(0, -1)
assert.Equal([]int{}, s2.ToSlice())
s3 := s.Range(0, 0)
assert.Equal([]int{}, s3.ToSlice())
s4 := s.Range(1, 1)
assert.Equal([]int{}, s4.ToSlice())
s5 := s.Range(0, 1)
assert.Equal([]int{1}, s5.ToSlice())
s6 := s.Range(0, 4)
assert.Equal([]int{1, 2, 3}, s6.ToSlice())
}
func TestStream_Concat(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_Concat")
s1 := FromSlice([]int{1, 2, 3})
s2 := FromSlice([]int{4, 5, 6})
s := Concat(s1, s2)
assert.Equal([]int{1, 2, 3, 4, 5, 6}, s.ToSlice())
}
func TestStream_Sorted(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_Sorted")
s := FromSlice([]int{4, 2, 1, 3})
s1 := s.Sorted(func(a, b int) bool { return a < b })
assert.Equal([]int{4, 2, 1, 3}, s.ToSlice())
assert.Equal([]int{1, 2, 3, 4}, s1.ToSlice())
}
func TestStream_Max(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_Max")
s := FromSlice([]int{4, 2, 1, 3})
max, ok := s.Max(func(a, b int) bool { return a > b })
assert.Equal(4, max)
assert.Equal(true, ok)
}
func TestStream_Min(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_Min")
s := FromSlice([]int{4, 2, 1, 3})
max, ok := s.Max(func(a, b int) bool { return a < b })
assert.Equal(1, max)
assert.Equal(true, ok)
}

View File

@@ -281,6 +281,10 @@ func IsZeroValue(value any) bool {
}
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if !rv.IsValid() {
return true
}

228
xerror/stack.go Normal file
View File

@@ -0,0 +1,228 @@
// Copyright 2023 dudaodong@gmail.com. All rights reserved.
// Use of this source code is governed by MIT license
package xerror
import (
"fmt"
"io"
"path"
"runtime"
"strconv"
"strings"
)
// Stack contains function, file and line number info in the stack trace.
type Stack struct {
Func string `json:"func"`
File string `json:"file"`
Line int `json:"line"`
}
// Stacks returns stack trace array generated by pkg/errors
func (e *XError) Stacks() []*Stack {
resp := make([]*Stack, len(*e.stack))
for i, st := range *e.stack {
f := frame(st)
resp[i] = &Stack{
Func: f.name(),
File: f.file(),
Line: f.line(),
}
}
return resp
}
// StackTrace returns stack trace which is compatible with pkg/errors
func (e *XError) StackTrace() StackTrace {
return e.stack.StackTrace()
}
// ---------------------------------------
// Stacktrace part is implemented based on copy of https://github.com/pkg/errors
//
// Copyright (c) 2015, Dave Cheney <dave@cheney.net>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
type frame uintptr
type stack []uintptr
// StackTrace is array of frame. It's exported for compatibility with github.com/pkg/errors
type StackTrace []frame
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// name returns the name of this function, if known.
func (f frame) name() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
return fn.Name()
}
// Format of frame formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
_, _ = io.WriteString(s, f.name())
_, _ = io.WriteString(s, "\n\t")
_, _ = io.WriteString(s, f.file())
default:
_, _ = io.WriteString(s, path.Base(f.file()))
}
case 'd':
_, _ = io.WriteString(s, strconv.Itoa(f.line()))
case 'n':
_, _ = io.WriteString(s, funcname(f.name()))
case 'v':
f.Format(s, 's')
_, _ = io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// MarshalText formats a stacktrace Frame as a text string. The output is the
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
func (f frame) MarshalText() ([]byte, error) {
name := f.name()
if name == "unknown" {
return []byte(name), nil
}
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
}
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
_, _ = io.WriteString(s, "\n")
f.Format(s, verb)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []frame(st))
default:
st.formatSlice(s, verb)
}
case 's':
st.formatSlice(s, verb)
}
}
// formatSlice will format this StackTrace into the given buffer as a slice of
// Frame, only valid when called with '%s' or '%v'.
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
_, _ = io.WriteString(s, "[")
for i, f := range st {
if i > 0 {
_, _ = io.WriteString(s, " ")
}
f.Format(s, verb)
}
_, _ = io.WriteString(s, "]")
}
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
frames := make([]frame, len(*s))
for i := 0; i < len(frames); i++ {
frames[i] = frame((*s)[i])
}
return frames
}
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(4, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}

View File

@@ -4,10 +4,202 @@
// Package xerror implements helpers for errors
package xerror
// Unwrap if err is nil then it returns a valid value
import (
"errors"
"fmt"
"io"
"strings"
"github.com/duke-git/lancet/v2/random"
)
// XError is to handle error related information.
type XError struct {
id string
message string
stack *stack
cause error
values map[string]any
}
// New creates a new XError with message
func New(format string, args ...any) *XError {
err := newXError()
err.message = fmt.Sprintf(format, args...)
return err
}
// Wrap creates a new XError and add message.
func Wrap(cause error, message ...any) *XError {
err := newXError()
if len(message) > 0 {
var newMsgs []string
for _, m := range message {
newMsgs = append(newMsgs, fmt.Sprintf("%v", m))
}
err.message = strings.Join(newMsgs, " ")
}
err.cause = cause
return err
}
// Unwrap returns unwrapped XError from err by errors.As. If no XError, returns nil
func Unwrap(err error) *XError {
var e *XError
if errors.As(err, &e) {
return e
}
return nil
}
func newXError() *XError {
id, err := random.UUIdV4()
if err != nil {
return nil
}
return &XError{
id: id,
stack: callers(),
values: make(map[string]any),
}
}
func (e *XError) copy(dest *XError) {
dest.message = e.message
dest.id = e.id
dest.cause = e.cause
for k, v := range e.values {
dest.values[k] = v
}
}
// Error implements standard error interface.
func (e *XError) Error() string {
msg := e.message
cause := e.cause
if cause == nil {
return msg
}
msg = fmt.Sprintf("%s: %v", msg, cause.Error())
return msg
}
// Format returns:
// - %v, %s, %q: formatted message
// - %+v: formatted message with stack trace
func (e *XError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
_, _ = io.WriteString(s, e.Error())
e.stack.Format(s, verb)
return
}
fallthrough
case 's':
_, _ = io.WriteString(s, e.Error())
case 'q':
fmt.Fprintf(s, "%q", e.Error())
}
}
// Wrap creates a new XError and copy message and id to new one.
func (e *XError) Wrap(cause error) *XError {
err := newXError()
e.copy(err)
err.cause = cause
return err
}
// Unwrap compatible with github.com/pkg/errors
func (e *XError) Unwrap() error {
return e.cause
}
// With adds key and value related to the error object
func (e *XError) With(key string, value any) *XError {
e.values[key] = value
return e
}
// Is checks if target error is XError and Error.id of two errors are matched.
func (e *XError) Is(target error) bool {
var err *XError
if errors.As(target, &err) {
if e.id != "" && e.id == err.id {
return true
}
}
return e == target
}
// Id sets id to check equality in XError.Is
func (e *XError) Id(id string) *XError {
e.id = id
return e
}
// Values returns map of key and value that is set by With. All wrapped xerror.XError key and values will be merged.
// Key and values of wrapped error is overwritten by upper xerror.XError.
func (e *XError) Values() map[string]any {
var values map[string]any
if cause := e.Unwrap(); cause != nil {
if err, ok := cause.(*XError); ok {
values = err.Values()
}
}
if values == nil {
values = make(map[string]any)
}
for key, value := range e.values {
values[key] = value
}
return values
}
type errInfo struct {
Message string `json:"message"`
Id string `json:"id"`
StackTrace []*Stack `json:"stacktrace"`
Cause error `json:"cause"`
Values map[string]any `json:"values"`
}
// Info returns information of xerror, which can be printed.
func (e *XError) Info() *errInfo {
errInfo := &errInfo{
Message: e.message,
Id: e.id,
StackTrace: e.Stacks(),
Cause: e.cause,
Values: make(map[string]any),
}
for k, v := range e.values {
errInfo.Values[k] = v
}
return errInfo
}
// TryUnwrap if err is nil then it returns a valid value
// If err is not nil, Unwrap panics with err.
// Play: https://go.dev/play/p/w84d7Mb3Afk
func Unwrap[T any](val T, err error) T {
func TryUnwrap[T any](val T, err error) T {
if err != nil {
panic(err)
}

View File

@@ -6,8 +6,8 @@ import (
"strconv"
)
func ExampleUnwrap() {
result1 := Unwrap(strconv.Atoi("42"))
func ExampleTryUnwrap() {
result1 := TryUnwrap(strconv.Atoi("42"))
fmt.Println(result1)
_, err := strconv.Atoi("4o2")
@@ -17,7 +17,7 @@ func ExampleUnwrap() {
fmt.Println(result2)
}()
Unwrap(strconv.Atoi("4o2"))
TryUnwrap(strconv.Atoi("4o2"))
// Output:
// 42

View File

@@ -1,19 +1,21 @@
package xerror
import (
"errors"
"strconv"
"strings"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestUnwrap(t *testing.T) {
assert := internal.NewAssert(t, "TestUnwrap")
assert.Equal(42, Unwrap(strconv.Atoi("42")))
func TestTryUnwrap(t *testing.T) {
assert := internal.NewAssert(t, "TestTryUnwrap")
assert.Equal(42, TryUnwrap(strconv.Atoi("42")))
}
func TestUnwrapFail(t *testing.T) {
assert := internal.NewAssert(t, "TestUnwrapFail")
func TestTryUnwrapFail(t *testing.T) {
assert := internal.NewAssert(t, "TestTryUnwrapFail")
_, err := strconv.Atoi("4o2")
defer func() {
@@ -21,5 +23,85 @@ func TestUnwrapFail(t *testing.T) {
assert.Equal(err.Error(), v.(*strconv.NumError).Error())
}()
Unwrap(strconv.Atoi("4o2"))
TryUnwrap(strconv.Atoi("4o2"))
}
func TestNew(t *testing.T) {
assert := internal.NewAssert(t, "TestNew")
err := New("error occurs")
assert.Equal("error occurs", err.Error())
}
func TestWrap(t *testing.T) {
assert := internal.NewAssert(t, "TestWrap")
err := New("wrong password")
wrapErr := Wrap(err, "error")
assert.Equal("error: wrong password", wrapErr.Error())
}
func TestXError_Wrap(t *testing.T) {
assert := internal.NewAssert(t, "TestXError_Wrap")
err1 := New("error").With("level", "high")
err2 := err1.Wrap(errors.New("bad"))
assert.Equal("error: bad", err2.Error())
}
func TestXError_Unwrap(t *testing.T) {
assert := internal.NewAssert(t, "TestXError_Unwrap")
err1 := New("error").With("level", "high")
err2 := err1.Wrap(errors.New("bad"))
err := err2.Unwrap()
assert.Equal("bad", err.Error())
}
func TestXError_StackTrace(t *testing.T) {
assert := internal.NewAssert(t, "TestXError_StackTrace")
err := New("error")
stacks := err.Stacks()
assert.Equal(3, len(stacks))
assert.Equal("github.com/duke-git/lancet/v2/xerror.TestXError_StackTrace", stacks[0].Func)
assert.Equal(69, stacks[0].Line)
assert.Equal(true, strings.Contains(stacks[0].File, "xerror_test.go"))
}
func TestXError_With_Id_Is_Values(t *testing.T) {
assert := internal.NewAssert(t, "TestXError_With_Id_Is_Values")
baseErr := New("baseError")
err1 := New("error1").Id("e001").With("level", "high")
err2 := New("error2").Id("e002").With("level", "low")
err := err1.Wrap(baseErr).With("v", "1.0")
assert.Equal(true, errors.Is(err, baseErr))
assert.NotEqual(err, err1)
assert.IsNotNil(err.Values()["v"])
assert.IsNil(err1.Values()["v"])
assert.Equal(false, errors.Is(err, err2))
}
func TestXError_Info(t *testing.T) {
assert := internal.NewAssert(t, "TestXError_Info")
cause := errors.New("error")
err := Wrap(cause, "invalid username").Id("e001").With("level", "high")
errInfo := err.Info()
assert.Equal("invalid username", errInfo.Message)
assert.Equal("e001", errInfo.Id)
assert.Equal(cause, errInfo.Cause)
assert.Equal("high", errInfo.Values["level"])
}