mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-12 00:32:27 +08:00
Compare commits
37 Commits
v2.3.6
...
350450bb67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
350450bb67 | ||
|
|
fc624195c7 | ||
|
|
5b3a59e785 | ||
|
|
74abb2d3f1 | ||
|
|
3f12b34eea | ||
|
|
cd43004a91 | ||
|
|
3ac9461c00 | ||
|
|
309b07ae8a | ||
|
|
8fe56b6dc7 | ||
|
|
15a0dad0d8 | ||
|
|
93c777a418 | ||
|
|
5ff1c6578f | ||
|
|
7d4b9510a2 | ||
|
|
9f0ad2354a | ||
|
|
55b66dee99 | ||
|
|
4c64a16204 | ||
|
|
385e64cc52 | ||
|
|
be45a259db | ||
|
|
6f703fe577 | ||
|
|
cb8d93c499 | ||
|
|
ae1014c572 | ||
|
|
d5b9e67330 | ||
|
|
a81403766f | ||
|
|
6307d624cb | ||
|
|
2f9f8b3f3d | ||
|
|
db5d9407bb | ||
|
|
83c069e234 | ||
|
|
55ee000684 | ||
|
|
fc7f2509ca | ||
|
|
a97d27c32e | ||
|
|
c176ba378e | ||
|
|
a3a24fc381 | ||
|
|
9caf2ffb1c | ||
|
|
1a5c31fd02 | ||
|
|
c175b202de | ||
|
|
d818219672 | ||
|
|
539078e6b8 |
31
README.md
31
README.md
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
@@ -216,28 +216,28 @@ import "github.com/duke-git/lancet/v2/concurrency"
|
||||
[[play](https://go.dev/play/p/3TQPKnCirrP)]
|
||||
- **<big>NewKeyedLocker</big>** : KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#NewKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/GzeyC33T5rw)]
|
||||
- **<big>Do</big>** :acquires a lock for the specified key and executes the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Do)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/GzeyC33T5rw)]
|
||||
- **<big>NewRWKeyedLocker</big>** :RRWKeyedLocker is a read-write version of KeyedLocker.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#NewRWKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/ZrCr8sMo77T)]
|
||||
- **<big>RLock</big>** : acquires a read lock for the specified key and executes the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#RLock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/ZrCr8sMo77T)]
|
||||
- **<big>Lock</big>** : acquires a write lock for the specified key and executes the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Lock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/WgAcXbOPKGk)]
|
||||
- **<big>NewTryKeyedLocker</big>** : TryKeyedLocker is a non-blocking version of KeyedLocker.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#NewTryKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
- **<big>TryLock</big>** : TryLock tries to acquire a lock for the specified key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#TryLock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
- **<big>Unlock</big>** : Unlock releases the lock for the specified key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Unlock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
|
||||
<h3 id="condition"> 4. Condition package contains some functions for conditional judgment. eg. And, Or, TernaryOperator... <a href="#index">index</a> </h3>
|
||||
|
||||
@@ -803,7 +803,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
||||
[[play](https://go.dev/play/p/P2y0XW8a1SH)]
|
||||
- **<big>RemoveDir</big>** : delete directory.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#RemoveDir)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/Oa6KnPek2uy)]
|
||||
- **<big>ReadFileToString</big>** : return string of file content.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ReadFileToString)]
|
||||
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
||||
@@ -1124,7 +1124,7 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
[[play](https://go.dev/play/p/99QjSYSBdiM)]
|
||||
- **<big>FindValuesBy</big>** : returns a slice of values from the map that satisfy the given predicate function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#FindValuesBy)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/bvNwNBZDm6v)]
|
||||
|
||||
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -1311,10 +1311,10 @@ import "github.com/duke-git/lancet/v2/netutil"
|
||||
[[play](https://go.dev/play/p/yiLCGtQv_ZG)]
|
||||
- **<big>BuildUrl</big>** : builds a URL from the given params.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#BuildUrl)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
- **<big>AddQueryParams</big>** : adds query parameters to the given URL.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#AddQueryParams)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
|
||||
<h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -1461,6 +1461,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ContainSubSlice</big>** : check if the slice contain a given subslice or not.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainSubSlice)]
|
||||
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
|
||||
- **<big>ContainAny</big>** : check if the slice contains any element from the targets slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainAny)]
|
||||
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
|
||||
- **<big>Chunk</big>** : creates a slice of elements split into groups the length of size.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Chunk)]
|
||||
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
|
||||
@@ -2228,7 +2231,7 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/LzaKocSV79u)]
|
||||
- **<big>IsAlphaNumeric</big>** : check if the string is alphanumeric.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsAlphaNumeric)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/RHeESLrLg9c)]
|
||||
- **<big>IsJSON</big>** : check if the string is valid JSON.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsJSON)]
|
||||
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
@@ -215,28 +215,28 @@ import "github.com/duke-git/lancet/v2/concurrency"
|
||||
[[play](https://go.dev/play/p/3TQPKnCirrP)]
|
||||
- **<big>NewKeyedLocker</big>** : NewKeyedLocker 创建一个新的 KeyedLocker,并为锁的过期设置指定的 TTL。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#NewKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/GzeyC33T5rw)]
|
||||
- **<big>Do</big>** :为指定的键获取锁并执行提供的函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Do)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/GzeyC33T5rw)]
|
||||
- **<big>NewRWKeyedLocker</big>** :RWKeyedLocker 是一个简单的键值读写锁实现,允许非阻塞的锁获取。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#NewRWKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/ZrCr8sMo77T)]
|
||||
- **<big>RLock</big>** : 为指定的键获取读锁并执行提供的函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#RLock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/ZrCr8sMo77T)]
|
||||
- **<big>Lock</big>** : 为指定的键获取锁并执行提供的函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Lock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/WgAcXbOPKGk)]
|
||||
- **<big>NewTryKeyedLocker</big>** : 创建一个 TryKeyedLocker 实例,TryKeyedLocker 是 KeyedLocker 的非阻塞版本。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#NewTryKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
- **<big>TryLock</big>** : TryLock 尝试获取指定键的锁。如果锁成功获取,则返回 true,否则返回 false。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#TryLock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
- **<big>Unlock</big>** : 释放指定键的锁。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Unlock)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
|
||||
<h3 id="condition"> 4. condition 包含一些用于条件判断的函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -378,9 +378,15 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
- **<big>AesCbcDecrypt</big>** : 使用 AES CBC 算法模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesCbcDecrypt)]
|
||||
[[play](https://go.dev/play/p/IOq_g8_lKZD)]
|
||||
- **<big>AesCtrCrypt</big>** : 使用 AES CTR 算法模式加密/解密数据。
|
||||
- **<big>AesCtrCrypt<sup>deprecated</sup></big>** : 使用 AES CTR 算法模式加密/解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesCtrCrypt)]
|
||||
[[play](https://go.dev/play/p/SpaZO0-5Nsp)]
|
||||
- **<big>AesCtrEncrypt</big>** : 使用 AES CTR 算法模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesCtrCrypt)]
|
||||
[[play](https://go.dev/play/p/x6pjPAvThRz)]
|
||||
- **<big>AesCtrDecrypt</big>** : 使用 AES CTR 算法模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesCtrCrypt)]
|
||||
[[play](https://go.dev/play/p/x6pjPAvThRz)]
|
||||
- **<big>AesCfbEncrypt</big>** : 使用 AES CFB 算法模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#AesCfbEncrypt)]
|
||||
[[play](https://go.dev/play/p/tfkF10B13kH)]
|
||||
@@ -417,9 +423,15 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
- **<big>DesCbcDecrypt</big>** : 使用 DES CBC 算法模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#DesCbcDecrypt)]
|
||||
[[play](https://go.dev/play/p/4cC4QvWfe3_1)]
|
||||
- **<big>DesCtrCrypt</big>** : 使用 DES CTR 算法模式加密/解密数据。
|
||||
- **<big>DesCtrCrypt<sup>deprecated</sup></big>** : 使用 DES CTR 算法模式加密/解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#DesCtrCrypt)]
|
||||
[[play](https://go.dev/play/p/9-T6OjKpcdw)]
|
||||
- **<big>DesCtrEncrypt</big>** : 使用 DES CTR 算法模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#DesCtrEncrypt)]
|
||||
[[play](https://go.dev/play/p/S6p_WHCgH1d)]
|
||||
- **<big>DesCtrDecrypt</big>** : 使用 DES CTR 算法模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#DesCtrDecrypt)]
|
||||
[[play](https://go.dev/play/p/S6p_WHCgH1d)]
|
||||
- **<big>DesCfbEncrypt</big>** : 使用 DES CFB 算法模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#DesCfbEncrypt)]
|
||||
[[play](https://go.dev/play/p/y-eNxcFBlxL)]
|
||||
@@ -801,7 +813,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
||||
[[play](https://go.dev/play/p/P2y0XW8a1SH)]
|
||||
- **<big>RemoveDir</big>** : 删除目录,支持传入删除前的回调函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#RemoveDir)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/Oa6KnPek2uy)]
|
||||
- **<big>ReadFileToString</big>** : 读取文件内容并返回字符串。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadFileToString)]
|
||||
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
||||
@@ -1122,7 +1134,7 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
[[play](https://go.dev/play/p/99QjSYSBdiM)]
|
||||
- **<big>FindValuesBy</big>** : 返回一个切片,包含满足给定谓词判断函数的 map 中的值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#FindValuesBy)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/bvNwNBZDm6v)]
|
||||
|
||||
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -1309,10 +1321,10 @@ import "github.com/duke-git/lancet/v2/netutil"
|
||||
[[play](https://go.dev/play/p/yiLCGtQv_ZG)]
|
||||
- **<big>BuildUrl</big>** : 创建 url 字符串。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#BuildUrl)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
- **<big>AddQueryParams</big>** : 向 url 添加查询参数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#AddQueryParams)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
|
||||
<h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -1456,6 +1468,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ContainSubSlice</big>** : 判断 slice 是否包含 subslice。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainSubSlice)]
|
||||
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
|
||||
- **<big>ContainAny</big>** : 判断 slice 是否包含 targets 切片中的任意一个元素。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainAny)]
|
||||
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
|
||||
- **<big>Chunk</big>** : 按照 size 参数均分 slice。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Chunk)]
|
||||
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
|
||||
@@ -2225,7 +2240,7 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/LzaKocSV79u)]
|
||||
- **<big>IsAlphaNumeric</big>** : 验证字符串是字母或数字。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsAlphaNumeric)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/RHeESLrLg9c)]
|
||||
- **<big>IsJSON</big>** : 验证字符串是否是有效 json。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsJSON)]
|
||||
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
||||
|
||||
@@ -25,12 +25,14 @@ type lockEntry struct {
|
||||
|
||||
// NewKeyedLocker creates a new KeyedLocker with the specified TTL for lock expiration.
|
||||
// The TTL is used to automatically release locks that are no longer held.
|
||||
// Play: https://go.dev/play/p/GzeyC33T5rw
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K] {
|
||||
return &KeyedLocker[K]{ttl: ttl}
|
||||
}
|
||||
|
||||
// Do acquires a lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/GzeyC33T5rw
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(key, entry, key)
|
||||
@@ -107,12 +109,14 @@ type rwLockEntry struct {
|
||||
|
||||
// NewRWKeyedLocker creates a new RWKeyedLocker with the specified TTL for lock expiration.
|
||||
// The TTL is used to automatically release locks that are no longer held.
|
||||
// Play: https://go.dev/play/p/CkaJWWwZm9
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K] {
|
||||
return &RWKeyedLocker[K]{ttl: ttl}
|
||||
}
|
||||
|
||||
// RLock acquires a read lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/ZrCr8sMo77T
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(entry, key)
|
||||
@@ -141,6 +145,7 @@ func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
|
||||
|
||||
// Lock acquires a write lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/WgAcXbOPKGk
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(entry, key)
|
||||
@@ -199,12 +204,14 @@ type TryKeyedLocker[K comparable] struct {
|
||||
}
|
||||
|
||||
// NewTryKeyedLocker creates a new TryKeyedLocker.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K] {
|
||||
return &TryKeyedLocker[K]{locks: make(map[K]*casMutex)}
|
||||
}
|
||||
|
||||
// TryLock tries to acquire a lock for the specified key.
|
||||
// It returns true if the lock was acquired, false otherwise.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool {
|
||||
l.mu.Lock()
|
||||
|
||||
@@ -219,6 +226,7 @@ func (l *TryKeyedLocker[K]) TryLock(key K) bool {
|
||||
}
|
||||
|
||||
// Unlock releases the lock for the specified key.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
@@ -228,6 +228,42 @@ func ToPointer[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
// ToPointers convert a slice of values to a slice of pointers.
|
||||
// Play: todo
|
||||
func ToPointers[T any](values []T) []*T {
|
||||
result := make([]*T, len(values))
|
||||
for i := range values {
|
||||
result[i] = &values[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FromPointer returns the value pointed to by the pointer.
|
||||
// Play: todo
|
||||
func FromPointer[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
return zeroValue
|
||||
}
|
||||
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// FromPointers convert a slice of pointers to a slice of values.
|
||||
// Play: todo
|
||||
func FromPointers[T any](pointers []*T) []T {
|
||||
result := make([]T, len(pointers))
|
||||
for i, ptr := range pointers {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
result[i] = zeroValue
|
||||
} else {
|
||||
result[i] = *ptr
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ToMap convert a slice of structs to a map based on iteratee function.
|
||||
// Play: https://go.dev/play/p/tVFy7E-t24l
|
||||
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V {
|
||||
@@ -406,15 +442,15 @@ func ToStdBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.StdEncoding.EncodeToString(value.([]byte))
|
||||
return base64.StdEncoding.EncodeToString(v)
|
||||
case string:
|
||||
return base64.StdEncoding.EncodeToString([]byte(value.(string)))
|
||||
return base64.StdEncoding.EncodeToString([]byte(v))
|
||||
case error:
|
||||
return base64.StdEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
return base64.StdEncoding.EncodeToString([]byte(v.Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -428,15 +464,15 @@ func ToUrlBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.URLEncoding.EncodeToString(value.([]byte))
|
||||
return base64.URLEncoding.EncodeToString(v)
|
||||
case string:
|
||||
return base64.URLEncoding.EncodeToString([]byte(value.(string)))
|
||||
return base64.URLEncoding.EncodeToString([]byte(v))
|
||||
case error:
|
||||
return base64.URLEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
return base64.URLEncoding.EncodeToString([]byte(v.Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -450,7 +486,7 @@ func ToRawStdBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.RawStdEncoding.EncodeToString(value.([]byte))
|
||||
case string:
|
||||
@@ -458,7 +494,7 @@ func ToRawStdBase64(value any) string {
|
||||
case error:
|
||||
return base64.RawStdEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -472,7 +508,7 @@ func ToRawUrlBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.RawURLEncoding.EncodeToString(value.([]byte))
|
||||
case string:
|
||||
@@ -480,7 +516,7 @@ func ToRawUrlBase64(value any) string {
|
||||
case error:
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -302,6 +302,75 @@ func TestToPointer(t *testing.T) {
|
||||
assert.Equal(*result, 123)
|
||||
}
|
||||
|
||||
func TestToPointers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToPointers")
|
||||
|
||||
intVals := []int{1, 2, 3}
|
||||
result := ToPointers(intVals)
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(1, *result[0])
|
||||
assert.Equal(2, *result[1])
|
||||
assert.Equal(3, *result[2])
|
||||
|
||||
stringVals := []string{"a", "b", "c"}
|
||||
resultStr := ToPointers(stringVals)
|
||||
assert.Equal(3, len(resultStr))
|
||||
assert.Equal("a", *resultStr[0])
|
||||
assert.Equal("b", *resultStr[1])
|
||||
assert.Equal("c", *resultStr[2])
|
||||
}
|
||||
|
||||
func TestFromPointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFromPointer")
|
||||
|
||||
intVal := 123
|
||||
pointer := &intVal
|
||||
result := FromPointer(pointer)
|
||||
|
||||
assert.Equal(123, result)
|
||||
|
||||
stringVal := "abc"
|
||||
stringPointer := &stringVal
|
||||
resultStr := FromPointer(stringPointer)
|
||||
|
||||
assert.Equal("abc", resultStr)
|
||||
}
|
||||
|
||||
func TestFromPointers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFromPointers")
|
||||
|
||||
intPointers := []*int{new(int), new(int), new(int)}
|
||||
*intPointers[0] = 1
|
||||
*intPointers[1] = 2
|
||||
*intPointers[2] = 3
|
||||
|
||||
result := FromPointers(intPointers)
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(1, result[0])
|
||||
assert.Equal(2, result[1])
|
||||
assert.Equal(3, result[2])
|
||||
|
||||
stringPointers := []*string{new(string), new(string), new(string)}
|
||||
*stringPointers[0] = "a"
|
||||
*stringPointers[1] = "b"
|
||||
*stringPointers[2] = "c"
|
||||
|
||||
resultStr := FromPointers(stringPointers)
|
||||
|
||||
assert.Equal(3, len(resultStr))
|
||||
assert.Equal("a", resultStr[0])
|
||||
assert.Equal("b", resultStr[1])
|
||||
assert.Equal("c", resultStr[2])
|
||||
}
|
||||
|
||||
func TestEncodeByte(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ func AesCtrCrypt(data, key []byte) []byte {
|
||||
|
||||
// AesCtrEncrypt encrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/x6pjPAvThRz
|
||||
func AesCtrEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
@@ -199,7 +199,7 @@ func AesCtrEncrypt(data, key []byte) []byte {
|
||||
|
||||
// AesCtrDecrypt decrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/x6pjPAvThRz
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
@@ -507,7 +507,7 @@ func DesCtrCrypt(data, key []byte) []byte {
|
||||
|
||||
// DesCtrEncrypt encrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/S6p_WHCgH1d
|
||||
func DesCtrEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
@@ -534,7 +534,7 @@ func DesCtrEncrypt(data, key []byte) []byte {
|
||||
|
||||
// DesCtrDecrypt decrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/S6p_WHCgH1d
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
|
||||
@@ -74,6 +74,32 @@ func ExampleAesCtrCrypt() {
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
enCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := AesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
enCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := AesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
@@ -227,6 +253,19 @@ func ExampleDesCtrCrypt() {
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCtrDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
enCrypt := DesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := DesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
-----BEGIN rsa private key-----
|
||||
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
|
||||
-----END rsa private key-----
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
-----BEGIN rsa public key-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw6Q11wDtWHZiyQbv+Xzn
|
||||
tJbEELkPQ3oVSVCVjTx+ls5I+yR6oq8LxyoUE6SHcBlludkzs02B0rDkkfj6vl0i
|
||||
PCzr3HP1XT1WwFiQJnXKtNNNiLvvewB/aynj/UIBCZZvMTYwK6nc76i4JWcajaiU
|
||||
I4u84Fp7gOn0IgXMhvrwtGHk7HK1Zjr0afUJVbodc6pMZfe0QWpnDq7zaeYdkPwz
|
||||
8klWAatLDZTeR5dYli2NiWOBepO1538ZvEK3S/QP2nWJzPJy2PbEHfDaFgr055w/
|
||||
wu0MzAQgkXGV8PubhYboJuQAkuXc63IPpmWVL/93WeCNRiL5fvNzUHsbPtT48A9N
|
||||
nnUn4QVuVGeXIxJ1skAFsoh7i+h+mK/p5rzjPtw56nE20gYXhxf7FzbHYtHn0ego
|
||||
BddUzlOY0yzvCgI5Yy1RdkNe/5vU1bpLsdU/WItjR2h6EfqLosQ4iKowPBYQXJOI
|
||||
NbpkvIhkhCVbPpMvmhfi/lATiKdoXgtvuB04wth011c5W/yXA+A3Ob73Sr2+zyxF
|
||||
870tOp3rNTWsYLvCOjf+JdjgM3AzNuFxE4NdMLvXDf7CIXSb3X9rj3qM769Lz6XX
|
||||
bRBVbO0HxGyqLTPPmSWV61jQuUTWNGnxkovoYLrpgyumIjjIcl0aY1hogmQeA6ZN
|
||||
gJxaUw9r/p8vnThL5UXKlbcCAwEAAQ==
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAudV/zW+ycOExUja9W3Zy
|
||||
hKWA2TN+FqTzfZKPB+btwe4Md0WJTM0+ZdT8UXujltTEWSUhY/qkOiNIutF2CiFW
|
||||
onDQeNzMobLB/pmq1P0Z+LVH4ERsbcl9zYCfpvTsnIqzjuPe30iozK0Er03qBxsH
|
||||
nWV3WbIl3+1f17T6OD5CkdT+9RCID1EqsQ+9aGIeR6cmoB+rxjPLb0xc5oS1hbb3
|
||||
FkiT7VLI2doeqP8Pmwdohbh7XmgJQkok+ALxKQ4bCMJ780k2KigKGjXxKlYJq1ZF
|
||||
301sbhvTo2cSci4ieXP0A4B4swSzLKl0G7IX/UbYACn3qNecvQt5OtFM644mqfSU
|
||||
ffFg7PefZVZhaUytvU92W+b0LXF2NbjhtVES5HByDwjjF/KOfV7U/o+YmAjlakie
|
||||
YM7pcggfgfqyZWdF70nSvgPgVt5qtOnYPeUrQV2aUmZE+BagOQ/HAIKPbhmyMEA3
|
||||
odgqaALsvD/58iVv9syqEEm5trLYA/p2uo0yv3iHrEggEZkjPXkrgbZ6lZNafGaZ
|
||||
Hs0ANg+7NIJR9joKvXGJkg8E2thpg9UKl/Z2astZk7o92trMp5DQW1vjV7JW+mEQ
|
||||
dztQxE5NgeE8cI/BdqMSyvG2UA7u5LqBHXx35s3gPva+eutKnTcRpO3T01PH2sba
|
||||
iXiiNG5oFUYjocikhY7f3TUCAwEAAQ==
|
||||
-----END rsa public key-----
|
||||
|
||||
@@ -60,7 +60,7 @@ func (q *ArrayQueue[T]) Front() T {
|
||||
|
||||
// Back return back value of queue
|
||||
func (q *ArrayQueue[T]) Back() T {
|
||||
return q.data[q.size-1]
|
||||
return q.data[q.tail-1]
|
||||
}
|
||||
|
||||
// EnQueue put element into queue
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# API概述
|
||||
|
||||
<b>lancet(柳叶刀)是一个功能强大、全面、高效、可复用的go语言工具函数库。包含25个包,超过600个工具函数。功能涵盖字符串处理、切片处理、网络、并发、加解密、文件处理、时间/日期、流处理、迭代器等等。</b>
|
||||
# API 概述
|
||||
|
||||
<b>lancet(柳叶刀)是一个功能强大、全面、高效、可复用的 go 语言工具函数库。包含 25 个包,超过 600 个工具函数。功能涵盖字符串处理、切片处理、网络、并发、加解密、文件处理、时间/日期、流处理、迭代器等等。</b>
|
||||
|
||||
<style>
|
||||
.package-title {
|
||||
@@ -27,7 +26,7 @@ outline: deep
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 40px;
|
||||
background: #10b981;
|
||||
background: #6cadf5;
|
||||
border: 1px solid;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
@@ -67,4 +66,4 @@ outline: deep
|
||||
<div class="package-cell">validator</div>
|
||||
<div class="package-cell">xerror</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ algorithm 算法包实现一些基本算法,sort,search,lrucache。
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sort.go](https://github.com/duke-git/lancet/blob/main/algorithm/sort.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
|
||||
@@ -477,7 +477,7 @@ func main() {
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -534,7 +534,7 @@ func main() {
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -591,7 +591,7 @@ func main() {
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/CkaJWWwZm9)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -640,7 +640,7 @@ func main() {
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -689,7 +689,7 @@ func main() {
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/WgAcXbOPKGk)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -738,7 +738,7 @@ func main() {
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -779,7 +779,7 @@ func main() {
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -820,7 +820,7 @@ func main() {
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -27,7 +27,9 @@ import (
|
||||
- [AesEcbDecrypt](#AesEcbDecrypt)
|
||||
- [AesCbcEncrypt](#AesCbcEncrypt)
|
||||
- [AesCbcDecrypt](#AesCbcDecrypt)
|
||||
- [AesCtrCrypt](#AesCtrCrypt)
|
||||
- [AesCtrCrypt<sup>deprecated</sup>](#AesCtrCrypt)
|
||||
- [AesCtrEncrypt](#AesCtrEncrypt)
|
||||
- [AesCtrDecrypt](#AesCtrDecrypt)
|
||||
- [AesCfbEncrypt](#AesCfbEncrypt)
|
||||
- [AesCfbDecrypt](#AesCfbDecrypt)
|
||||
- [AesOfbEncrypt](#AesOfbEncrypt)
|
||||
@@ -40,7 +42,7 @@ import (
|
||||
- [DesEcbDecrypt](#DesEcbDecrypt)
|
||||
- [DesCbcEncrypt](#DesCbcEncrypt)
|
||||
- [DesCbcDecrypt](#DesCbcDecrypt)
|
||||
- [DesCtrCrypt](#DesCtrCrypt)
|
||||
- [DesCtrCrypt<sup>deprecated</sup>](#DesCtrCrypt)
|
||||
- [DesCfbEncrypt](#DesCfbEncrypt)
|
||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||
- [DesOfbEncrypt](#DesOfbEncrypt)
|
||||
@@ -73,7 +75,6 @@ import (
|
||||
- [RsaSign](#RsaSign)
|
||||
- [RsaVerifySign](#RsaVerifySign)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
@@ -218,6 +219,8 @@ func main() {
|
||||
|
||||
<p>使用AES CTR算法模式加密/解密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`AesCtrEncrypt`和`AesCtrDecrypt`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -248,6 +251,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrEncrypt">AesCtrEncrypt</span>
|
||||
|
||||
<p>使用AES CTR算法模式加密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrDecrypt">AesCtrDecrypt</span>
|
||||
|
||||
<p>使用AES CTR算法模式解密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCfbEncrypt">AesCfbEncrypt</span>
|
||||
|
||||
<p>使用AES CFB算法模式加密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
@@ -648,10 +719,80 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrEncrypt">DesCtrEncrypt</span>
|
||||
|
||||
<p>使用DES CTR算法模式加密数据,参数`key`的长度是8</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func DesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrDecrypt">DesCtrDecrypt</span>
|
||||
|
||||
<p>使用DES CTR算法模式加密数据,参数`key`的长度是8</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrCrypt">DesCtrCrypt</span>
|
||||
|
||||
<p>使用DES CTR算法模式加密/解密数据,参数`key`的长度是8</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`DesCtrEncrypt`和`DesCtrDecrypt`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
|
||||
@@ -427,7 +427,7 @@ func main() {
|
||||
func RemoveDir(path string, onDelete ...func(path string)) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -970,7 +970,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uNep3Tr8fqF)</span></b>
|
||||
@@ -994,9 +994,9 @@ func main() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
fmt.Println(string(dat))
|
||||
|
||||
|
||||
// Output:
|
||||
// User-agent: *
|
||||
// Disallow: /deny
|
||||
@@ -1025,7 +1025,7 @@ import (
|
||||
|
||||
func main() {
|
||||
const mb = 1024 * 1024
|
||||
const defaultChunkSizeMB = 100
|
||||
const defaultChunkSizeMB = 100
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
@@ -1089,7 +1089,7 @@ func main() {
|
||||
numParsers := runtime.NumCPU()
|
||||
|
||||
linesCh := make(chan []string, numParsers)
|
||||
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
// Jim,21,male
|
||||
@@ -1114,6 +1114,7 @@ func main() {
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetExeOrDllVersion">GetExeOrDllVersion</span>
|
||||
|
||||
<p>返回exe,dll文件版本号(仅Window平台).</p>
|
||||
@@ -1143,4 +1144,4 @@ func main() {
|
||||
// Output:
|
||||
// 3.9.10.19
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -79,6 +79,7 @@ import (
|
||||
- [SortByKey](#SortByKey)
|
||||
- [GetOrDefault](#GetOrDefault)
|
||||
- [FindValuesBy](#FindValuesBy)
|
||||
- [ToMarkdownTable](#ToMarkdownTable)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1095,7 +1096,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
|
||||
@@ -1259,7 +1260,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
|
||||
|
||||
<p>删除给定键的键值对。</p>
|
||||
@@ -2192,7 +2192,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="GetOrSet">GetOrSet</span>
|
||||
|
||||
<p>返回给定键的值,如果不存在则设置该值。</p>
|
||||
@@ -2276,7 +2275,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/99QjSYSBdiM)</span></b>
|
||||
@@ -2319,7 +2318,7 @@ func main() {
|
||||
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -2346,4 +2345,69 @@ func main() {
|
||||
// Output:
|
||||
// [b d]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToMarkdownTable">ToMarkdownTable</span>
|
||||
|
||||
<p>将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
编辑
|
||||
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/maputil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 基本用法:自动从数据中提取列名作为表头
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
{"name": "Bob", "age": 30, "salary": 60000},
|
||||
}
|
||||
|
||||
result := maputil.ToMarkdownTable(data, nil, nil)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// |name|age|salary|
|
||||
// |---|---|---|
|
||||
// |Alice|25|50000|
|
||||
// |Bob|30|60000|
|
||||
|
||||
// 自定义表头显示名称
|
||||
headerMap := map[string]string{
|
||||
"name": "姓名",
|
||||
"age": "年龄",
|
||||
"salary": "薪资",
|
||||
}
|
||||
result = maputil.ToMarkdownTable(data, headerMap, nil)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// |姓名|年龄|薪资|
|
||||
// |---|---|---|
|
||||
// |Alice|25|50000|
|
||||
// |Bob|30|60000|
|
||||
|
||||
// 自定义列顺序
|
||||
columnOrder := []string{"salary", "name"}
|
||||
result = maputil.ToMarkdownTable(data, nil, columnOrder)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// |salary|name|
|
||||
// |---|---|
|
||||
// |50000|Alice|
|
||||
// |60000|Bob|
|
||||
}
|
||||
```
|
||||
@@ -1044,7 +1044,7 @@ func main() {
|
||||
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -1084,7 +1084,7 @@ func main() {
|
||||
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
- [Contain](#Contain)
|
||||
- [ContainBy](#ContainBy)
|
||||
- [ContainSubSlice](#ContainSubSlice)
|
||||
- [ContainAny](#ContainAny)
|
||||
- [Chunk](#Chunk)
|
||||
- [Compact](#Compact)
|
||||
- [Concat](#Concat)
|
||||
@@ -256,6 +257,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ContainAny">ContainAny</span>
|
||||
|
||||
<p>判断slice是否包含targets切片中的任意一个元素</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ContainAny[T comparable](slice []T, targets []T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
|
||||
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
|
||||
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
|
||||
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Chunk">Chunk</span>
|
||||
|
||||
<p>按照size参数均分slice</p>
|
||||
|
||||
@@ -834,7 +834,7 @@ func main() {
|
||||
func IsAlphaNumeric(s string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/RHeESLrLg9c)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -6,7 +6,6 @@ outline: deep
|
||||
|
||||
<b>Lancet (Lancet) is a powerful, comprehensive, efficient and reusable go language tool function library. Contains 25 packages, more than 600 utility functions. Functions cover string processing, slice processing, network, concurrency, encryption and decryption, file processing, time/date, stream processing, iterators, and more.</b>
|
||||
|
||||
|
||||
<style>
|
||||
.package-title {
|
||||
color: black;
|
||||
@@ -27,7 +26,7 @@ outline: deep
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 40px;
|
||||
background: #059669;
|
||||
background: #6cadf5;
|
||||
border: 1px solid;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
@@ -47,6 +46,7 @@ outline: deep
|
||||
<div class="package-cell">cryptor</div>
|
||||
<div class="package-cell">datastructure</div>
|
||||
<div class="package-cell">datetime</div>
|
||||
<div class="package-cell">eventbus</div>
|
||||
<div class="package-cell">fileutil</div>
|
||||
<div class="package-cell">formatter</div>
|
||||
<div class="package-cell">function</div>
|
||||
@@ -67,4 +67,3 @@ outline: deep
|
||||
<div class="package-cell">xerror</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Package algorithm implements some basic algorithm. eg. sort, search.
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sort.go](https://github.com/duke-git/lancet/blob/main/algorithm/sort.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
|
||||
@@ -479,7 +479,7 @@ func main() {
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -536,7 +536,7 @@ func main() {
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -593,7 +593,7 @@ func main() {
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -642,7 +642,7 @@ func main() {
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -691,7 +691,7 @@ func main() {
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/WgAcXbOPKGk)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -740,7 +740,7 @@ func main() {
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -781,7 +781,7 @@ func main() {
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -822,7 +822,7 @@ func main() {
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -27,7 +27,9 @@ import (
|
||||
- [AesEcbDecrypt](#AesEcbDecrypt)
|
||||
- [AesCbcEncrypt](#AesCbcEncrypt)
|
||||
- [AesCbcDecrypt](#AesCbcDecrypt)
|
||||
- [AesCtrCrypt](#AesCtrCrypt)
|
||||
- [AesCtrCrypt<sup>deprecated</sup>](#AesCtrCrypt)
|
||||
- [AesCtrEncrypt](#AesCtrEncrypt)
|
||||
- [AesCtrDecrypt](#AesCtrDecrypt)
|
||||
- [AesCfbEncrypt](#AesCfbEncrypt)
|
||||
- [AesCfbDecrypt](#AesCfbDecrypt)
|
||||
- [AesOfbEncrypt](#AesOfbEncrypt)
|
||||
@@ -40,7 +42,9 @@ import (
|
||||
- [DesEcbDecrypt](#DesEcbDecrypt)
|
||||
- [DesCbcEncrypt](#DesCbcEncrypt)
|
||||
- [DesCbcDecrypt](#DesCbcDecrypt)
|
||||
- [DesCtrCrypt](#DesCtrCrypt)
|
||||
- [DesCtrCrypt<sup>deprecated</sup>](#DesCtrCrypt)
|
||||
- [DesCfbEncrypt](#DesCfbEncrypt)
|
||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||
- [DesCfbEncrypt](#DesCfbEncrypt)
|
||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||
- [DesOfbEncrypt](#DesOfbEncrypt)
|
||||
@@ -217,6 +221,8 @@ func main() {
|
||||
|
||||
<p>Encrypt or decrypt data with key use AES CTR algorithm. Length of `key` param should be 16, 24 or 32.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `AesCtrEncrypt` and `AesCtrDecrypt` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -247,6 +253,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrEncrypt">AesCtrEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use AES CTR algorithm</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrDecrypt">AesCtrDecrypt</span>
|
||||
|
||||
<p>Decrypt data with key use AES CTR algorithm</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCfbEncrypt">AesCfbEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use AES CFB algorithm. Length of `key` param should be 16, 24 or 32.</p>
|
||||
@@ -651,6 +725,8 @@ func main() {
|
||||
|
||||
<p>Encrypt or decrypt data with key use DES CTR algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `DesCtrEncrypt` and `DesCtrDecrypt` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -681,6 +757,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrEncrypt">DesCtrCrypt</span>
|
||||
|
||||
<p>Encrypt data with key use DES CTR algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func DesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrDecrypt">DesCtrDecrypt</span>
|
||||
|
||||
<p>Decrypt data with key use DES CTR algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCfbEncrypt">DesCfbEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use DES CFB algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
@@ -427,7 +427,7 @@ func main() {
|
||||
func RemoveDir(path string, onDelete ...func(path string)) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -970,7 +970,7 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uNep3Tr8fqF)</span></b>
|
||||
@@ -1023,7 +1023,7 @@ import (
|
||||
|
||||
func main() {
|
||||
const mb = 1024 * 1024
|
||||
const defaultChunkSizeMB = 100
|
||||
const defaultChunkSizeMB = 100
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
@@ -1087,7 +1087,7 @@ func main() {
|
||||
numParsers := runtime.NumCPU()
|
||||
|
||||
linesCh := make(chan []string, numParsers)
|
||||
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
// Jim,21,male
|
||||
@@ -1140,8 +1140,8 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
|
||||
|
||||
// Output:
|
||||
// 3.9.10.19
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -620,7 +620,7 @@ func main() {
|
||||
|
||||
### <span id="Nand">Nand</span>
|
||||
|
||||
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to true only if all predicates evaluate to false for the given value.</p>
|
||||
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to false only if all predicates evaluate to true for the given value.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -650,7 +650,7 @@ func main() {
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,7 +10,6 @@ Package maputil includes some functions to manipulate map.
|
||||
- [https://github.com/duke-git/lancet/blob/main/maputil/concurrentmap.go](https://github.com/duke-git/lancet/blob/main/maputil/concurrentmap.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/maputil/orderedmap.go](https://github.com/duke-git/lancet/blob/main/maputil/orderedmap.go)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Example:
|
||||
@@ -1111,7 +1110,7 @@ Translate the key and value of the map into two slices that are sorted according
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
|
||||
@@ -1275,7 +1274,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
|
||||
|
||||
<p>Deletes the key-value pair for the given key.</p>
|
||||
@@ -1838,7 +1836,7 @@ func main() {
|
||||
fmt.Println(om.Elements())
|
||||
|
||||
// Output:
|
||||
// [{a 1} {b 2} {c 3}]
|
||||
// [{a 1} {b 2} {c 3}]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2294,7 +2292,7 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/99QjSYSBdiM)</span></b>
|
||||
@@ -2337,7 +2335,7 @@ func main() {
|
||||
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -2364,4 +2362,4 @@ func main() {
|
||||
// Output:
|
||||
// [b d]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -51,7 +51,6 @@ import (
|
||||
- [BuildUrl](#BuildUrl)
|
||||
- [AddQueryParams](#AddQueryParams)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/styles/api_doc.css">
|
||||
@@ -1045,7 +1044,7 @@ func main() {
|
||||
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -1085,7 +1084,7 @@ func main() {
|
||||
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -1113,4 +1112,4 @@ func main() {
|
||||
// https://example.com/query?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
- [Contain](#Contain)
|
||||
- [ContainBy](#ContainBy)
|
||||
- [ContainSubSlice](#ContainSubSlice)
|
||||
- [ContainAny](#ContainAny)
|
||||
- [Chunk](#Chunk)
|
||||
- [Compact](#Compact)
|
||||
- [Concat](#Concat)
|
||||
@@ -256,6 +257,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ContainAny">ContainAny</span>
|
||||
|
||||
<p>Check if the slice contains any element from the targets slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ContainAny[T comparable](slice []T, targets []T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
|
||||
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
|
||||
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
|
||||
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Chunk">Chunk</span>
|
||||
|
||||
<p>Creates an slice of elements split into groups the length of `size`.</p>
|
||||
|
||||
@@ -836,7 +836,7 @@ func main() {
|
||||
func IsAlphaNumeric(s string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/RHeESLrLg9c)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
312
enum/enum.go
Normal file
312
enum/enum.go
Normal file
@@ -0,0 +1,312 @@
|
||||
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package enum provides a simple enum implementation.
|
||||
package enum
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Enum defines a common enum interface.
|
||||
type Enum[T comparable] interface {
|
||||
Value() T
|
||||
String() string
|
||||
Name() string
|
||||
Valid(checker ...func(T) bool) bool
|
||||
}
|
||||
|
||||
// Item defines a common enum item struct implement Enum interface.
|
||||
type Item[T comparable] struct {
|
||||
value T
|
||||
name string
|
||||
}
|
||||
|
||||
func NewItem[T comparable](value T, name string) *Item[T] {
|
||||
return &Item[T]{value: value, name: name}
|
||||
}
|
||||
|
||||
// Pair represents a value-name pair for creating enum items
|
||||
type Pair[T comparable] struct {
|
||||
Value T
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewItemsFromPairs creates enum items from a slice of Pair structs.
|
||||
func NewItems[T comparable](pairs ...Pair[T]) []*Item[T] {
|
||||
if len(pairs) == 0 {
|
||||
return []*Item[T]{}
|
||||
}
|
||||
|
||||
items := make([]*Item[T], 0, len(pairs))
|
||||
for _, pair := range pairs {
|
||||
items = append(items, &Item[T]{value: pair.Value, name: pair.Name})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (e *Item[T]) Value() T {
|
||||
return e.value
|
||||
}
|
||||
|
||||
func (e *Item[T]) Name() string {
|
||||
return e.name
|
||||
}
|
||||
|
||||
func (e *Item[T]) String() string {
|
||||
return e.name
|
||||
}
|
||||
|
||||
// Valid checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
|
||||
func (e *Item[T]) Valid(checker ...func(T) bool) bool {
|
||||
if len(checker) > 0 {
|
||||
return checker[0](e.value) && e.name != ""
|
||||
}
|
||||
var zero T
|
||||
return e.value != zero && e.name != ""
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (e *Item[T]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"value": e.value,
|
||||
"name": e.name,
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (e *Item[T]) UnmarshalJSON(data []byte) error {
|
||||
type alias struct {
|
||||
Value any `json:"value"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var temp alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v T
|
||||
rv := reflect.TypeOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val, ok := temp.Value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want int family")
|
||||
}
|
||||
converted := reflect.ValueOf(int64(val)).Convert(rv)
|
||||
e.value = converted.Interface().(T)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
val, ok := temp.Value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want uint family")
|
||||
}
|
||||
converted := reflect.ValueOf(uint64(val)).Convert(rv)
|
||||
e.value = converted.Interface().(T)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val, ok := temp.Value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want float family")
|
||||
}
|
||||
converted := reflect.ValueOf(val).Convert(rv)
|
||||
e.value = converted.Interface().(T)
|
||||
case reflect.String:
|
||||
val, ok := temp.Value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want string")
|
||||
}
|
||||
e.value = any(val).(T)
|
||||
case reflect.Bool:
|
||||
val, ok := temp.Value.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want bool")
|
||||
}
|
||||
e.value = any(val).(T)
|
||||
default:
|
||||
// 枚举类型底层通常是 int,可以尝试 float64->int64->底层类型
|
||||
val, ok := temp.Value.(float64)
|
||||
if ok {
|
||||
converted := reflect.ValueOf(int64(val)).Convert(rv)
|
||||
e.value = converted.Interface().(T)
|
||||
} else {
|
||||
val2, ok2 := temp.Value.(T)
|
||||
if !ok2 {
|
||||
return fmt.Errorf("invalid type for value")
|
||||
}
|
||||
e.value = val2
|
||||
}
|
||||
}
|
||||
e.name = temp.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registry defines a common enum registry struct.
|
||||
type Registry[T comparable] struct {
|
||||
mu sync.RWMutex
|
||||
values map[T]*Item[T]
|
||||
names map[string]*Item[T]
|
||||
items []*Item[T]
|
||||
}
|
||||
|
||||
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T] {
|
||||
r := &Registry[T]{
|
||||
values: make(map[T]*Item[T]),
|
||||
names: make(map[string]*Item[T]),
|
||||
items: make([]*Item[T], 0, len(items)),
|
||||
}
|
||||
|
||||
r.Add(items...)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Add adds enum items to the registry.
|
||||
func (r *Registry[T]) Add(items ...*Item[T]) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for _, item := range items {
|
||||
if _, exists := r.values[item.value]; exists {
|
||||
continue
|
||||
}
|
||||
if _, exists := r.names[item.name]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
r.values[item.value] = item
|
||||
r.names[item.name] = item
|
||||
r.items = append(r.items, item)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes an enum item from the registry by its value.
|
||||
func (r *Registry[T]) Remove(value T) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
item, ok := r.values[value]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(r.values, value)
|
||||
delete(r.names, item.name)
|
||||
for i, it := range r.items {
|
||||
if it.value == value {
|
||||
r.items = append(r.items[:i], r.items[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Update updates the name of an enum item in the registry by its value.
|
||||
func (r *Registry[T]) Update(value T, newName string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
item, ok := r.values[value]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(r.names, item.name)
|
||||
item.name = newName
|
||||
r.names[newName] = item
|
||||
return true
|
||||
}
|
||||
|
||||
// GetByValue retrieves an enum item by its value.
|
||||
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
item, ok := r.values[value]
|
||||
return item, ok
|
||||
}
|
||||
|
||||
// GetByName retrieves an enum item by its name.
|
||||
func (r *Registry[T]) GetByName(name string) (*Item[T], bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
item, ok := r.names[name]
|
||||
return item, ok
|
||||
}
|
||||
|
||||
// Items returns a slice of all enum items in the registry.
|
||||
func (r *Registry[T]) Items() []*Item[T] {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
result := make([]*Item[T], len(r.items))
|
||||
copy(result, r.items)
|
||||
return result
|
||||
}
|
||||
|
||||
// Contains checks if an enum item with the given value exists in the registry.
|
||||
func (r *Registry[T]) Contains(value T) bool {
|
||||
_, ok := r.GetByValue(value)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Validate checks if the given value is a valid enum item in the registry.
|
||||
func (r *Registry[T]) Validate(value T) error {
|
||||
if !r.Contains(value) {
|
||||
return fmt.Errorf("invalid enum value: %v", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAll checks if all given values are valid enum items in the registry.
|
||||
func (r *Registry[T]) ValidateAll(values ...T) error {
|
||||
for _, value := range values {
|
||||
if err := r.Validate(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns the number of enum items in the registry.
|
||||
func (r *Registry[T]) Size() int {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
return len(r.items)
|
||||
}
|
||||
|
||||
// Range iterates over all enum items in the registry and applies the given function.
|
||||
func (r *Registry[T]) Range(fn func(*Item[T]) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
for _, item := range r.items {
|
||||
if !fn(item) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SortedItems returns a slice of all enum items sorted by the given less function.
|
||||
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T] {
|
||||
items := r.Items()
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return less(items[i], items[j])
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
// Filter returns a slice of enum items that satisfy the given predicate function.
|
||||
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T] {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
var result []*Item[T]
|
||||
for _, item := range r.items {
|
||||
if predicate(item) {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
222
enum/enum_example_test.go
Normal file
222
enum/enum_example_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package enum
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewItem() {
|
||||
items := NewItems(
|
||||
Pair[Status]{Value: Active, Name: "Active"},
|
||||
Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleItem_Valid() {
|
||||
item := NewItem(Active, "Active")
|
||||
fmt.Println(item.Valid())
|
||||
|
||||
invalidItem := NewItem(Unknown, "")
|
||||
fmt.Println(invalidItem.Valid())
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleItem_MarshalJSON() {
|
||||
item := NewItem(Active, "Active")
|
||||
data, _ := item.MarshalJSON()
|
||||
fmt.Println(string(data))
|
||||
|
||||
var unmarshaledItem Item[Status]
|
||||
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||
|
||||
// Output:
|
||||
// {"name":"Active","value":1}
|
||||
// Active 1
|
||||
}
|
||||
|
||||
func ExampleRegistry_Add() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found by value:", item.Name())
|
||||
}
|
||||
|
||||
if item, found := registry.GetByName("Inactive"); found {
|
||||
fmt.Println("Found by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found by value: Active
|
||||
// Found by name: 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_Remove() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
fmt.Println("Size before removal:", registry.Size())
|
||||
|
||||
removed := registry.Remove(Active)
|
||||
fmt.Println("Removed:", removed)
|
||||
fmt.Println("Size after removal:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Size before removal: 1
|
||||
// Removed: true
|
||||
// Size after removal: 0
|
||||
}
|
||||
|
||||
func ExampleRegistry_Update() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
updated := registry.Update(Active, "Activated")
|
||||
fmt.Println("Updated:", updated)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("New name:", item.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Updated: true
|
||||
// New name: Activated
|
||||
}
|
||||
|
||||
func ExampleRegistry_Items() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.Items() {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_Contains() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
registry.Add(item1)
|
||||
|
||||
fmt.Println("Contains Active:", registry.Contains(Active))
|
||||
fmt.Println("Contains Inactive:", registry.Contains(Inactive))
|
||||
}
|
||||
|
||||
func ExampleRegistry_Validate() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
fmt.Println("Validate Active:", registry.Validate(Active))
|
||||
fmt.Println("Validate Inactive:", registry.Validate(Inactive))
|
||||
fmt.Println("Validate Unknown:", registry.Validate(Unknown))
|
||||
|
||||
// Output:
|
||||
// Validate Active: <nil>
|
||||
// Validate Inactive: <nil>
|
||||
// Validate Unknown: invalid enum value: 0
|
||||
}
|
||||
|
||||
func ExampleRegistry_ValidateAll() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
fmt.Println("ValidateAll Active, Inactive:", registry.ValidateAll(Active, Inactive))
|
||||
fmt.Println("ValidateAll Active, Unknown:", registry.ValidateAll(Active, Unknown))
|
||||
|
||||
// Output:
|
||||
// ValidateAll Active, Inactive: <nil>
|
||||
// ValidateAll Active, Unknown: invalid enum value: 0
|
||||
}
|
||||
|
||||
func ExampleRegistry_Size() {
|
||||
registry := NewRegistry[Status]()
|
||||
fmt.Println("Initial size:", registry.Size())
|
||||
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
fmt.Println("Size after adding items:", registry.Size())
|
||||
|
||||
registry.Remove(Active)
|
||||
fmt.Println("Size after removing an item:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Initial size: 0
|
||||
// Size after adding items: 2
|
||||
// Size after removing an item: 1
|
||||
}
|
||||
|
||||
func ExampleRegistry_Range() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
registry.Range(func(item *Item[Status]) bool {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
return true // continue iteration
|
||||
})
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_SortedItems() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Inactive, "Inactive")
|
||||
item2 := NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||
return i1.value < i2.value
|
||||
}) {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_Filter() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
activeItems := registry.Filter(func(item *Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
for _, item := range activeItems {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
}
|
||||
218
enum/enum_test.go
Normal file
218
enum/enum_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
package enum
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func TestNewItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestNewItem")
|
||||
|
||||
items := NewItems(
|
||||
Pair[Status]{Value: Active, Name: "Active"},
|
||||
Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
assert.Equal(2, len(items))
|
||||
assert.Equal(Active, items[0].Value())
|
||||
assert.Equal("Active", items[0].Name())
|
||||
assert.Equal(Inactive, items[1].Value())
|
||||
assert.Equal("Inactive", items[1].Name())
|
||||
}
|
||||
|
||||
func TestItem_Valid(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestItem_Valid")
|
||||
|
||||
item := NewItem(Active, "Active")
|
||||
assert.Equal(true, item.Valid())
|
||||
|
||||
invalidItem := NewItem(Unknown, "")
|
||||
assert.Equal(false, invalidItem.Valid())
|
||||
}
|
||||
|
||||
func TestItem_MarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestItem_MarshalJSON")
|
||||
|
||||
item := NewItem(Active, "Active")
|
||||
data, err := item.MarshalJSON()
|
||||
assert.IsNil(err)
|
||||
assert.Equal("{\"name\":\"Active\",\"value\":1}", string(data))
|
||||
|
||||
var unmarshaledItem Item[Status]
|
||||
err = unmarshaledItem.UnmarshalJSON(data)
|
||||
assert.IsNil(err)
|
||||
assert.Equal(item.Value(), unmarshaledItem.Value())
|
||||
assert.Equal(item.Name(), unmarshaledItem.Name())
|
||||
}
|
||||
|
||||
func TestRegistry_AddAndGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_AddAndGet")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
assert.Equal(2, registry.Size())
|
||||
|
||||
item, ok := registry.GetByValue(Active)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal("Active", item.Name())
|
||||
|
||||
item, ok = registry.GetByName("Inactive")
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(Inactive, item.Value())
|
||||
}
|
||||
|
||||
func TestRegistry_Remove(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Remove")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
assert.Equal(2, registry.Size())
|
||||
|
||||
removed := registry.Remove(Active)
|
||||
assert.Equal(true, removed)
|
||||
assert.Equal(1, registry.Size())
|
||||
|
||||
_, ok := registry.GetByValue(Active)
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestRegistry_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Update")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
registry.Add(item1)
|
||||
|
||||
updated := registry.Update(Active, "Activated")
|
||||
assert.Equal(true, updated)
|
||||
|
||||
item, ok := registry.GetByValue(Active)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal("Activated", item.Name())
|
||||
}
|
||||
|
||||
func TestRegistry_Contains(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Contains")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
registry.Add(item1)
|
||||
|
||||
assert.Equal(true, registry.Contains(Active))
|
||||
assert.Equal(false, registry.Contains(Inactive))
|
||||
}
|
||||
|
||||
func TestRegistry_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Validate")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
err := registry.Validate(Active)
|
||||
assert.IsNil(err)
|
||||
err = registry.Validate(Inactive)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = registry.Validate(Unknown)
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestRegistry_ValidateAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_ValidateAll")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
err := registry.ValidateAll(Active, Inactive)
|
||||
assert.IsNil(err)
|
||||
|
||||
err = registry.ValidateAll(Active, Unknown)
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestRegistry_Range(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Range")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
var values []Status
|
||||
registry.Range(func(item *Item[Status]) bool {
|
||||
values = append(values, item.Value())
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(2, len(values))
|
||||
assert.Equal(Active, values[0])
|
||||
assert.Equal(Inactive, values[1])
|
||||
}
|
||||
|
||||
func TestRegistry_SortedItems(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_SortedItems")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Inactive, "Inactive")
|
||||
item2 := NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
sortedItems := registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||
return i1.value < i2.value
|
||||
})
|
||||
|
||||
assert.Equal(2, len(sortedItems))
|
||||
assert.Equal(Active, sortedItems[0].Value())
|
||||
assert.Equal(Inactive, sortedItems[1].Value())
|
||||
}
|
||||
|
||||
func TestRegistry_Filter(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Filter")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
filteredItems := registry.Filter(func(item *Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
assert.Equal(1, len(filteredItems))
|
||||
assert.Equal(Active, filteredItems[0].Value())
|
||||
}
|
||||
@@ -21,7 +21,7 @@ type EventBus[T any] struct {
|
||||
// listeners map[string][]*EventListener[T]
|
||||
listeners sync.Map
|
||||
mu sync.RWMutex
|
||||
errorHandler func(err error)
|
||||
errorHandler func(topic string, err error)
|
||||
}
|
||||
|
||||
// EventListener is the struct that holds the listener function and its priority.
|
||||
@@ -117,7 +117,7 @@ func (eb *EventBus[T]) Publish(event Event[T]) {
|
||||
func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event[T]) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil && eb.errorHandler != nil {
|
||||
eb.errorHandler(fmt.Errorf("%v", r))
|
||||
eb.errorHandler(event.Topic, fmt.Errorf("%v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -126,7 +126,7 @@ func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event
|
||||
|
||||
// SetErrorHandler sets the error handler function.
|
||||
// Play: https://go.dev/play/p/gmB0gnFe5mc
|
||||
func (eb *EventBus[T]) SetErrorHandler(handler func(err error)) {
|
||||
func (eb *EventBus[T]) SetErrorHandler(handler func(topic string, err error)) {
|
||||
eb.errorHandler = handler
|
||||
}
|
||||
|
||||
|
||||
@@ -189,8 +189,8 @@ func ExampleEventBus_GetListenersCount() {
|
||||
func ExampleEventBus_SetErrorHandler() {
|
||||
eb := NewEventBus[int]()
|
||||
|
||||
eb.SetErrorHandler(func(err error) {
|
||||
fmt.Println(err)
|
||||
eb.SetErrorHandler(func(topic string, err error) {
|
||||
fmt.Println(topic, err)
|
||||
})
|
||||
|
||||
eb.Subscribe("event1", func(eventData int) {
|
||||
@@ -200,7 +200,7 @@ func ExampleEventBus_SetErrorHandler() {
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
// Output:
|
||||
// error
|
||||
// event1 error
|
||||
}
|
||||
|
||||
func ExampleEventBus_GetAllListenersCount() {
|
||||
|
||||
@@ -114,7 +114,8 @@ func TestEventBus_ErrorHandler(t *testing.T) {
|
||||
|
||||
eb := NewEventBus[string]()
|
||||
|
||||
eb.SetErrorHandler(func(err error) {
|
||||
eb.SetErrorHandler(func(topic string, err error) {
|
||||
assert.Equal("event1", topic)
|
||||
assert.Equal("error", err.Error())
|
||||
})
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ func RemoveFile(path string, onDelete ...func(path string)) error {
|
||||
}
|
||||
|
||||
// RemoveDir remove the path directory.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/Oa6KnPek2uy
|
||||
func RemoveDir(path string, onDelete ...func(path string)) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ func And[T any](predicates ...func(T) bool) func(T) bool {
|
||||
}
|
||||
|
||||
// Nand returns a composed predicate that represents the logical NAND of a list of predicates.
|
||||
// It evaluates to true only if all predicates evaluate to false for the given value.
|
||||
// It evaluates to false only if all predicates evaluate to true for the given value.
|
||||
// Play: https://go.dev/play/p/Rb-FdNGpgSO
|
||||
func Nand[T any](predicates ...func(T) bool) func(T) bool {
|
||||
if len(predicates) < 2 {
|
||||
@@ -26,11 +26,11 @@ func Nand[T any](predicates ...func(T) bool) func(T) bool {
|
||||
}
|
||||
return func(value T) bool {
|
||||
for _, predicate := range predicates {
|
||||
if predicate(value) {
|
||||
return false // Short-circuit on the first true predicate
|
||||
if !predicate(value) {
|
||||
return true // Short-circuit on the first false predicate
|
||||
}
|
||||
}
|
||||
return true // True if all predicates are false
|
||||
return false // False if all predicates are true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func ExampleNand() {
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestPredicatesNandPure(t *testing.T) {
|
||||
)
|
||||
|
||||
assert.ShouldBeFalse(isNumericAndLength5("12345"))
|
||||
assert.ShouldBeFalse(isNumericAndLength5("1234"))
|
||||
assert.ShouldBeTrue(isNumericAndLength5("1234"))
|
||||
assert.ShouldBeTrue(isNumericAndLength5("abcdef"))
|
||||
}
|
||||
|
||||
|
||||
149
maputil/map.go
149
maputil/map.go
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
@@ -668,7 +669,7 @@ func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
|
||||
}
|
||||
|
||||
// FindValuesBy returns a slice of values from the map that satisfy the given predicate function.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/bvNwNBZDm6v
|
||||
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V {
|
||||
result := make([]V, 0)
|
||||
|
||||
@@ -680,3 +681,149 @@ func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ToMarkdownTable converts a slice of maps to a Markdown table.
|
||||
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string {
|
||||
if len(data) == 0 {
|
||||
return "| |\n|---|\n"
|
||||
}
|
||||
|
||||
var headers []string
|
||||
|
||||
// 如果提供了columnOrder,则按指定顺序排列
|
||||
if len(columnOrder) > 0 {
|
||||
headers = make([]string, len(columnOrder))
|
||||
copy(headers, columnOrder)
|
||||
} else {
|
||||
// 否则按自然顺序提取headers
|
||||
seen := make(map[string]bool)
|
||||
for _, row := range data {
|
||||
for k := range row {
|
||||
if !seen[k] {
|
||||
seen[k] = true
|
||||
headers = append(headers, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
|
||||
// Header row - 使用映射的中文标题
|
||||
builder.WriteString("| ")
|
||||
for i, h := range headers {
|
||||
// 如果有映射则使用中文标题,否则使用原字段名
|
||||
displayHeader := h
|
||||
if headerMap != nil && headerMap[h] != "" {
|
||||
displayHeader = headerMap[h]
|
||||
}
|
||||
builder.WriteString(displayHeader)
|
||||
if i < len(headers)-1 {
|
||||
builder.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(" |\n")
|
||||
|
||||
// Separator
|
||||
builder.WriteString("|")
|
||||
for i := range headers {
|
||||
if i > 0 {
|
||||
builder.WriteString("|")
|
||||
}
|
||||
builder.WriteString("---")
|
||||
}
|
||||
builder.WriteString("|\n")
|
||||
|
||||
// Data rows
|
||||
for _, row := range data {
|
||||
builder.WriteString("| ")
|
||||
for i, h := range headers {
|
||||
val, exists := row[h]
|
||||
var cell string
|
||||
if !exists {
|
||||
cell = ""
|
||||
} else {
|
||||
cell = formatValue(val)
|
||||
}
|
||||
builder.WriteString(cell)
|
||||
if i < len(headers)-1 {
|
||||
builder.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(" |\n")
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// formatValue formats any value for display in Markdown table
|
||||
func formatValue(v interface{}) string {
|
||||
switch val := v.(type) {
|
||||
case int:
|
||||
return commaInt64(int64(val))
|
||||
case int8:
|
||||
return commaInt64(int64(val))
|
||||
case int16:
|
||||
return commaInt64(int64(val))
|
||||
case int32:
|
||||
return commaInt64(int64(val))
|
||||
case int64:
|
||||
return commaInt64(val)
|
||||
case uint:
|
||||
return commaUint64(uint64(val))
|
||||
case uint8:
|
||||
return commaUint64(uint64(val))
|
||||
case uint16:
|
||||
return commaUint64(uint64(val))
|
||||
case uint32:
|
||||
return commaUint64(uint64(val))
|
||||
case uint64:
|
||||
return commaUint64(val)
|
||||
case float32:
|
||||
return fmt.Sprintf("%.2f", val)
|
||||
case float64:
|
||||
return fmt.Sprintf("%.2f", val)
|
||||
case string:
|
||||
return val
|
||||
case bool:
|
||||
return fmt.Sprintf("%t", val)
|
||||
case nil:
|
||||
return ""
|
||||
default:
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
}
|
||||
|
||||
// commaInt64 adds comma separators to int64
|
||||
func commaInt64(n int64) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := n < 0
|
||||
if neg {
|
||||
n = -n
|
||||
}
|
||||
s := strconv.FormatInt(n, 10)
|
||||
return addCommas(s)
|
||||
}
|
||||
|
||||
// commaUint64 adds comma separators to uint64
|
||||
func commaUint64(n uint64) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
s := strconv.FormatUint(n, 10)
|
||||
return addCommas(s)
|
||||
}
|
||||
|
||||
// addCommas inserts commas every 3 digits
|
||||
func addCommas(s string) string {
|
||||
var result strings.Builder
|
||||
for i, c := range s {
|
||||
if i > 0 && (len(s)-i)%3 == 0 {
|
||||
result.WriteRune(',')
|
||||
}
|
||||
result.WriteRune(c)
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/cmplx"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -926,3 +927,46 @@ func TestFindValuesBy(t *testing.T) {
|
||||
assert.Equal(tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToMarkdownTable(t *testing.T) {
|
||||
// 测试空数据
|
||||
emptyResult := ToMarkdownTable([]map[string]interface{}{}, nil, nil)
|
||||
expectedEmpty := "| |\n|---|\n"
|
||||
if emptyResult != expectedEmpty {
|
||||
t.Errorf("Expected empty table, got: %s", emptyResult)
|
||||
}
|
||||
|
||||
// 测试基本数据
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
{"name": "Bob", "age": 30, "salary": 60000},
|
||||
}
|
||||
result := ToMarkdownTable(data, nil, nil)
|
||||
fmt.Printf("%s", result)
|
||||
// 验证结果包含预期的表头和数据行
|
||||
}
|
||||
|
||||
// 测试自定义列顺序
|
||||
func TestToMarkdownTableWithColumnOrder(t *testing.T) {
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
}
|
||||
columnOrder := []string{"salary", "name", "age"}
|
||||
result := ToMarkdownTable(data, nil, columnOrder)
|
||||
fmt.Printf("%s", result)
|
||||
// 验证列顺序是否正确
|
||||
}
|
||||
|
||||
// 测试自定义表头
|
||||
func TestToMarkdownTableWithHeaderMap(t *testing.T) {
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25},
|
||||
}
|
||||
headerMap := map[string]string{
|
||||
"name": "姓名",
|
||||
"age": "年龄",
|
||||
}
|
||||
result := ToMarkdownTable(data, headerMap, nil)
|
||||
fmt.Printf("%s", result)
|
||||
// 验证中文表头是否正确显示
|
||||
}
|
||||
|
||||
@@ -238,6 +238,9 @@ func Sum[T constraints.Integer | constraints.Float](numbers ...T) T {
|
||||
// Average return average value of numbers.
|
||||
// Play: https://go.dev/play/p/Vv7LBwER-pz
|
||||
func Average[T constraints.Integer | constraints.Float](numbers ...T) float64 {
|
||||
if len(numbers) == 0 {
|
||||
return 0
|
||||
}
|
||||
var sum float64
|
||||
for _, num := range numbers {
|
||||
sum += float64(num)
|
||||
|
||||
@@ -309,7 +309,7 @@ func IsTelnetConnected(host string, port string) bool {
|
||||
}
|
||||
|
||||
// BuildUrl builds a URL from the given params.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/JLXl1hZK7l4
|
||||
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error) {
|
||||
if err := validateScheme(scheme); err != nil {
|
||||
return "", err
|
||||
@@ -365,13 +365,13 @@ func validateScheme(scheme string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])(\.[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])*$`)
|
||||
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)(\.[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)+$`)
|
||||
var pathRegex = regexp.MustCompile(`^\/([a-zA-Z0-9%_-]+(?:\/[a-zA-Z0-9%_-]+)*)$`)
|
||||
|
||||
var alphaNumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
|
||||
|
||||
// AddQueryParams adds query parameters to the given URL.
|
||||
// Play: todoå
|
||||
// Play: https://go.dev/play/p/JLXl1hZK7l4
|
||||
func AddQueryParams(urlStr string, params map[string][]string) (string, error) {
|
||||
parsedUrl, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
|
||||
@@ -196,6 +196,14 @@ func TestBuildUrl(t *testing.T) {
|
||||
want: "https://www.test.com/path%20with%20spaces",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
scheme: "https",
|
||||
host: "my.api.edu.cn",
|
||||
path: "/api",
|
||||
query: nil,
|
||||
want: "https://my.api.edu.cn/api",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -128,16 +128,19 @@ func Retry(retryFunc RetryFunc, opts ...Option) error {
|
||||
}
|
||||
|
||||
var i uint
|
||||
var lastErr error
|
||||
for i < config.retryTimes {
|
||||
err := retryFunc()
|
||||
if err != nil {
|
||||
lastErr = retryFunc()
|
||||
if lastErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i < config.retryTimes-1 { // Only wait if it's not the last retry
|
||||
select {
|
||||
case <-time.After(config.backoffStrategy.CalculateInterval()):
|
||||
case <-config.context.Done():
|
||||
return errors.New("retry is cancelled")
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
@@ -146,7 +149,7 @@ func Retry(retryFunc RetryFunc, opts ...Option) error {
|
||||
lastSlash := strings.LastIndex(funcPath, "/")
|
||||
funcName := funcPath[lastSlash+1:]
|
||||
|
||||
return fmt.Errorf("function %s run failed after %d times retry", funcName, i)
|
||||
return fmt.Errorf("function %s run failed after %d times retry, last error: %w", funcName, i, lastErr)
|
||||
}
|
||||
|
||||
// BackoffStrategy is an interface that defines a method for calculating backoff intervals.
|
||||
|
||||
@@ -118,7 +118,7 @@ func ExampleRetryTimes() {
|
||||
}
|
||||
|
||||
// Output:
|
||||
// function retry.ExampleRetryTimes.func1 run failed after 2 times retry
|
||||
// function retry.ExampleRetryTimes.func1 run failed after 2 times retry, last error: error occurs
|
||||
}
|
||||
|
||||
func ExampleRetry() {
|
||||
|
||||
@@ -15,14 +15,16 @@ func TestRetryFailed(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestRetryFailed")
|
||||
|
||||
var number int
|
||||
customError := errors.New("error occurs")
|
||||
increaseNumber := func() error {
|
||||
number++
|
||||
return errors.New("error occurs")
|
||||
return customError
|
||||
}
|
||||
|
||||
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
|
||||
|
||||
assert.IsNotNil(err)
|
||||
assert.Equal(errors.Is(err, customError), true)
|
||||
assert.Equal(DefaultRetryTimes, number)
|
||||
}
|
||||
|
||||
|
||||
111
slice/slice.go
111
slice/slice.go
@@ -74,6 +74,27 @@ func ContainSubSlice[T comparable](slice, subSlice []T) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ContainAny check if the slice contains any element from the targets slice.
|
||||
// Play: https://go.dev/play/p/4xoxhc9XSSw
|
||||
func ContainAny[T comparable](slice []T, targets []T) bool {
|
||||
if len(targets) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
sliceMap := make(map[T]struct{}, len(slice))
|
||||
for _, item := range slice {
|
||||
sliceMap[item] = struct{}{}
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if _, exists := sliceMap[target]; exists {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Chunk creates a slice of elements split into groups the length of size.
|
||||
// Play: https://go.dev/play/p/b4Pou5j2L_C
|
||||
func Chunk[T any](slice []T, size int) [][]T {
|
||||
@@ -507,19 +528,17 @@ func flattenRecursive(value reflect.Value, result reflect.Value) reflect.Value {
|
||||
}
|
||||
|
||||
// ForEach iterates over elements of slice and invokes function for each element.
|
||||
// Play: https://go.dev/play/p/DrPaa4YsHRF
|
||||
func ForEach[T any](slice []T, iteratee func(index int, item T)) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
iteratee(i, slice[i])
|
||||
for idx, elem := range slice {
|
||||
iteratee(idx, elem)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachWithBreak iterates over elements of slice and invokes function for each element,
|
||||
// when iteratee return false, will break the for each loop.
|
||||
// Play: https://go.dev/play/p/qScs39f3D9W
|
||||
// when function return false, will break the for each loop.
|
||||
func ForEachWithBreak[T any](slice []T, iteratee func(index int, item T) bool) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
if !iteratee(i, slice[i]) {
|
||||
for idx, elem := range slice {
|
||||
if !iteratee(idx, elem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -692,11 +711,12 @@ func IntSlice(slice any) []int {
|
||||
// DeleteAt delete the element of slice at index.
|
||||
// Play: https://go.dev/play/p/800B1dPBYyd
|
||||
func DeleteAt[T any](slice []T, index int) []T {
|
||||
result := append([]T(nil), slice...)
|
||||
|
||||
if index < 0 || index >= len(slice) {
|
||||
return slice[:len(slice)-1]
|
||||
return result[:len(slice)-1]
|
||||
}
|
||||
|
||||
result := append([]T(nil), slice...)
|
||||
copy(result[index:], result[index+1:])
|
||||
|
||||
// Set the last element to zero value, clean up the memory.
|
||||
@@ -736,7 +756,8 @@ func Drop[T any](slice []T, n int) []T {
|
||||
}
|
||||
|
||||
if n <= 0 {
|
||||
return slice
|
||||
result := make([]T, 0, size)
|
||||
return append(result, slice...)
|
||||
}
|
||||
|
||||
result := make([]T, 0, size-n)
|
||||
@@ -754,7 +775,8 @@ func DropRight[T any](slice []T, n int) []T {
|
||||
}
|
||||
|
||||
if n <= 0 {
|
||||
return slice
|
||||
result := make([]T, 0, size)
|
||||
return append(result, slice...)
|
||||
}
|
||||
|
||||
result := make([]T, 0, size-n)
|
||||
@@ -800,7 +822,9 @@ func InsertAt[T any](slice []T, index int, value any) []T {
|
||||
size := len(slice)
|
||||
|
||||
if index < 0 || index > size {
|
||||
return slice
|
||||
result := make([]T, size)
|
||||
copy(result, slice)
|
||||
return result
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
@@ -817,21 +841,21 @@ func InsertAt[T any](slice []T, index int, value any) []T {
|
||||
copy(result[index+len(v):], slice[index:])
|
||||
return result
|
||||
default:
|
||||
return slice
|
||||
result := make([]T, size)
|
||||
copy(result, slice)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateAt update the slice element at index.
|
||||
// Play: https://go.dev/play/p/f3mh2KloWVm
|
||||
func UpdateAt[T any](slice []T, index int, value T) []T {
|
||||
if index < 0 || index >= len(slice) {
|
||||
return slice
|
||||
}
|
||||
|
||||
result := make([]T, len(slice))
|
||||
copy(result, slice)
|
||||
|
||||
result[index] = value
|
||||
if index >= 0 && index < len(slice) {
|
||||
result[index] = value
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1021,7 +1045,9 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
|
||||
return []T{}
|
||||
}
|
||||
if len(slices) == 1 {
|
||||
return Unique(slices[0])
|
||||
result := make([]T, len(slices[0]))
|
||||
copy(result, slices[0])
|
||||
return Unique(result)
|
||||
}
|
||||
|
||||
result := make([]T, 0)
|
||||
@@ -1042,6 +1068,7 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
|
||||
}
|
||||
|
||||
// Reverse return slice of element order is reversed to the given slice.
|
||||
// Reverse modifies the slice in place.
|
||||
// Play: https://go.dev/play/p/8uI8f1lwNrQ
|
||||
func Reverse[T any](slice []T) {
|
||||
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
|
||||
@@ -1049,7 +1076,8 @@ func Reverse[T any](slice []T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReverseCopy return a new slice of element order is reversed to the given slice.
|
||||
// ReverseCopy return a new slice of element where the order is reversed to the given
|
||||
// slice.
|
||||
// Play: https://go.dev/play/p/c9arEaP7Cg-
|
||||
func ReverseCopy[T any](slice []T) []T {
|
||||
result := make([]T, len(slice))
|
||||
@@ -1067,7 +1095,7 @@ func Shuffle[T any](slice []T) []T {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
rand.Shuffle(len(slice), func(i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
swap(slice, i, j)
|
||||
})
|
||||
|
||||
return slice
|
||||
@@ -1240,11 +1268,12 @@ func SortByField[T any](slice []T, field string, sortType ...string) error {
|
||||
// Without creates a slice excluding all given items.
|
||||
// Play: https://go.dev/play/p/bwhEXEypThg
|
||||
func Without[T comparable](slice []T, items ...T) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
|
||||
if len(items) == 0 || len(slice) == 0 {
|
||||
return slice
|
||||
return append(result, slice...)
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(slice))
|
||||
for _, v := range slice {
|
||||
if !Contain(items, v) {
|
||||
result = append(result, v)
|
||||
@@ -1465,36 +1494,28 @@ func Random[T any](slice []T) (val T, idx int) {
|
||||
return slice[idx], idx
|
||||
}
|
||||
|
||||
// RightPadding adds padding to the right end of a slice.
|
||||
// RightPadding returns a copy of the slice padding the given value to the right end of a slice.
|
||||
// If paddingLength is zero or less, the function returns a copy of the slice.
|
||||
// Play: https://go.dev/play/p/0_2rlLEMBXL
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
suffix := []T{}
|
||||
if paddingLength > 0 {
|
||||
suffix = repeat([]T{paddingValue}, paddingLength)
|
||||
}
|
||||
for i := 0; i < paddingLength; i++ {
|
||||
slice = append(slice, paddingValue)
|
||||
}
|
||||
return slice
|
||||
padded := concat(slice, suffix)
|
||||
return padded
|
||||
}
|
||||
|
||||
// LeftPadding adds padding to the left begin of a slice.
|
||||
// LeftPadding returns a copy of the slice padding the given value to the left begin of a slice.
|
||||
// If paddingLength is zero or less, the function returns a copy of the slice.
|
||||
// Play: https://go.dev/play/p/jlQVoelLl2k
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
prefix := []T{}
|
||||
if paddingLength > 0 {
|
||||
prefix = repeat([]T{paddingValue}, paddingLength)
|
||||
}
|
||||
|
||||
paddedSlice := make([]T, len(slice)+paddingLength)
|
||||
i := 0
|
||||
for ; i < paddingLength; i++ {
|
||||
paddedSlice[i] = paddingValue
|
||||
}
|
||||
for j := 0; j < len(slice); j++ {
|
||||
paddedSlice[i] = slice[j]
|
||||
i++
|
||||
}
|
||||
|
||||
return paddedSlice
|
||||
padded := concat(prefix, slice)
|
||||
return padded
|
||||
}
|
||||
|
||||
// Frequency counts the frequency of each element in the slice.
|
||||
|
||||
@@ -125,6 +125,8 @@ func ReduceConcurrent[T any](slice []T, initial T, reducer func(index int, item
|
||||
func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool, numThreads int) []T {
|
||||
result := make([]T, 0)
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
|
||||
workerChan := make(chan struct{}, numThreads)
|
||||
|
||||
@@ -137,7 +139,9 @@ func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool,
|
||||
defer wg.Done()
|
||||
|
||||
if predicate(i, v) {
|
||||
mu.Lock()
|
||||
result = append(result, v)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
<-workerChan
|
||||
|
||||
@@ -58,6 +58,24 @@ func ExampleContainSubSlice() {
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleContainAny() {
|
||||
result1 := ContainAny([]string{"a", "b", "c"}, []string{"a"})
|
||||
result2 := ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
|
||||
result3 := ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
|
||||
result4 := ContainAny([]string{"a", "b", "c"}, []string{})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleChunk() {
|
||||
arr := []string{"a", "b", "c", "d", "e"}
|
||||
|
||||
@@ -831,7 +849,7 @@ func ExampleUniqueByComparator() {
|
||||
})
|
||||
|
||||
caseInsensitiveStrings := UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
return strings.EqualFold(item, other)
|
||||
})
|
||||
|
||||
fmt.Println(uniqueNums)
|
||||
|
||||
@@ -2,6 +2,7 @@ package slice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
@@ -96,3 +97,71 @@ func partitionAnySlice[T any](slice []T, lowIndex, highIndex int, less func(a, b
|
||||
func swap[T any](slice []T, i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
|
||||
// `repeat` returns a new slice that repeats the provided slice the given number of
|
||||
// times. The result has length and capacity (len(x) * count). The result is never nil.
|
||||
// Repeat panics if count is negative or if the result of (len(x) * count) overflows.
|
||||
//
|
||||
// repeat has been provided in the standard lib within the package `slices` under the
|
||||
// name Repeat since GO version 1.21 onwards. As lancet commits to compatibility with GO
|
||||
// 1.18 onwards, we implement the functionality as an internal function.
|
||||
func repeat[S ~[]E, E any](x S, count int) S {
|
||||
if count < 0 {
|
||||
panic("count cannot be negative")
|
||||
}
|
||||
|
||||
const maxInt = ^uint(0) >> 1
|
||||
hi, lo := bits.Mul(uint(len(x)), uint(count))
|
||||
if hi > 0 || lo > maxInt {
|
||||
panic("the result of (len(x) * count) overflows")
|
||||
}
|
||||
|
||||
newslice := make(S, int(lo)) // lo = len(x) * count
|
||||
n := copy(newslice, x)
|
||||
for n < len(newslice) {
|
||||
n += copy(newslice[n:], newslice[:n])
|
||||
}
|
||||
return newslice
|
||||
}
|
||||
|
||||
// concat returns a new slice concatenating the passed in slices.
|
||||
//
|
||||
// concat has been provided in the standard lib within the package `slices` under the
|
||||
// name Concat since GO version 1.21 onwards. As lancet commits to compatibility with GO
|
||||
// 1.18 onwards, we implement the functionality as an internal function.
|
||||
func concat[S ~[]E, E any](slices ...S) S {
|
||||
size := 0
|
||||
for _, s := range slices {
|
||||
size += len(s)
|
||||
if size < 0 {
|
||||
panic("len out of range")
|
||||
}
|
||||
}
|
||||
// Use Grow, not make, to round up to the size class:
|
||||
// the extra space is otherwise unused and helps
|
||||
// callers that append a few elements to the result.
|
||||
newslice := grow[S](nil, size)
|
||||
for _, s := range slices {
|
||||
newslice = append(newslice, s...)
|
||||
}
|
||||
return newslice
|
||||
}
|
||||
|
||||
// grow increases the slice's capacity, if necessary, to guarantee space for
|
||||
// another n elements. After grow(n), at least n elements can be appended
|
||||
// to the slice without another allocation. If n is negative or too large to
|
||||
// allocate the memory, grow panics.
|
||||
//
|
||||
// grow has been provided in the standard lib within the package `slices` under the
|
||||
// name Grow since GO version 1.21 onwards. As lancet commits to compatibility with GO
|
||||
// 1.18 onwards, we implement the functionality as an internal function.
|
||||
func grow[S ~[]E, E any](s S, n int) S {
|
||||
if n < 0 {
|
||||
panic("cannot be negative")
|
||||
}
|
||||
if n -= cap(s) - len(s); n > 0 {
|
||||
// This expression allocates only once.
|
||||
s = append(s[:cap(s)], make([]E, n)...)[:len(s)]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -89,6 +89,46 @@ func TestContainSubSlice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainAny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestContainAny")
|
||||
|
||||
tests := []struct {
|
||||
slice []string
|
||||
targets []string
|
||||
want bool
|
||||
}{
|
||||
{[]string{"a", "b", "c"}, []string{"a"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{"a", "b"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{"d", "e"}, false},
|
||||
{[]string{"a", "b", "c"}, []string{"d", "a"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{}, false},
|
||||
{[]string{}, []string{"a"}, false},
|
||||
{[]string{}, []string{}, false},
|
||||
{[]string{"a", "b", "c"}, []string{"c", "d", "e"}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
|
||||
}
|
||||
|
||||
intTests := []struct {
|
||||
slice []int
|
||||
targets []int
|
||||
want bool
|
||||
}{
|
||||
{[]int{1, 2, 3, 4, 5}, []int{3}, true},
|
||||
{[]int{1, 2, 3, 4, 5}, []int{6, 7}, false},
|
||||
{[]int{1, 2, 3, 4, 5}, []int{5, 6, 7}, true},
|
||||
{[]int{1, 2, 3, 4, 5}, []int{}, false},
|
||||
}
|
||||
|
||||
for _, tt := range intTests {
|
||||
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunk(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1008,7 +1048,7 @@ func TestUniqueByComparator(t *testing.T) {
|
||||
t.Run("case-insensitive string comparison", func(t *testing.T) {
|
||||
stringSlice := []string{"apple", "banana", "Apple", "cherry", "Banana", "date"}
|
||||
caseInsensitiveComparator := func(item, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
return strings.EqualFold(item, other)
|
||||
}
|
||||
|
||||
result := UniqueByComparator(stringSlice, caseInsensitiveComparator)
|
||||
@@ -1756,6 +1796,20 @@ func TestRightPaddingAndLeftPadding(t *testing.T) {
|
||||
|
||||
padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded)
|
||||
|
||||
// Test with negative padding length
|
||||
paddedNegative := LeftPadding(RightPadding(nums, 0, -3), 0, -3)
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, paddedNegative)
|
||||
|
||||
// Test with empty slice
|
||||
empty := []int{}
|
||||
paddedEmpty := LeftPadding(RightPadding(empty, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedEmpty)
|
||||
|
||||
// Test with nil
|
||||
nilSlice := []int(nil)
|
||||
paddedNil := LeftPadding(RightPadding(nilSlice, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedNil)
|
||||
}
|
||||
|
||||
func TestUniqueByConcurrent(t *testing.T) {
|
||||
|
||||
@@ -122,3 +122,8 @@ func (s *Struct) IsStruct() bool {
|
||||
func ToMap(v any) (map[string]any, error) {
|
||||
return New(v).ToMap()
|
||||
}
|
||||
|
||||
// TypeName return struct type name
|
||||
func (s *Struct) TypeName() string {
|
||||
return s.rtype.Name()
|
||||
}
|
||||
|
||||
@@ -177,3 +177,21 @@ func TestStruct_IsStruct(t *testing.T) {
|
||||
assert.Equal(true, s1.IsStruct())
|
||||
assert.Equal(false, s2.IsStruct())
|
||||
}
|
||||
|
||||
func TestStruct_TypeName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestStruct_TypeName")
|
||||
|
||||
type Test1 struct{}
|
||||
t1 := &Test1{}
|
||||
|
||||
s1 := New(t1)
|
||||
assert.Equal("Test1", s1.TypeName())
|
||||
|
||||
type Test2 struct{}
|
||||
t2 := Test2{}
|
||||
|
||||
s2 := New(t2)
|
||||
assert.Equal("Test2", s2.TypeName())
|
||||
}
|
||||
|
||||
@@ -19,30 +19,48 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
||||
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
||||
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
||||
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
||||
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
||||
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
||||
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^(\d{17})([0-9]|X|x)$`)
|
||||
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
|
||||
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
|
||||
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
||||
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
|
||||
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
|
||||
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
|
||||
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
|
||||
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
|
||||
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
|
||||
unionPay *regexp.Regexp = regexp.MustCompile("^62[0-5]\\d{13,16}$")
|
||||
chinaUnionPay *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
||||
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
||||
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
||||
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
||||
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
||||
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
||||
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
||||
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||
// emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||
emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*`)
|
||||
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
|
||||
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
|
||||
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
|
||||
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
||||
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
|
||||
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
|
||||
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
|
||||
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
|
||||
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
|
||||
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
|
||||
unionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-5]\\d{13,16}$`)
|
||||
chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
||||
chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`)
|
||||
)
|
||||
|
||||
var passportMatcher = map[string]*regexp.Regexp{
|
||||
"CN": regexp.MustCompile(`^P\d{9}$`),
|
||||
"US": regexp.MustCompile(`^\d{9}$`),
|
||||
"GB": regexp.MustCompile(`^[A-Z0-9]{9}$`),
|
||||
"RU": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
|
||||
"DE": regexp.MustCompile(`^\d{9}$`),
|
||||
"FR": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
|
||||
"JP": regexp.MustCompile(`^\d{8}$`),
|
||||
"IT": regexp.MustCompile(`^\d{8}$`),
|
||||
"AU": regexp.MustCompile(`^[A-Z]{1}\d{8}$`),
|
||||
"BR": regexp.MustCompile(`^\d{9}$`),
|
||||
"IN": regexp.MustCompile(`^[A-Z]{1,2}\d{7}$`),
|
||||
"HK": regexp.MustCompile(`^M\d{8}$`),
|
||||
"MO": regexp.MustCompile(`^[A-Z]\d{8}$`),
|
||||
}
|
||||
|
||||
var (
|
||||
// Identity card formula
|
||||
factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
|
||||
@@ -183,7 +201,7 @@ func IsJSON(str string) bool {
|
||||
}
|
||||
|
||||
// IsAlphaNumeric check if the string is alphanumeric.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/RHeESLrLg9c
|
||||
func IsAlphaNumeric(s string) bool {
|
||||
return alphaNumericMatcher.MatchString(s)
|
||||
}
|
||||
@@ -258,21 +276,45 @@ func IsPort(str string) bool {
|
||||
// IsUrl check if the string is url.
|
||||
// Play: https://go.dev/play/p/pbJGa7F98Ka
|
||||
func IsUrl(str string) bool {
|
||||
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(u.Host, ".") {
|
||||
return false
|
||||
}
|
||||
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
|
||||
if str == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return urlMatcher.MatchString(str)
|
||||
u, err := url.Parse(str)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
allowedSchemes := map[string]struct{}{
|
||||
"http": {},
|
||||
"https": {},
|
||||
"ftp": {},
|
||||
"ws": {},
|
||||
"wss": {},
|
||||
"file": {},
|
||||
"mailto": {},
|
||||
"data": {},
|
||||
}
|
||||
|
||||
if _, ok := allowedSchemes[u.Scheme]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.Scheme == "file" || u.Scheme == "mailto" || u.Scheme == "data" {
|
||||
return true
|
||||
}
|
||||
|
||||
host := u.Hostname()
|
||||
if !strings.Contains(host, ".") || strings.HasSuffix(host, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
// domainRegexp := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$`)
|
||||
if !dnsMatcher.MatchString(host) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsDns check if the string is dns.
|
||||
@@ -286,8 +328,6 @@ func IsDns(dns string) bool {
|
||||
func IsEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
|
||||
// return emailMatcher.MatchString(email)
|
||||
}
|
||||
|
||||
// IsChineseMobile check if the string is chinese mobile number.
|
||||
@@ -460,12 +500,13 @@ func IsZeroValue(value any) bool {
|
||||
func IsGBK(data []byte) bool {
|
||||
i := 0
|
||||
for i < len(data) {
|
||||
if data[i] <= 0xff {
|
||||
if data[i] < 0x81 {
|
||||
i++
|
||||
continue
|
||||
} else {
|
||||
if data[i] >= 0x81 &&
|
||||
data[i] <= 0xfe &&
|
||||
i+1 < len(data) &&
|
||||
data[i+1] >= 0x40 &&
|
||||
data[i+1] <= 0xfe &&
|
||||
data[i+1] != 0xf7 {
|
||||
@@ -561,12 +602,71 @@ func IsAmericanExpress(v string) bool {
|
||||
|
||||
// IsUnionPay check if a give string is a valid union pay nubmer or not.
|
||||
// Play: https://go.dev/play/p/CUHPEwEITDf
|
||||
func IsUnionPay(v string) bool {
|
||||
return unionPay.MatchString(v)
|
||||
func IsUnionPay(cardNo string) bool {
|
||||
if len(cardNo) < 16 || len(cardNo) > 19 {
|
||||
return false
|
||||
}
|
||||
|
||||
matched, _ := regexp.MatchString(`^\d+$`, cardNo)
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(cardNo) < 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
prefix := cardNo[:3]
|
||||
prefixNum, err := strconv.Atoi(prefix)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if prefixNum < 620 || prefixNum > 625 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsChinaUnionPay check if a give string is a valid china union pay nubmer or not.
|
||||
// Play: https://go.dev/play/p/yafpdxLiymu
|
||||
func IsChinaUnionPay(v string) bool {
|
||||
return chinaUnionPay.MatchString(v)
|
||||
func IsChinaUnionPay(cardNo string) bool {
|
||||
return chinaUnionPayMatcher.MatchString(cardNo)
|
||||
}
|
||||
|
||||
// luhnCheck checks if the credit card number is valid using the Luhn algorithm.
|
||||
func luhnCheck(card string) bool {
|
||||
var sum int
|
||||
alt := false
|
||||
for i := len(card) - 1; i >= 0; i-- {
|
||||
n := int(card[i] - '0')
|
||||
if alt {
|
||||
n *= 2
|
||||
if n > 9 {
|
||||
n -= 9
|
||||
}
|
||||
}
|
||||
sum += n
|
||||
alt = !alt
|
||||
}
|
||||
return sum%10 == 0
|
||||
}
|
||||
|
||||
// IsPassport checks if the passport number is valid for a given country.
|
||||
// country is a two-letter country code (ISO 3166-1 alpha-2).
|
||||
// Play: todo
|
||||
func IsPassport(passport, country string) bool {
|
||||
if matcher, ok := passportMatcher[country]; ok {
|
||||
return matcher.MatchString(passport)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsChineseHMPassport checks if the string is a valid Chinese Hong Kong and Macau Travel Permit number.
|
||||
// Chinese Hong Kong and Macau Travel Permit format: C or M + 8 digits (e.g., C12345678, M12345678).
|
||||
// Play: https://go.dev/play/p/TODO
|
||||
func IsChineseHMPassport(hmPassport string) bool {
|
||||
return chineseHMPassportMatcher.MatchString(hmPassport)
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ func ExampleIsUrl() {
|
||||
fmt.Println(result3)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
@@ -683,3 +683,42 @@ func ExampleIsAlphaNumeric() {
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleIsPassport() {
|
||||
result1 := IsPassport("P123456789", "CN")
|
||||
result2 := IsPassport("123456789", "US")
|
||||
result3 := IsPassport("AB1234567", "RU")
|
||||
result4 := IsPassport("123456789", "CN")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleIsChineseHMPassport() {
|
||||
result1 := IsChineseHMPassport("C12345678")
|
||||
result2 := IsChineseHMPassport("C00000000")
|
||||
result3 := IsChineseHMPassport("M12345678")
|
||||
result4 := IsChineseHMPassport("c12345678")
|
||||
result5 := IsChineseHMPassport("C1234567")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
@@ -437,10 +437,32 @@ func TestIsUrl(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsUrl")
|
||||
|
||||
assert.Equal(true, IsUrl("http://abc.com"))
|
||||
assert.Equal(true, IsUrl("abc.com"))
|
||||
assert.Equal(true, IsUrl("a.b.com"))
|
||||
assert.Equal(false, IsUrl("abc"))
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"http://abc.com", true},
|
||||
{"https://abc.com", true},
|
||||
{"ftp://abc.com", true},
|
||||
{"http://abc.com/path?query=123", true},
|
||||
{"https://abc.com/path/to/resource", true},
|
||||
{"ws://abc.com", true},
|
||||
{"wss://abc.com", true},
|
||||
{"mailto://abc.com", true},
|
||||
{"file://path/to/file", true},
|
||||
{"data://text/plain;base64,SGVsbG8sIFdvcmxkIQ==", true},
|
||||
{"http://abc.com/path/to/resource?query=123#fragment", true},
|
||||
|
||||
{"abc", false},
|
||||
{"http://", false},
|
||||
{"http://abc", false},
|
||||
{"http://abc:8080", false},
|
||||
{"http://abc:99999999", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, IsUrl(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDns(t *testing.T) {
|
||||
@@ -477,12 +499,24 @@ func TestIsEmail(t *testing.T) {
|
||||
|
||||
func TestContainChinese(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestContainChinese")
|
||||
|
||||
assert.Equal(true, ContainChinese("你好"))
|
||||
assert.Equal(true, ContainChinese("你好hello"))
|
||||
assert.Equal(false, ContainChinese("hello"))
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"你好", true},
|
||||
{"hello", false},
|
||||
{"你好hello", true},
|
||||
{"hello你好", true},
|
||||
{"", false},
|
||||
{"123", false},
|
||||
{"!@#$%^&*()", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, ContainChinese(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsChineseMobile(t *testing.T) {
|
||||
@@ -490,8 +524,20 @@ func TestIsChineseMobile(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsChineseMobile")
|
||||
|
||||
assert.Equal(true, IsChineseMobile("13263527980"))
|
||||
assert.Equal(false, IsChineseMobile("434324324"))
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"13263527980", true},
|
||||
{"1326352798", false},
|
||||
{"132635279801", false},
|
||||
{"1326352798a", false},
|
||||
{"1326352798@", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, IsChineseMobile(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsChinesePhone(t *testing.T) {
|
||||
@@ -887,7 +933,7 @@ func TestIsUnionPay(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestIsUnionPay")
|
||||
|
||||
assert.Equal(true, IsUnionPay("6221263430109903"))
|
||||
assert.Equal(true, IsUnionPay("6228480402564890"))
|
||||
assert.Equal(false, IsUnionPay("3782822463100007"))
|
||||
}
|
||||
|
||||
@@ -895,8 +941,25 @@ func TestIsChinaUnionPay(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestIsChinaUnionPay")
|
||||
|
||||
assert.Equal(true, IsChinaUnionPay("6250941006528599"))
|
||||
assert.Equal(false, IsChinaUnionPay("3782822463100007"))
|
||||
tests := []struct {
|
||||
cardNumber string
|
||||
expected bool
|
||||
}{
|
||||
{"6228480420000000000", true},
|
||||
{"6214830000000000", true},
|
||||
{"6230580000000000000", true},
|
||||
{"6259640000000000000", true},
|
||||
{"6260000000000000000", true},
|
||||
{"6288888888888888", true},
|
||||
|
||||
// 非银联前缀
|
||||
{"4123456789012345", false},
|
||||
{"3528000000000000", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, IsChinaUnionPay(tt.cardNumber))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphaNumeric(t *testing.T) {
|
||||
@@ -924,3 +987,72 @@ func TestIsAlphaNumeric(t *testing.T) {
|
||||
assert.Equal(tt.expected, IsAlphaNumeric(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPassport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsPassport")
|
||||
|
||||
tests := []struct {
|
||||
passport string
|
||||
countryCode string
|
||||
expected bool
|
||||
}{
|
||||
{"P123456789", "CN", true},
|
||||
{"123456789", "US", true},
|
||||
{"A12345678", "GB", true},
|
||||
{"AB1234567", "FR", true},
|
||||
{"12345678", "JP", true},
|
||||
{"M12345678", "HK", true},
|
||||
{"A12345678", "MO", true},
|
||||
{"A1234567", "IN", true},
|
||||
{"12345678", "IT", true},
|
||||
{"A12345678", "AU", true},
|
||||
{"123456789", "BR", true},
|
||||
{"AB1234567", "RU", true},
|
||||
{"123456789", "CN", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, IsPassport(tt.passport, tt.countryCode))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsChineseHMPassport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestIsChineseHMPassport")
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"C12345678", true},
|
||||
{"C00000000", true},
|
||||
{"C99999999", true},
|
||||
{"M12345678", true}, // M prefix
|
||||
{"M00000000", true}, // M prefix
|
||||
{"M99999999", true}, // M prefix
|
||||
{"c12345678", false}, // lowercase c
|
||||
{"m12345678", false}, // lowercase m
|
||||
{"C1234567", false}, // 7 digits
|
||||
{"M1234567", false}, // 7 digits with M
|
||||
{"C123456789", false}, // 9 digits
|
||||
{"M123456789", false}, // 9 digits with M
|
||||
{"C1234567a", false}, // contains letter
|
||||
{"M1234567a", false}, // contains letter with M
|
||||
{"D12345678", false}, // starts with D
|
||||
{"12345678", false}, // no prefix
|
||||
{"CC12345678", false}, // double C
|
||||
{"MM12345678", false}, // double M
|
||||
{"C 12345678", false}, // contains space
|
||||
{"M 12345678", false}, // contains space with M
|
||||
{"C12345-678", false}, // contains dash
|
||||
{"M12345-678", false}, // contains dash with M
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.expected, IsChineseHMPassport(tt.input))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user