1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-06 05:42:25 +08:00

Compare commits

..

80 Commits

Author SHA1 Message Date
dudaodong
6307d624cb Merge branch 'rc' of github.com:duke-git/lancet into rc 2025-07-07 10:18:37 +08:00
dudaodong
2f9f8b3f3d release v2.3.7 2025-07-07 10:18:05 +08:00
jake
db5d9407bb Update slice_concurrent.go (#316) 2025-06-25 15:33:07 +08:00
dudaodong
55ee000684 merge rc 2025-06-23 11:32:51 +08:00
dudaodong
fc7f2509ca fix: fix issue #314 2025-06-23 11:31:54 +08:00
残念
a97d27c32e perf(retry): the error returned by the Retry function contains the last error (#315) 2025-06-23 11:04:54 +08:00
Grigoris Thanasoulas
c176ba378e arrayqueue: Fix bug in Back() method (#313) 2025-06-20 23:05:14 +08:00
dudaodong
a3a24fc381 doc: update doc styles 2025-06-06 14:33:54 +08:00
Axiss
9caf2ffb1c fix one typo in doc (#310) 2025-06-03 10:10:51 +08:00
残念
1a5c31fd02 perf(retry): remove the waiting time after the last retry (#309) 2025-06-03 09:57:29 +08:00
dudaodong
c175b202de doc: update cryptordoc 2025-05-29 14:04:09 +08:00
dudaodong
d818219672 doc: update cryptordoc 2025-05-29 14:01:12 +08:00
dudaodong
539078e6b8 doc: add play ground demo for v2.3.6 2025-05-29 11:50:17 +08:00
dudaodong
cdefbde9f5 release v2.3.6 2025-05-29 10:07:19 +08:00
dudaodong
1e57a743af Merge branch 'rc' into v2 2025-05-29 10:05:13 +08:00
Yurun
7d4184a365 doc: fix typo (#308) 2025-05-29 10:01:38 +08:00
dudaodong
1b73483945 doc: update doc for v2.3.6 2025-05-28 11:30:12 +08:00
RigelShrimp
622aacaf44 doc: fix typo in chinese translation (#305) 2025-05-14 15:09:10 +08:00
燕归来
e78ac65605 fix: Fixed the issue of missing cap when StringToBytes returns result (#306)
Co-authored-by: 燕归来 <dylan@infinni.io>
2025-05-14 15:08:12 +08:00
dudaodong
03f0d4d905 fix: fix ExampleFindValuesBy 2025-04-29 10:18:51 +08:00
dudaodong
b2ae71c983 update rsa crypto file 2025-04-29 10:04:11 +08:00
dudaodong
093f4a2286 doc: add documention for keyed locker 2025-04-28 19:32:51 +08:00
dudaodong
f7ada6093c feat: add example for keyed_locker 2025-04-27 19:44:15 +08:00
dudaodong
47e82aad39 refactor: refact some sliceutil functions 2025-04-24 10:38:43 +08:00
dudaodong
9e813d236b fix: fix issue #159 2025-04-22 14:46:03 +08:00
dudaodong
72a23d2cb9 fix: fix issue #159 and refact crypto function 2025-04-22 14:22:01 +08:00
dudaodong
27667f8b3a fix: fix bugs in test 2025-04-21 15:08:16 +08:00
dudaodong
0b5dc86d70 refactor: refact some strutil functions 2025-04-21 14:07:52 +08:00
dudaodong
d88bba07dd feat: add TryKeyedLocker 2025-04-21 10:49:48 +08:00
dudaodong
d7f3354b98 feat: add RWKeyedLocker 2025-04-17 14:25:19 +08:00
dudaodong
f4dee28ebb feat: add KeyedLocker 2025-04-17 14:06:59 +08:00
残念
c841a5b88c Merge pull request #301 from ppd324/main
feat: add ToMap for stream
2025-04-14 10:34:17 +08:00
Peidong Pei
333038634b Update stream.go 2025-04-07 11:15:39 +08:00
Peidong Pei
ef0fed23b2 Update stream_example_test.go 2025-04-07 11:14:42 +08:00
dudaodong
1e0ee1fac1 fix: fix issue #302 2025-04-03 10:23:03 +08:00
Pei PeiDong
4947327ed6 feat: add ToMap for stream 2025-04-02 10:53:46 +08:00
dudaodong
ceb706b874 feat: add FindValuesBy 2025-04-01 11:35:24 +08:00
dudaodong
3849337919 doc: update example link for BeginOfWeek and EndOfWeek 2025-03-31 10:27:09 +08:00
CyJaySong
f4427b9fbc BeginOfWeek,EndOfWeek 的beginFrom和endWith改为必传 (#300) 2025-03-31 10:18:50 +08:00
dudaodong
0ef45b533b fix: fix issue #292, change query params type to map[string][]string 2025-03-28 14:00:11 +08:00
dudaodong
169f280c9c feat: add RemoveDir 2025-03-28 11:06:52 +08:00
dudaodong
e74126a0bd test: format test code 2025-03-27 11:17:07 +08:00
dudaodong
f7be4190f1 test: format test code 2025-03-27 10:49:40 +08:00
dudaodong
8089b71bfd feat: add IsAlphaNumeric 2025-03-15 14:57:21 +08:00
dudaodong
5b9543255a feat: add AddQueryParams 2025-03-15 14:52:49 +08:00
dudaodong
bf859387f4 feat: add BuildUrl 2025-03-15 14:25:03 +08:00
dudaodong
a8a92844f3 doc: update doc for website 2025-03-07 14:37:52 +08:00
dudaodong
6dfdadd34e doc: update for release v2.3.5 2025-03-07 14:19:07 +08:00
dudaodong
8120c4db78 test: fix TestEventBus 2025-03-07 10:42:22 +08:00
dudaodong
0d0f213d36 test: fix TestEventBus 2025-03-07 10:37:41 +08:00
dudaodong
41d4bbf0e3 test: fix TestEventBus_GetEvents 2025-03-07 10:34:20 +08:00
dudaodong
fed1d5220e release v2.3.4 2025-03-07 10:29:19 +08:00
dudaodong
29a8318d6e doc: update for release v2.3.5 2025-03-06 20:11:21 +08:00
dudaodong
3696213e5d doc: update for release v2.3.5 2025-03-06 20:02:33 +08:00
dudaodong
90a3b87b67 doc: add doc for eventbus package 2025-03-06 15:50:12 +08:00
dudaodong
a7b28ee864 update crypto fileg 2025-02-18 15:49:52 +08:00
dudaodong
f2823014f2 doc: add doc for AddDaySafe, AddMonthSafe, AddYearSafe 2025-02-18 15:47:43 +08:00
dudaodong
4181c42805 feat: add AddDaySafe, AddMonthSafe, AddYearSafe 2025-02-18 15:37:56 +08:00
dudaodong
a09f5623d6 feat: add AddWeek, AddMonth and update example of some add time functions 2025-02-18 14:10:04 +08:00
chentong
a9c75b081d feat(validator): add IsIpPort (#294)
* fix: json tag omitempty convert error

* feat(validator): add IsIpPort

- Add IsIpPort function to check if a string is in the format ip:port
- Update validator_test.go to include tests for the new IsIpPort function
- Add an example test for IsIpPort in validator_example_test.go
2025-02-17 10:03:15 +08:00
dudaodong
db479ef1bc fix: fix issue #292 2025-02-14 17:35:48 +08:00
dudaodong
df8121fbbd Merge branch 'rc' into v2 2025-02-14 17:13:11 +08:00
dudaodong
0e7297cb97 feat: add ShuffleCopy 2025-02-14 16:33:22 +08:00
dudaodong
2e619e48a3 feat: add ReverseCopy 2025-02-14 16:23:46 +08:00
残念
3069acba4a Merge pull request #293 from YoghurtFree/fix_slice_test
fix(slice_test) , should not assume the order of the slice
2025-02-09 23:10:28 +08:00
jialulu
fc43138a0e fix: fix slice_test.go ,We should not assume the order of the slice when using multithreads,sort them before compare 2025-02-09 22:23:35 +08:00
残念
1e56e9964c Merge pull request #291 from guanhonly/feature/compatible-with-pointer-in-ToString
compatible with pointer in convert.ToString
2025-01-30 22:26:34 +08:00
guanhongli
f861e18bc3 compatible with pointer in convert.ToString 2025-01-26 12:04:55 +08:00
Tuuuuuuuuu
e27df00fa8 make Bridge not block in the first stream that not closed (#288)
* not block in the first channel

* make Bridge not block in the first stream that not closed

* Bridge with test
2025-01-14 16:19:45 +08:00
dudaodong
23e61f1acf update pem file 2025-01-14 15:58:58 +08:00
dudaodong
cb308f628c feat: add eventbus 2025-01-14 15:57:43 +08:00
dudaodong
7653afa919 feat: add FindAllOccurrences 2025-01-10 14:03:45 +08:00
dudaodong
a0d97cf38e Merge branch 'rc' into v2 2024-12-25 16:13:04 +08:00
残念
1f6bab467c Merge pull request #282 from Yurunsoft/fix-RandFloats
fix the bug of random.RandFloats() infinite loop and the result of random.RandFloat() sometimes equals to max.
2024-12-25 15:14:17 +08:00
Yurun
2e5b9bc200 fix: fix the bug of random.RandFloats() infinite loop and the result of random.RandFloat() sometimes equals to max. 2024-12-25 14:13:11 +08:00
dudaodong
ab89f0aee1 feat: add EqualUnordered 2024-12-19 19:36:17 +08:00
tangke
1f64e02df4 fix(fileutil): unzip Chinese garbled code during decompression (#279)
解压时出现中文乱码
2024-12-19 19:06:27 +08:00
dudaodong
d4b425e39c doc: fix doc error 2024-12-09 09:52:40 +08:00
dudaodong
a1652c7523 doc: update v1 latest version 2024-12-04 19:13:23 +08:00
dudaodong
ecafed511c doc: add play ground demo for v2.3.4 2024-12-04 14:25:51 +08:00
94 changed files with 7817 additions and 1365 deletions

169
README.md
View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.4-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.7-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -39,7 +39,7 @@
go get github.com/duke-git/lancet/v2 // will install latest version of v2.x.x
```
2. <b>For users who use version below go1.18, you should install v1.x.x. The latest of v1.x.x is v1.4.5. </b>
2. <b>For users who use version below go1.18, you should install v1.x.x. The latest of v1.x.x is v1.4.6. </b>
```go
go get github.com/duke-git/lancet // below go1.18, install latest version of v1.x.x
@@ -214,6 +214,30 @@ import "github.com/duke-git/lancet/v2/concurrency"
- **<big>Tee</big>** : split one chanel into two channels, until cancel the context.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Tee)]
[[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/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/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/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/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/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/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/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/VG9qLvyetE2)]
<h3 id="condition"> 4. Condition package contains some functions for conditional judgment. eg. And, Or, TernaryOperator...&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a> </h3>
@@ -331,9 +355,9 @@ import "github.com/duke-git/lancet/v2/convertor"
- **<big>ToRawUrlBase64</big>** : converts a value to a string encoded in raw url Base64.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToRawUrlBase64)]
[[play](https://go.dev/play/p/HwdDPFcza1O)]
- **<big>ToBigInt</big>** : converts an integer of any supported type (int, int64, uint64, etc.) to *big.Int.
- **<big>ToBigInt</big>** : converts an integer of any supported type (int, int64, uint64, etc.) to \*big.Int.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToBigInt)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/X3itkCxwB_x)]
<h3 id="cryptor"> 6. Cryptor package is for data encryption and decryption.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -469,10 +493,10 @@ import "github.com/duke-git/lancet/v2/cryptor"
[[play](https://go.dev/play/p/zutRHrDqs0X)]
- **<big>RsaEncrypt</big>** : encrypt data with ras algorithm.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#RsaEncrypt)]
[[play](https://go.dev/play/p/uef0q1fz53I)]
[[play](https://go.dev/play/p/7_zo6mrx-eX)]
- **<big>RsaDecrypt</big>** : decrypt data with ras algorithm.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#RsaDecrypt)]
[[play](https://go.dev/play/p/uef0q1fz53I)]
[[play](https://go.dev/play/p/7_zo6mrx-eX)]
- **<big>GenerateRsaKeyPair</big>** : creates rsa private and public key.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#GenerateRsaKeyPair)]
[[play](https://go.dev/play/p/sSVmkfENKMz)]
@@ -484,10 +508,10 @@ import "github.com/duke-git/lancet/v2/cryptor"
[[play](https://go.dev/play/p/sSVmkfENKMz)]
- **<big>RsaSign</big>** : signs the data with RSA.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#RsaSign)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
- **<big>RsaVerifySign</big>** : verifies the signature of the data with RSA.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#RsaVerifySign)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
<h3 id="datetime"> 7. Datetime package supports date and time format and compare. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -506,9 +530,24 @@ import "github.com/duke-git/lancet/v2/datetime"
- **<big>AddMinute</big>** : add or sub day to the time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMinute)]
[[play](https://go.dev/play/p/nT1heB1KUUK)]
- **<big>AddWeek</big>** : add or sub week to time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddWeek)]
[[play](https://go.dev/play/p/M9TqdMiaA2p)]
- **<big>AddMonth</big>** : add or sub months to time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMonth)]
[[play](https://go.dev/play/p/DLoiOnpLvsN)]
- **<big>AddYear</big>** : add or sub year to the time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddYear)]
[[play](https://go.dev/play/p/MqW2ujnBx10)]
- **<big>AddDaySafe</big>** : add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddDaySafe)]
[[play](https://go.dev/play/p/JTohZFpoDJ3)]
- **<big>AddMonthSafe</big>** : add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMonthSafe)]
[[play](https://go.dev/play/p/KLw0lo6mbVW)]
- **<big>AddYearSafe</big>** : Add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddYearSafe)]
[[play](https://go.dev/play/p/KVGXWZZ54ZH)]
- **<big>BeginOfMinute</big>** : return the date time at the begin of minute of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMinute)]
[[play](https://go.dev/play/p/ieOLVJ9CiFT)]
@@ -520,7 +559,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/94m_UT6cWs9)]
- **<big>BeginOfWeek</big>** : return the date time at the begin of week of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfWeek)]
[[play](https://go.dev/play/p/ynjoJPz7VNV)]
[[play](https://go.dev/play/p/DCHdcL6gnfV)]
- **<big>BeginOfMonth</big>** : return the date time at the begin of month of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMonth)]
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
@@ -538,7 +577,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
- **<big>EndOfWeek</big>** : return the date time at the end of week of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfWeek)]
[[play](https://go.dev/play/p/i08qKXD9flf)]
[[play](https://go.dev/play/p/mGSA162YgX9)]
- **<big>EndOfMonth</big>** : return the date time at the end of month of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfMonth)]
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
@@ -633,13 +672,13 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/6kHBpAxD9ZC)]
- **<big>Min</big>** : returns the earliest time among the given times.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#Min)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/MCIDvHNOGGb)]
- **<big>Max</big>** : returns the latest time among the given times.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#Max)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/9m6JMk1LB7-)]
- **<big>MaxMin</big>** : returns the latest and earliest time among the given times.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#MaxMin)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/rbW51cDtM_2)]
<h3 id="datastructure"> 8. Datastructure package contains some common data structure. eg. list, linklist, stack, queue, set, tree, graph. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -678,7 +717,45 @@ import optional "github.com/duke-git/lancet/v2/datastructure/optional"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/hashmap.md)]
- **<big>Optional</big>** : Optional container.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/optional.md)]
<h3 id="eventbus"> 9. EventBus is an event bus used for handling events within an application. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">Index</a></h3>
```go
import "github.com/duke-git/lancet/v2/eventbus"
```
#### 函数列表:
- **<big>NewEventBus</big>** : Create an EventBus instance.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#NewEventBus)]
[[play](https://go.dev/play/p/gHbOPV_NUOJ)]
- **<big>Subscribe</big>** : subscribes to an event with a specific event topic and listener function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Subscribe)]
[[play](https://go.dev/play/p/EYGf_8cHei-)]
- **<big>Unsubscribe</big>** : unsubscribes from an event with a specific event topic and listener function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Unsubscribe)]
[[play](https://go.dev/play/p/Tmh7Ttfvprf)]
- **<big>Publish</big>** : publishes an event with a specific event topic and data payload.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Publish)]
[[play](https://go.dev/play/p/gHTtVexFSH9)]
- **<big>ClearListeners</big>** : clears all the listeners.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#ClearListeners)]
[[play](https://go.dev/play/p/KBfBYlKPgqD)]
- **<big>ClearListenersByTopic</big>** : clears all the listeners by topic.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#ClearListenersByTopic)]
[[play](https://go.dev/play/p/gvMljmJOZmU)]
- **<big>GetListenersCount</big>** : returns the number of listeners for a specific event topic.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetListenersCount)]
[[play](https://go.dev/play/p/8VPJsMQgStM)]
- **<big>GetAllListenersCount</big>** : returns the total number of all listeners.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetAllListenersCount)]
[[play](https://go.dev/play/p/PUlr0xcpEOz)]
- **<big>GetEvents</big>** : returns all the events topics.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetEvents)]
[[play](https://go.dev/play/p/etgjjcOtAjX)]
- **<big>SetErrorHandler</big>** : sets the error handler function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#SetErrorHandler)]
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
<h3 id="fileutil"> 9. Fileutil package implements some basic functions for file operations. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -724,6 +801,9 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>RemoveFile</big>** : remove file, param should be file path.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#RemoveFile)]
[[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/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)]
@@ -777,7 +857,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[play](https://go.dev/play/p/teMXnCsdSEw)]
- **<big>GetExeOrDllVersion</big>** : Get the version of exe or dll file on windows os.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#GetExeOrDllVersion)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/iLRrDBhE38E)]
<h3 id="formatter"> 10. Formatter contains some functions for data formatting. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -872,7 +952,6 @@ import "github.com/duke-git/lancet/v2/function"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Watcher)]
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
<h3 id="maputil"> 12. Maputil package includes some functions to manipulate map.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
@@ -1043,7 +1122,9 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>GetOrDefault</big>** : returns the value of the given key or a default value if the key is not present.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#GetOrDefault)]
[[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/bvNwNBZDm6v)]
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1145,16 +1226,16 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[play](https://go.dev/play/p/WLxDdGXXYat)]
- **<big>Variance</big>** : returns the variance of numbers.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Variance)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/uHuV4YgXf8F)]
- **<big>StdDev</big>** : returns the standard deviation of numbers.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#StdDev)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/FkNZDXvHD2l)]
- **<big>Permutation</big>** : calculates P(n, k).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Permutation)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/MgobwH_FOxj)]
- **<big>Combination</big>** : calculates C(n, k).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Combination)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/ENFQRDQUFi9)]
<h3 id="netutil"> 14. Netutil package contains functions to get net information and send http request. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1228,6 +1309,12 @@ import "github.com/duke-git/lancet/v2/netutil"
- **<big>IsTelnetConnected</big>** : checks if can if can telnet the specified host or not.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#IsTelnetConnected)]
[[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/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/JLXl1hZK7l4)]
<h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1317,7 +1404,7 @@ import "github.com/duke-git/lancet/v2/random"
[[play](https://go.dev/play/p/68UikN9d6VT)]
- **<big>RandNumberOfLength</big>** : generates a random int number of specified length.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandNumberOfLength)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/oyZbuV7bu7b)]
<h3 id="retry"> 17. Retry package is for executing a function repeatedly until it was successful or canceled by the context. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1353,7 +1440,6 @@ import "github.com/duke-git/lancet/v2/retry"
- **<big>RetryWithExponentialWithJitterBackoff</big>** : set exponential strategy backoff.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
[[play](https://go.dev/play/p/xp1avQmn16X)]
<h3 id="slice"> 18. Slice contains some functions to manipulate slice. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1423,6 +1509,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>EqualWith</big>** : checks if two slices are equal with comparator func.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#EqualWith)]
[[play](https://go.dev/play/p/b9iygtgsHI1)]
- **<big>EqualUnordered</big>** : Checks if two slices are equal: the same length and all elements value are equal (unordered).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#EqualUnordered)]
[[play](https://go.dev/play/p/n8fSc2w8ZgX)]
- **<big>Every</big>** : return true if all of the values in the slice pass the predicate function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Every)]
[[play](https://go.dev/play/p/R8U6Sl-j8cD)]
@@ -1522,6 +1611,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Shuffle</big>** : shuffle the slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Shuffle)]
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
- **<big>ShuffleCopy</big>** : return a new slice with elements shuffled.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ShuffleCopy)]
[[play](https://go.dev/play/p/vqDa-Gs1vT0)]
- **<big>IsAscending</big>** : Checks if a slice is ascending order.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#IsAscending)]
[[play](https://go.dev/play/p/9CtsFjet4SH)]
@@ -1594,7 +1686,7 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Partition</big>** : partition all slice elements with the evaluation of the given predicate functions.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Partition)]
[[play](https://go.dev/play/p/lkQ3Ri2NQhV)]
- **<big>Random</big>** : get a random item of slice, return its index, when slice is empty, return -1.
- **<big>Random</big>** : get a random item of slice, return its index, when slice is empty, return -1.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Random)]
[[play](https://go.dev/play/p/UzpGQptWppw)]
- **<big>SetToDefaultIf</big>** : set elements to their default value if they match the given predicate.
@@ -1613,10 +1705,10 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/CW3UVNdUZOq)]
- **<big>JoinFunc</big>** : joins the slice elements into a single string with the given separator.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#JoinFunc)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/55ib3SB5fM2)]
- **<big>ConcatBy</big>** : concats the elements of a slice into a single value using the provided separator and connector function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ConcatBy)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/6QcUpcY4UMW)]
<h3 id="stream"> 19. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1668,6 +1760,9 @@ import "github.com/duke-git/lancet/v2/stream"
- **<big>Reverse</big>** : returns a stream whose elements are reverse order of given stream.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#Reverse)]
[[play](https://go.dev/play/p/A8_zkJnLHm4)]
- **<big>ReverseCopy</big>** : returns a new slice of element order is reversed to the given slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#ReverseCopy)]
[[play](https://go.dev/play/p/c9arEaP7Cg-)]
- **<big>Range</big>** : returns a stream whose elements are in the range from start(included) to end(excluded) original stream.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#Range)]
[[play](https://go.dev/play/p/indZY5V2f4j)]
@@ -1709,10 +1804,10 @@ import "github.com/duke-git/lancet/v2/stream"
[[play](https://go.dev/play/p/jI6_iZZuVFE)]
- **<big>IndexOf</big>** : returns the index of the first occurrence of the specified element in this stream.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#IndexOf)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/tBV5Nc-XDX2)]
- **<big>LastIndexOf</big>** : returns the index of the last occurrence of the specified element in this stream.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#LastIndexOf)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/CjeoNw2eac_G)]
<h3 id="structs"> 20. Structs package provides several high level functions to manipulate struct, tag, and field. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1896,8 +1991,10 @@ import "github.com/duke-git/lancet/v2/strutil"
[[play](https://go.dev/play/p/JZiu0RXpgN-)]
- **<big>ExtractContent</big>** : extracts the content between the start and end strings in the source string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#ExtractContent)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/Ay9UIk7Rum9)]
- **<big>FindAllOccurrences</big>** : Returns the positions of all occurrences of a substring in a string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#FindAllOccurrences)]
[[play](https://go.dev/play/p/uvyA6azGLB1)]
<h3 id="system"> 22. System package contain some functions about os, runtime, shell command. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1947,7 +2044,6 @@ import "github.com/duke-git/lancet/v2/system"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetProcessInfo)]
[[play](https://go.dev/play/p/NQDVywEYYx7)]
<h3 id="tuple"> 23. Tuple package implements tuple data type and some operations on it. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
@@ -2130,6 +2226,9 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsNumberStr</big>** : check if the string can convert to a number.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsNumberStr)]
[[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/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)]
@@ -2151,6 +2250,9 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsIpV6</big>** : check if the string is ipv6.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsIpV6)]
[[play](https://go.dev/play/p/AHA0r0AzIdC)]
- **<big>IsIpPort</big>** : check if the string is ip:port.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsIpPort)]
[[play](https://go.dev/play/p/xUmls_b9L29)]
- **<big>IsStrongPassword</big>** : check if the string is strong password.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsStrongPassword)]
[[play](https://go.dev/play/p/QHdVcSQ3uDg)]
@@ -2247,15 +2349,16 @@ import "github.com/duke-git/lancet/v2/xerror"
- **<big>TryUnwrap</big>** : check if err is nil then it returns a valid value. If err is not nil, TryUnwrap panics with err.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/xerror.md#TryUnwrap)]
[[play](https://go.dev/play/p/acyZVkNZEeW)]
- **<big>TryCatch</big>** : simple simulation of Java-style try-catch.
- **<big>TryCatch</big>** : simple simulation of Java-style try-catch.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/xerror.md#TryCatch)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/D5Mdb0mRj0P)]
## How to Contribute
#### [Contribution Guide](./CONTRIBUTION.md)
## Contributors
Thank you to all the people who contributed to lancet!
<a href="https://github.com/duke-git/lancet/graphs/contributors">

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.4-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.7-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -38,7 +38,7 @@
go get github.com/duke-git/lancet/v2 //安装v2最新版本v2.x.x
```
2. <b>使用 go1.18 以下版本的用户,必须安装 v1.x.x。目前最新的 v1 版本是 v1.4.5。</b>
2. <b>使用 go1.18 以下版本的用户,必须安装 v1.x.x。目前最新的 v1 版本是 v1.4.6。</b>
```go
go get github.com/duke-git/lancet// 使用go1.18以下版本, 必须安装v1.x.x版本
@@ -101,7 +101,6 @@ func main() {
- [Validator](#user-content-validator)
- [Xerror](#user-content-xerror)
<h3 id="algorithm"> 1. algorithm 包实现一些基本查找和排序算法。 &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -214,6 +213,30 @@ import "github.com/duke-git/lancet/v2/concurrency"
- **<big>Tee</big>** : 将一个 channel 分成两个 channel直到取消上下文。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Tee)]
[[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/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/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/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/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/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/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/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/VG9qLvyetE2)]
<h3 id="condition"> 4. condition 包含一些用于条件判断的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -319,21 +342,21 @@ import "github.com/duke-git/lancet/v2/convertor"
- **<big>GbkToUtf8</big>** : GBK 编码转 utf8 编码。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#GbkToUtf8)]
[[play](https://go.dev/play/p/OphmHCN_9u8)]
- **<big>ToStdBase64</big>** : 将值转换为StdBase64编码的字符串。
- **<big>ToStdBase64</big>** : 将值转换为 StdBase64 编码的字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToStdBase64)]
[[play](https://go.dev/play/p/_fLJqJD3NMo)]
- **<big>ToUrlBase64</big>** : 将值转换为url Base64编码的字符串。
- **<big>ToUrlBase64</big>** : 将值转换为 url Base64 编码的字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToUrlBase64)]
[[play](https://go.dev/play/p/C_d0GlvEeUR)]
- **<big>ToRawStdBase64</big>** : 将值转换为RawStdBase64编码的字符串。
- **<big>ToRawStdBase64</big>** : 将值转换为 RawStdBase64 编码的字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToRawStdBase64)]
[[play](https://go.dev/play/p/wSAr3sfkDcv)]
- **<big>ToRawUrlBase64</big>** : 将值转换为RawUrlBase64编码的字符串。
- **<big>ToRawUrlBase64</big>** : 将值转换为 RawUrlBase64 编码的字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToRawUrlBase64)]
[[play](https://go.dev/play/p/HwdDPFcza1O)]
- **<big>ToBigInt</big>** : 将整数转为*big.Int。
- **<big>ToBigInt</big>** : 将整数转为\*big.Int。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToBigInt)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/X3itkCxwB_x)]
<h3 id="cryptor"> 6. cryptor 加密包支持数据加密和解密,获取 md5hash 值。支持 base64, md5, hmac, aes, des, rsa。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -355,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)]
@@ -394,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)]
@@ -470,25 +505,25 @@ import "github.com/duke-git/lancet/v2/cryptor"
[[play](https://go.dev/play/p/zutRHrDqs0X)]
- **<big>RsaEncrypt</big>** : 用公钥文件 ras 加密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaEncrypt)]
[[play](https://go.dev/play/p/uef0q1fz53I)]
[[play](https://go.dev/play/p/7_zo6mrx-eX)]
- **<big>RsaDecrypt</big>** : 用私钥文件 rsa 解密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaDecrypt)]
[[play](https://go.dev/play/p/uef0q1fz53I)]
- **<big>GenerateRsaKeyPair</big>** : 创建rsa公钥私钥和key。
[[play](https://go.dev/play/p/7_zo6mrx-eX)]
- **<big>GenerateRsaKeyPair</big>** : 创建 rsa 公钥私钥和 key。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#GenerateRsaKeyPair)]
[[play](https://go.dev/play/p/sSVmkfENKMz)]
- **<big>RsaEncryptOAEP</big>** : rsa OAEP加密。
- **<big>RsaEncryptOAEP</big>** : rsa OAEP 加密。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaEncryptOAEP)]
[[play](https://go.dev/play/p/sSVmkfENKMz)]
- **<big>RsaDecryptOAEP</big>** : rsa OAEP解密。
- **<big>RsaDecryptOAEP</big>** : rsa OAEP 解密。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaDecryptOAEP)]
[[play](https://go.dev/play/p/sSVmkfENKMz)]
- **<big>RsaSign</big>** : 应用RSA算法签名数据。
- **<big>RsaSign</big>** : 应用 RSA 算法签名数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaSign)]
[[play](https://go.dev/play/p/todo)]
- **<big>RsaVerifySign</big>** : 验证数据的签名是否符合RSA算法。
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
- **<big>RsaVerifySign</big>** : 验证数据的签名是否符合 RSA 算法。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaVerifySign)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
<h3 id="datetime"> 7. datetime日期时间处理包格式化日期比较日期。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -507,9 +542,24 @@ import "github.com/duke-git/lancet/v2/datetime"
- **<big>AddMinute</big>** : 将日期加/减分钟数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddMinute)]
[[play](https://go.dev/play/p/nT1heB1KUUK)]
- **<big>AddWeek</big>** : 将日期加/减星期数.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddWeek)]
[[play](https://go.dev/play/p/M9TqdMiaA2p)]
- **<big>AddMonth</big>** : 将日期加/减月数.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddMonth)]
[[play](https://go.dev/play/p/DLoiOnpLvsN)]
- **<big>AddYear</big>** : 将日期加/减分年数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddYear)]
[[play](https://go.dev/play/p/MqW2ujnBx10)]
- **<big>AddDaySafe</big>** : 增加/减少指定的天数,并确保日期是有效日期。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddDaySafe)]
[[play](https://go.dev/play/p/JTohZFpoDJ3)]
- **<big>AddMonthSafe</big>** : 增加/减少指定的月份,并确保日期是有效日期。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddMonthSafe)]
[[play](https://go.dev/play/p/KLw0lo6mbVW)]
- **<big>AddYearSafe</big>** : 增加/减少指定的年份,并确保日期是有效日期。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddYearSafe)]
[[play](https://go.dev/play/p/KVGXWZZ54ZH)]
- **<big>BeginOfMinute</big>** : 返回指定时间的分钟开始时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfMinute)]
[[play](https://go.dev/play/p/ieOLVJ9CiFT)]
@@ -521,7 +571,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/94m_UT6cWs9)]
- **<big>BeginOfWeek</big>** : 返回指定时间的每周开始时间,默认开始时间星期日。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfWeek)]
[[play](https://go.dev/play/p/ynjoJPz7VNV)]
[[play](https://go.dev/play/p/DCHdcL6gnfV)]
- **<big>BeginOfMonth</big>** : 返回指定时间的当月开始时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfMonth)]
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
@@ -539,7 +589,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
- **<big>EndOfWeek</big>** : 返回指定时间的星期结束时间,默认结束时间星期六。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfWeek)]
[[play](https://go.dev/play/p/i08qKXD9flf)]
[[play](https://go.dev/play/p/mGSA162YgX9)]
- **<big>EndOfMonth</big>** : 返回指定时间的月份结束时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfMonth)]
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
@@ -630,19 +680,18 @@ import "github.com/duke-git/lancet/v2/datetime"
- **<big>DaysBetween</big>** : 返回两个日期之间的天数差。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#DaysBetween)]
[[play](https://go.dev/play/p/qD6qGb3TbOy)]
- **<big>GenerateDatetimesBetween</big>** : 生成从startend的所有日期时间的字符串列表。
- **<big>GenerateDatetimesBetween</big>** : 生成从 startend 的所有日期时间的字符串列表。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#GenerateDatetimesBetween)]
[[play](https://go.dev/play/p/6kHBpAxD9ZC)]
- **<big>Min</big>** : 返回最早时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#Min)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/MCIDvHNOGGb)]
- **<big>Max</big>** : 返回最晚时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#Max)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/9m6JMk1LB7-)]
- **<big>MaxMin</big>** : 返回最早和最晚时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#MaxMin)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/rbW51cDtM_2)]
<h3 id="datastructure"> 8. datastructure 包含一些普通的数据结构实现。例如list, linklist, stack, queue, set, tree, graph。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -679,7 +728,46 @@ import hashmap "github.com/duke-git/lancet/v2/datastructure/hashmap"
- **<big>Hashmap</big>** : 哈希映射。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datastructure/hashmap.md)]
<h3 id="fileutil"> 9. fileutil 包含文件基本操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="eventbus"> 9. EventbBus是一个事件总线用于在应用程序中处理事件。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/eventbus"
```
#### 函数列表:
- **<big>NewEventBus</big>** : 创建 EventBus 实例。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#NewEventBus)]
[[play](https://go.dev/play/p/gHbOPV_NUOJ)]
- **<big>Subscribe</big>** : 订阅具有特定事件主题和监听函数的事件。支持异步,事件优先级,事件过滤器。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#Subscribe)]
[[play](https://go.dev/play/p/EYGf_8cHei-)]
- **<big>Unsubscribe</big>** : 取消订阅具有特定事件主题的事件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#Unsubscribe)]
[[play](https://go.dev/play/p/Tmh7Ttfvprf)]
- **<big>Publish</big>** : 发布一个带有特定事件主题和数据负载的事件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#Publish)]
[[play](https://go.dev/play/p/gHTtVexFSH9)]
- **<big>ClearListeners</big>** : 清空所有事件监听器。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#ClearListeners)]
[[play](https://go.dev/play/p/KBfBYlKPgqD)]
- **<big>ClearListenersByTopic</big>** : 清空特定事件监听器。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#ClearListenersByTopic)]
[[play](https://go.dev/play/p/gvMljmJOZmU)]
- **<big>GetListenersCount</big>** : 获取特定事件的监听器数量。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#GetListenersCount)]
[[play](https://go.dev/play/p/8VPJsMQgStM)]
- **<big>GetAllListenersCount</big>** : 获取所有事件的监听器数量。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#GetAllListenersCount)]
[[play](https://go.dev/play/p/PUlr0xcpEOz)]
- **<big>GetEvents</big>** : 获取所有事件的 topic。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#GetEvents)]
[[play](https://go.dev/play/p/etgjjcOtAjX)]
- **<big>SetErrorHandler</big>** : 设置事件的错误处理函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#SetErrorHandler)]
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
<h3 id="fileutil"> 10. fileutil 包含文件基本操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/fileutil"
@@ -723,6 +811,9 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>RemoveFile</big>** : 删除文件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#RemoveFile)]
[[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/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)]
@@ -755,9 +846,9 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>ReadCsvFile</big>** : 读取 csv 文件内容到切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadCsvFile)]
[[play](https://go.dev/play/p/OExTkhGEd3_u)]
- **<big>WriteCsvFile</big>** : 向csv文件写入切片数据。
- **<big>WriteCsvFile</big>** : 向 csv 文件写入切片数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : 将map切片写入csv文件中。
- **<big>WriteMapsToCsv</big>** : 将 map 切片写入 csv 文件中。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/umAIomZFV1c)]
- **<big>WriteBytesToFile</big>** : 将 bytes 写入文件。
@@ -766,7 +857,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>WriteStringToFile</big>** : 将字符串写入文件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteStringToFile)]
[[play](https://go.dev/play/p/GhLS6d8lH_g)]
- **<big>ReadFile</big>** : 读取文件或者URL。
- **<big>ReadFile</big>** : 读取文件或者 URL。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadFile)]
- **<big>ChunkRead</big>** : 从文件的指定偏移读取块并返回块内所有行。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ChunkRead)]
@@ -774,12 +865,11 @@ import "github.com/duke-git/lancet/v2/fileutil"
- **<big>ParallelChunkRead</big>** : 并行读取文件并将每个块的行发送到指定通道。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ParallelChunkRead)]
[[play](https://go.dev/play/p/teMXnCsdSEw)]
- **<big>GetExeOrDllVersion</big>** : 返回exe,dll文件版本号(仅Window平台)。
- **<big>GetExeOrDllVersion</big>** : 返回 exe,dll 文件版本号(仅 Window 平台)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#GetExeOrDllVersion)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/iLRrDBhE38E)]
<h3 id="formatter"> 10. formatter 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/formatter"
@@ -809,7 +899,7 @@ import "github.com/duke-git/lancet/v2/formatter"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParseBinaryBytes)]
[[play](https://go.dev/play/p/69v1tTT62x8)]
<h3 id="function"> 11. function 函数包控制函数执行流程,包含部分函数式编程。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="function"> 12. function 函数包控制函数执行流程,包含部分函数式编程。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/function"
@@ -847,34 +937,32 @@ import "github.com/duke-git/lancet/v2/function"
- **<big>Pipeline</big>** : 从右至左执行函数列表。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Pipeline)]
[[play](https://go.dev/play/p/mPdUVvj6HD6)]
- **<big>AcceptIf</big>** : AcceptIf函数会返回另一个函数该函数的签名与apply函数相同但同时还会包含一个布尔值来表示成功或失败。
- **<big>AcceptIf</big>** : AcceptIf 函数会返回另一个函数,该函数的签名与 apply 函数相同,但同时还会包含一个布尔值来表示成功或失败。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#AcceptIf)]
[[play](https://go.dev/play/p/XlXHHtzCf7d)]
- **<big>And</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑and操作。
- **<big>And</big>** : 返回一个复合谓词判断函数,该判断函数表示一组谓词的逻辑 and 操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#And)]
[[play](https://go.dev/play/p/dTBHJMQ0zD2)]
- **<big>Or</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑or操作。
- **<big>Or</big>** : 返回一个复合谓词判断函数,该判断函数表示一组谓词的逻辑 or 操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Or)]
[[play](https://go.dev/play/p/LitCIsDFNDA)]
- **<big>Negate</big>** : 返回一个谓词函数,该谓词函数表示当前谓词的逻辑否定。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Negate)]
[[play](https://go.dev/play/p/jbI8BtgFnVE)]
- **<big>Nor</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑非或nor的操作。
- **<big>Nor</big>** : 返回一个复合谓词判断函数,该判断函数表示一组谓词的逻辑非或 nor 的操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Nor)]
[[play](https://go.dev/play/p/2KdCoBEOq84)]
- **<big>Nand</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑非与nand的操作。
- **<big>Nand</big>** : 返回一个复合谓词判断函数,该判断函数表示一组谓词的逻辑非与 nand 的操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Nand)]
[[play](https://go.dev/play/p/Rb-FdNGpgSO)]
- **<big>Xnor</big>** : 返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑异或xnor的操作。
- **<big>Xnor</big>** : 返回一个复合谓词判断函数,该判断函数表示一组谓词的逻辑异或 xnor 的操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Xnor)]
[[play](https://go.dev/play/p/FJxko8SFbqc)]
- **<big>Watcher</big>** : Watcher 用于记录代码执行时间。可以启动/停止/重置手表定时器。获取函数执行的时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Watcher)]
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
<h3 id="maputil"> 12. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="maputil"> 13. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/maputil"
@@ -951,13 +1039,13 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>GetOrSet</big>** : 返回给定键的值,如果不存在则设置该值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrSet)]
[[play](https://go.dev/play/p/IVQwO1OkEJC)]
- **<big>MapToStruct</big>** : 将map转成struct。
- **<big>MapToStruct</big>** : 将 map 转成 struct。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#MapToStruct)]
[[play](https://go.dev/play/p/7wYyVfX38Dp)]
- **<big>ToSortedSlicesDefault</big>** : 将map的key和value转化成两个根据key的值从小到大排序的切片value切片中元素的位置与key对应。
- **<big>ToSortedSlicesDefault</big>** : 将 map 的 key 和 value 转化成两个根据 key 的值从小到大排序的切片value 切片中元素的位置与 key 对应。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToSortedSlicesDefault)]
[[play](https://go.dev/play/p/43gEM2po-qy)]
- **<big>ToSortedSlicesWithComparator</big>** : 将map的key和value转化成两个使用比较器函数根据key的值自定义排序规则的切片value切片中元素的位置与key对应。
- **<big>ToSortedSlicesWithComparator</big>** : 将 map 的 key 和 value 转化成两个使用比较器函数根据 key 的值自定义排序规则的切片value 切片中元素的位置与 key 对应。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToSortedSlicesWithComparator)]
[[play](https://go.dev/play/p/0nlPo6YLdt3)]
- **<big>NewOrderedMap</big>** : 创建有序映射。有序映射是键值对的集合,其中键是唯一的,并且保留键插入的顺序。
@@ -972,7 +1060,7 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>OrderedMap_Delete</big>** : 删除给定键的键值对。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Delete)]
[[play](ttps://go.dev/play/p/5bIi4yaZ3K-)]
- **<big>OrderedMap_Clear</big>** : 清空map数据。
- **<big>OrderedMap_Clear</big>** : 清空 map 数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Clear)]
[[play](https://go.dev/play/p/8LwoJyEfuFr)]
- **<big>OrderedMap_Front</big>** : 返回第一个键值对。
@@ -996,7 +1084,7 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>OrderedMap_Len</big>** : 返回键值对的数量。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Len)]
[[play](https://go.dev/play/p/cLe6z2VX5N-)]
- **<big>OrderedMap_Contains</big>** : 如果给定的键存在则返回true。
- **<big>OrderedMap_Contains</big>** : 如果给定的键存在则返回 true。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_Contains)]
[[play](https://go.dev/play/p/QuwqqnzwDNX)]
- **<big>OrderedMap_Iter</big>** : 返回按顺序产生键值对的通道。
@@ -1005,13 +1093,13 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>OrderedMap_ReverseIter</big>** : 返回以相反顺序产生键值对的通道。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_ReverseIter)]
[[play](https://go.dev/play/p/8Q0ssg6hZzO)]
- **<big>OrderedMap_SortByKey</big>** : 使用传入的比较函数排序map key。
- **<big>OrderedMap_SortByKey</big>** : 使用传入的比较函数排序 map key。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_SortByKey)]
[[play](https://go.dev/play/p/N7hjD_alZPq)]
- **<big>OrderedMap_MarshalJSON</big>** : 实现json.Marshaler接口。
- **<big>OrderedMap_MarshalJSON</big>** : 实现 json.Marshaler 接口。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_MarshalJSON)]
[[play](https://go.dev/play/p/C-wAwydIAC7)]
- **<big>OrderedMap_UnmarshalJSON</big>** : 实现json.Unmarshaler接口。
- **<big>OrderedMap_UnmarshalJSON</big>** : 实现 json.Unmarshaler 接口。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#OrderedMap_UnmarshalJSON)]
[[play](https://go.dev/play/p/8C3MvJ3-mut)]
- **<big>NewConcurrentMap</big>** : ConcurrentMap 协程安全的 map 结构。
@@ -1038,14 +1126,17 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>ConcurrentMap_Range</big>** : 为 map 中每个键和值顺序调用迭代器。 如果 iterator 返回 false则停止迭代。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ConcurrentMap_Range)]
[[play](https://go.dev/play/p/iqcy7P8P0Pr)]
- **<big>SortByKey</big>** : 对传入的map根据key进行排序。
- **<big>SortByKey</big>** : 对传入的 map 根据 key 进行排序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#SortByKey)]
[[play](https://go.dev/play/p/PVdmBSnm6P_W)]
- **<big>GetOrDefault</big>** : 返回给定键的值,如果键不存在,则返回默认值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrDefault)]
[[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/bvNwNBZDm6v)]
<h3 id="mathutil"> 13. mathutil 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/mathutil"
@@ -1089,16 +1180,16 @@ import "github.com/duke-git/lancet/v2/mathutil"
- **<big>TruncRound</big>** : 截短 n 位小数(不进行四舍五入)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#TruncRound)]
[[play](https://go.dev/play/p/aumarSHIGzP)]
- **<big>CeilToFloat</big>** : 向上舍入(进一法),保留n位小数。
- **<big>CeilToFloat</big>** : 向上舍入(进一法),保留 n 位小数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#CeilToFloat)]
[[play](https://go.dev/play/p/8hOeSADZPCo)]
- **<big>CeilToString</big>** : 向上舍入(进一法),保留n位小数,返回字符串。
- **<big>CeilToString</big>** : 向上舍入(进一法),保留 n 位小数,返回字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#CeilToString)]
[[play](https://go.dev/play/p/wy5bYEyUKKG)]
- **<big>FloorToFloat</big>** : 向下舍入(去尾法),保留n位小数。
- **<big>FloorToFloat</big>** : 向下舍入(去尾法),保留 n 位小数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#FloorToFloat)]
[[play](https://go.dev/play/p/vbCBrQHZEED)]
- **<big>FloorToString</big>** : 向下舍入(去尾法),保留n位小数,返回字符串。
- **<big>FloorToString</big>** : 向下舍入(去尾法),保留 n 位小数,返回字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#FloorToString)]
[[play](https://go.dev/play/p/Qk9KPd2IdDb)]
- **<big>Range</big>** : 根据指定的起始值和数量,创建一个数字切片。
@@ -1145,18 +1236,18 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[play](https://go.dev/play/p/WLxDdGXXYat)]
- **<big>Variance</big>** : 计算方差。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Variance)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/uHuV4YgXf8F)]
- **<big>StdDev</big>** : 计算标准差。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#StdDev)]
[[play](https://go.dev/play/p/todo)]
- **<big>Permutation</big>** : 计算排列数P(n, k)。
[[play](https://go.dev/play/p/FkNZDXvHD2l)]
- **<big>Permutation</big>** : 计算排列数 P(n, k)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Permutation)]
[[play](https://go.dev/play/p/todo)]
- **<big>Combination</big>** : 计算组合数C(n, k)。
[[play](https://go.dev/play/p/MgobwH_FOxj)]
- **<big>Combination</big>** : 计算组合数 C(n, k)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Combination)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/ENFQRDQUFi9)]
<h3 id="netutil"> 14. netutil 网络包支持获取 ip 地址,发送 http 请求。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="netutil"> 15. netutil 网络包支持获取 ip 地址,发送 http 请求。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/netutil"
@@ -1228,8 +1319,14 @@ import "github.com/duke-git/lancet/v2/netutil"
- **<big>IsTelnetConnected</big>** : 检查能否 telnet 到主机。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#IsTelnetConnected)]
[[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/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/JLXl1hZK7l4)]
<h3 id="pointer"> 15. pointer 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/pointer"
@@ -1253,7 +1350,7 @@ import "github.com/duke-git/lancet/v2/pointer"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/pointer.md#UnwrapOrDefault)]
[[play](https://go.dev/play/p/ZnGIHf8_o4E)]
<h3 id="random"> 16. random 随机数生成器包,可以生成随机[]bytes, int, string。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="random"> 17. random 随机数生成器包,可以生成随机[]bytes, int, string。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/random"
@@ -1285,28 +1382,28 @@ import "github.com/duke-git/lancet/v2/random"
- **<big>UUIdV4</big>** : 生成 UUID v4 字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#UUIdV4)]
[[play](https://go.dev/play/p/_Z9SFmr28ft)]
- **<big>RandUniqueIntSlice</big>** : 生成一个不重复的随机int切片。
- **<big>RandUniqueIntSlice</big>** : 生成一个不重复的随机 int 切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandUniqueIntSlice)]
[[play](https://go.dev/play/p/uBkRSOz73Ec)]
- **<big>RandSymbolChar</big>** : 生成给定长度的随机符号字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandSymbolChar)]
[[play](https://go.dev/play/p/Im6ZJxAykOm)]
- **<big>RandFloat</big>** : 生成随机float64数字可以指定范围和精度。
- **<big>RandFloat</big>** : 生成随机 float64 数字,可以指定范围和精度。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandFloat)]
[[play](https://go.dev/play/p/zbD_tuobJtr)]
- **<big>RandFloats</big>** : 生成随机float64数字切片可以指定长度范围和精度.
- **<big>RandFloats</big>** : 生成随机 float64 数字切片,可以指定长度,范围和精度.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandFloats)]
[[play](https://go.dev/play/p/uBkRSOz73Ec)]
- **<big>RandStringSlice</big>** : 生成随机字符串slice。
- **<big>RandStringSlice</big>** : 生成随机字符串 slice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandStringSlice)]
[[play](https://go.dev/play/p/2_-PiDv3tGn)]
- **<big>RandBool</big>** : 生成随机bool值(true or false)。
- **<big>RandBool</big>** : 生成随机 bool 值(true or false)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandBool)]
[[play](https://go.dev/play/p/to6BLc26wBv)]
- **<big>RandBoolSlice</big>** : 生成特定长度的随机bool slice。
- **<big>RandBoolSlice</big>** : 生成特定长度的随机 bool slice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandBoolSlice)]
[[play](https://go.dev/play/p/o-VSjPjnILI)]
- **<big>RandIntSlice</big>** : 生成一个特定长度的随机int切片数值范围[min, max)。
- **<big>RandIntSlice</big>** : 生成一个特定长度的随机 int 切片,数值范围[min, max)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandIntSlice)]
[[play](https://go.dev/play/p/GATTQ5xTEG8)]
- **<big>RandFromGivenSlice</big>** : 从给定切片中随机生成元素。
@@ -1317,9 +1414,9 @@ import "github.com/duke-git/lancet/v2/random"
[[play](https://go.dev/play/p/68UikN9d6VT)]
- **<big>RandNumberOfLength</big>** : 生成指定长度的随机数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandNumberOfLength)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/oyZbuV7bu7b)]
<h3 id="retry"> 17. retry 重试执行函数直到函数运行成功或被 context cancel。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="retry"> 18. retry 重试执行函数直到函数运行成功或被 context cancel。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/retry"
@@ -1336,7 +1433,7 @@ import "github.com/duke-git/lancet/v2/retry"
- **<big>RetryFunc</big>** : 重试执行的函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryFunc)]
[[play](https://go.dev/play/p/nk2XRmagfVF)]
- **<big>RetryTimes</big>** : 设置重试次数默认5。
- **<big>RetryTimes</big>** : 设置重试次数,默认 5。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryTimes)]
[[play](https://go.dev/play/p/ssfVeU2SwLO)]
- **<big>BackoffStrategy</big>** : 定义计算退避间隔的方法的接口。
@@ -1351,9 +1448,7 @@ import "github.com/duke-git/lancet/v2/retry"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
[[play](https://go.dev/play/p/xp1avQmn16X)]
<h3 id="slice"> 18. slice 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <a href="#index">回到目录</a></h3>
<h3 id="slice"> 19. slice 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/slice"
@@ -1421,6 +1516,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>EqualWith</big>** : 检查两个切片是否相等,相等条件:对两个切片的元素调用比较函数 comparator返回 true。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#EqualWith)]
[[play](https://go.dev/play/p/b9iygtgsHI1)]
- **<big>EqualUnordered</big>** : 检查两个切片是否相等,元素数量相同,值相等,不考虑元素顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#EqualUnordered)]
[[play](https://go.dev/play/p/n8fSc2w8ZgX)]
- **<big>Every</big>** : 如果切片中的所有值都通过谓词函数,则返回 true。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Every)]
[[play](https://go.dev/play/p/R8U6Sl-j8cD)]
@@ -1454,7 +1552,7 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>ForEach</big>** : 遍历切片的元素并为每个元素调用 iteratee 函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ForEach)]
[[play](https://go.dev/play/p/DrPaa4YsHRF)]
- **<big>ForEachConcurrent</big>** : 对slice并发执行foreach操作。
- **<big>ForEachConcurrent</big>** : 对 slice 并发执行 foreach 操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ForEachConcurrent)]
[[play](https://go.dev/play/p/kT4XW7DKVoV)]
- **<big>ForEachWithBreak</big>** : 遍历切片的元素并为每个元素调用 iteratee 函数,当 iteratee 函数返回 false 时,终止遍历。
@@ -1487,7 +1585,7 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Map</big>** : 对 slice 中的每个元素执行 map 函数以创建一个新切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Map)]
[[play](https://go.dev/play/p/biaTefqPquw)]
- **<big>MapConcurrent</big>** : 对slice并发执行map操作。
- **<big>MapConcurrent</big>** : 对 slice 并发执行 map 操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#MapConcurrent)]
[[play](https://go.dev/play/p/H1ehfPkPen0)]
- **<big>Merge</big>** : 合并多个切片(不会消除重复元素)。
@@ -1496,6 +1594,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Reverse</big>** : 反转切片中的元素顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Reverse)]
[[play](https://go.dev/play/p/8uI8f1lwNrQ)]
- **<big>ReverseCopy</big>** : 反转切片中的元素顺序, 不改变原 slice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#ReverseCopy)]
[[play](https://go.dev/play/p/c9arEaP7Cg-)]
- **<big>Reduce<sup>deprecated</sup></big>** : 将切片中的元素依次运行 iteratee 函数,返回运行结果。(废弃:建议使用 ReduceBy)
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Reduce)]
[[play](https://go.dev/play/p/_RfXJJWIsIm)]
@@ -1505,7 +1606,7 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>ReduceRight</big>** : 类似 ReduceBy 操作,迭代切片元素顺序从右至左。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ReduceRight)]
[[play](https://go.dev/play/p/qT9dZC03A1K)]
- **<big>ReduceConcurrent</big>** : 对切片元素执行并发reduce操作。
- **<big>ReduceConcurrent</big>** : 对切片元素执行并发 reduce 操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ReduceConcurrent)]
[[play](https://go.dev/play/p/Tjwe6OtaG07)]
- **<big>Replace</big>** : 返回切片的副本,其中前 n 个不重叠的 old 替换为 new。
@@ -1520,6 +1621,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Shuffle</big>** : 随机打乱切片中的元素顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Shuffle)]
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
- **<big>ShuffleCopy</big>** : 随机打乱切片中的元素顺序, 不改变原切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ShuffleCopy)]
[[play](https://go.dev/play/p/vqDa-Gs1vT0)]
- **<big>IsAscending</big>** : 检查切片元素是否按升序排列。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#IsAscending)]
[[play](https://go.dev/play/p/9CtsFjet4SH)]
@@ -1565,7 +1669,7 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>UniqueByComparator</big>** : 使用提供的比较器函数从输入切片中移除重复元素。此函数保持元素的顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueByComparator)]
[[play](https://go.dev/play/p/rwSacr-ZHsR)]
- **<big>UniqueByField</big>** : 根据struct字段对struct切片去重复。
- **<big>UniqueByField</big>** : 根据 struct 字段对 struct 切片去重复。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueByField)]
[[play](https://go.dev/play/p/6cifcZSPIGu)]
- **<big>UniqueByConcurrent</big>** : 并发的从输入切片中移除重复元素,结果保持元素的顺序。
@@ -1588,13 +1692,13 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>Join</big>** : 用指定的分隔符链接切片元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Join)]
[[play](https://go.dev/play/p/huKzqwNDD7V)]
- **<big>Partition</big>** : 根据给定的predicate判断函数分组切片元素。
- **<big>Partition</big>** : 根据给定的 predicate 判断函数分组切片元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Partition)]
[[play](https://go.dev/play/p/lkQ3Ri2NQhV)]
- **<big>Random</big>** : 随机返回切片中元素以及下标, 当切片长度为0时返回下标-1。
- **<big>Random</big>** : 随机返回切片中元素以及下标, 当切片长度为 0 时返回下标-1。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Random)]
[[play](https://go.dev/play/p/UzpGQptWppw)]
- **<big>SetToDefaultIf</big>** : 根据给定给定的predicate判定函数来修改切片中的元素。
- **<big>SetToDefaultIf</big>** : 根据给定给定的 predicate 判定函数来修改切片中的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#SetToDefaultIf)]
[[play](https://go.dev/play/p/9AXGlPRC0-A)]
- **<big>Break</big>** : 根据判断函数将切片分成两部分。它开始附加到与函数匹配的第一个元素之后的第二个切片。第一个匹配之后的所有元素都包含在第二个切片中,无论它们是否与函数匹配。
@@ -1610,12 +1714,12 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/CW3UVNdUZOq)]
- **<big>JoinFunc</big>** : 将切片元素用给定的分隔符连接成一个单一的字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#JoinFunc)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/55ib3SB5fM2)]
- **<big>ConcatBy</big>** : 将切片中的元素连接成一个值,使用指定的分隔符和连接器函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ConcatBy)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/6QcUpcY4UMW)]
<h3 id="stream"> 19. stream 流,该包仅验证简单的 stream 实现,功能有限。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="stream"> 20. stream 流,该包仅验证简单的 stream 实现,功能有限。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/stream"
@@ -1641,13 +1745,13 @@ import "github.com/duke-git/lancet/v2/stream"
- **<big>Concat</big>** : 创建一个延迟连接 stream其元素是第一个 stream 的所有元素,后跟第二个 stream 的全部元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Concat)]
[[play](https://go.dev/play/p/HM4OlYk_OUC)]
- **<big>Distinct</big>** : 创建并返回一个stream用于删除重复的项。
- **<big>Distinct</big>** : 创建并返回一个 stream用于删除重复的项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Distinct)]
[[play](https://go.dev/play/p/eGkOSrm64cB)]
- **<big>Filter</big>** : 返回一个通过判定函数的stream。
- **<big>Filter</big>** : 返回一个通过判定函数的 stream。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#Filter)]
[[play](https://go.dev/play/p/MFlSANo-buc)]
- **<big>FilterConcurrent</big>** : 对slice并发执行filter操作。
- **<big>FilterConcurrent</big>** : 对 slice 并发执行 filter 操作。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#FilterConcurrent)]
[[play](https://go.dev/play/p/t_pkwerIRVx)]
- **<big>Map</big>** : 返回一个 stream该 stream 由将给定函数应用于源 stream 元素的元素组成。
@@ -1704,14 +1808,14 @@ import "github.com/duke-git/lancet/v2/stream"
- **<big>ToSlice</big>** : 返回 stream 中的元素切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#ToSlice)]
[[play](https://go.dev/play/p/jI6_iZZuVFE)]
- **<big>IndexOf</big>** : 返回在stream中找到值的第一个匹配项的索引如果找不到值则返回-1。
- **<big>IndexOf</big>** : 返回在 stream 中找到值的第一个匹配项的索引,如果找不到值,则返回-1。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#IndexOf)]
[[play](https://go.dev/play/p/todo)]
- **<big>LastIndexOf</big>** : 返回在stream中找到值的最后一个匹配项的索引如果找不到值则返回-1。
[[play](https://go.dev/play/p/tBV5Nc-XDX2)]
- **<big>LastIndexOf</big>** : 返回在 stream 中找到值的最后一个匹配项的索引,如果找不到值,则返回-1。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#LastIndexOf)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/CjeoNw2eac_G)]
<h3 id="structs"> 20. structs 提供操作 struct, tag, field 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="structs"> 21. structs 提供操作 struct, tag, field 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/structs"
@@ -1748,7 +1852,7 @@ import "github.com/duke-git/lancet/v2/structs"
- **<big>IsTargetType</big>** : 判断属性是否是目标类型。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsTargetType)]
<h3 id="strutil"> 21. strutil 包含字符串处理的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="strutil"> 22. strutil 包含字符串处理的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/strutil"
@@ -1870,7 +1974,7 @@ import "github.com/duke-git/lancet/v2/strutil"
- **<big>RemoveWhiteSpace</big>** : 删除字符串中的空格。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#RemoveWhiteSpace)]
[[play](https://go.dev/play/p/HzLC9vsTwkf)]
- **<big>SubInBetween</big>** : 获取字符串中指定的起始字符串start和终止字符串end直接的子字符串。
- **<big>SubInBetween</big>** : 获取字符串中指定的起始字符串 start 和终止字符串 end 直接的子字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#SubInBetween)]
[[play](https://go.dev/play/p/EDbaRvjeNsv)]
- **<big>HammingDistance</big>** : 计算两个字符串之间的汉明距离。
@@ -1888,7 +1992,7 @@ import "github.com/duke-git/lancet/v2/strutil"
- **<big>Rotate</big>** : 按指定的字符数旋转字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#Rotate)]
[[play](https://go.dev/play/p/Kf03iOeT5bd)]
- **<big>TemplateReplace</big>** : 将模板字符串中的占位符替换为map中的相应值。
- **<big>TemplateReplace</big>** : 将模板字符串中的占位符替换为 map 中的相应值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#TemplateReplace)]
[[play](https://go.dev/play/p/cXSuFvyZqv9)]
- **<big>RegexMatchAllGroups</big>** : 使用正则表达式匹配字符串中的所有子组并返回结果。
@@ -1896,9 +2000,12 @@ import "github.com/duke-git/lancet/v2/strutil"
[[play](https://go.dev/play/p/JZiu0RXpgN-)]
- **<big>ExtractContent</big>** : 提取两个标记之间的内容。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#ExtractContent)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/Ay9UIk7Rum9)]
- **<big>FindAllOccurrences</big>** : 返回子字符串在字符串中所有出现的位置。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#FindAllOccurrences)]
[[play](https://go.dev/play/p/uvyA6azGLB1)]
<h3 id="system"> 22. system 包含 os, runtime, shell command 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="system"> 23. system 包含 os, runtime, shell command 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/system"
@@ -1942,13 +2049,11 @@ import "github.com/duke-git/lancet/v2/system"
- **<big>KillProcess</big>** : 杀掉进程。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#KillProcess)]
[[play](https://go.dev/play/p/XKmvV-ExBWa)]
- **<big>GetProcessInfo</big>** : 根据进程id获取进程信息。
- **<big>GetProcessInfo</big>** : 根据进程 id 获取进程信息。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#GetProcessInfo)]
[[play](https://go.dev/play/p/NQDVywEYYx7)]
<h3 id="tuple"> 23. Tuple 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="tuple"> 24. Tuple 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/tuple"
@@ -2065,7 +2170,7 @@ import "github.com/duke-git/lancet/v2/tuple"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/tuple.md#Unzip10)]
[[play](https://go.dev/play/p/-taQB6Wfre_z)]
<h3 id="validator"> 24. validator 验证器包,包含常用字符串格式验证函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="validator"> 25. validator 验证器包,包含常用字符串格式验证函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/validator"
@@ -2109,7 +2214,7 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsCreditCard</big>** : 验证字符串是否是信用卡号码。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsCreditCard)]
[[play](https://go.dev/play/p/sNwwL6B0-v4)]
- **<big>IsDns</big>** : 验证字符串是否是有效dns。
- **<big>IsDns</big>** : 验证字符串是否是有效 dns。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsDns)]
[[play](https://go.dev/play/p/jlYApVLLGTZ)]
- **<big>IsEmail</big>** : 验证字符串是否是有效电子邮件地址。
@@ -2130,6 +2235,9 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsNumberStr</big>** : 验证字符串是否是可以转换为数字。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsNumberStr)]
[[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/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)]
@@ -2145,12 +2253,15 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsIp</big>** : 验证字符串是否是 ip 地址。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsIp)]
[[play](https://go.dev/play/p/FgcplDvmxoD)]
- **<big>IsIpV4</big>** : 验证字符串是否是ipv4地址。
- **<big>IsIpV4</big>** : 验证字符串是否是 ipv4 地址。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsIpV4)]
[[play](https://go.dev/play/p/zBGT99EjaIu)]
- **<big>IsIpV6</big>** : 验证字符串是否是ipv6地址。
- **<big>IsIpV6</big>** : 验证字符串是否是 ipv6 地址。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsIpV6)]
[[play](https://go.dev/play/p/AHA0r0AzIdC)]
- **<big>IsIpPort</big>** : 检查字符串是否是 ip:port 格式。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsIpPort)]
[[play](https://go.dev/play/p/xUmls_b9L29)]
- **<big>IsStrongPassword</big>** : 验证字符串是否是强密码:(字母+数字+特殊字符)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsStrongPassword)]
[[play](https://go.dev/play/p/QHdVcSQ3uDg)]
@@ -2163,10 +2274,10 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsZeroValue</big>** : 判断传入的参数值是否为零值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsZeroValue)]
[[play](https://go.dev/play/p/UMrwaDCi_t4)]
- **<big>IsGBK</big>** : 检查数据编码是否为gbk汉字内部代码扩展规范
- **<big>IsGBK</big>** : 检查数据编码是否为 gbk汉字内部代码扩展规范
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsGBK)]
[[play](https://go.dev/play/p/E2nt3unlmzP)]
- **<big>IsASCII</big>** : 验证字符串全部为ASCII字符。
- **<big>IsASCII</big>** : 验证字符串全部为 ASCII 字符。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsASCII)]
[[play](https://go.dev/play/p/hfQNPLX0jNa)]
- **<big>IsPrintable</big>** : 检查字符串是否全部为可打印字符。
@@ -2181,13 +2292,13 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsBase64URL</big>** : 检查字符串是否是有效的 base64 url。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsBase64URL)]
[[play](https://go.dev/play/p/vhl4mr8GZ6S)]
- **<big>IsJWT</big>** : 检查字符串是否是有效的JSON Web Token (JWT)。
- **<big>IsJWT</big>** : 检查字符串是否是有效的 JSON Web Token (JWT)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsJWT)]
[[play](https://go.dev/play/p/R6Op7heJbKI)]
- **<big>IsVisa</big>** : 检查字符串是否是有效的visa卡号。
- **<big>IsVisa</big>** : 检查字符串是否是有效的 visa 卡号。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsVisa)]
[[play](https://go.dev/play/p/SdS2keOyJsl)]
- **<big>IsMasterCard</big>** : 检查字符串是否是有效的MasterCard卡号。
- **<big>IsMasterCard</big>** : 检查字符串是否是有效的 MasterCard 卡号。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsMasterCard)]
[[play](https://go.dev/play/p/CwWBFRrG27b)]
- **<big>IsAmericanExpress</big>** : 检查字符串是否是有效的 American Express 卡号。
@@ -2200,7 +2311,7 @@ import "github.com/duke-git/lancet/v2/validator"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChinaUnionPay)]
[[play](https://go.dev/play/p/yafpdxLiymu)]
<h3 id="xerror"> 25. xerror 包实现一些错误处理函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="xerror"> 26. xerror 包实现一些错误处理函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/xerror"
@@ -2223,33 +2334,33 @@ import "github.com/duke-git/lancet/v2/xerror"
- **<big>XError_Unwrap</big>** : 解构 XEerror 为 error 对象。适配 github.com/pkg/errors。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_Unwrap)
[[play](https://go.dev/play/p/VUXJ8BST4c6)]
- **<big>XError_With</big>** : 添加与XError对象的键和值。
- **<big>XError_With</big>** : 添加与 XError 对象的键和值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_With)]
[[play](https://go.dev/play/p/ow8UISXX_Dp)]
- **<big>XError_Id</big>** : 设置XError对象的id。
- **<big>XError_Id</big>** : 设置 XError 对象的 id。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_Id)]
[[play](https://go.dev/play/p/X6HBlsy58U9)]
- **<big>XError_Is</big>** : 检查目标error是否为XError两个错误中的error.id是否匹配。
- **<big>XError_Is</big>** : 检查目标 error 是否为 XError两个错误中的 error.id 是否匹配。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_Is)]
[[play](https://go.dev/play/p/X6HBlsy58U9)]
- **<big>XError_Values</big>** : 返回由With设置的键和值的映射。将合并所有XError键和值。
- **<big>XError_Values</big>** : 返回由 With 设置的键和值的映射。将合并所有 XError 键和值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_Values)]
[[play](https://go.dev/play/p/ow8UISXX_Dp)]
- **<big>XError_StackTrace</big>** : 返回与pkg/error兼容的堆栈信息。
- **<big>XError_StackTrace</big>** : 返回与 pkg/error 兼容的堆栈信息。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_StackTrace)]
[[play](https://go.dev/play/p/6FAvSQpa7pc)]
- **<big>XError_Info</big>** : 返回可打印的XError对象信息。
- **<big>XError_Info</big>** : 返回可打印的 XError 对象信息。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_Info)]
[[play](https://go.dev/play/p/1ZX0ME1F-Jb)]
- **<big>XError_Error</big>** : 实现标准库的error接口。
- **<big>XError_Error</big>** : 实现标准库的 error 接口。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#XError_Error)]
[[play](https://go.dev/play/p/w4oWZts7q7f)]
- **<big>TryUnwrap</big>** : 检查error, 如果errnil则展开则它返回一个有效值如果err不是nilUnwrap使用err发生panic。
- **<big>TryUnwrap</big>** : 检查 error, 如果 errnil 则展开,则它返回一个有效值,如果 err 不是 nilUnwrap 使用 err 发生 panic。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#TryUnwrap)]
[[play](https://go.dev/play/p/acyZVkNZEeW)]
- **<big>TryCatch</big>** : 简单实现的java风格异常处理try-catch-finally
- **<big>TryCatch</big>** : 简单实现的 java 风格异常处理try-catch-finally
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/xerror.md#TryCatch)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/D5Mdb0mRj0P)]
## 如何贡献代码
@@ -2257,7 +2368,7 @@ import "github.com/duke-git/lancet/v2/xerror"
## 贡献者
感谢所有为lancet贡献过代码的人
感谢所有为 lancet 贡献过代码的人!
<a href="https://github.com/duke-git/lancet/graphs/contributors">
<img src="https://contrib.rocks/image?repo=duke-git/lancet" />
@@ -2265,4 +2376,4 @@ import "github.com/duke-git/lancet/v2/xerror"
## GitHub Stars
[![Star History Chart](https://api.star-history.com/svg?repos=duke-git/lancet&type=Date)](https://star-history.com/#duke-git/lancet&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=duke-git/lancet&type=Date)](https://star-history.com/#duke-git/lancet&Date)

View File

@@ -157,10 +157,10 @@ func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T)
// Play: https://go.dev/play/p/qmWSy1NVF-Y
func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T {
valStream := make(chan T)
go func() {
defer close(valStream)
wg := sync.WaitGroup{}
defer wg.Wait()
for {
var stream <-chan T
select {
@@ -169,19 +169,22 @@ func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-c
return
}
stream = maybeStream
wg.Add(1)
case <-ctx.Done():
return
}
for val := range c.OrDone(ctx, stream) {
select {
case valStream <- val:
case <-ctx.Done():
go func() {
defer wg.Done()
for val := range c.OrDone(ctx, stream) {
select {
case valStream <- val:
case <-ctx.Done():
}
}
}
}()
}
}()
return valStream
}

View File

@@ -3,6 +3,7 @@ package concurrency
import (
"context"
"fmt"
"log"
"time"
)
@@ -168,7 +169,8 @@ func ExampleChannel_Tee() {
func ExampleChannel_Bridge() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
m1 := make(map[int]int)
m2 := make(map[int]int)
c := NewChannel[int]()
genVals := func() <-chan <-chan int {
out := make(chan (<-chan int))
@@ -177,6 +179,7 @@ func ExampleChannel_Bridge() {
for i := 1; i <= 5; i++ {
stream := make(chan int, 1)
stream <- i
m1[i]++
close(stream)
out <- stream
}
@@ -185,12 +188,171 @@ func ExampleChannel_Bridge() {
}
for v := range c.Bridge(ctx, genVals()) {
fmt.Println(v)
m2[v]++
}
for k, v := range m1 {
fmt.Println(m2[k] == v)
}
// Output:
// 1
// 2
// 3
// 4
// 5
// true
// true
// true
// true
// true
}
func ExampleKeyedLocker_Do() {
locker := NewKeyedLocker[string](2 * time.Second)
task := func() {
fmt.Println("Executing task...")
time.Sleep(1 * time.Second)
fmt.Println("Task completed.")
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := locker.Do(ctx, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel2()
if err := locker.Do(ctx2, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
// Output:
// Executing task...
// Task completed.
// Task successfully executed.
// Executing task...
// Task completed.
// Task successfully executed.
}
func ExampleRWKeyedLocker_Lock() {
locker := NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.Lock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
func ExampleRWKeyedLocker_RLock() {
locker := NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.RLock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
func ExampleTryKeyedLocker() {
locker := NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
func ExampleTryKeyedLocker_TryLock() {
locker := NewTryKeyedLocker[string]()
key := "resource_key"
done := make(chan struct{})
go func() {
if locker.TryLock(key) {
time.Sleep(2 * time.Second)
locker.Unlock(key)
}
close(done)
}()
time.Sleep(100 * time.Millisecond)
if locker.TryLock(key) {
fmt.Println("Lock acquired")
locker.Unlock(key)
} else {
fmt.Println("Lock failed")
}
// wait for the goroutine to finish
<-done
fmt.Println("Retrying...")
time.Sleep(100 * time.Millisecond)
if locker.TryLock(key) {
fmt.Println("Lock acquired")
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
// Output:
// Lock failed
// Retrying...
// Lock acquired
// Lock released
}

View File

@@ -169,7 +169,8 @@ func TestTee(t *testing.T) {
func TestBridge(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestBridge")
m1 := make(map[int]int)
m2 := make(map[int]int)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -181,6 +182,7 @@ func TestBridge(t *testing.T) {
for i := 0; i < 10; i++ {
stream := make(chan int, 1)
stream <- i
m1[i]++
close(stream)
chanStream <- stream
}
@@ -188,9 +190,11 @@ func TestBridge(t *testing.T) {
return chanStream
}
index := 0
for val := range c.Bridge(ctx, genVals()) {
assert.Equal(index, val)
index++
m2[val]++
}
for k, v := range m1 {
assert.Equal(m2[k], v)
}
}

257
concurrency/keyed_locker.go Normal file
View File

@@ -0,0 +1,257 @@
// Copyright 2025 dudaodong@gmail.com. All rights reserved.
// Use of this source code is governed by MIT license
// Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel, locker.
package concurrency
import (
"context"
"sync"
"sync/atomic"
"time"
)
// KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.
type KeyedLocker[K comparable] struct {
locks sync.Map
ttl time.Duration
}
type lockEntry struct {
mu sync.Mutex
ref int32
timer atomic.Pointer[time.Timer]
}
// 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)
done := make(chan struct{})
go func() {
entry.mu.Lock()
defer entry.mu.Unlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// acquire tries to acquire a lock for the specified key.
func (l *KeyedLocker[K]) acquire(key K) *lockEntry {
lock, _ := l.locks.LoadOrStore(key, &lockEntry{})
entry := lock.(*lockEntry)
atomic.AddInt32(&entry.ref, 1)
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
return entry
}
// release releases the lock for the specified key.
func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) {
if atomic.AddInt32(&entry.ref, -1) == 0 {
entry.mu.Lock()
defer entry.mu.Unlock()
if entry.ref == 0 {
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
l.locks.Delete(rawKey)
} else {
if entry.timer.Load() == nil {
t := time.AfterFunc(l.ttl, func() {
l.release(key, entry, rawKey)
})
entry.timer.Store(t)
}
}
}
}
// RWKeyedLocker is a read-write version of KeyedLocker.
type RWKeyedLocker[K comparable] struct {
locks sync.Map
ttl time.Duration
}
type rwLockEntry struct {
mu sync.RWMutex
ref int32
timer atomic.Pointer[time.Timer]
}
// 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)
done := make(chan struct{})
go func() {
entry.mu.RLock()
defer entry.mu.RUnlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// 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)
done := make(chan struct{})
go func() {
entry.mu.Lock()
defer entry.mu.Unlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// acquire tries to acquire a read lock for the specified key.
func (l *RWKeyedLocker[K]) acquire(key K) *rwLockEntry {
actual, _ := l.locks.LoadOrStore(key, &rwLockEntry{})
entry := actual.(*rwLockEntry)
atomic.AddInt32(&entry.ref, 1)
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
return entry
}
// release releases the lock for the specified key.
func (l *RWKeyedLocker[K]) release(entry *rwLockEntry, rawKey K) {
if atomic.AddInt32(&entry.ref, -1) == 0 {
timer := time.AfterFunc(l.ttl, func() {
if atomic.LoadInt32(&entry.ref) == 0 {
l.locks.Delete(rawKey)
}
})
entry.timer.Store(timer)
}
}
// TryKeyedLocker is a non-blocking version of KeyedLocker.
// It allows for trying to acquire a lock without blocking if the lock is already held.
type TryKeyedLocker[K comparable] struct {
mu sync.Mutex
locks map[K]*casMutex
}
// 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()
lock, ok := l.locks[key]
if !ok {
lock = &casMutex{}
l.locks[key] = lock
}
l.mu.Unlock()
return lock.TryLock()
}
// 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()
lock, ok := l.locks[key]
if ok {
lock.Unlock()
if lock.lock == 0 {
delete(l.locks, key)
}
}
}
// casMutex is a simple mutex that uses atomic operations to provide a non-blocking lock.
type casMutex struct {
lock int32
}
// TryLock tries to acquire the lock without blocking.
// It returns true if the lock was acquired, false otherwise.
func (m *casMutex) TryLock() bool {
return atomic.CompareAndSwapInt32(&m.lock, 0, 1)
}
// Unlock releases the lock.
func (m *casMutex) Unlock() {
atomic.StoreInt32(&m.lock, 0)
}

View File

@@ -0,0 +1,230 @@
package concurrency
import (
"context"
"strconv"
"sync"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
func TestKeyedLocker_SerialExecutionSameKey(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_SerialExecutionSameKey")
locker := NewKeyedLocker[string](100 * time.Millisecond)
var result []int
var mu sync.Mutex
wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
err := locker.Do(context.Background(), "key1", func() {
time.Sleep(10 * time.Millisecond)
mu.Lock()
defer mu.Unlock()
result = append(result, i)
})
assert.IsNil(err)
}(i)
}
wg.Wait()
assert.Equal(5, len(result))
}
func TestKeyedLocker_ParallelExecutionDifferentKeys(t *testing.T) {
locker := NewKeyedLocker[string](100 * time.Millisecond)
start := time.Now()
wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := "key" + strconv.Itoa(i)
locker.Do(context.Background(), key, func() {
time.Sleep(50 * time.Millisecond)
})
}(i)
}
wg.Wait()
elapsed := time.Since(start)
if elapsed > 100*time.Millisecond {
t.Errorf("parallel execution took too long: %s", elapsed)
}
}
func TestKeyedLocker_ContextTimeout(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_ContextTimeout")
locker := NewKeyedLocker[string](100 * time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
// Lock key before calling
go func() {
_ = locker.Do(context.Background(), "key-timeout", func() {
time.Sleep(50 * time.Millisecond)
})
}()
time.Sleep(1 * time.Millisecond) // ensure lock is acquired first
err := locker.Do(ctx, "key-timeout", func() {
t.Error("should not execute")
})
assert.IsNotNil(err)
}
func TestKeyedLocker_LockReleaseAfterTTL(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
locker := NewKeyedLocker[string](50 * time.Millisecond)
err := locker.Do(context.Background(), "ttl-key", func() {})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// Wait for TTL to pass
time.Sleep(100 * time.Millisecond)
err = locker.Do(context.Background(), "ttl-key", func() {})
assert.IsNil(err)
}
func TestRWKeyedLocker_LockAndUnlock(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
locker := NewRWKeyedLocker[string](500 * time.Millisecond)
var locked bool
err := locker.Lock(context.Background(), "key1", func() {
locked = true
})
assert.IsNil(err)
assert.Equal(true, locked)
}
func TestRWKeyedLocker_RLockParallel(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
locker := NewRWKeyedLocker[string](1 * time.Second)
var mu sync.Mutex
var count int
wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
err := locker.RLock(context.Background(), "shared-key", func() {
time.Sleep(10 * time.Millisecond)
mu.Lock()
count++
mu.Unlock()
})
assert.IsNil(err)
}()
}
wg.Wait()
assert.Equal(5, count)
}
func TestRWKeyedLocker_LockTimeout(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRWKeyedLocker_LockTimeout")
locker := NewRWKeyedLocker[string](1 * time.Second)
start := make(chan struct{})
go func() {
locker.Lock(context.Background(), "key-timeout", func() {
close(start)
time.Sleep(200 * time.Millisecond)
})
}()
<-start
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
err := locker.Lock(ctx, "key-timeout", func() {
t.Error("should not reach here")
})
assert.IsNotNil(err)
}
func TestTryKeyedLocker_SimpleLockUnlock(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryKeyedLocker_SimpleLockUnlock")
locker := NewTryKeyedLocker[string]()
ok := locker.TryLock("key1")
assert.Equal(true, ok)
ok = locker.TryLock("key1")
assert.Equal(false, ok)
locker.Unlock("key1")
ok = locker.TryLock("key1")
assert.Equal(true, ok)
locker.Unlock("key1")
}
func TestTryKeyedLocker_ParallelTry(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryKeyedLocker_ParallelTry")
locker := NewTryKeyedLocker[string]()
var wg sync.WaitGroup
var mu sync.Mutex
var count int
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
ok := locker.TryLock("key" + strconv.Itoa(i))
mu.Lock()
if ok {
count++
}
mu.Unlock()
time.Sleep(10 * time.Millisecond)
if ok {
locker.Unlock("key" + strconv.Itoa(i))
}
}(i)
}
wg.Wait()
assert.Equal(5, count)
assert.Equal(0, len(locker.locks))
}

View File

@@ -108,6 +108,13 @@ func ToString(value any) string {
if value == nil {
return ""
}
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
return ""
}
return ToString(rv.Elem().Interface())
}
switch val := value.(type) {
case float32:
@@ -143,12 +150,8 @@ func ToString(value any) string {
if err != nil {
return ""
}
return string(b)
// todo: maybe we should't supprt other type conversion
// v := reflect.ValueOf(value)
// log.Panicf("Unsupported data type: %s ", v.String())
// return ""
return string(b)
}
}
@@ -486,7 +489,7 @@ func ToRawUrlBase64(value any) string {
}
// ToBigInt converts an integer of any supported type (int, int64, uint64, etc.) to *big.Int
// Play: todo
// Play: https://go.dev/play/p/X3itkCxwB_x
func ToBigInt[T any](v T) (*big.Int, error) {
result := new(big.Int)

View File

@@ -142,13 +142,24 @@ func TestToString(t *testing.T) {
}
aStruct := TestStruct{Name: "TestStruct"}
i32Val := int32(123)
i64Val := int64(123)
iZeroVal := 0
fVal := 12.3
sVal := "abc"
var iNilPointer *int
var sNilPointer *string
cases := []any{
"", nil,
int(0), int8(1), int16(-1), int32(123), int64(123),
uint(123), uint8(123), uint16(123), uint32(123), uint64(123),
float64(12.3), float32(12.3),
true, false,
[]int{1, 2, 3}, aMap, aStruct, []byte{104, 101, 108, 108, 111}}
[]int{1, 2, 3}, aMap, aStruct, []byte{104, 101, 108, 108, 111},
&i32Val, &i64Val, &fVal, &sVal, &aStruct, iNilPointer, sNilPointer,
&iZeroVal,
}
expected := []string{
"", "",
@@ -157,6 +168,8 @@ func TestToString(t *testing.T) {
"12.3", "12.3",
"true", "false",
"[1,2,3]", "{\"a\":1,\"b\":2,\"c\":3}", "{\"Name\":\"TestStruct\"}", "hello",
"123", "123", "12.3", "abc", "{\"Name\":\"TestStruct\"}", "", "",
"0",
}
for i := 0; i < len(cases); i++ {

View File

@@ -6,7 +6,6 @@
package cryptor
import (
"bufio"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
@@ -66,34 +65,37 @@ func Md5ByteWithBase64(data []byte) string {
// Md5File return the md5 value of file.
func Md5File(filename string) (string, error) {
if fileInfo, err := os.Stat(filename); err != nil {
return "", err
} else if fileInfo.IsDir() {
return "", nil
}
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
hash := md5.New()
chunkSize := 65536
for buf, reader := make([]byte, chunkSize), bufio.NewReader(file); ; {
n, err := reader.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return "", err
}
hash.Write(buf[:n])
stat, err := file.Stat()
if err != nil {
return "", err
}
if stat.IsDir() {
return "", nil
}
checksum := fmt.Sprintf("%x", hash.Sum(nil))
return checksum, nil
hash := md5.New()
buf := make([]byte, 65536) // 64KB
for {
n, err := file.Read(buf)
if err != nil && err != io.EOF {
return "", err
}
if n > 0 {
hash.Write(buf[:n])
}
if err == io.EOF {
break
}
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
// HmacMd5 return the hmac hash of string use md5.

View File

@@ -15,39 +15,40 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"os"
"strings"
)
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/jT5irszHx-j
func AesEcbEncrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
length := (len(data) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
blockSize := aes.BlockSize
dataLen := len(data)
padding := blockSize - (dataLen % blockSize)
paddedLen := dataLen + padding
copy(plain, data)
paddedData := make([]byte, paddedLen)
copy(paddedData, data)
pad := byte(len(plain) - len(data))
for i := len(data); i < len(plain); i++ {
plain[i] = pad
for i := dataLen; i < paddedLen; i++ {
paddedData[i] = byte(padding)
}
encrypted := make([]byte, len(plain))
cipher, _ := aes.NewCipher(generateAesKey(key, size))
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
encrypted := make([]byte, paddedLen)
for bs := 0; bs < paddedLen; bs += blockSize {
cipher.Encrypt(encrypted[bs:], paddedData[bs:])
}
return encrypted
@@ -57,77 +58,107 @@ func AesEcbEncrypt(data, key []byte) []byte {
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/jT5irszHx-j
func AesEcbDecrypt(encrypted, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
cipher, _ := aes.NewCipher(generateAesKey(key, size))
blockSize := aes.BlockSize
if len(encrypted)%blockSize != 0 {
panic("aes: encrypted data length is not a multiple of block size")
}
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
decrypted := make([]byte, len(encrypted))
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
for i := 0; i < len(encrypted); i += blockSize {
cipher.Decrypt(decrypted[i:], encrypted[i:])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
if len(decrypted) == 0 {
return nil
}
padding := int(decrypted[len(decrypted)-1])
if padding == 0 || padding > blockSize {
panic("aes: invalid PKCS#7 padding")
}
for i := len(decrypted) - padding; i < len(decrypted); i++ {
if decrypted[i] != byte(padding) {
panic("aes: invalid PKCS#7 padding content")
}
}
return decrypted[:trim]
return decrypted[:len(decrypted)-padding]
}
// AesCbcEncrypt encrypt data with key use AES CBC algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/IOq_g8_lKZD
func AesCbcEncrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, _ := aes.NewCipher(key)
data = pkcs7Padding(data, block.BlockSize())
block, err := aes.NewCipher(key)
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
encrypted := make([]byte, aes.BlockSize+len(data))
iv := encrypted[:aes.BlockSize]
padding := aes.BlockSize - len(data)%aes.BlockSize
padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...)
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
panic("aes: failed to generate IV: " + err.Error())
}
encrypted := make([]byte, len(padded))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encrypted[aes.BlockSize:], data)
mode.CryptBlocks(encrypted, padded)
return encrypted
return append(iv, encrypted...)
}
// AesCbcDecrypt decrypt data with key use AES CBC algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/IOq_g8_lKZD
func AesCbcDecrypt(encrypted, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, _ := aes.NewCipher(key)
if len(encrypted) < aes.BlockSize {
panic("aes: ciphertext too short")
}
if len(encrypted)%aes.BlockSize != 0 {
panic("aes: ciphertext is not a multiple of the block size")
}
iv := encrypted[:aes.BlockSize]
encrypted = encrypted[aes.BlockSize:]
ciphertext := encrypted[aes.BlockSize:]
block, err := aes.NewCipher(key)
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
decrypted := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(encrypted, encrypted)
mode.CryptBlocks(decrypted, ciphertext)
decrypted := pkcs7UnPadding(encrypted)
return decrypted
return pkcs7UnPadding(decrypted)
}
// AesCtrCrypt encrypt data with key use AES CTR algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/SpaZO0-5Nsp
// deprecated: use AesCtrEncrypt and AesCtrDecrypt instead.
func AesCtrCrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, _ := aes.NewCipher(key)
@@ -141,158 +172,214 @@ func AesCtrCrypt(data, key []byte) []byte {
return dst
}
// AesCfbEncrypt encrypt data with key use AES CFB algorithm
// AesCtrEncrypt encrypt data with key use AES CTR algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/tfkF10B13kH
func AesCfbEncrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
// 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)")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
panic("aes: failed to create cipher: " + err.Error())
}
encrypted := make([]byte, aes.BlockSize+len(data))
iv := encrypted[:aes.BlockSize]
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
panic("aes: failed to generate IV: " + err.Error())
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
stream := cipher.NewCTR(block, iv)
ciphertext := make([]byte, len(data))
stream.XORKeyStream(ciphertext, data)
return encrypted
return append(iv, ciphertext...)
}
// AesCtrDecrypt decrypt data with key use AES CTR algorithm
// len(key) should be 16, 24 or 32.
// 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)")
}
if len(encrypted) < aes.BlockSize {
panic("aes: invalid ciphertext length")
}
iv := encrypted[:aes.BlockSize]
ciphertext := encrypted[aes.BlockSize:]
block, err := aes.NewCipher(key)
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
stream := cipher.NewCTR(block, iv)
plaintext := make([]byte, len(ciphertext))
stream.XORKeyStream(plaintext, ciphertext)
return plaintext
}
// AesCfbEncrypt encrypt data with key use AES CFB algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/tfkF10B13kH
func AesCfbEncrypt(data, key []byte) []byte {
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, err := aes.NewCipher(key)
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("aes: failed to generate IV: " + err.Error())
}
ciphertext := make([]byte, len(data))
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext, data)
return append(iv, ciphertext...)
}
// AesCfbDecrypt decrypt data with key use AES CFB algorithm
// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/tfkF10B13kH
func AesCfbDecrypt(encrypted, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
if len(encrypted) < aes.BlockSize {
panic("encrypted data is too short")
panic("aes: encrypted data too short")
}
block, _ := aes.NewCipher(key)
iv := encrypted[:aes.BlockSize]
encrypted = encrypted[aes.BlockSize:]
ciphertext := encrypted[aes.BlockSize:]
block, err := aes.NewCipher(key)
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
plaintext := make([]byte, len(ciphertext))
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(plaintext, ciphertext)
stream.XORKeyStream(encrypted, encrypted)
return encrypted
return plaintext
}
// AesOfbEncrypt encrypt data with key use AES OFB algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/VtHxtkUj-3F
func AesOfbEncrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
panic("aes: failed to create cipher: " + err.Error())
}
data = pkcs7Padding(data, aes.BlockSize)
encrypted := make([]byte, aes.BlockSize+len(data))
iv := encrypted[:aes.BlockSize]
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
panic("aes: failed to generate IV: " + err.Error())
}
ciphertext := make([]byte, len(data))
stream := cipher.NewOFB(block, iv)
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
stream.XORKeyStream(ciphertext, data)
return encrypted
return append(iv, ciphertext...)
}
// AesOfbDecrypt decrypt data with key use AES OFB algorithm
// len(key) should be 16, 24 or 32.
// Play: https://go.dev/play/p/VtHxtkUj-3F
func AesOfbDecrypt(data, key []byte) []byte {
size := len(key)
if size != 16 && size != 24 && size != 32 {
panic("key length shoud be 16 or 24 or 32")
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
if len(data) < aes.BlockSize {
panic("aes: encrypted data too short")
}
iv := data[:aes.BlockSize]
data = data[aes.BlockSize:]
if len(data)%aes.BlockSize != 0 {
return nil
ciphertext := data[aes.BlockSize:]
block, err := aes.NewCipher(key)
if err != nil {
panic("aes: failed to create cipher: " + err.Error())
}
decrypted := make([]byte, len(data))
mode := cipher.NewOFB(block, iv)
mode.XORKeyStream(decrypted, data)
plaintext := make([]byte, len(ciphertext))
stream := cipher.NewOFB(block, iv)
stream.XORKeyStream(plaintext, ciphertext)
decrypted = pkcs7UnPadding(decrypted)
return decrypted
return plaintext
}
// AesGcmEncrypt encrypt data with key use AES GCM algorithm
// Play: https://go.dev/play/p/rUt0-DmsPCs
func AesGcmEncrypt(data, key []byte) []byte {
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
panic("aes: failed to create cipher: " + err.Error())
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(err)
panic("aes: failed to create GCM: " + err.Error())
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
panic(err)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
panic("aes: failed to generate nonce: " + err.Error())
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
ciphertext := gcm.Seal(nil, nonce, data, nil)
return ciphertext
return append(nonce, ciphertext...)
}
// AesGcmDecrypt decrypt data with key use AES GCM algorithm
// Play: https://go.dev/play/p/rUt0-DmsPCs
func AesGcmDecrypt(data, key []byte) []byte {
if !isAesKeyLengthValid(len(key)) {
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
panic("aes: failed to create cipher: " + err.Error())
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(err)
panic("aes: failed to create GCM: " + err.Error())
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
panic("ciphertext too short")
panic("aes: ciphertext too short")
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
panic(err)
panic("aes: decryption failed: " + err.Error())
}
return plaintext
@@ -302,20 +389,17 @@ func AesGcmDecrypt(data, key []byte) []byte {
// len(key) should be 8.
// Play: https://go.dev/play/p/8qivmPeZy4P
func DesEcbEncrypt(data, key []byte) []byte {
length := (len(data) + des.BlockSize) / des.BlockSize
plain := make([]byte, length*des.BlockSize)
copy(plain, data)
pad := byte(len(plain) - len(data))
for i := len(data); i < len(plain); i++ {
plain[i] = pad
cipher, err := des.NewCipher(generateDesKey(key))
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
encrypted := make([]byte, len(plain))
cipher, _ := des.NewCipher(generateDesKey(key))
blockSize := cipher.BlockSize()
padded := pkcs5Padding(data, blockSize)
encrypted := make([]byte, len(padded))
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
for i := 0; i < len(padded); i += blockSize {
cipher.Encrypt(encrypted[i:], padded[i:])
}
return encrypted
@@ -325,42 +409,50 @@ func DesEcbEncrypt(data, key []byte) []byte {
// len(key) should be 8.
// Play: https://go.dev/play/p/8qivmPeZy4P
func DesEcbDecrypt(encrypted, key []byte) []byte {
cipher, _ := des.NewCipher(generateDesKey(key))
cipher, err := des.NewCipher(generateDesKey(key))
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
blockSize := cipher.BlockSize()
if len(encrypted)%blockSize != 0 {
panic("des: invalid encrypted data length")
}
decrypted := make([]byte, len(encrypted))
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
for i := 0; i < len(encrypted); i += blockSize {
cipher.Decrypt(decrypted[i:], encrypted[i:])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}
return decrypted[:trim]
// Remove padding
return pkcs5UnPadding(decrypted)
}
// DesCbcEncrypt encrypt data with key use DES CBC algorithm
// len(key) should be 8.
// Play: https://go.dev/play/p/4cC4QvWfe3_1
func DesCbcEncrypt(data, key []byte) []byte {
size := len(key)
if size != 8 {
panic("key length shoud be 8")
if len(key) != 8 {
panic("des: key length must be 8 bytes")
}
block, _ := des.NewCipher(key)
data = pkcs7Padding(data, block.BlockSize())
block, err := des.NewCipher(key)
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
encrypted := make([]byte, des.BlockSize+len(data))
iv := encrypted[:des.BlockSize]
blockSize := block.BlockSize()
data = pkcs7Padding(data, blockSize)
encrypted := make([]byte, blockSize+len(data))
iv := encrypted[:blockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
panic("des: failed to generate IV: " + err.Error())
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encrypted[des.BlockSize:], data)
mode.CryptBlocks(encrypted[blockSize:], data)
return encrypted
}
@@ -369,26 +461,33 @@ func DesCbcEncrypt(data, key []byte) []byte {
// len(key) should be 8.
// Play: https://go.dev/play/p/4cC4QvWfe3_1
func DesCbcDecrypt(encrypted, key []byte) []byte {
size := len(key)
if size != 8 {
panic("key length shoud be 8")
if len(key) != 8 {
panic("des: key length must be 8 bytes")
}
block, _ := des.NewCipher(key)
block, err := des.NewCipher(key)
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
iv := encrypted[:des.BlockSize]
encrypted = encrypted[des.BlockSize:]
blockSize := block.BlockSize()
if len(encrypted) < blockSize || len(encrypted)%blockSize != 0 {
panic("des: invalid encrypted data length")
}
iv := encrypted[:blockSize]
ciphertext := encrypted[blockSize:]
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(encrypted, encrypted)
mode.CryptBlocks(ciphertext, ciphertext)
decrypted := pkcs7UnPadding(encrypted)
return decrypted
return pkcs7UnPadding(ciphertext)
}
// DesCtrCrypt encrypt data with key use DES CTR algorithm
// len(key) should be 8.
// Play: https://go.dev/play/p/9-T6OjKpcdw
// deprecated: use DesCtrEncrypt and DesCtrDecrypt instead.
func DesCtrCrypt(data, key []byte) []byte {
size := len(key)
if size != 8 {
@@ -406,25 +505,83 @@ func DesCtrCrypt(data, key []byte) []byte {
return dst
}
// DesCfbEncrypt encrypt data with key use DES CFB algorithm
// DesCtrEncrypt encrypt data with key use DES CTR algorithm
// len(key) should be 8.
// Play: https://go.dev/play/p/y-eNxcFBlxL
func DesCfbEncrypt(data, key []byte) []byte {
size := len(key)
if size != 8 {
panic("key length shoud be 8")
// 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")
}
block, err := des.NewCipher(key)
if err != nil {
panic(err)
panic("des: failed to create cipher: " + err.Error())
}
iv := make([]byte, block.BlockSize())
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("des: failed to generate IV: " + err.Error())
}
stream := cipher.NewCTR(block, iv)
encrypted := make([]byte, len(data))
stream.XORKeyStream(encrypted, data)
// 返回前缀包含 IV便于解密
return append(iv, encrypted...)
}
// DesCtrDecrypt decrypt data with key use DES CTR algorithm
// len(key) should be 8.
// 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")
}
block, err := des.NewCipher(key)
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
blockSize := block.BlockSize()
if len(encrypted) < blockSize {
panic("des: ciphertext too short")
}
iv := encrypted[:blockSize]
ciphertext := encrypted[blockSize:]
stream := cipher.NewCTR(block, iv)
decrypted := make([]byte, len(ciphertext))
stream.XORKeyStream(decrypted, ciphertext)
return decrypted
}
// DesCfbEncrypt encrypt data with key use DES CFB algorithm
// len(key) should be 8.
// Play: https://go.dev/play/p/y-eNxcFBlxL
func DesCfbEncrypt(data, key []byte) []byte {
if len(key) != 8 {
panic("des: key length must be 8 bytes")
}
block, err := des.NewCipher(key)
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
iv := make([]byte, des.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("des: failed to generate IV: " + err.Error())
}
encrypted := make([]byte, des.BlockSize+len(data))
iv := encrypted[:des.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
copy(encrypted[:des.BlockSize], iv)
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted[des.BlockSize:], data)
@@ -436,44 +593,51 @@ func DesCfbEncrypt(data, key []byte) []byte {
// len(encrypted) should be great than 16, len(key) should be 8.
// Play: https://go.dev/play/p/y-eNxcFBlxL
func DesCfbDecrypt(encrypted, key []byte) []byte {
size := len(key)
if size != 8 {
panic("key length shoud be 8")
if len(key) != 8 {
panic("des: key length must be 8 bytes")
}
block, _ := des.NewCipher(key)
if len(encrypted) < des.BlockSize {
panic("encrypted data is too short")
block, err := des.NewCipher(key)
if err != nil {
panic("des: failed to create cipher: " + err.Error())
}
if len(encrypted) < des.BlockSize {
panic("des: encrypted data too short")
}
iv := encrypted[:des.BlockSize]
encrypted = encrypted[des.BlockSize:]
ciphertext := encrypted[des.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(encrypted, encrypted)
stream.XORKeyStream(ciphertext, ciphertext)
return encrypted
return ciphertext
}
// DesOfbEncrypt encrypt data with key use DES OFB algorithm
// len(key) should be 8.
// Play: https://go.dev/play/p/74KmNadjN1J
func DesOfbEncrypt(data, key []byte) []byte {
size := len(key)
if size != 8 {
panic("key length shoud be 8")
if len(key) != 8 {
panic("des: key length must be 8 bytes")
}
block, err := des.NewCipher(key)
if err != nil {
panic(err)
panic("des: failed to create cipher: " + err.Error())
}
data = pkcs7Padding(data, des.BlockSize)
encrypted := make([]byte, des.BlockSize+len(data))
iv := encrypted[:des.BlockSize]
iv := make([]byte, des.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
panic("des: failed to generate IV: " + err.Error())
}
encrypted := make([]byte, des.BlockSize+len(data))
copy(encrypted[:des.BlockSize], iv)
stream := cipher.NewOFB(block, iv)
stream.XORKeyStream(encrypted[des.BlockSize:], data)
@@ -484,25 +648,25 @@ func DesOfbEncrypt(data, key []byte) []byte {
// len(key) should be 8.
// Play: https://go.dev/play/p/74KmNadjN1J
func DesOfbDecrypt(data, key []byte) []byte {
size := len(key)
if size != 8 {
panic("key length shoud be 8")
if len(key) != 8 {
panic("des: key length must be 8 bytes")
}
block, err := des.NewCipher(key)
if err != nil {
panic(err)
panic("des: failed to create cipher: " + err.Error())
}
if len(data) < des.BlockSize {
panic("des: encrypted data too short")
}
iv := data[:des.BlockSize]
data = data[des.BlockSize:]
if len(data)%des.BlockSize != 0 {
return nil
}
ciphertext := data[des.BlockSize:]
decrypted := make([]byte, len(data))
mode := cipher.NewOFB(block, iv)
mode.XORKeyStream(decrypted, data)
stream := cipher.NewOFB(block, iv)
decrypted := make([]byte, len(ciphertext))
stream.XORKeyStream(decrypted, ciphertext)
decrypted = pkcs7UnPadding(decrypted)
@@ -565,7 +729,7 @@ func GenerateRsaKey(keySize int, priKeyFile, pubKeyFile string) error {
}
// RsaEncrypt encrypt data with ras algorithm.
// Play: https://go.dev/play/p/rDqTT01SPkZ
// Play: https://go.dev/play/p/7_zo6mrx-eX
func RsaEncrypt(data []byte, pubKeyFileName string) []byte {
file, err := os.Open(pubKeyFileName)
if err != nil {
@@ -600,7 +764,7 @@ func RsaEncrypt(data []byte, pubKeyFileName string) []byte {
}
// RsaDecrypt decrypt data with ras algorithm.
// Play: https://go.dev/play/p/rDqTT01SPkZ
// Play: https://go.dev/play/p/7_zo6mrx-eX
func RsaDecrypt(data []byte, privateKeyFileName string) []byte {
file, err := os.Open(privateKeyFileName)
if err != nil {
@@ -663,7 +827,7 @@ func RsaDecryptOAEP(ciphertext []byte, label []byte, key rsa.PrivateKey) ([]byte
}
// RsaSign signs the data with RSA.
// Play: todo
// Play: https://go.dev/play/p/qhsbf8BJ6Mf
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, error) {
privateKey, err := loadRasPrivateKey(privateKeyFileName)
if err != nil {
@@ -679,7 +843,7 @@ func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte,
}
// RsaVerifySign verifies the signature of the data with RSA.
// Play: todo
// Play: https://go.dev/play/p/qhsbf8BJ6Mf
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName string) error {
publicKey, err := loadRsaPublicKey(pubKeyFileName)
if err != nil {
@@ -693,118 +857,3 @@ func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName stri
return rsa.VerifyPKCS1v15(publicKey, hash, hashed, signature)
}
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
func loadRsaPublicKey(filename string) (*rsa.PublicKey, error) {
pubKeyData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(pubKeyData)
if block == nil {
return nil, errors.New("failed to decode PEM block containing the public key")
}
var pubKey *rsa.PublicKey
blockType := strings.ToUpper(block.Type)
if blockType == "RSA PUBLIC KEY" {
pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
// todo: here should be a bug, should return nil, err
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
pubKey, ok = key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
}
} else if blockType == "PUBLIC KEY" {
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
pubKey, ok = key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
} else {
return nil, errors.New("unsupported key type")
}
return pubKey, nil
}
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
func loadRasPrivateKey(filename string) (*rsa.PrivateKey, error) {
priKeyData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(priKeyData)
if block == nil {
return nil, errors.New("failed to decode PEM block containing the private key")
}
var privateKey *rsa.PrivateKey
blockType := strings.ToUpper(block.Type)
// PKCS#1 format
if blockType == "RSA PRIVATE KEY" {
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
} else if blockType == "PRIVATE KEY" { // PKCS#8 format
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
privateKey, ok = priKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
} else {
return nil, errors.New("unsupported key type")
}
return privateKey, nil
}
// hashData returns the hash value of the data, using the specified hash function
func hashData(hash crypto.Hash, data []byte) ([]byte, error) {
if !hash.Available() {
return nil, errors.New("unsupported hash algorithm")
}
var hashed []byte
switch hash {
case crypto.SHA224:
h := sha256.Sum224(data)
hashed = h[:]
case crypto.SHA256:
h := sha256.Sum256(data)
hashed = h[:]
case crypto.SHA384:
h := sha512.Sum384(data)
hashed = h[:]
case crypto.SHA512:
h := sha512.Sum512(data)
hashed = h[:]
default:
return nil, errors.New("unsupported hash algorithm")
}
return hashed, nil
}

View File

@@ -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"

View File

@@ -1,6 +1,17 @@
package cryptor
import "bytes"
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"errors"
"os"
"strings"
)
func generateAesKey(key []byte, size int) []byte {
genKey := make([]byte, size)
@@ -35,3 +46,139 @@ func pkcs7UnPadding(src []byte) []byte {
unPadding := int(src[length-1])
return src[:(length - unPadding)]
}
func pkcs5Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
func pkcs5UnPadding(data []byte) []byte {
length := len(data)
if length == 0 {
return nil
}
padLen := int(data[length-1])
if padLen == 0 || padLen > length {
return nil
}
return data[:length-padLen]
}
func isAesKeyLengthValid(n int) bool {
return n == 16 || n == 24 || n == 32
}
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
func loadRsaPublicKey(filename string) (*rsa.PublicKey, error) {
pubKeyData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(pubKeyData)
if block == nil {
return nil, errors.New("failed to decode PEM block containing the public key")
}
var pubKey *rsa.PublicKey
blockType := strings.ToUpper(block.Type)
if blockType == "RSA PUBLIC KEY" {
pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
pubKey, ok = key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
}
} else if blockType == "PUBLIC KEY" {
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
pubKey, ok = key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
} else {
return nil, errors.New("unsupported key type")
}
return pubKey, nil
}
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
func loadRasPrivateKey(filename string) (*rsa.PrivateKey, error) {
priKeyData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(priKeyData)
if block == nil {
return nil, errors.New("failed to decode PEM block containing the private key")
}
var privateKey *rsa.PrivateKey
blockType := strings.ToUpper(block.Type)
// PKCS#1 format
if blockType == "RSA PRIVATE KEY" {
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
} else if blockType == "PRIVATE KEY" { // PKCS#8 format
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
privateKey, ok = priKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
} else {
return nil, errors.New("unsupported key type")
}
return privateKey, nil
}
// hashData returns the hash value of the data, using the specified hash function
func hashData(hash crypto.Hash, data []byte) ([]byte, error) {
if !hash.Available() {
return nil, errors.New("unsupported hash algorithm")
}
var hashed []byte
switch hash {
case crypto.SHA224:
h := sha256.Sum224(data)
hashed = h[:]
case crypto.SHA256:
h := sha256.Sum256(data)
hashed = h[:]
case crypto.SHA384:
h := sha512.Sum384(data)
hashed = h[:]
case crypto.SHA512:
h := sha512.Sum512(data)
hashed = h[:]
default:
return nil, errors.New("unsupported hash algorithm")
}
return hashed, nil
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/duke-git/lancet/v2/internal"
)
func TestAesEcbEncrypt(t *testing.T) {
func TestAesEcbCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -16,11 +16,11 @@ func TestAesEcbEncrypt(t *testing.T) {
aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
assert := internal.NewAssert(t, "TestAesEcbEncrypt")
assert := internal.NewAssert(t, "TestAesEcbCrypt")
assert.Equal(data, string(aesEcbDecrypt))
}
func TestAesCbcEncrypt(t *testing.T) {
func TestAesCbcCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -29,7 +29,7 @@ func TestAesCbcEncrypt(t *testing.T) {
aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
assert := internal.NewAssert(t, "TestAesCbcEncrypt")
assert := internal.NewAssert(t, "TestAesCbcCrypt")
assert.Equal(data, string(aesCbcDecrypt))
}
@@ -39,14 +39,14 @@ func TestAesCtrCrypt(t *testing.T) {
data := "hello world"
key := "abcdefghijklmnop"
aesCtrCrypt := AesCtrCrypt([]byte(data), []byte(key))
aesCtrDeCrypt := AesCtrCrypt(aesCtrCrypt, []byte(key))
aesCtrCrypt := AesCtrEncrypt([]byte(data), []byte(key))
aesCtrDeCrypt := AesCtrDecrypt(aesCtrCrypt, []byte(key))
assert := internal.NewAssert(t, "TestAesCtrCrypt")
assert.Equal(data, string(aesCtrDeCrypt))
}
func TestAesCfbEncrypt(t *testing.T) {
func TestAesCfbCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -55,11 +55,11 @@ func TestAesCfbEncrypt(t *testing.T) {
aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
assert := internal.NewAssert(t, "TestAesCfbEncrypt")
assert := internal.NewAssert(t, "TestAesCfbCrypt")
assert.Equal(data, string(aesCfbDecrypt))
}
func TestAesOfbEncrypt(t *testing.T) {
func TestAesOfbCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -72,7 +72,7 @@ func TestAesOfbEncrypt(t *testing.T) {
assert.Equal(data, string(aesOfbDecrypt))
}
func TestDesEcbEncrypt(t *testing.T) {
func TestDesEcbCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -85,7 +85,7 @@ func TestDesEcbEncrypt(t *testing.T) {
assert.Equal(data, string(desEcbDecrypt))
}
func TestDesCbcEncrypt(t *testing.T) {
func TestDesCbcCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -104,14 +104,14 @@ func TestDesCtrCrypt(t *testing.T) {
data := "hello world"
key := "abcdefgh"
desCtrCrypt := DesCtrCrypt([]byte(data), []byte(key))
desCtrDeCrypt := DesCtrCrypt(desCtrCrypt, []byte(key))
desCtrCrypt := DesCtrEncrypt([]byte(data), []byte(key))
desCtrDeCrypt := DesCtrDecrypt(desCtrCrypt, []byte(key))
assert := internal.NewAssert(t, "TestDesCtrCrypt")
assert.Equal(data, string(desCtrDeCrypt))
}
func TestDesCfbEncrypt(t *testing.T) {
func TestDesCfbCrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -124,7 +124,7 @@ func TestDesCfbEncrypt(t *testing.T) {
assert.Equal(data, string(desCfbDecrypt))
}
func TestDesOfbEncrypt(t *testing.T) {
func TestDesOfbCrypt(t *testing.T) {
t.Parallel()
data := "hello world"

View File

@@ -1,51 +1,51 @@
-----BEGIN rsa private key-----
MIIJJwIBAAKCAgEAwGdN8KE2NTK41cVN7i6mZZS5J86gjNs0LJDHylb2GG+K4O8s
fq98EMKMJ2xGnKoVRJXucVohr5Eiuf5zgxQe9mpYDyDQ0vnpBkoNkfzJNZQvcQFa
IllUeH+eN4hSBhMegPspCr2BcmC/m/N40+PjBMDaHSzeNE2SyIuyflLC7GhQvHnk
cQXkqJVNC1yesV7zYKDV/xYPI0NxN0kE+4eR650A77jso4gloRUek0cpU4ztpe6Z
z4za7AEKHmZO8pVHpQJihbNMhItbo6BcgeOFWImXyJfcbCtu1ayciaJ0Q45Z1btu
F2wFwQjwZGt7DAvbzYwMBBiTpGxig/8kzibm7bL1Td7huW6Knqbfh4/v+2m+2aNK
1u0TfncFUr17wDDfeVj/xeNOSbPu2X/AppjVX0rxmvWN+AByFfAuw7/kqMK7QZlA
+5nNjmOojvryGOSu1he4PUnhRyJ7jofOIrLZ9YYBXpRiY/WB8t+xWIlaoPXFmsOx
SgMwzQFGMlnSFy7nK9T4znE0QFknOQNbELPEB4UGLOEa5Tg6YD2ORDDSqCzMRdkE
1kAgoMRC9DkRXE45KEcQlhMX9Pahwx3mPS4lMWABVudiEWyhGaR279OWjezqvtoL
LBJ5GD9QL1XqaqCaJjp+qsHmX8re+MTA3ZJuyUtRoPXPsKCe2nZ6Ma87DlUCAwEA
AQKCAgAWT+SJ8ygGI0ur/qV66Y4CWazfIOcdbo4uXNvOayc+zjCcxR+z0UXh6621
JKlLoa21tm1gV8NwSLRuPUPH/51Xlh2AI54T2Udco1nPhDERNY4K3M1HDnTtRF9k
sTpR2gW/j2DDDhbk3LNbsnBgohzBgFvK5lkeV6CeARVB8PcJ008JjFkhgj1yD15P
4v3EM+6lVgF7A2PeAwQuFRmu0ZnqaNqi8h7/F9rFQ124VphESCOHCpRrrTn2BGjX
/aVKHGWijRQ/zPsio8aMwxv3NBtSmSIw8Otu39qKjOnaTCyPaQKh9opdzPkd2ZuH
Ca/LRdTHkWYTU4ZLmwYRqJTsEzbURlaTULIvsUjVAjHJd3derDeFrBOa8txYqzQi
HSKk52vC5mhfeGYmm415SQGhsL1blxuQE6yoAClNeY+16FSjIoZOcFBA46xkc6I/
y3nWNcJOTcYkYOpb/R2voDabAen98sZHdcD/V86wNAt/JtwdUveeBKCX7YPtAMhu
3m6Me5c8b7F5dRTe0bnzdjjaReywooW9+XGRhbmPijqiPBjsHb8dfkZW9vtkyORL
l8wtMvowTDLxJPB1qgPZDOFpsY/0whZpZi14vNnmGjaFRNrwWwqF5eWBy+eGsQpj
VsvAx9PqTwkoof37h1xkdPT9Ft6T5X6gl+l83H9y9XVksenAQQKCAQEA1HRmshIk
RSlGrLrVqiAnndRAHV6flhKsF0+1IOzr7dL7WVfVNoQV2jPcaHGW+bZoi4hP8kIj
YXo2IBF6xSiPiIksJJ2xBmFY+afu/HLtBMwwNhF3oFGunu8ab/ZEDlLg2oRQ1616
229MCRvgGSM59Q3JTZF0svSHu4xGZvIriRw2g500fTKh9+YiNl1hTeCp3A4f/MKl
mo4XvFgcIC9DdfMrb0ST/RtnM9vLCIXYl5ej862PcWD0y2FbvpXtD+WA05bWUsPW
PBseF6PUKC1IgVMo7oCBAkmnth5gkK1+a6cdZ5j+LC+q5rmK+cDa5IIJzCbXapuA
f6NrRSd18203EwKCAQEA59bPN2eNf90W7pi2+bmGETtgz/wl4DBmVH/PsOCuMlGr
MnnufkBD9ig7ZAK1iZhZw3tgDrlh7rBGHdFPtwLad9tH5MhwK39WSfqUwLt6CRjx
3s6U61riGQzVLKb8iWlxek1IE7s0y/3m4YH17wdLjCOTEe1Jyi1dXcu3+oHGqFy+
HJyBesyroaHswcwV1tUh3QgzuT6McJEolEVdu6XPvXnerYd+LNgYiz8gsrViU8E0
WKrLnvdRMnn3ySJH7wkLBdeFi8N0glrEcF/Kcbyh6vTKuQkPzIC7szIy+Yvkinva
6fSBoeL72i2najXWFhZsXJbpHxMmw95ZeC7SH4/J9wKCAQAaK7CO1O9E2b3L/0Pc
rhNTPNcdBw/vg6NRR89PHABADpJJwikQixrKA0NuVje70P112rfGZuFG27AZKS4P
ZVyw+/zFEevBlnJIZqhozptl0OVLc8FhrU4uY9PE4PgnL4xlPpFa0BLnPwGFybpE
PnOgPS+D75wJg1fJAZGWktRMEn6gndfeaENNbzrdqYkX98nUwqSsFSojLMe9urjU
Oh48RFUgYrk8H4kJ+VQ8W4h/u/1FQib+V2wwNXEAvCU0pRfGeLkz/s3AH7MIRHUY
8eMRkzXik0/RAVO4emt4xvZgunhDz7PXq5OI0mhNNbWBGoesb0hv6HHexzmqjh7Y
eqajAoIBAFiIpJsw1U1l3bMB6KYW3gbImSDz1nb1pK5SHLscIgmfPHRLMfNOkWV4
Wa3IhxDjeCv5emZFDwv6jtwmKX3m/gzVVXAdxxAlUYtwwMuVDHZa60q9swrpqvwL
9YBWyIulE6uzxXmbfP8Fl9y4J3W/YG9Eyo4HAq3NgyElgb2NP5Ldz8/XSG7fqA9S
abpcOF7RB1yEHFR6eWEnXcq5bqERIfLmjk3QNzPi1gSe99qm/8SiPF474wRyx7Qx
9Zj+mV/EIUx60EneOyjohqmvOv0SHvc9wgjFWB4tbwBwhBzd+kmUILZFJBfxOWJJ
GuypYHcQ2xLEooO2aZBU4e/OWXmqDGMCggEAU+t1zgjUM2MTUMYDQry49j0SEQmc
nGAxhpUxwWHH9LDJeMiD/Tb5DMlqSUwpl5CMCWyvieoG+dyydIT9T3NRa9j+8ga3
MSVrpDtM8O6m1t/8TdbWHFH/En48KNAIQFP5DOLydF0zIfNzLlhlDn03HoIKf5XD
mcoKiuqr9ycnh1Yp6ns9EJLRMBR2w5yJXE0eAMfj2De+GQFUzSRfHkCFSs5kK+Wp
JGzruiS0pX24KrTV4boOfhc9yNOJ+p/1t/lbBdp0ruEeATzQO2XaUvyY2iyctllp
fOQtpLwQSFnxDn/hkd9R/fQThQzcXinqCAv8db1hYUR4sVTmPH9lYjW2Og==
MIIJKAIBAAKCAgEAw0anfgtraA2uaZwoLpLBvo1EkfYvDBgeXoMQ4WMKbcw6jU8k
18E+f3WM52I2RssEk6g0X1vIiarHZU5qyxbv2iNoT7EfgizzlXYvx06pM69GNBQr
V46+lNhiOv4eeLZcOHBnxAyizlIKyLuwO+C1cX/6BxuXjX3ogw+6IaBFZN/EOMmT
Wc6sPKrnNCEqgCNiTbPAXb+N8j5Iv8QPs0AwVTB3jC9LP0mRt2GGD0cu+QIZkoGE
hyW9Dd+0tgeIK32uXCF8uDjNpUV1TxCdn5H+lvgddxfiaIJAdY5UaTx48XRIdULh
QrVKXJNyTYWTXbezViYIf1nXOdDI2hsGgKTfNVCocDT1LcT5zebijFHVCWfAAKEP
RLdzPZomkFIa3rQQgChgePzE8Oqsaxuwx8sU09NAo3giSq1OmBBwly7h60Lwtm8+
XuwmWtEgZoSQy2Hm8UMAJ3guotfYuS8mPQBi55JZpdqVN0D5SdwuWXhHpO4xDodN
YAoMMei1NNgrX2zN3FCS8PVi97wD6cQ8xsWM62nByrzIjmPwTfoCb3qP/928FMJM
g7b3bYBrUnOrpVWZfAIDs4H7DoP8VLL4rMgB5PuWNQ13gtX7y5ZOyLhopiKLnar9
XiN8GAMANBdKRI2anGEozrfJoelJ5POXZwSatRjbL+nWO3YnBzEunCw1xmMCAwEA
AQKCAgBAYYABP3SW5sPVD+XzjPERiPPNh7P1MdJ5aI7dMFEU6Bt50VkdRRn83d2p
v6iTaIXGxNMXiWQxdzusO9FbyeEkMz5F3+i6e2WHpmKUPGvunV/w9aFgibBt1HV2
a6fSNpVrCiw758qZaVUi3zZ4V1qa5A2j4EX0IUnSRBIi2ftnCZtg+Zx6JHiGu/Xk
KvcfLgtQAO5wOiJrdnt3tgVTHNuSipsvfbw6TmAbbKzNRrPG5xlVQxxVjmypMVMc
HJmZdSNSPrwm5JtwXNkTSzAclv6v+XeFdztvJ1pnJ5jO5WAegy8MchNgcfLlWLt7
sYlngZQ/1+Q/UHh0GFDQD87yBOmNz8KK5n+5gWB5iumdJ4BHTgUOfXpWilwb0JWG
r7ctqCYrbXXTvsIbRl47zGPzEsbs0mSLAuLzZ3IQ60uaYRt322bqzZQNBwJcUXYM
lRb6nc9BVAzqhvUemOlACbYlqXENmQy/Nz14nsNdy4Qrynsfon92dRZ0m+9rJ9Hj
99K4CNPz0FdayC7jTL76b8QEzoF2MGiKIL5yQYXm9Pt9p0g8g9sER7G7UyrMFqtl
tfylkAWRX5hgDCwQQ/Gqefn7xb/kG/4D1FBE5i2yU4tYw5NCzENo8Y3mUhBqiQql
G33sSv2JK/woxRWSbyGLfu1Iq2L8H/q4CdN55xat2iKbpL8omQKCAQEA6qX7V8A0
uCg0E/Uid6/Xma0dvZ7e5BMKcx0uElSUhUpB6JnEQRlgljRXF/Op8Iae09Gox6bZ
nU5Ow61wtSrnJY+f/2RVs9xYB+SSO0L0yE/XPKsBveH0dH9R4BWmH+KZ3sLaYovs
ZDsApR782Zh+1TthUT2s4vZ0G25f46xsjKpUzQLmgWeC3UEOThtQo/UZzLeprImI
fijMw+5jYUgHSXN80BXO56JzHQJU6SIDmA4BrlD0qPaDyzLVdNhG/nIbYKvf120p
ogWqEYIgVN4KyjLsvVgfxCEF4Ucwov9VCNgsVTlEtYWzAXEXqf2AW1j7Sh4GlVOz
W4UsfiGaSCjM7wKCAQEA1QuDLQ4cf4UEKsdPOnDYrkHwx6JBBHpkyZZcLhlT/S5A
AcvVcEJjeseNfCEexO7SChenlRQEVB8iXO28ZEumWePEz2JK9rq9p7AItk+ba/D9
qzfvZ/XE+1xs5szTfwr12Of8b9dXxhoW8gKcFPnKOHxvua6SyocmRlnZtaJRFZ7x
RxOZhfWoOUnc+ySYKhKyuipKR4KmyDd2d2ovxptlMFnj2RJzfjUIZiQpKTa8kXf7
sYaOgFiNC0AFAs9ZLCEX3NYTKpgVbVKNIaKtNj8GIAG2YPnT/VcbQtj9ULyJcvEw
IdzJXn+Cv6ie1nP05P+eo/gtGmm5okXzMQNv0wcFzQKCAQBmDVBWJtMG8P1NXMTj
1wdm3+LacHkyKpHV5O//qud5XQVzO0UepwHZ8eObGC9l27bCGyJTyt5ESyV4dztY
n9MuA9wrQCEB+6gRrrhmq8U4RXkv+pPkWJxv+lvKoL/CiFQxjP9b8s0Z/otWRTbl
ECzBYnT911wUzelLcOKla30+ZGpDS6qixzkkL0IgeELHPDc/UPWrg5lofSgpYsm4
KpJ4wJCdE48MMRvtlvEE//UeMaFLhgwSXDyPqIkrq1CdI1WC4t2UnPaJb/s6aCTV
pEh/DkzmQKh4LYCYLNUbXv9FvHbzjdezNvXWf7AyD32+vOF1p79nPKL5/96M8OJf
1dbjAoIBABKld02yNnxSwBKebyjGR7C4xMI0SUyDCd868cZ3IQq/yYpetMemh95v
KMr8exzxaiDIATrjDZ3vO6q2hA6jMGQds1QTXkxJ+995YMnUHd5MsWcS9jk7IYp+
hGmO89PiubHKXCXNyzjjf66e29paIoDfI0g1J1PikE8H/i4Pjtk9mBCIfp9i6N5a
wKSah1bnXA0/NlEb9kz/zbaV7KiNYUXiGDcfjkw1iA6oi5G34Lk6ryTSihZhqbaa
W9XrH/rkypnhgrvvo7B10TRocJCW44pZnATQ2OULgq9PHpy6Y61Tvsq38Ef9EQyF
TaGndH+2f8QKLKhrKHwzcx2PF3J44uECggEBAM0UGu/Aj4tIRmrcuPGHypkcxMY6
BS2irwiVD9/8Xnx0/r8RSnBuAXEUY8wTrP0GqGm9PZjFCXKyxk3gi6SkahTu6/SF
WecgomVnONI+ivpHRLmRXTTPEv7iu1F+2jgVQyg0mOR5WLE0r25S6NS6IlnnrTSo
QuIJa1wRIfyXrMpYk77YIOny+mYB4FYr25tChgieQGR4m3dlZICPYqOyFh9GORZ8
k1cVboGtKGYtAemzAh/PyUp716tMz44fnnHPzINUFI3ucybqUwpGiR9s0E3L+GsV
3h7a2v90RdyWcuAPJL0B5FL5NoHhOMYb1rCMu00FyqCKqXCgte2w2psOP60=
-----END rsa private key-----

View File

@@ -1,14 +1,14 @@
-----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwGdN8KE2NTK41cVN7i6m
ZZS5J86gjNs0LJDHylb2GG+K4O8sfq98EMKMJ2xGnKoVRJXucVohr5Eiuf5zgxQe
9mpYDyDQ0vnpBkoNkfzJNZQvcQFaIllUeH+eN4hSBhMegPspCr2BcmC/m/N40+Pj
BMDaHSzeNE2SyIuyflLC7GhQvHnkcQXkqJVNC1yesV7zYKDV/xYPI0NxN0kE+4eR
650A77jso4gloRUek0cpU4ztpe6Zz4za7AEKHmZO8pVHpQJihbNMhItbo6BcgeOF
WImXyJfcbCtu1ayciaJ0Q45Z1btuF2wFwQjwZGt7DAvbzYwMBBiTpGxig/8kzibm
7bL1Td7huW6Knqbfh4/v+2m+2aNK1u0TfncFUr17wDDfeVj/xeNOSbPu2X/AppjV
X0rxmvWN+AByFfAuw7/kqMK7QZlA+5nNjmOojvryGOSu1he4PUnhRyJ7jofOIrLZ
9YYBXpRiY/WB8t+xWIlaoPXFmsOxSgMwzQFGMlnSFy7nK9T4znE0QFknOQNbELPE
B4UGLOEa5Tg6YD2ORDDSqCzMRdkE1kAgoMRC9DkRXE45KEcQlhMX9Pahwx3mPS4l
MWABVudiEWyhGaR279OWjezqvtoLLBJ5GD9QL1XqaqCaJjp+qsHmX8re+MTA3ZJu
yUtRoPXPsKCe2nZ6Ma87DlUCAwEAAQ==
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw0anfgtraA2uaZwoLpLB
vo1EkfYvDBgeXoMQ4WMKbcw6jU8k18E+f3WM52I2RssEk6g0X1vIiarHZU5qyxbv
2iNoT7EfgizzlXYvx06pM69GNBQrV46+lNhiOv4eeLZcOHBnxAyizlIKyLuwO+C1
cX/6BxuXjX3ogw+6IaBFZN/EOMmTWc6sPKrnNCEqgCNiTbPAXb+N8j5Iv8QPs0Aw
VTB3jC9LP0mRt2GGD0cu+QIZkoGEhyW9Dd+0tgeIK32uXCF8uDjNpUV1TxCdn5H+
lvgddxfiaIJAdY5UaTx48XRIdULhQrVKXJNyTYWTXbezViYIf1nXOdDI2hsGgKTf
NVCocDT1LcT5zebijFHVCWfAAKEPRLdzPZomkFIa3rQQgChgePzE8Oqsaxuwx8sU
09NAo3giSq1OmBBwly7h60Lwtm8+XuwmWtEgZoSQy2Hm8UMAJ3guotfYuS8mPQBi
55JZpdqVN0D5SdwuWXhHpO4xDodNYAoMMei1NNgrX2zN3FCS8PVi97wD6cQ8xsWM
62nByrzIjmPwTfoCb3qP/928FMJMg7b3bYBrUnOrpVWZfAIDs4H7DoP8VLL4rMgB
5PuWNQ13gtX7y5ZOyLhopiKLnar9XiN8GAMANBdKRI2anGEozrfJoelJ5POXZwSa
tRjbL+nWO3YnBzEunCw1xmMCAwEAAQ==
-----END rsa public key-----

View File

@@ -15,7 +15,6 @@ func TestSinglyLink_InsertAtFirst(t *testing.T) {
link.InsertAtHead(1)
link.InsertAtHead(2)
link.InsertAtHead(3)
link.Print()
expected := []int{3, 2, 1}
values := link.Values()
@@ -32,7 +31,6 @@ func TestSinglyLink_InsertAtTail(t *testing.T) {
link.InsertAtTail(1)
link.InsertAtTail(2)
link.InsertAtTail(3)
link.Print()
expected := []int{1, 2, 3}
values := link.Values()
@@ -77,7 +75,6 @@ func TestSinglyLink_DeleteAtHead(t *testing.T) {
link.InsertAtTail(4)
link.DeleteAtHead()
link.Print()
expected := []int{2, 3, 4}
values := link.Values()

View File

@@ -55,12 +55,12 @@ func (q *ArrayQueue[T]) IsFull() bool {
// Front return front value of queue
func (q *ArrayQueue[T]) Front() T {
return q.data[0]
return q.data[q.head]
}
// 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

View File

@@ -64,28 +64,95 @@ func init() {
}
}
// AddMinute add or sub minute to the time.
// AddMinute add or sub minutes to the time.
// Play: https://go.dev/play/p/nT1heB1KUUK
func AddMinute(t time.Time, minute int64) time.Time {
return t.Add(time.Minute * time.Duration(minute))
func AddMinute(t time.Time, minutes int64) time.Time {
return t.Add(time.Minute * time.Duration(minutes))
}
// AddHour add or sub hour to the time.
// AddHour add or sub hours to the time.
// Play: https://go.dev/play/p/rcMjd7OCsi5
func AddHour(t time.Time, hour int64) time.Time {
return t.Add(time.Hour * time.Duration(hour))
func AddHour(t time.Time, hours int64) time.Time {
return t.Add(time.Hour * time.Duration(hours))
}
// AddDay add or sub day to the time.
// AddDay add or sub days to the time.
// Play: https://go.dev/play/p/dIGbs_uTdFa
func AddDay(t time.Time, day int64) time.Time {
return t.Add(24 * time.Hour * time.Duration(day))
func AddDay(t time.Time, days int64) time.Time {
return t.Add(24 * time.Hour * time.Duration(days))
}
// AddWeek add or sub weeks to the time.
// play: https://go.dev/play/p/M9TqdMiaA2p
func AddWeek(t time.Time, weeks int64) time.Time {
return t.Add(7 * 24 * time.Hour * time.Duration(weeks))
}
// AddMonth add or sub months to the time.
// Play: https://go.dev/play/p/DLoiOnpLvsN
func AddMonth(t time.Time, months int64) time.Time {
return t.AddDate(0, int(months), 0)
}
// AddYear add or sub year to the time.
// Play: https://go.dev/play/p/MqW2ujnBx10
func AddYear(t time.Time, year int64) time.Time {
return t.Add(365 * 24 * time.Hour * time.Duration(year))
return t.AddDate(int(year), 0, 0)
}
// AddDaySafe add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
// Play: https://go.dev/play/p/JTohZFpoDJ3
func AddDaySafe(t time.Time, days int) time.Time {
t = t.AddDate(0, 0, days)
year, month, day := t.Date()
lastDayOfMonth := time.Date(year, month+1, 0, 0, 0, 0, 0, t.Location()).Day()
if day > lastDayOfMonth {
t = time.Date(year, month, lastDayOfMonth, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
}
return t
}
// AddMonthSafe add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
// Play: https://go.dev/play/p/KLw0lo6mbVW
func AddMonthSafe(t time.Time, months int) time.Time {
year := t.Year()
month := int(t.Month()) + months
for month > 12 {
month -= 12
year++
}
for month < 1 {
month += 12
year--
}
daysInMonth := time.Date(year, time.Month(month+1), 0, 0, 0, 0, 0, time.UTC).Day()
day := t.Day()
if day > daysInMonth {
day = daysInMonth
}
return time.Date(year, time.Month(month), day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
}
// AddYearSafe add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
// Play: https://go.dev/play/p/KVGXWZZ54ZH
func AddYearSafe(t time.Time, years int) time.Time {
year, month, day := t.Date()
year += years
if month == time.February && day == 29 {
if !IsLeapYear(year) {
day = 28
}
}
return time.Date(year, month, day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
}
// GetNowDate return format yyyy-mm-dd of current date.
@@ -213,13 +280,9 @@ func EndOfDay(t time.Time) time.Time {
}
// BeginOfWeek return beginning week, default week begin from Sunday.
// Play: https://go.dev/play/p/ynjoJPz7VNV
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
var beginFromWeekday = time.Sunday
if len(beginFrom) > 0 {
beginFromWeekday = beginFrom[0]
}
y, m, d := t.AddDate(0, 0, int(beginFromWeekday-t.Weekday())).Date()
// Play: https://go.dev/play/p/DCHdcL6gnfV
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time {
y, m, d := t.AddDate(0, 0, int(beginFrom-t.Weekday())).Date()
beginOfWeek := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
if beginOfWeek.After(t) {
return beginOfWeek.AddDate(0, 0, -7)
@@ -228,13 +291,9 @@ func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
}
// EndOfWeek return end week time, default week end with Saturday.
// Play: https://go.dev/play/p/i08qKXD9flf
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time {
var endWithWeekday = time.Saturday
if len(endWith) > 0 {
endWithWeekday = endWith[0]
}
y, m, d := t.AddDate(0, 0, int(endWithWeekday-t.Weekday())).Date()
// Play: https://go.dev/play/p/mGSA162YgX9
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time {
y, m, d := t.AddDate(0, 0, int(endWith-t.Weekday())).Date()
var endWithWeek = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
if endWithWeek.Before(t) {
endWithWeek = endWithWeek.AddDate(0, 0, 7)
@@ -445,7 +504,7 @@ func GenerateDatetimesBetween(start, end time.Time, layout string, interval stri
}
// Min returns the earliest time among the given times.
// Play: todo
// Play: https://go.dev/play/p/MCIDvHNOGGb
func Min(t1 time.Time, times ...time.Time) time.Time {
minTime := t1
@@ -459,7 +518,7 @@ func Min(t1 time.Time, times ...time.Time) time.Time {
}
// Max returns the latest time among the given times.
// Play: todo
// Play: https://go.dev/play/p/9m6JMk1LB7-
func Max(t1 time.Time, times ...time.Time) time.Time {
maxTime := t1
@@ -473,7 +532,7 @@ func Max(t1 time.Time, times ...time.Time) time.Time {
}
// MaxMin returns the latest and earliest time among the given times.
// Play: todo
// Play: https://go.dev/play/p/rbW51cDtM_2
func MaxMin(t1 time.Time, times ...time.Time) (maxTime time.Time, minTime time.Time) {
maxTime = t1
minTime = t1

View File

@@ -7,71 +7,141 @@ import (
)
func ExampleAddDay() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
tomorrow := AddDay(now, 1)
diff1 := tomorrow.Sub(now)
after1Day := AddDay(date, 1)
before1Day := AddDay(date, -1)
yesterday := AddDay(now, -1)
diff2 := yesterday.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after1Day.Format("2006-01-02 15:04:05"))
fmt.Println(before1Day.Format("2006-01-02 15:04:05"))
// Output:
// 24h0m0s
// -24h0m0s
// 2021-01-02 00:00:00
// 2020-12-31 00:00:00
}
func ExampleAddWeek() {
date, _ := time.Parse("2006-01-02", "2021-01-01")
after2Weeks := AddWeek(date, 2)
before2Weeks := AddWeek(date, -2)
fmt.Println(after2Weeks.Format("2006-01-02"))
fmt.Println(before2Weeks.Format("2006-01-02"))
// Output:
// 2021-01-15
// 2020-12-18
}
func ExampleAddMonth() {
date, _ := time.Parse("2006-01-02", "2021-01-01")
after2Months := AddMonth(date, 2)
before2Months := AddMonth(date, -2)
fmt.Println(after2Months.Format("2006-01-02"))
fmt.Println(before2Months.Format("2006-01-02"))
// Output:
// 2021-03-01
// 2020-11-01
}
func ExampleAddHour() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
after2Hours := AddHour(now, 2)
diff1 := after2Hours.Sub(now)
after2Hours := AddHour(date, 2)
before2Hours := AddHour(date, -2)
before2Hours := AddHour(now, -2)
diff2 := before2Hours.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Hours.Format("2006-01-02 15:04:05"))
fmt.Println(before2Hours.Format("2006-01-02 15:04:05"))
// Output:
// 2h0m0s
// -2h0m0s
// 2021-01-01 02:00:00
// 2020-12-31 22:00:00
}
func ExampleAddMinute() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
after2Minutes := AddMinute(now, 2)
diff1 := after2Minutes.Sub(now)
after2Minutes := AddMinute(date, 2)
before2Minutes := AddMinute(date, -2)
before2Minutes := AddMinute(now, -2)
diff2 := before2Minutes.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Minutes.Format("2006-01-02 15:04:05"))
fmt.Println(before2Minutes.Format("2006-01-02 15:04:05"))
// Output:
// 2m0s
// -2m0s
// 2021-01-01 00:02:00
// 2020-12-31 23:58:00
}
func ExampleAddYear() {
now := time.Now()
date, _ := time.Parse("2006-01-02", "2021-01-01")
after1Year := AddYear(now, 1)
diff1 := after1Year.Sub(now)
after2Years := AddYear(date, 2)
before2Years := AddYear(date, -2)
before1Year := AddYear(now, -1)
diff2 := before1Year.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Years.Format("2006-01-02"))
fmt.Println(before2Years.Format("2006-01-02"))
// Output:
// 8760h0m0s
// -8760h0m0s
// 2023-01-01
// 2019-01-01
}
func ExampleAddDaySafe() {
leapYearDate1, _ := time.Parse("2006-01-02", "2024-02-29")
result1 := AddDaySafe(leapYearDate1, 1)
leapYearDate2, _ := time.Parse("2006-01-02", "2024-03-01")
result2 := AddDaySafe(leapYearDate2, -1)
nonLeapYearDate1, _ := time.Parse("2006-01-02", "2025-02-28")
result3 := AddDaySafe(nonLeapYearDate1, 1)
nonLeaYearDate2, _ := time.Parse("2006-01-02", "2025-03-01")
result4 := AddDaySafe(nonLeaYearDate2, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
fmt.Println(result3.Format("2006-01-02"))
fmt.Println(result4.Format("2006-01-02"))
// Output:
// 2024-03-01
// 2024-02-29
// 2025-03-01
// 2025-02-28
}
func ExampleAddMonthSafe() {
date1, _ := time.Parse("2006-01-02", "2025-01-31")
result1 := AddMonthSafe(date1, 1)
date2, _ := time.Parse("2006-01-02", "2024-02-29")
result2 := AddMonthSafe(date2, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
// Output:
// 2025-02-28
// 2024-01-29
}
func ExampleAddYearSafe() {
date, _ := time.Parse("2006-01-02", "2020-02-29")
result1 := AddYearSafe(date, 1)
result2 := AddYearSafe(date, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
// Output:
// 2021-02-28
// 2019-02-28
}
func ExampleGetNowDate() {
@@ -229,23 +299,23 @@ func ExampleEndOfDay() {
func ExampleBeginOfWeek() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := BeginOfWeek(input)
result := BeginOfWeek(input, time.Monday)
fmt.Println(result)
// Output:
// 2023-01-08 00:00:00 +0000 UTC
// 2023-01-02 00:00:00 +0000 UTC
}
func ExampleEndOfWeek() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := EndOfWeek(input)
result := EndOfWeek(input, time.Sunday)
fmt.Println(result)
// Output:
// 2023-01-14 23:59:59.999999999 +0000 UTC
// 2023-01-08 23:59:59.999999999 +0000 UTC
}
func ExampleBeginOfMonth() {

View File

@@ -9,78 +9,427 @@ import (
func TestAddYear(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddDay")
now := time.Now()
after2Years := AddYear(now, 1)
diff1 := after2Years.Sub(now)
assert.Equal(float64(8760), diff1.Hours())
tests := []struct {
inputDate string
years int
expected string
}{
{
inputDate: "2021-01-01 00:00:00",
years: 1,
expected: "2022-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
years: -1,
expected: "2020-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
years: 0,
expected: "2021-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
years: 2,
expected: "2023-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
years: 3,
expected: "2024-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
years: 4,
expected: "2025-01-01 00:00:00",
},
}
before2Years := AddYear(now, -1)
diff2 := before2Years.Sub(now)
assert.Equal(float64(-8760), diff2.Hours())
}
func TestBetweenSeconds(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestBetweenSeconds")
today := time.Now()
tomorrow := AddDay(today, 1)
yesterday := AddDay(today, -1)
result1 := BetweenSeconds(today, tomorrow)
result2 := BetweenSeconds(today, yesterday)
assert.Equal(int64(86400), result1)
assert.Equal(int64(-86400), result2)
for _, tt := range tests {
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
result := AddYear(date, int64(tt.years))
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
}
}
func TestAddDay(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddDay")
now := time.Now()
after2Days := AddDay(now, 2)
diff1 := after2Days.Sub(now)
assert.Equal(float64(48), diff1.Hours())
tests := []struct {
inputDate string
days int
expected string
}{
{
inputDate: "2021-01-01 00:00:00",
days: 1,
expected: "2021-01-02 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
days: -1,
expected: "2020-12-31 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
days: 0,
expected: "2021-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
days: 2,
expected: "2021-01-03 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
days: 3,
expected: "2021-01-04 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
days: 4,
expected: "2021-01-05 00:00:00",
},
}
before2Days := AddDay(now, -2)
diff2 := before2Days.Sub(now)
assert.Equal(float64(-48), diff2.Hours())
for _, tt := range tests {
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
result := AddDay(date, int64(tt.days))
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
}
}
func TestAddHour(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddHour")
now := time.Now()
after2Hours := AddHour(now, 2)
diff1 := after2Hours.Sub(now)
assert.Equal(float64(2), diff1.Hours())
tests := []struct {
inputDate string
hours int
expected string
}{
{
inputDate: "2021-01-01 00:00:00",
hours: 1,
expected: "2021-01-01 01:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
hours: -1,
expected: "2020-12-31 23:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
hours: 0,
expected: "2021-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
hours: 24,
expected: "2021-01-02 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
hours: 25,
expected: "2021-01-02 01:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
hours: 48,
expected: "2021-01-03 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
hours: 49,
expected: "2021-01-03 01:00:00",
},
}
for _, tt := range tests {
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
result := AddHour(date, int64(tt.hours))
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
}
before2Hours := AddHour(now, -2)
diff2 := before2Hours.Sub(now)
assert.Equal(float64(-2), diff2.Hours())
}
func TestAddMinute(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddMinute")
now := time.Now()
after2Minutes := AddMinute(now, 2)
diff1 := after2Minutes.Sub(now)
assert.Equal(float64(2), diff1.Minutes())
tests := []struct {
inputDate string
minutes int
expected string
}{
{
inputDate: "2021-01-01 00:00:00",
minutes: 1,
expected: "2021-01-01 00:01:00",
},
{
inputDate: "2021-01-01 00:00:00",
minutes: -1,
expected: "2020-12-31 23:59:00",
},
{
inputDate: "2021-01-01 00:00:00",
minutes: 0,
expected: "2021-01-01 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
minutes: 60,
expected: "2021-01-01 01:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
minutes: 61,
expected: "2021-01-01 01:01:00",
},
{
inputDate: "2021-01-01 00:00:00",
minutes: 1440,
expected: "2021-01-02 00:00:00",
},
{
inputDate: "2021-01-01 00:00:00",
minutes: 1441,
expected: "2021-01-02 00:01:00",
},
}
before2Minutes := AddMinute(now, -2)
diff2 := before2Minutes.Sub(now)
assert.Equal(float64(-2), diff2.Minutes())
for _, tt := range tests {
date, _ := time.Parse("2006-01-02 15:04:05", tt.inputDate)
result := AddMinute(date, int64(tt.minutes))
assert.Equal(tt.expected, result.Format("2006-01-02 15:04:05"))
}
}
func TestAddWeek(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddWeek")
tests := []struct {
inputDate string
weeks int
expected string
}{
{
inputDate: "2021-01-01",
weeks: 1,
expected: "2021-01-08",
},
{
inputDate: "2021-01-01",
weeks: -1,
expected: "2020-12-25",
},
{
inputDate: "2021-01-01",
weeks: 0,
expected: "2021-01-01",
},
{
inputDate: "2021-01-01",
weeks: 52,
expected: "2021-12-31",
},
{
inputDate: "2021-01-01",
weeks: 53,
expected: "2022-01-07",
},
{
inputDate: "2021-01-01",
weeks: 104,
expected: "2022-12-30",
},
}
for _, tt := range tests {
date, _ := time.Parse("2006-01-02", tt.inputDate)
result := AddWeek(date, int64(tt.weeks))
assert.Equal(tt.expected, result.Format("2006-01-02"))
}
}
func TestAddMonth(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddMonth")
tests := []struct {
inputDate string
months int
expected string
}{
{
inputDate: "2021-01-01",
months: 1,
expected: "2021-02-01",
},
{
inputDate: "2021-01-01",
months: -1,
expected: "2020-12-01",
},
{
inputDate: "2021-01-01",
months: 0,
expected: "2021-01-01",
},
{
inputDate: "2021-01-01",
months: 12,
expected: "2022-01-01",
},
{
inputDate: "2021-01-01",
months: 13,
expected: "2022-02-01",
},
{
inputDate: "2021-01-01",
months: 24,
expected: "2023-01-01",
},
}
for _, tt := range tests {
date, _ := time.Parse("2006-01-02", tt.inputDate)
result := AddMonth(date, int64(tt.months))
assert.Equal(tt.expected, result.Format("2006-01-02"))
}
}
func TestAddDaySafe(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddDaySafe")
tests := []struct {
inputDate string
days int
expected string
}{
{"2025-01-31", 10, "2025-02-10"},
{"2025-01-01", 30, "2025-01-31"},
{"2025-01-31", 1, "2025-02-01"},
{"2025-02-28", 1, "2025-03-01"},
{"2024-02-29", 1, "2024-03-01"},
{"2024-02-29", 365, "2025-02-28"},
{"2025-01-31", -10, "2025-01-21"},
{"2025-01-01", -30, "2024-12-02"},
{"2025-02-01", -1, "2025-01-31"},
{"2025-03-01", -1, "2025-02-28"},
{"2024-03-01", -1, "2024-02-29"},
{"2025-01-31", -31, "2024-12-31"},
{"2025-12-31", 1, "2026-01-01"},
}
for _, tt := range tests {
date, _ := time.Parse("2006-01-02", tt.inputDate)
result := AddDaySafe(date, tt.days)
assert.Equal(tt.expected, result.Format("2006-01-02"))
}
}
func TestAddMonthSafe(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddMonthSafe")
tests := []struct {
inputDate string
months int
expected string
}{
{
inputDate: "2025-01-31",
months: 1,
expected: "2025-02-28",
},
{
inputDate: "2025-01-31",
months: -1,
expected: "2024-12-31",
},
{
inputDate: "2025-12-31",
months: 1,
expected: "2026-01-31",
},
{
inputDate: "2025-01-31",
months: -1,
expected: "2024-12-31",
},
{
inputDate: "2024-02-29",
months: 1,
expected: "2024-03-29",
},
{
inputDate: "2024-02-29",
months: -1,
expected: "2024-01-29",
},
}
for _, tt := range tests {
date, _ := time.Parse("2006-01-02", tt.inputDate)
result := AddMonthSafe(date, tt.months)
assert.Equal(tt.expected, result.Format("2006-01-02"))
}
}
func TestAddYearSafe(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddYearSafe")
tests := []struct {
inputDate string
years int
expected string
}{
{
inputDate: "2020-02-29",
years: 1,
expected: "2021-02-28",
},
{
inputDate: "2020-02-29",
years: 2,
expected: "2022-02-28",
},
{
inputDate: "2020-02-29",
years: -1,
expected: "2019-02-28",
},
{
inputDate: "2020-02-29",
years: -2,
expected: "2018-02-28",
},
}
for _, tt := range tests {
date, _ := time.Parse("2006-01-02", tt.inputDate)
result := AddYearSafe(date, tt.years)
assert.Equal(tt.expected, result.Format("2006-01-02"))
}
}
func TestGetNowDate(t *testing.T) {
@@ -262,9 +611,9 @@ func TestBeginOfWeek(t *testing.T) {
assert := internal.NewAssert(t, "TestBeginOfWeek")
expected := time.Date(2022, 2, 13, 0, 0, 0, 0, time.Local)
expected := time.Date(2022, 2, 14, 0, 0, 0, 0, time.Local)
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
actual := BeginOfWeek(td)
actual := BeginOfWeek(td, time.Monday)
assert.Equal(expected, actual)
}
@@ -274,9 +623,9 @@ func TestEndOfWeek(t *testing.T) {
assert := internal.NewAssert(t, "TestEndOfWeek")
expected := time.Date(2022, 2, 19, 23, 59, 59, 999999999, time.Local)
expected := time.Date(2022, 2, 20, 23, 59, 59, 999999999, time.Local)
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
actual := EndOfWeek(td)
actual := EndOfWeek(td, time.Sunday)
assert.Equal(expected, actual)
}
@@ -578,3 +927,19 @@ func TestMaxMin(t *testing.T) {
assert.Equal(oneMinuteAgo, min)
assert.Equal(oneMinuteAfter, max)
}
func TestBetweenSeconds(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestBetweenSeconds")
today := time.Now()
tomorrow := AddDay(today, 1)
yesterday := AddDay(today, -1)
result1 := BetweenSeconds(today, tomorrow)
result2 := BetweenSeconds(today, yesterday)
assert.Equal(int64(86400), result1)
assert.Equal(int64(-86400), result2)
}

View File

@@ -114,6 +114,7 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
],
},
{ text: 'datetime', link: '/en/api/packages/datetime' },
{ text: 'eventbus', link: '/en/api/packages/eventbus' },
{ text: 'fileutil', link: '/en/api/packages/fileutil' },
{ text: 'formatter', link: '/en/api/packages/formatter' },
{ text: 'function', link: '/en/api/packages/function' },

View File

@@ -127,6 +127,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
],
},
{ text: '日期&时间', link: '/api/packages/datetime' },
{ text: '事件总线', link: '/api/packages/eventbus' },
{ text: '文件', link: '/api/packages/fileutil' },
{ text: '格式化工具', link: '/api/packages/formatter' },
{ text: '函数', link: '/api/packages/function' },

View File

@@ -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;
@@ -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>
@@ -66,4 +66,4 @@ outline: deep
<div class="package-cell">validator</div>
<div class="package-cell">xerror</div>
</div>
</div>
</div>

View File

@@ -8,7 +8,7 @@ algorithm 算法包实现一些基本算法sortsearchlrucache。
- [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>

View File

@@ -7,6 +7,7 @@
## 源码:
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
- [https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go](https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go)
<div STYLE="page-break-after: always;"></div>
@@ -35,6 +36,17 @@ import (
- [Take](#Take)
- [Tee](#Tee)
### KeyedLocker
- [NewKeyedLocker](#NewKeyedLocker)
- [KeyedLocker_Do](#Do)
- [NewRWKeyedLocker](#NewRWKeyedLocker)
- [RLock](#RLock)
- [Lock](#Lock)
- [NewTryKeyedLocker](#NewTryKeyedLocker)
- [TryLock](#TryLock)
- [Unlock](#Unlock)
<div STYLE="page-break-after: always;"></div>
## 文档
@@ -452,3 +464,389 @@ func main() {
// 1
}
```
### KeyedLocker
### <span id="NewKeyedLocker">NewKeyedLocker</span>
<p>NewKeyedLocker创建一个新的KeyedLocker并为锁的过期设置指定的 TTL。KeyedLocker 是一个简单的键值锁实现,允许非阻塞的锁获取。</p>
<b>函数签名:</b>
```go
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
task := func() {
fmt.Println("Executing task...")
time.Sleep(1 * time.Second)
fmt.Println("Task completed.")
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := locker.Do(ctx, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel2()
if err := locker.Do(ctx2, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
// Output:
// Executing task...
// Task completed.
// Task successfully executed.
// Executing task...
// Task completed.
// Task successfully executed.
}
```
### <span id="Do">Do</span>
<p>为指定的键获取锁并执行提供的函数。</p>
<b>函数签名:</b>
```go
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/GzeyC33T5rw)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
task := func() {
fmt.Println("Executing task...")
time.Sleep(1 * time.Second)
fmt.Println("Task completed.")
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := locker.Do(ctx, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel2()
if err := locker.Do(ctx2, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
// Output:
// Executing task...
// Task completed.
// Task successfully executed.
// Executing task...
// Task completed.
// Task successfully executed.
}
```
### <span id="NewRWKeyedLocker">NewRWKeyedLocker</span>
<p>NewRWKeyedLocker创建一个新的RWKeyedLocker并为锁的过期设置指定的 TTL。RWKeyedLocker 是一个简单的键值读写锁实现,允许非阻塞的锁获取。</p>
<b>函数签名:</b>
```go
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/CkaJWWwZm9)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.Lock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
```
### <span id="RLock">RLock</span>
<p>RLock为指定的键获取读锁并执行提供的函数。</p>
<b>函数签名:</b>
```go
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/ZrCr8sMo77T)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.RLock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
```
### <span id="Lock">Lock</span>
<p>Lock为指定的键获取锁并执行提供的函数。</p>
<b>函数签名:</b>
```go
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/WgAcXbOPKGk)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.Lock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
```
### <span id="NewTryKeyedLocker">NewTryKeyedLocker</span>
<p>创建一个TryKeyedLocker实例TryKeyedLocker是KeyedLocker的非阻塞版本。</p>
<b>函数签名:</b>
```go
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
```
### <span id="TryLock">TryLock</span>
<p>TryLock尝试获取指定键的锁。如果锁成功获取则返回true否则返回false。</p>
<b>函数签名:</b>
```go
func (l *TryKeyedLocker[K]) TryLock(key K) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
```
### <span id="Unlock">Unlock</span>
<p>释放指定键的锁。</p>
<b>函数签名:</b>
```go
func (l *TryKeyedLocker[K]) Unlock(key K)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
```

View File

@@ -373,7 +373,7 @@ import (
func main() {
aMap := map[string]int{"a": 1, "b": 2, "c": 3}
result, err := ToJson(aMap)
result, err := convertor.ToJson(aMap)
if err != nil {
fmt.Printf("%v", err)
@@ -1155,7 +1155,7 @@ func main() {
<p>将整数值转换为bigInt。</p>
<b>函数签名:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>函数签名:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/X3itkCxwB_x)</span></b>
```go
func ToBigInt[T any](v T) (*big.Int, error)

View File

@@ -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
@@ -1436,7 +1577,7 @@ func main() {
func RsaEncrypt(data []byte, pubKeyFileName string) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uef0q1fz53I)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/7_zo6mrx-eX)</span></b>
```go
package main
@@ -1473,7 +1614,7 @@ func main() {
func RsaDecrypt(data []byte, privateKeyFileName string) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uef0q1fz53I)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/7_zo6mrx-eX)</span></b>
```go
package main
@@ -1621,7 +1762,7 @@ func main() {
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, 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/qhsbf8BJ6Mf)</span></b>
```go
package main
@@ -1638,12 +1779,12 @@ func main() {
privateKey := "./rsa_private.pem"
publicKey := "./rsa_public.pem"
signature, err := RsaSign(hash, data, privateKey)
signature, err := cryptor.RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
err = cryptor.RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
@@ -1660,7 +1801,7 @@ func main() {
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName 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/qhsbf8BJ6Mf)</span></b>
```go
package main
@@ -1677,12 +1818,12 @@ func main() {
privateKey := "./rsa_private.pem"
publicKey := "./rsa_public.pem"
signature, err := RsaSign(hash, data, privateKey)
signature, err := cryptor.RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
err = cryptor.RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}

View File

@@ -1092,7 +1092,7 @@ func main() {
### 4. PriorityQueue
切片实现的优先级队列。
切片实现的优先级队列。
### <span id="NewPriorityQueue">NewPriorityQueue</span>
<p>返回一个具有特定容量的PriorityQueue指针参数 `comarator` 用于比较队列中T类型的值。</p>

View File

@@ -23,9 +23,14 @@ import (
## 目录
- [AddDay](#AddDay)
- [AddWeek](#AddWeek)
- [AddMonth](#AddMonth)
- [AddHour](#AddHour)
- [AddMinute](#AddMinute)
- [AddYear](#AddYear)
- [AddDaySafe](#AddDaySafe)
- [AddMonthSafe](#AddMonthSafe)
- [AddYearSafe](#AddYearSafe)
- [BeginOfMinute](#BeginOfMinute)
- [BeginOfHour](#BeginOfHour)
- [BeginOfDay](#BeginOfDay)
@@ -109,7 +114,7 @@ import (
<b>函数签名:</b>
```go
func AddDay(t time.Time, day int64) time.Time
func AddDay(t time.Time, days int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/dIGbs_uTdFa)</span></b>
@@ -124,20 +129,89 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
tomorrow := datetime.AddDay(now, 1)
diff1 := tomorrow.Sub(now)
after1Day := datetime.AddDay(date, 1)
before1Day := datetime.AddDay(date, -1)
yesterday := datetime.AddDay(now, -1)
diff2 := yesterday.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after1Day.Format("2006-01-02 15:04:05"))
fmt.Println(before1Day.Format("2006-01-02 15:04:05"))
// Output:
// 24h0m0s
// -24h0m0s
// 2021-01-02 00:00:00
// 2020-12-31 00:00:00
}
```
### <span id="AddWeek">AddWeek</span>
<p>将日期加/减星期数。</p>
<b>函数签名:</b>
```go
func AddWeek(t time.Time, weeks int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/M9TqdMiaA2p)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date, _ := time.Parse("2006-01-02", "2021-01-01")
after2Weeks := datetime.AddWeek(date, 2)
before2Weeks := datetime.AddWeek(date, -2)
fmt.Println(after2Weeks.Format("2006-01-02"))
fmt.Println(before2Weeks.Format("2006-01-02"))
// Output:
// 2021-01-15
// 2020-12-18
}
```
### <span id="AddMonth">AddMonth</span>
<p>将日期加/减月数。</p>
<b>函数签名:</b>
```go
func AddMonth(t time.Time, months int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/DLoiOnpLvsN)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date, _ := time.Parse("2006-01-02", "2021-01-01")
after2Months := datetime.AddMonth(date, 2)
before2Months := datetime.AddMonth(date, -2)
fmt.Println(after2Months.Format("2006-01-02"))
fmt.Println(before2Months.Format("2006-01-02"))
// Output:
// 2021-03-01
// 2020-11-01
}
```
@@ -148,7 +222,7 @@ func main() {
<b>函数签名:</b>
```go
func AddHour(t time.Time, hour int64) time.Time
func AddHour(t time.Time, hours int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/rcMjd7OCsi5)</span></b>
@@ -163,20 +237,17 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
after2Hours := datetime.AddHour(now, 2)
diff1 := after2Hours.Sub(now)
after2Hours := datetime.AddHour(date, 2)
before2Hours := datetime.AddHour(date, -2)
before2Hours := datetime.AddHour(now, -2)
diff2 := before2Hours.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Hours.Format("2006-01-02 15:04:05"))
fmt.Println(before2Hours.Format("2006-01-02 15:04:05"))
// Output:
// 2h0m0s
// -2h0m0s
// 2021-01-01 02:00:00
// 2020-12-31 22:00:00
}
```
@@ -187,7 +258,7 @@ func main() {
<b>函数签名:</b>
```go
func AddMinute(t time.Time, minute int64) time.Time
func AddMinute(t time.Time, minutes int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/nT1heB1KUUK)</span></b>
@@ -202,20 +273,17 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
after2Minutes := datetime.AddMinute(now, 2)
diff1 := after2Minutes.Sub(now)
after2Minutes := datetime.AddMinute(date, 2)
before2Minutes := datetime.AddMinute(date, -2)
before2Minutes := datetime.AddMinute(now, -2)
diff2 := before2Minutes.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Minutes.Format("2006-01-02 15:04:05"))
fmt.Println(before2Minutes.Format("2006-01-02 15:04:05"))
// Output:
// 2m0s
// -2m0s
// 2021-01-01 00:02:00
// 2020-12-31 23:58:00
}
```
@@ -226,7 +294,7 @@ func main() {
<b>函数签名:</b>
```go
func AddYear(t time.Time, year int64) time.Time
func AddYear(t time.Time, years int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/MqW2ujnBx10)</span></b>
@@ -241,20 +309,137 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02", "2021-01-01")
after1Year := datetime.AddYear(now, 1)
diff1 := after1Year.Sub(now)
after2Years := AddYear(date, 2)
before2Years := AddYear(date, -2)
before1Year := datetime.AddYear(now, -1)
diff2 := before1Year.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Years.Format("2006-01-02"))
fmt.Println(before2Years.Format("2006-01-02"))
// Output:
// 8760h0m0s
// -8760h0m0s
// 2023-01-01
// 2019-01-01
}
```
### <span id="AddDaySafe">AddDaySafe</span>
<p>增加/减少指定的天数,并确保日期是有效日期。</p>
<b>函数签名:</b>
```go
func AddDaySafe(t time.Time, days int) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JTohZFpoDJ3)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
leapYearDate1, _ := time.Parse("2006-01-02", "2024-02-29")
result1 := datetime.AddDaySafe(leapYearDate1, 1)
leapYearDate2, _ := time.Parse("2006-01-02", "2024-03-01")
result2 := datetime.AddDaySafe(leapYearDate2, -1)
nonLeapYearDate1, _ := time.Parse("2006-01-02", "2025-02-28")
result3 := datetime.AddDaySafe(nonLeapYearDate1, 1)
nonLeaYearDate2, _ := time.Parse("2006-01-02", "2025-03-01")
result4 := datetime.AddDaySafe(nonLeaYearDate2, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
fmt.Println(result3.Format("2006-01-02"))
fmt.Println(result4.Format("2006-01-02"))
// Output:
// 2024-03-01
// 2024-02-29
// 2025-03-01
// 2025-02-28
}
```
### <span id="AddMonthSafe">AddMonthSafe</span>
<p>增加/减少指定的月份,并确保日期是有效日期。</p>
<b>函数签名:</b>
```go
func AddMonthSafe(t time.Time, months int) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KLw0lo6mbVW)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date1, _ := time.Parse("2006-01-02", "2025-01-31")
result1 := datetime.AddMonthSafe(date1, 1)
date2, _ := time.Parse("2006-01-02", "2024-02-29")
result2 := datetime.AddMonthSafe(date2, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
// Output:
// 2025-02-28
// 2024-01-29
}
```
### <span id="AddYearSafe">AddYearSafe</span>
<p>增加/减少指定的年份,并确保日期是有效日期。</p>
<b>函数签名:</b>
```go
func AddYearSafe(t time.Time, years int) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KVGXWZZ54ZH)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date, _ := time.Parse("2006-01-02", "2020-02-29")
result1 := datetime.AddYearSafe(date, 1)
result2 := datetime.AddYearSafe(date, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
// Output:
// 2021-02-28
// 2019-02-28
}
```
@@ -361,7 +546,7 @@ func main() {
<b>函数签名:</b>
```go
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
@@ -377,12 +562,12 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.BeginOfWeek(input)
result := datetime.BeginOfWeek(input, time.Monday)
fmt.Println(result)
// Output:
// 2023-01-08 00:00:00 +0000 UTC
// 2023-01-09 00:00:00 +0000 UTC
}
```
@@ -542,7 +727,7 @@ func main() {
fmt.Println(result)
// Output:
// 2023-01-08 23:59:59.999999999 +0000 UTC
// 2023-01-02 00:00:00 +0000 UTC
}
```
@@ -553,10 +738,10 @@ func main() {
<b>函数签名:</b>
```go
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/i08qKXD9flf)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/mGSA162YgX9)</span></b>
```go
package main
@@ -569,12 +754,12 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.EndOfWeek(input)
result := datetime.EndOfWeek(input, time.Sunday)
fmt.Println(result)
// Output:
// 2023-01-14 23:59:59.999999999 +0000 UTC
// 2023-01-08 23:59:59.999999999 +0000 UTC
}
```
@@ -1585,7 +1770,7 @@ func main() {
func Min(t1 time.Time, times ...time.Time) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/MCIDvHNOGGb)</span></b>
```go
package main
@@ -1615,7 +1800,7 @@ func main() {
func Max(t1 time.Time, times ...time.Time) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/9m6JMk1LB7-)</span></b>
```go
package main
@@ -1645,7 +1830,7 @@ func main() {
func MaxMin(t1 time.Time, times ...time.Time) (maxTime time.Time, minTime time.Time)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/rbW51cDtM_2)</span></b>
```go
package main

View File

@@ -0,0 +1,407 @@
# EventBus
EventbBus是一个事件总线用于在应用程序中处理事件。
<div STYLE="page-break-after: always;"></div>
## 源码:
- [https://github.com/duke-git/lancet/blob/main/eventbus/eventbus.go](https://github.com/duke-git/lancet/blob/main/eventbus/eventbus.go)
<div STYLE="page-break-after: always;"></div>
## 用法:
```go
import (
"github.com/duke-git/lancet/v2/eventbus"
)
```
<div STYLE="page-break-after: always;"></div>
## 目录
- [NewEventBus](#NewEventBus)
- [Subscribe](#Subscribe)
- [Unsubscribe](#Unsubscribe)
- [Publish](#Publish)
- [ClearListeners](#ClearListeners)
- [ClearListenersByTopic](#ClearListenersByTopic)
- [GetListenersCount](#GetListenersCount)
- [GetAllListenersCount](#GetAllListenersCount)
- [GetEvents](#GetEvents)
- [SetErrorHandler](#SetErrorHandler)
<div STYLE="page-break-after: always;"></div>
## 文档
### <span id="NewEventBus">NewEventBus</span>
<p>创建EventBus实例。</p>
<b>函数签名:</b>
```go
func NewEventBus[T any]() *EventBus[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gHbOPV_NUOJ)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
// Output:
// 1
}
```
### <span id="Subscribe">Subscribe</span>
<p>订阅具有特定事件主题和监听函数的事件。支持异步,事件优先级,事件过滤器。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/EYGf_8cHei-)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
filter := func(eventData int) bool {
return eventData == 1
}
eb.Subscribe("event1", listener, false, 0, filter)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 2})
fmt.Println(receivedData)
// Output:
// 1
}
```
### <span id="Unsubscribe">Unsubscribe</span>
<p>取消订阅具有特定事件主题的事件。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T))
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Tmh7Ttfvprf)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Unsubscribe("event1", listener)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
// Output:
// 0
}
```
### <span id="Publish">Publish</span>
<p>发布一个带有特定事件主题和数据负载的事件。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) Publish(event eventbus.Event[T])
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gHTtVexFSH9)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {
fmt.Println(eventData)
}, false, 0, nil)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// 1
}
```
### <span id="ClearListeners">ClearListeners</span>
<p>清空所有事件监听器。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) ClearListeners()
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KBfBYlKPgqD)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListeners()
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 0
}
```
### <span id="ClearListenersByTopic">ClearListenersByTopic</span>
<p>清空特定事件监听器。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) ClearListenersByTopic(topic string)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gvMljmJOZmU)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 2
}
```
### <span id="GetListenersCount">GetListenersCount</span>
<p>获取特定事件的监听器数量。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) GetListenersCount(topic string) int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/j6yaJ2xAmFz)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetListenersCount("event1")
fmt.Println(count)
// Output:
// 1
}
```
### <span id="GetAllListenersCount">GetAllListenersCount</span>
<p>获取所有事件的监听器数量。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) GetAllListenersCount() int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/PUlr0xcpEOz)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetAllListenersCount()
fmt.Println(count)
// Output:
// 2
}
```
### <span id="GetEvents">GetEvents</span>
<p>获取所有事件的topic。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) GetEvents() []string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/etgjjcOtAjX)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
events := eb.GetEvents()
for _, event := range events {
fmt.Println(event)
}
// Output:
// event2
// event1
}
```
### <span id="SetErrorHandler">SetErrorHandler</span>
<p>设置事件的错误处理函数。</p>
<b>函数签名:</b>
```go
func (eb *EventBus[T]) SetErrorHandler(handler func(err error))
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gmB0gnFe5mc)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.SetErrorHandler(func(err error) {
fmt.Println(err)
})
eb.Subscribe("event1", func(eventData int) {
panic("error")
}, false, 0, nil)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// error
}
```

View File

@@ -35,6 +35,7 @@ import (
- [IsDir](#IsDir)
- [ListFileNames](#ListFileNames)
- [RemoveFile](#RemoveFile)
- [RemoveDir](#RemoveDir)
- [ReadFileToString](#ReadFileToString)
- [ReadFileByLine](#ReadFileByLine)
- [Zip](#Zip)
@@ -390,12 +391,12 @@ func main() {
### <span id="RemoveFile">RemoveFile</span>
<p>删除文件</p>
<p>删除文件,支持传入删除前的回调函数。</p>
<b>函数签名:</b>
```go
func RemoveFile(path string) error
func RemoveFile(path string, onDelete ...func(path string)) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/P2y0XW8a1SH)</span></b>
@@ -416,6 +417,41 @@ func main() {
}
```
### <span id="RemoveDir">RemoveDir</span>
<p>删除目录,支持传入删除前的回调函数。</p>
<b>函数签名:</b>
```go
func RemoveDir(path string, onDelete ...func(path string)) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
var deletedPaths []string
err := fileutil.RemoveDir("./tempdir", func(p string) {
deletedPaths = append(deletedPaths, p)
})
if err != nil {
fmt.Println(err)
}
fmt.Println(deletedPaths)
}
```
### <span id="ReadFileToString">ReadFileToString</span>
<p>读取文件内容并返回字符串</p>
@@ -934,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>
@@ -958,9 +994,9 @@ func main() {
if err != nil {
return
}
fmt.Println(string(dat))
// Output:
// User-agent: *
// Disallow: /deny
@@ -989,7 +1025,7 @@ import (
func main() {
const mb = 1024 * 1024
const defaultChunkSizeMB = 100
const defaultChunkSizeMB = 100
// test1.csv file content:
// Lili,22,female
@@ -1053,7 +1089,7 @@ func main() {
numParsers := runtime.NumCPU()
linesCh := make(chan []string, numParsers)
// test1.csv file content:
// Lili,22,female
// Jim,21,male
@@ -1078,6 +1114,7 @@ func main() {
// 2
}
```
### <span id="GetExeOrDllVersion">GetExeOrDllVersion</span>
<p>返回exe,dll文件版本号(仅Window平台).</p>
@@ -1088,7 +1125,7 @@ func main() {
func GetExeOrDllVersion(filePath 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/iLRrDBhE38E)</span></b>
```go
package main
@@ -1107,4 +1144,4 @@ func main() {
// Output:
// 3.9.10.19
}
```
```

View File

@@ -78,7 +78,7 @@ import (
- [GetOrSet](#GetOrSet)
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
<div STYLE="page-break-after: always;"></div>
@@ -1095,7 +1095,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 +1259,6 @@ func main() {
}
```
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
<p>删除给定键的键值对。</p>
@@ -2192,7 +2191,6 @@ func main() {
}
```
### <span id="GetOrSet">GetOrSet</span>
<p>返回给定键的值,如果不存在则设置该值。</p>
@@ -2276,7 +2274,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>
@@ -2307,4 +2305,43 @@ func main() {
// a
// default
}
```
```
### <span id="FindValuesBy">FindValuesBy</span>
<p>返回一个切片包含满足给定谓词判断函数的map中的值。</p>
<b>函数签名:</b>
```go
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;">[运行](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
2: "b",
3: "c",
4: "d",
}
result := maputil.FindValuesBy(m, func(k int, v string) bool {
return k%2 == 0
})
fmt.Println(result)
// Output:
// [b d]
}
```

View File

@@ -466,14 +466,14 @@ func main() {
}
```
### <span id="T运行cRound">T运行cRound</span>
### <span id="TruncRound">TruncRound</span>
<p>截短n位小数不进行四舍五入</p>
<b>函数签名:</b>
```go
func T运行cRound[T constraints.Float | constraints.Integer](x T, n int) T
func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/aumarSHIGzP)</span></b>
@@ -487,9 +487,9 @@ import (
)
func main() {
result1 := mathutil.T运行cRound(0.124, 2)
result2 := mathutil.T运行cRound(0.125, 2)
result3 := mathutil.T运行cRound(0.125, 3)
result1 := mathutil.TruncRound(0.124, 2)
result2 := mathutil.TruncRound(0.125, 2)
result3 := mathutil.TruncRound(0.125, 3)
fmt.Println(result1)
fmt.Println(result2)
@@ -1178,7 +1178,7 @@ func main() {
func Variance[T constraints.Float | constraints.Integer](numbers []T) float64
```
<b>示例:<span style="float:right;display:inline-block;">[示例](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[示例](https://go.dev/play/p/uHuV4YgXf8F)</span></b>
```go
package main
@@ -1211,7 +1211,7 @@ func main() {
func StdDev[T constraints.Float | constraints.Integer](numbers []T) float64
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/FkNZDXvHD2l)</span></b>
```go
package main
@@ -1222,8 +1222,8 @@ import (
)
func main() {
result1 := mathutil.T运行cRound(mathutil.StdDev([]int{1, 2, 3, 4, 5}), 2)
result2 := mathutil.T运行cRound(mathutil.StdDev([]float64{1.1, 2.2, 3.3, 4.4, 5.5}), 2)
result1 := mathutil.TruncRound(mathutil.StdDev([]int{1, 2, 3, 4, 5}), 2)
result2 := mathutil.TruncRound(mathutil.StdDev([]float64{1.1, 2.2, 3.3, 4.4, 5.5}), 2)
fmt.Println(result1)
fmt.Println(result2)
@@ -1244,7 +1244,7 @@ func main() {
func Permutation(n, k uint) uint
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/MgobwH_FOxj)</span></b>
```go
package main
@@ -1277,7 +1277,7 @@ func main() {
func Combination(n, k uint) uint
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ENFQRDQUFi9)</span></b>
```go
package main

View File

@@ -48,6 +48,8 @@ import (
- [UploadFile](#UploadFile)
- [IsPingConnected](#IsPingConnected)
- [IsTelnetConnected](#IsTelnetConnected)
- [BuildUrl](#BuildUrl)
- [AddQueryParams](#AddQueryParams)
<div STYLE="page-break-after: always;"></div>
@@ -1031,3 +1033,83 @@ func main() {
// false
}
```
### <span id="BuildUrl">BuildUrl</span>
<p>创建url字符串。</p>
<b>函数签名:</b>
```go
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/netutil"
)
func main() {
urlStr, err := netutil.BuildUrl(
"https",
"example.com",
"query",
map[string][]string{
"a": {"foo", "bar"},
"b": {"baz"},
},
)
fmt.Println(urlStr)
fmt.Println(err)
// Output:
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
```
### <span id="AddQueryParams">AddQueryParams</span>
<p>向url添加查询参数。</p>
<b>函数签名:</b>
```go
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/netutil"
)
func main() {
urlStr, err := netutil.BuildUrl(
"https",
"example.com",
"query",
map[string][]string{
"a": {"foo", "bar"},
"b": {"baz"},
},
)
fmt.Println(urlStr)
fmt.Println(err)
// Output:
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
```

View File

@@ -534,7 +534,7 @@ func main() {
func RandNumberOfLength(len int) int
```
<b>实例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>实例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/oyZbuV7bu7b)</span></b>
```go
package main

View File

@@ -44,6 +44,7 @@ import (
- [Every](#Every)
- [Equal](#Equal)
- [EqualWith](#EqualWith)
- [EqualUnordered](#EqualUnordered)
- [Filter](#Filter)
- [FilterConcurrent](#FilterConcurrent)
- [Find<sup>deprecated</sup>](#Find)
@@ -69,6 +70,7 @@ import (
- [FlatMap](#FlatMap)
- [Merge](#Merge)
- [Reverse](#Reverse)
- [ReverseCopy](#ReverseCopy)
- [Reduce<sup>deprecated</sup>](#Reduce)
- [ReduceConcurrent](#ReduceConcurrent)
- [ReduceBy](#ReduceBy)
@@ -77,6 +79,7 @@ import (
- [ReplaceAll](#ReplaceAll)
- [Repeat](#Repeat)
- [Shuffle](#Shuffle)
- [ShuffleCopy](#ShuffleCopy)
- [IsAscending](#IsAscending)
- [IsDescending](#IsDescending)
- [IsSorted](#IsSorted)
@@ -874,6 +877,37 @@ func main() {
}
```
### <span id="EqualUnordered">EqualUnordered</span>
<p>检查两个切片是否相等,元素数量相同,值相等,不考虑元素顺序。</p>
<b>函数签名:</b>
```go
func EqualUnordered[T comparable](slice1, slice2 []T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/n8fSc2w8ZgX)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.EqualUnordered([]int{1, 2, 3}, []int{3, 2, 1})
result2 := slice.EqualUnordered([]int{1, 2, 3}, []int{4, 5, 6})
fmt.Println(result1)
fmt.Println(result2)
// Output:
// true
// false
}
```
### <span id="Filter">Filter</span>
<p>返回切片中通过predicate函数真值测试的所有元素</p>
@@ -1728,6 +1762,38 @@ func main() {
}
```
### <span id="ReverseCopy">ReverseCopy</span>
<p>反转切片中的元素顺序, 不改变原slice。</p>
<b>函数签名:</b>
```go
func ReverseCopy[T any](slice []T) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/c9arEaP7Cg-)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
strs := []string{"a", "b", "c", "d"}
reversedStrs := slice.ReverseCopy(strs)
fmt.Println(reversedStrs)
fmt.Println(strs)
// Output:
// [d c b a]
// [a b c d]
}
```
### <span id="Reduce">Reduce</span>
<p>将切片中的元素依次运行iteratee函数返回运行结果。</p>
@@ -1962,7 +2028,7 @@ func main() {
### <span id="Shuffle">Shuffle</span>
<p>随机打乱切片中的元素顺序</p>
<p>随机打乱切片中的元素顺序</p>
<b>函数签名:</b>
@@ -1989,6 +2055,37 @@ func main() {
}
```
### <span id="ShuffleCopy">ShuffleCopy</span>
<p>随机打乱切片中的元素顺序, 不改变原切片。</p>
<b>函数签名:</b>
```go
func ShuffleCopy[T any](slice []T) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/vqDa-Gs1vT0)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
result := slice.ShuffleCopy(nums)
fmt.Println(result)
fmt.Println(nums)
// Output:
// [3 1 5 4 2] (random order)
// [1 2 3 4 5]
}
```
### <span id="IsAscending">IsAscending</span>
<p>检查切片元素是否按升序排列。</p>
@@ -2999,7 +3096,7 @@ func main() {
func JoinFunc[T any](slice []T, sep string, transform func(T) T) string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/55ib3SB5fM2)</span></b>
```go
import (
@@ -3029,7 +3126,7 @@ func main() {
func ConcatBy[T any](slice []T, sep T, connector func(T, T) T) T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/6QcUpcY4UMW)</span></b>
```go
import (

View File

@@ -951,7 +951,7 @@ func main() {
func (s Stream[T]) IndexOf(target T, equal func(a, b T) bool) int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/tBV5Nc-XDX2)</span></b>
```go
import (
@@ -984,7 +984,7 @@ func main() {
func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/CjeoNw2eac_G)</span></b>
```go
import (

View File

@@ -69,6 +69,7 @@ import (
- [TemplateReplace](#TemplateReplace)
- [RegexMatchAllGroups](#RegexMatchAllGroups)
- [ExtractContent](#ExtractContent)
- [FindAllOccurrences](#FindAllOccurrences)
<div STYLE="page-break-after: always;"></div>
@@ -1708,7 +1709,7 @@ func main() {
func RegexMatchAllGroups(pattern, str string) [][]string
```
<b>示例:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JZiu0RXpgN-)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JZiu0RXpgN-)</span></b>
```go
import (
@@ -1741,7 +1742,7 @@ func main() {
func ExtractContent(s, start, end string) []string
```
<b>示例:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ay9UIk7Rum9)</span></b>
```go
import (
@@ -1759,4 +1760,32 @@ func main() {
// Output:
// [content1 content2 content1]
}
```
### <span id="FindAllOccurrences">FindAllOccurrences</span>
<p>返回子字符串在字符串中所有出现的位置。</p>
<b>函数签名:</b>
```go
func FindAllOccurrences(str, substr string) []int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uvyA6azGLB1)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
result := strutil.FindAllOccurrences("ababab", "ab")
fmt.Println(result)
// Output:
// [0 2 4]
}
```

View File

@@ -49,6 +49,7 @@ import (
- [IsIp](#IsIp)
- [IsIpV4](#IsIpV4)
- [IsIpV6](#IsIpV6)
- [IsIpPort](#IsIpPort)
- [IsStrongPassword](#IsStrongPassword)
- [IsUrl](#IsUrl)
- [IsWeakPassword](#IsWeakPassword)
@@ -823,6 +824,43 @@ func main() {
}
```
### <span id="IsAlphaNumeric">IsAlphaNumeric</span>
<p>验证字符串是字母或数字。</p>
<b>函数签名:</b>
```go
func IsAlphaNumeric(s string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/RHeESLrLg9c)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsAlphaNumeric("ABC")
result2 := validator.IsAlphaNumeric("123")
result3 := validator.IsAlphaNumeric("abc123")
result4 := validator.IsAlphaNumeric("abc123@#$")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
```
### <span id="IsJSON">IsJSON</span>
<p>验证字符串是否是有效json。</p>
@@ -990,6 +1028,43 @@ func main() {
}
```
### <span id="IsIpPort">IsIpPort</span>
<p>检查字符串是否是ip:port格式。</p>
<b>函数签名:</b>
```go
func IsIpPort(str string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/xUmls_b9L29)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsIpPort("127.0.0.1:8080")
result2 := validator.IsIpPort("[0:0:0:0:0:0:0:1]:8080")
result3 := validator.IsIpPort(":8080")
result4 := validator.IsIpPort("::0:0:0:0:")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// false
// false
}
```
### <span id="IsStrongPassword">IsStrongPassword</span>
<p>验证字符串是否是强密码:(alpha(lower+upper) + number + special chars(!@#$%^&*()?&gt&lt))。</p>

View File

@@ -509,7 +509,7 @@ func (tc *TryCatch) Finally(finallyFunc func(ctx context.Context)) *TryCatch
func (tc *TryCatch) Do()
```
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/D5Mdb0mRj0P)</span></b>
```go
package main
@@ -529,8 +529,6 @@ func main() {
return errors.New("error in try block")
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
// Error in try block at /path/xxx.go:{line_number} - Cause: error message
// fmt.Println(err.Error())
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()

View File

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

View File

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

View File

@@ -1,15 +1,18 @@
# Concurrency
Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel.
<div STYLE="page-break-after: always;"></div>
## Source:
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
- [https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go](https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go)
<div STYLE="page-break-after: always;"></div>
## Usage:
```go
import (
"github.com/duke-git/lancet/v2/concurrency"
@@ -19,24 +22,39 @@ import (
<div STYLE="page-break-after: always;"></div>
## Index
### Channel
- [NewChannel](#NewChannel)
- [Bridge](#Bridge)
- [FanIn](#FanIn)
- [Generate](#Generate)
- [Or](#Or)
- [OrDone](#OrDone)
- [Repeat](#Repeat)
- [RepeatFn](#RepeatFn)
- [Take](#Take)
- [Tee](#Tee)
- [NewChannel](#NewChannel)
- [Bridge](#Bridge)
- [FanIn](#FanIn)
- [Generate](#Generate)
- [Or](#Or)
- [OrDone](#OrDone)
- [Repeat](#Repeat)
- [RepeatFn](#RepeatFn)
- [Take](#Take)
- [Tee](#Tee)
### KeyedLocker
- [NewKeyedLocker](#NewKeyedLocker)
- [Do](#Do)
- [NewRWKeyedLocker](#NewRWKeyedLocker)
- [RLock](#RLock)
- [Lock](#Lock)
- [NewTryKeyedLocker](#NewTryKeyedLocker)
- [TryLock](#TryLock)
- [Unlock](#Unlock)
<div STYLE="page-break-after: always;"></div>
## Documentation
## Channel
### <span id="NewChannel">NewChannel</span>
<p>Create a Channel pointer instance.</p>
<b>Signature:</b>
@@ -45,6 +63,7 @@ import (
type Channel[T any] struct
func NewChannel[T any]() *Channel[T]
```
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
```go
@@ -69,6 +88,7 @@ func main() {
```go
func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T
```
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qmWSy1NVF-Y)</span></b>
```go
@@ -121,6 +141,7 @@ func main() {
```go
func (c *Channel[T]) FanIn(ctx context.Context, channels ...<-chan T) <-chan T
```
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/2VYFMexEvTm)</span></b>
```go
@@ -160,6 +181,7 @@ func main() {
```go
func (c *Channel[T]) Repeat(ctx context.Context, values ...T) <-chan T
```
<b>Example:</b>
```go
@@ -199,6 +221,7 @@ func main() {
```go
func (c *Channel[T]) Generate(ctx context.Context, values ...T) <-chan T
```
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
```go
@@ -237,6 +260,7 @@ func main() {
```go
func (c *Channel[T]) RepeatFn(ctx context.Context, fn func() T) <-chan T
```
<b>Example:</b>
```go
@@ -279,6 +303,7 @@ func main() {
```go
func (c *Channel[T]) Or(channels ...<-chan T) <-chan T
```
<b>Example:</b>
```go
@@ -322,6 +347,7 @@ func main() {
```go
func (c *Channel[T]) OrDone(ctx context.Context, channel <-chan T) <-chan T
```
<b>Example:</b>
```go
@@ -360,6 +386,7 @@ func main() {
```go
func (c *Channel[T]) Take(ctx context.Context, valueStream <-chan T, number int) <-chan T
```
<b>Example:</b>
```go
@@ -406,6 +433,7 @@ func main() {
```go
func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T)
```
<b>Example:</b>
```go
@@ -430,11 +458,397 @@ func main() {
fmt.Println(v)
fmt.Println(<-ch2)
}
// Output:
// 1
// 1
// 1
// 1
}
```
```
### KeyedLocker
### <span id="NewKeyedLocker">NewKeyedLocker</span>
<p>KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.</p>
<b>Signature:</b>
```go
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GzeyC33T5rw)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
task := func() {
fmt.Println("Executing task...")
time.Sleep(1 * time.Second)
fmt.Println("Task completed.")
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := locker.Do(ctx, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel2()
if err := locker.Do(ctx2, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
// Output:
// Executing task...
// Task completed.
// Task successfully executed.
// Executing task...
// Task completed.
// Task successfully executed.
}
```
### <span id="Do">Do</span>
<p>Acquires a lock for the specified key and executes the provided function.</p>
<b>Signature:</b>
```go
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/GzeyC33T5rw)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
task := func() {
fmt.Println("Executing task...")
time.Sleep(1 * time.Second)
fmt.Println("Task completed.")
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := locker.Do(ctx, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel2()
if err := locker.Do(ctx2, "mykey", task); err != nil {
log.Fatalf("Error executing task: %v\n", err)
} else {
fmt.Println("Task successfully executed.")
}
// Output:
// Executing task...
// Task completed.
// Task successfully executed.
// Executing task...
// Task completed.
// Task successfully executed.
}
```
### <span id="NewRWKeyedLocker">NewRWKeyedLocker</span>
<p>RWKeyedLocker is a read-write version of KeyedLocker.</p>
<b>Signature:</b>
```go
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.Lock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
```
### <span id="RLock">RLock</span>
<p>Acquires a read lock for the specified key and executes the provided function.</p>
<b>Signature:</b>
```go
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/ZrCr8sMo77T)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.RLock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
```
### <span id="Lock">Lock</span>
<p>Acquires a write lock for the specified key and executes the provided function.</p>
<b>Signature:</b>
```go
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/WgAcXbOPKGk)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := NewRWKeyedLocker[string](2 * time.Second)
// Simulate a key
key := "resource_key"
fn := func() {
fmt.Println("Starting write operation...")
// Simulate write operation, assuming it takes 2 seconds
time.Sleep(200 * time.Millisecond)
fmt.Println("Write operation completed!")
}
// Acquire the write lock and execute the operation
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Execute the lock operation with a 3-second timeout
err := locker.Lock(ctx, key, fn)
if err != nil {
return
}
//output:
//Starting write operation...
//Write operation completed!
}
```
### <span id="NewTryKeyedLocker">NewTryKeyedLocker</span>
<p>TryKeyedLocker is a non-blocking version of KeyedLocker.</p>
<b>Signature:</b>
```go
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
```
### <span id="TryLock">TryLock</span>
<p>TryLock tries to acquire a lock for the specified key.</p>
<b>Signature:</b>
```go
func (l *TryKeyedLocker[K]) TryLock(key K) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
```
### <span id="Unlock">Unlock</span>
<p>Unlock releases the lock for the specified key.</p>
<b>Signature:</b>
```go
func (l *TryKeyedLocker[K]) Unlock(key K)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/concurrency"
)
func main() {
locker := concurrency.NewTryKeyedLocker[string]()
key := "resource_key"
if locker.TryLock(key) {
fmt.Println("Lock acquired")
time.Sleep(1 * time.Second)
// Unlock after work is done
locker.Unlock(key)
fmt.Println("Lock released")
} else {
fmt.Println("Lock failed")
}
//output:
//Lock acquired
//Lock released
}
```

View File

@@ -373,7 +373,7 @@ import (
func main() {
aMap := map[string]int{"a": 1, "b": 2, "c": 3}
result, err := ToJson(aMap)
result, err := convertor.ToJson(aMap)
if err != nil {
fmt.Printf("%v", err)
@@ -1123,7 +1123,7 @@ func main() {
<p>Converts an integer of any supported type (int, int64, uint64, etc.) to *big.Int</p>
<b>Signature:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Signature:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/X3itkCxwB_x)</span></b>
```go
func ToBigInt[T any](v T) (*big.Int, error)

View File

@@ -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>
@@ -1435,7 +1579,7 @@ func main() {
func RsaEncrypt(data []byte, pubKeyFileName string) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uef0q1fz53I)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7_zo6mrx-eX)</span></b>
```go
package main
@@ -1472,7 +1616,7 @@ func main() {
func RsaDecrypt(data []byte, privateKeyFileName string) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uef0q1fz53I)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7_zo6mrx-eX)</span></b>
```go
package main
@@ -1620,7 +1764,7 @@ func main() {
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, 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/qhsbf8BJ6Mf)</span></b>
```go
package main
@@ -1659,7 +1803,7 @@ func main() {
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName 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/qhsbf8BJ6Mf)</span></b>
```go
package main

View File

@@ -24,9 +24,14 @@ import (
## Index
- [AddDay](#AddDay)
- [AddWeek](#AddWeek)
- [AddMonth](#AddMonth)
- [AddHour](#AddHour)
- [AddMinute](#AddMinute)
- [AddYear](#AddYear)
- [AddDaySafe](#AddDaySafe)
- [AddMonthSafe](#AddMonthSafe)
- [AddYearSafe](#AddYearSafe)
- [BeginOfMinute](#BeginOfMinute)
- [BeginOfHour](#BeginOfHour)
- [BeginOfDay](#BeginOfDay)
@@ -111,7 +116,7 @@ import (
<b>Signature:</b>
```go
func AddDay(t time.Time, day int64) time.Time
func AddDay(t time.Time, days int64) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/dIGbs_uTdFa)</span></b>
@@ -126,20 +131,89 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
tomorrow := datetime.AddDay(now, 1)
diff1 := tomorrow.Sub(now)
after1Day := datetime.AddDay(date, 1)
before1Day := datetime.AddDay(date, -1)
yesterday := datetime.AddDay(now, -1)
diff2 := yesterday.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after1Day.Format("2006-01-02 15:04:05"))
fmt.Println(before1Day.Format("2006-01-02 15:04:05"))
// Output:
// 24h0m0s
// -24h0m0s
// 2021-01-02 00:00:00
// 2020-12-31 00:00:00
}
```
### <span id="AddWeek">AddWeek</span>
<p>Add or sub weeks to time.</p>
<b>Signature:</b>
```go
func AddWeek(t time.Time, weeks int64) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/M9TqdMiaA2p)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date, _ := time.Parse("2006-01-02", "2021-01-01")
after2Weeks := datetime.AddWeek(date, 2)
before2Weeks := datetime.AddWeek(date, -2)
fmt.Println(after2Weeks.Format("2006-01-02"))
fmt.Println(before2Weeks.Format("2006-01-02"))
// Output:
// 2021-01-15
// 2020-12-18
}
```
### <span id="AddMonth">AddMonth</span>
<p>Add or sub months to time.</p>
<b>Signature:</b>
```go
func AddMonth(t time.Time, months int64) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DLoiOnpLvsN)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date, _ := time.Parse("2006-01-02", "2021-01-01")
after2Months := datetime.AddMonth(date, 2)
before2Months := datetime.AddMonth(date, -2)
fmt.Println(after2Months.Format("2006-01-02"))
fmt.Println(before2Months.Format("2006-01-02"))
// Output:
// 2021-03-01
// 2020-11-01
}
```
@@ -165,20 +239,17 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
after2Hours := datetime.AddHour(now, 2)
diff1 := after2Hours.Sub(now)
after2Hours := datetime.AddHour(date, 2)
before2Hours := datetime.AddHour(date, -2)
before2Hours := datetime.AddHour(now, -2)
diff2 := before2Hours.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Hours.Format("2006-01-02 15:04:05"))
fmt.Println(before2Hours.Format("2006-01-02 15:04:05"))
// Output:
// 2h0m0s
// -2h0m0s
// 2021-01-01 02:00:00
// 2020-12-31 22:00:00
}
```
@@ -204,20 +275,17 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
after2Minutes := datetime.AddMinute(now, 2)
diff1 := after2Minutes.Sub(now)
after2Minutes := datetime.AddMinute(date, 2)
before2Minutes := datetime.AddMinute(date, -2)
before2Minutes := datetime.AddMinute(now, -2)
diff2 := before2Minutes.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Minutes.Format("2006-01-02 15:04:05"))
fmt.Println(before2Minutes.Format("2006-01-02 15:04:05"))
// Output:
// 2m0s
// -2m0s
// 2021-01-01 00:02:00
// 2020-12-31 23:58:00
}
```
@@ -243,20 +311,137 @@ import (
)
func main() {
now := time.Now()
date, _ := time.Parse("2006-01-02", "2021-01-01")
after1Year := datetime.AddYear(now, 1)
diff1 := after1Year.Sub(now)
after2Years := AddYear(date, 2)
before2Years := AddYear(date, -2)
before1Year := datetime.AddYear(now, -1)
diff2 := before1Year.Sub(now)
fmt.Println(diff1)
fmt.Println(diff2)
fmt.Println(after2Years.Format("2006-01-02"))
fmt.Println(before2Years.Format("2006-01-02"))
// Output:
// 8760h0m0s
// -8760h0m0s
// 2023-01-01
// 2019-01-01
}
```
### <span id="AddDaySafe">AddDaySafe</span>
<p>Add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.</p>
<b>Signature:</b>
```go
func AddDaySafe(t time.Time, days int) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JTohZFpoDJ3)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
leapYearDate1, _ := time.Parse("2006-01-02", "2024-02-29")
result1 := datetime.AddDaySafe(leapYearDate1, 1)
leapYearDate2, _ := time.Parse("2006-01-02", "2024-03-01")
result2 := datetime.AddDaySafe(leapYearDate2, -1)
nonLeapYearDate1, _ := time.Parse("2006-01-02", "2025-02-28")
result3 := datetime.AddDaySafe(nonLeapYearDate1, 1)
nonLeaYearDate2, _ := time.Parse("2006-01-02", "2025-03-01")
result4 := datetime.AddDaySafe(nonLeaYearDate2, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
fmt.Println(result3.Format("2006-01-02"))
fmt.Println(result4.Format("2006-01-02"))
// Output:
// 2024-03-01
// 2024-02-29
// 2025-03-01
// 2025-02-28
}
```
### <span id="AddMonthSafe">AddMonthSafe</span>
<p>Add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.</p>
<b>Signature:</b>
```go
func AddMonthSafe(t time.Time, months int) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KLw0lo6mbVW)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date1, _ := time.Parse("2006-01-02", "2025-01-31")
result1 := datetime.AddMonthSafe(date1, 1)
date2, _ := time.Parse("2006-01-02", "2024-02-29")
result2 := datetime.AddMonthSafe(date2, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
// Output:
// 2025-02-28
// 2024-01-29
}
```
### <span id="AddYearSafe">AddYearSafe</span>
<p>Add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.</p>
<b>Signature:</b>
```go
func AddYearSafe(t time.Time, years int) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KVGXWZZ54ZH)</span></b>
```go
package main
import (
"fmt"
"time"
"github.com/duke-git/lancet/v2/datetime"
)
func main() {
date, _ := time.Parse("2006-01-02", "2020-02-29")
result1 := datetime.AddYearSafe(date, 1)
result2 := datetime.AddYearSafe(date, -1)
fmt.Println(result1.Format("2006-01-02"))
fmt.Println(result2.Format("2006-01-02"))
// Output:
// 2021-02-28
// 2019-02-28
}
```
@@ -366,7 +551,7 @@ func main() {
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DCHdcL6gnfV)</span></b>
```go
package main
@@ -379,12 +564,13 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.BeginOfWeek(input)
result := datetime.BeginOfWeek(input, time.Monday)
fmt.Println(result)
// Output:
// 2023-01-08 00:00:00 +0000 UTC
// 2023-01-02 00:00:00 +0000 UTC
}
```
@@ -558,7 +744,7 @@ func main() {
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/i08qKXD9flf)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/mGSA162YgX9)</span></b>
```go
package main
@@ -571,12 +757,12 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.EndOfWeek(input)
result := datetime.EndOfWeek(input, time.Sunday)
fmt.Println(result)
// Output:
// 2023-01-14 23:59:59.999999999 +0000 UTC
// 2023-01-08 23:59:59.999999999 +0000 UTC
}
```
@@ -1342,7 +1528,7 @@ import (
func main() {
result1 := datetime.NowDateOrTime("yyyy-mm-dd hh:mm:ss")
result2 := datetime.NowDateOrTime("yyyy-mm-dd hh:mm:ss", "EST")
result2 := datetime.NowDateOrTime("yyyy-mm-dd hh:mm:ss", "EST")
fmt.Println(result1)
fmt.Println(result2)
@@ -1586,7 +1772,7 @@ func main() {
func Min(t1 time.Time, times ...time.Time) time.Time
```
<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/MCIDvHNOGGb)</span></b>
```go
package main
@@ -1616,7 +1802,7 @@ func main() {
func Max(t1 time.Time, times ...time.Time) time.Time
```
<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/9m6JMk1LB7-)</span></b>
```go
package main
@@ -1646,7 +1832,7 @@ func main() {
func MaxMin(t1 time.Time, times ...time.Time) (maxTime time.Time, minTime time.Time)
```
<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/rbW51cDtM_2)</span></b>
```go
package main

View File

@@ -0,0 +1,407 @@
# EventBus
EventBus is an event bus used for handling events within an application.
<div STYLE="page-break-after: always;"></div>
## Source:
- [https://github.com/duke-git/lancet/blob/main/eventbus/eventbus.go](https://github.com/duke-git/lancet/blob/main/eventbus/eventbus.go)
<div STYLE="page-break-after: always;"></div>
## Usage:
```go
import (
"github.com/duke-git/lancet/v2/eventbus"
)
```
<div STYLE="page-break-after: always;"></div>
## Index
- [NewEventBus](#NewEventBus)
- [Subscribe](#Subscribe)
- [Unsubscribe](#Unsubscribe)
- [Publish](#Publish)
- [ClearListeners](#ClearListeners)
- [ClearListenersByTopic](#ClearListenersByTopic)
- [GetListenersCount](#GetListenersCount)
- [GetAllListenersCount](#GetAllListenersCount)
- [GetEvents](#GetEvents)
- [SetErrorHandler](#SetErrorHandler)
<div STYLE="page-break-after: always;"></div>
## Documentation
### <span id="NewEventBus">NewEventBus</span>
<p>Create an EventBus instance.</p>
<b>Signature:</b>
```go
func NewEventBus[T any]() *EventBus[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gHbOPV_NUOJ)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
// Output:
// 1
}
```
### <span id="Subscribe">Subscribe</span>
<p>Subscribes to an event with a specific event topic and listener function.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/EYGf_8cHei-)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
filter := func(eventData int) bool {
return eventData == 1
}
eb.Subscribe("event1", listener, false, 0, filter)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 2})
fmt.Println(receivedData)
// Output:
// 1
}
```
### <span id="Unsubscribe">Unsubscribe</span>
<p>Unsubscribes from an event with a specific event topic and listener function.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T))
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Tmh7Ttfvprf)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Unsubscribe("event1", listener)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
// Output:
// 0
}
```
### <span id="Publish">Publish</span>
<p>Publishes an event with a specific event topic and data payload.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) Publish(event eventbus.Event[T])
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gHTtVexFSH9)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {
fmt.Println(eventData)
}, false, 0, nil)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// 1
}
```
### <span id="ClearListeners">ClearListeners</span>
<p>Clears all the listeners.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) ClearListeners()
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KBfBYlKPgqD)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListeners()
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 0
}
```
### <span id="ClearListenersByTopic">ClearListenersByTopic</span>
<p>Clears all the listeners by topic.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) ClearListenersByTopic(topic string)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gvMljmJOZmU)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 2
}
```
### <span id="GetListenersCount">GetListenersCount</span>
<p>Returns the number of listeners for a specific event topic.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) GetListenersCount(topic string) int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/j6yaJ2xAmFz)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetListenersCount("event1")
fmt.Println(count)
// Output:
// 1
}
```
### <span id="GetAllListenersCount">GetAllListenersCount</span>
<p>Returns the total number of all listeners.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) GetAllListenersCount() int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/PUlr0xcpEOz)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetAllListenersCount()
fmt.Println(count)
// Output:
// 2
}
```
### <span id="GetEvents">GetEvents</span>
<p>Returns all the events topics.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) GetEvents() []string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/etgjjcOtAjX)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
events := eb.GetEvents()
for _, event := range events {
fmt.Println(event)
}
// Output:
// event2
// event1
}
```
### <span id="SetErrorHandler">SetErrorHandler</span>
<p>Sets the error handler function.</p>
<b>Signature:</b>
```go
func (eb *EventBus[T]) SetErrorHandler(handler func(err error))
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gmB0gnFe5mc)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/eventbus"
)
func main() {
eb := eventbus.NewEventBus[int]()
eb.SetErrorHandler(func(err error) {
fmt.Println(err)
})
eb.Subscribe("event1", func(eventData int) {
panic("error")
}, false, 0, nil)
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// error
}
```

View File

@@ -35,6 +35,7 @@ import (
- [IsDir](#IsDir)
- [ListFileNames](#ListFileNames)
- [RemoveFile](#RemoveFile)
- [RemoveDir](#RemoveDir)
- [ReadFileToString](#ReadFileToString)
- [ReadFileByLine](#ReadFileByLine)
- [Zip](#Zip)
@@ -395,7 +396,7 @@ func main() {
<b>Signature:</b>
```go
func RemoveFile(path string) error
func RemoveFile(path string, onDelete ...func(path string)) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/P2y0XW8a1SH)</span></b>
@@ -416,6 +417,41 @@ func main() {
}
```
### <span id="RemoveDir">RemoveDir</span>
<p>Delete directory.</p>
<b>Signature:</b>
```go
func RemoveDir(path string, onDelete ...func(path string)) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
var deletedPaths []string
err := fileutil.RemoveDir("./tempdir", func(p string) {
deletedPaths = append(deletedPaths, p)
})
if err != nil {
fmt.Println(err)
}
fmt.Println(deletedPaths)
}
```
### <span id="ReadFileToString">ReadFileToString</span>
<p>Return string of file content.</p>
@@ -934,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>
@@ -987,7 +1023,7 @@ import (
func main() {
const mb = 1024 * 1024
const defaultChunkSizeMB = 100
const defaultChunkSizeMB = 100
// test1.csv file content:
// Lili,22,female
@@ -1051,7 +1087,7 @@ func main() {
numParsers := runtime.NumCPU()
linesCh := make(chan []string, numParsers)
// test1.csv file content:
// Lili,22,female
// Jim,21,male
@@ -1087,7 +1123,7 @@ func main() {
func GetExeOrDllVersion(filePath 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/iLRrDBhE38E)</span></b>
```go
package main
@@ -1104,8 +1140,8 @@ func main() {
}
fmt.Println(v)
// Output:
// 3.9.10.19
}
```
```

View File

@@ -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:
@@ -79,6 +78,7 @@ import (
- [GetOrSet](#GetOrSet)
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
<div STYLE="page-break-after: always;"></div>
@@ -1110,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>
@@ -1274,7 +1274,6 @@ func main() {
}
```
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
<p>Deletes the key-value pair for the given key.</p>
@@ -1837,7 +1836,7 @@ func main() {
fmt.Println(om.Elements())
// Output:
// [{a 1} {b 2} {c 3}]
// [{a 1} {b 2} {c 3}]
}
```
@@ -2276,8 +2275,8 @@ func main() {
}
result := maputil.SortByKey(m, func(a, b int) bool {
return a < b
})
return a < b
})
fmt.Println(result)
@@ -2288,15 +2287,15 @@ func main() {
### <span id="GetOrDefault">GetOrDefault</span>
<p>returns the value of the given key or a default value if the key is not present.</p>
<p>Returns the value of the given key or a default value if the key is not present.</p>
<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;">[运行](https://go.dev/play/p/99QjSYSBdiM)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/99QjSYSBdiM)</span></b>
```go
package main
@@ -2324,4 +2323,43 @@ func main() {
// a
// default
}
```
```
### <span id="FindValuesBy">FindValuesBy</span>
<p>Returns a slice of values from the map that satisfy the given predicate function.</p>
<b>Signature:</b>
```go
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](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
2: "b",
3: "c",
4: "d",
}
result := maputil.FindValuesBy(m, func(k int, v string) bool {
return k%2 == 0
})
fmt.Println(result)
// Output:
// [b d]
}
```

View File

@@ -1177,7 +1177,7 @@ func main() {
func Variance[T constraints.Float | constraints.Integer](numbers []T) float64
```
<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/uHuV4YgXf8F)</span></b>
```go
package main
@@ -1210,7 +1210,7 @@ func main() {
func StdDev[T constraints.Float | constraints.Integer](numbers []T) float64
```
<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/FkNZDXvHD2l)</span></b>
```go
package main
@@ -1243,7 +1243,7 @@ func main() {
func Permutation(n, k uint) uint
```
<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/MgobwH_FOxj)</span></b>
```go
package main
@@ -1276,7 +1276,7 @@ func main() {
func Combination(n, k uint) uint
```
<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/ENFQRDQUFi9)</span></b>
```go
package main

View File

@@ -48,6 +48,8 @@ import (
- [UploadFile](#UploadFile)
- [IsPingConnected](#IsPingConnected)
- [IsTelnetConnected](#IsTelnetConnected)
- [BuildUrl](#BuildUrl)
- [AddQueryParams](#AddQueryParams)
<div STYLE="page-break-after: always;"></div>
@@ -1031,3 +1033,83 @@ func main() {
// false
}
```
### <span id="BuildUrl">BuildUrl</span>
<p>Builds a URL from the given params.</p>
<b>Signature:</b>
```go
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/netutil"
)
func main() {
urlStr, err := netutil.BuildUrl(
"https",
"example.com",
"query",
map[string][]string{
"a": {"foo", "bar"},
"b": {"baz"},
},
)
fmt.Println(urlStr)
fmt.Println(err)
// Output:
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
```
### <span id="AddQueryParams">AddQueryParams</span>
<p>Adds query parameters to the given URL.</p>
<b>Signature:</b>
```go
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/netutil"
)
func main() {
urlStr, err := netutil.BuildUrl(
"https",
"example.com",
"query",
map[string][]string{
"a": {"foo", "bar"},
"b": {"baz"},
},
)
fmt.Println(urlStr)
fmt.Println(err)
// Output:
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
```

View File

@@ -536,7 +536,7 @@ func main() {
func RandNumberOfLength(len int) int
```
<b>Signature:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Signature:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/oyZbuV7bu7b)</span></b>
```go
package main

View File

@@ -43,6 +43,7 @@ import (
- [DropRightWhile](#DropRightWhile)
- [Equal](#Equal)
- [EqualWith](#EqualWith)
- [EqualUnordered](#EqualUnordered)
- [Every](#Every)
- [Filter](#Filter)
- [FilterConcurrent](#FilterConcurrent)
@@ -69,6 +70,7 @@ import (
- [FlatMap](#FlatMap)
- [Merge](#Merge)
- [Reverse](#Reverse)
- [ReverseCopy](#ReverseCopy)
- [Reduce<sup>deprecated</sup>](#Reduce)
- [ReduceConcurrent](#ReduceConcurrent)
- [ReduceBy](#ReduceBy)
@@ -77,6 +79,7 @@ import (
- [ReplaceAll](#ReplaceAll)
- [Repeat](#Repeat)
- [Shuffle](#Shuffle)
- [ShuffleCopy](#ShuffleCopy)
- [IsAscending](#IsAscending)
- [IsDescending](#IsDescending)
- [IsSorted](#IsSorted)
@@ -839,6 +842,37 @@ func main() {
}
```
### <span id="EqualUnordered">EqualUnordered</span>
<p>Checks if two slices are equal: the same length and all elements value are equal (unordered).</p>
<b>Signature:</b>
```go
func EqualUnordered[T comparable](slice1, slice2 []T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/n8fSc2w8ZgX)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.EqualUnordered([]int{1, 2, 3}, []int{3, 2, 1})
result2 := slice.EqualUnordered([]int{1, 2, 3}, []int{4, 5, 6})
fmt.Println(result1)
fmt.Println(result2)
// Output:
// true
// false
}
```
### <span id="Every">Every</span>
<p>Return true if all of the values in the slice pass the predicate function.</p>
@@ -1724,6 +1758,38 @@ func main() {
}
```
### <span id="ReverseCopy">ReverseCopy</span>
<p>Return a new slice of element order is reversed to the given slice.</p>
<b>Signature:</b>
```go
func ReverseCopy[T any](slice []T) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/c9arEaP7Cg-)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
strs := []string{"a", "b", "c", "d"}
reversedStrs := slice.ReverseCopy(strs)
fmt.Println(reversedStrs)
fmt.Println(strs)
// Output:
// [d c b a]
// [a b c d]
}
```
### <span id="Reduce">Reduce</span>
<p>Reduce slice.</p>
@@ -1986,6 +2052,37 @@ func main() {
}
```
### <span id="ShuffleCopy">ShuffleCopy</span>
<p>Return a new slice with elements shuffled.</p>
<b>Signature:</b>
```go
func ShuffleCopy[T any](slice []T) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/vqDa-Gs1vT0)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
result := slice.ShuffleCopy(nums)
fmt.Println(result)
fmt.Println(nums)
// Output:
// [3 1 5 4 2] (random order)
// [1 2 3 4 5]
}
```
### <span id="IsAscending">IsAscending</span>
<p>Checks if a slice is ascending order.</p>
@@ -2995,7 +3092,7 @@ func main() {
func JoinFunc[T any](slice []T, sep string, transform func(T) T) string
```
<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/55ib3SB5fM2)</span></b>
```go
import (
@@ -3025,7 +3122,7 @@ func main() {
func ConcatBy[T any](slice []T, sep T, connector func(T, T) T) T
```
<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/6QcUpcY4UMW)</span></b>
```go
import (

View File

@@ -950,7 +950,7 @@ func main() {
func (s Stream[T]) IndexOf(target T, equal func(a, b T) bool) int
```
<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/tBV5Nc-XDX2)</span></b>
```go
import (
@@ -983,7 +983,7 @@ func main() {
func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int
```
<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/CjeoNw2eac_G)</span></b>
```go
import (

View File

@@ -68,7 +68,8 @@ import (
- [Rotate](#Rotate)
- [TemplateReplace](#TemplateReplace)
- [RegexMatchAllGroups](#RegexMatchAllGroups)
- [ExtractContent](#RegexMatchAllGroups)
- [ExtractContent](#ExtractContent)
- [FindAllOccurrences](#FindAllOccurrences)
<div STYLE="page-break-after: always;"></div>
@@ -1743,7 +1744,7 @@ func main() {
func ExtractContent(s, start, end string) []string
```
<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/Ay9UIk7Rum9)</span></b>
```go
import (
@@ -1761,4 +1762,32 @@ func main() {
// Output:
// [content1 content2 content1]
}
```
### <span id="FindAllOccurrences">FindAllOccurrences</span>
<p>Returns the positions of all occurrences of a substring in a string.</p>
<b>Signature:</b>
```go
func FindAllOccurrences(str, substr string) []int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uvyA6azGLB1)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
result := strutil.FindAllOccurrences("ababab", "ab")
fmt.Println(result)
// Output:
// [0 2 4]
}
```

View File

@@ -49,6 +49,7 @@ import (
- [IsIp](#IsIp)
- [IsIpV4](#IsIpV4)
- [IsIpV6](#IsIpV6)
- [IsIpPort](#IsIpPort)
- [IsStrongPassword](#IsStrongPassword)
- [IsUrl](#IsUrl)
- [IsWeakPassword](#IsWeakPassword)
@@ -825,6 +826,43 @@ func main() {
}
```
### <span id="IsAlphaNumeric">IsAlphaNumeric</span>
<p>Check if the string is alphanumeric.</p>
<b>Signature:</b>
```go
func IsAlphaNumeric(s string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/RHeESLrLg9c)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsAlphaNumeric("ABC")
result2 := validator.IsAlphaNumeric("123")
result3 := validator.IsAlphaNumeric("abc123")
result4 := validator.IsAlphaNumeric("abc123@#$")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
```
### <span id="IsJSON">IsJSON</span>
<p>Check if the string is valid JSON.</p>
@@ -992,6 +1030,43 @@ func main() {
}
```
### <span id="IsIpPort">IsIpPort</span>
<p>Check if the string is ip:port</p>
<b>Signature:</b>
```go
func IsIpPort(str string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/xUmls_b9L29)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsIpPort("127.0.0.1:8080")
result2 := validator.IsIpPort("[0:0:0:0:0:0:0:1]:8080")
result3 := validator.IsIpPort(":8080")
result4 := validator.IsIpPort("::0:0:0:0:")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// false
// false
}
```
### <span id="IsStrongPassword">IsStrongPassword</span>
<p>Check if the string is strong password (alpha(lower+upper) + number + special chars(!@#$%^&*()?gt&lt)).</p>

View File

@@ -507,7 +507,7 @@ func (tc *TryCatch) Finally(finallyFunc func(ctx context.Context)) *TryCatch
func (tc *TryCatch) Do()
```
<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/D5Mdb0mRj0P)</span></b>
```go
package main

View File

@@ -10,7 +10,7 @@ outline: deep
go get github.com/duke-git/lancet/v2 // will install latest version of v2.x.x
```
2. <b>For users who use version below go1.18, you should install v1.x.x. The latest of v1.x.x is v1.4.5. </b>
2. <b>For users who use version below go1.18, you should install v1.x.x. The latest of v1.x.x is v1.4.6. </b>
```go
go get github.com/duke-git/lancet // below go1.18, install latest version of v1.x.x

View File

@@ -10,7 +10,7 @@ outline: deep
go get github.com/duke-git/lancet/v2 // will install latest version of v2.x.x
```
2. <b>使用 go1.18 以下版本的用户,必须安装 v1.x.x。目前最新的 v1 版本是 v1.4.5。</b>
2. <b>使用 go1.18 以下版本的用户,必须安装 v1.x.x。目前最新的 v1 版本是 v1.4.6。</b>
```go
go get github.com/duke-git/lancet // below go1.18, install latest version of v1.x.x

195
eventbus/eventbus.go Normal file
View File

@@ -0,0 +1,195 @@
// Copyright 2025 dudaodong@gmail.com. All rights reserved.
// Use of this source code is governed by MIT license
// Package eventbus implements a simple event bus.
package eventbus
import (
"fmt"
"sort"
"sync"
)
// Event is the struct that is passed to the event listener, now it directly uses the generic Payload type.
type Event[T any] struct {
Topic string
Payload T
}
// EventBus is the struct that holds the listeners and the error handler.
type EventBus[T any] struct {
// listeners map[string][]*EventListener[T]
listeners sync.Map
mu sync.RWMutex
errorHandler func(err error)
}
// EventListener is the struct that holds the listener function and its priority.
type EventListener[T any] struct {
priority int
listener func(eventData T)
async bool
filter func(eventData T) bool
}
// NewEventBus creates a new EventBus.
// Play: https://go.dev/play/p/gHbOPV_NUOJ
func NewEventBus[T any]() *EventBus[T] {
return &EventBus[T]{
listeners: sync.Map{},
}
}
// Subscribe subscribes to an event with a specific event topic and listener function.
// Play: https://go.dev/play/p/EYGf_8cHei-
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool) {
eb.mu.Lock()
defer eb.mu.Unlock()
el := &EventListener[T]{
priority: priority,
listener: listener,
async: async,
filter: filter,
}
listenersInterface, _ := eb.listeners.LoadOrStore(topic, []*EventListener[T]{})
listeners := listenersInterface.([]*EventListener[T])
listeners = append(listeners, el)
sort.Slice(listeners, func(i, j int) bool {
return listeners[i].priority > listeners[j].priority
})
eb.listeners.Store(topic, listeners)
}
// Unsubscribe unsubscribes from an event with a specific event topic and listener function.
// Play: https://go.dev/play/p/Tmh7Ttfvprf
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T)) {
eb.mu.Lock()
defer eb.mu.Unlock()
listenersInterface, ok := eb.listeners.Load(topic)
if !ok {
return
}
listeners := listenersInterface.([]*EventListener[T])
listenerPtr := fmt.Sprintf("%p", listener)
var updatedListeners []*EventListener[T]
for _, l := range listeners {
if fmt.Sprintf("%p", l.listener) != listenerPtr {
updatedListeners = append(updatedListeners, l)
}
}
eb.listeners.Store(topic, updatedListeners)
}
// Publish publishes an event with a specific event topic and data payload.
// Play: https://go.dev/play/p/gHTtVexFSH9
func (eb *EventBus[T]) Publish(event Event[T]) {
eb.mu.RLock()
defer eb.mu.RUnlock()
listenersInterface, exists := eb.listeners.Load(event.Topic)
if !exists {
return
}
listeners := listenersInterface.([]*EventListener[T])
for _, listener := range listeners {
if listener.filter != nil && !listener.filter(event.Payload) {
continue
}
if listener.async {
go eb.publishToListener(listener, event)
} else {
eb.publishToListener(listener, event)
}
}
}
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))
}
}()
listener.listener(event.Payload)
}
// SetErrorHandler sets the error handler function.
// Play: https://go.dev/play/p/gmB0gnFe5mc
func (eb *EventBus[T]) SetErrorHandler(handler func(err error)) {
eb.errorHandler = handler
}
// ClearListeners clears all the listeners.
// Play: https://go.dev/play/p/KBfBYlKPgqD
func (eb *EventBus[T]) ClearListeners() {
eb.mu.Lock()
defer eb.mu.Unlock()
eb.listeners = sync.Map{}
}
// ClearListenersByTopic clears all the listeners by topic.
// Play: https://go.dev/play/p/gvMljmJOZmU
func (eb *EventBus[T]) ClearListenersByTopic(topic string) {
eb.mu.Lock()
defer eb.mu.Unlock()
eb.listeners.Delete(topic)
}
// GetListenersCount returns the number of listeners for a specific event topic.
// Play: https://go.dev/play/p/8VPJsMQgStM
func (eb *EventBus[T]) GetListenersCount(topic string) int {
eb.mu.RLock()
defer eb.mu.RUnlock()
listenersInterface, ok := eb.listeners.Load(topic)
if !ok {
return 0
}
listeners := listenersInterface.([]*EventListener[T])
return len(listeners)
}
// GetAllListenersCount returns the total number of listeners.
// Play: https://go.dev/play/p/PUlr0xcpEOz
func (eb *EventBus[T]) GetAllListenersCount() int {
eb.mu.RLock()
defer eb.mu.RUnlock()
count := 0
eb.listeners.Range(func(key, value interface{}) bool {
count += len(value.([]*EventListener[T]))
return true
})
return count
}
// GetEvents returns all the events topics.
// Play: https://go.dev/play/p/etgjjcOtAjX
func (eb *EventBus[T]) GetEvents() []string {
eb.mu.RLock()
defer eb.mu.RUnlock()
var events []string
eb.listeners.Range(func(key, value interface{}) bool {
events = append(events, key.(string))
return true
})
return events
}

View File

@@ -0,0 +1,237 @@
package eventbus
import (
"fmt"
"sort"
"sync"
"time"
)
func ExampleEventBus_Subscribe() {
eb := NewEventBus[string]()
eb.Subscribe("event1", func(eventData string) {
fmt.Println(eventData)
}, false, 0, nil)
eb.Publish(Event[string]{Topic: "event1", Payload: "hello"})
// Output:
// hello
}
func ExampleEventBus_Unsubscribe() {
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Unsubscribe("event1", listener)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
// Output:
// 0
}
func ExampleEventBus_Subscribe_withFilter() {
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
filter := func(eventData int) bool {
return eventData == 1
}
eb.Subscribe("event1", listener, false, 0, filter)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
fmt.Println(receivedData)
// Output:
// 1
}
func ExampleEventBus_Subscribe_withPriority() {
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {
fmt.Println(eventData)
}, false, 0, nil)
eb.Subscribe("event1", func(eventData int) {
fmt.Println(eventData)
}, false, 1, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
// Output:
// 1
// 1
}
func ExampleEventBus_Subscribe_async() {
eb := NewEventBus[int]()
var wg sync.WaitGroup
wg.Add(1)
eb.Subscribe("event1", func(eventData int) {
time.Sleep(100 * time.Millisecond)
fmt.Println(eventData)
wg.Done()
}, true, 1, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
wg.Wait()
// Output:
// 1
}
func ExampleEventBus_Publish() {
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {
fmt.Println(eventData)
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
// Output:
// 1
}
func ExampleEventBus() {
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
// Output:
// 1
}
func ExampleEventBus_ClearListeners() {
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListeners()
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 0
}
func ExampleEventBus_ClearListenersByTopic() {
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 2
}
func ExampleEventBus_GetListenersCount() {
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetListenersCount("event1")
fmt.Println(count)
// Output:
// 1
}
func ExampleEventBus_SetErrorHandler() {
eb := NewEventBus[int]()
eb.SetErrorHandler(func(err error) {
fmt.Println(err)
})
eb.Subscribe("event1", func(eventData int) {
panic("error")
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
// Output:
// error
}
func ExampleEventBus_GetAllListenersCount() {
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetAllListenersCount()
fmt.Println(count)
// Output:
// 2
}
func ExampleEventBus_GetEvents() {
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
events := eb.GetEvents()
sort.Strings(events)
for _, event := range events {
fmt.Println(event)
}
// Output:
// event1
// event2
}

221
eventbus/eventbus_test.go Normal file
View File

@@ -0,0 +1,221 @@
package eventbus
import (
"sort"
"sync"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
func TestEventBus_Subscribe(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_Subscribe")
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {
assert.Equal(1, eventData)
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
}
func TestEventBus_Unsubscribe(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_Unsubscribe")
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Unsubscribe("event1", listener)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
assert.Equal(0, receivedData)
}
func TestEventBus_Subscribe_withFilter(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_Subscribe_withFilter")
eb := NewEventBus[int]()
receivedData := 0
listener := func(eventData int) {
receivedData = eventData
}
filter := func(eventData int) bool {
return eventData == 1
}
eb.Subscribe("event1", listener, false, 0, filter)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
assert.Equal(1, receivedData)
}
func TestEventBus_Subscribe_withPriority(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_Subscribe_withPriority")
eb := NewEventBus[int]()
var receivedData []int
listener1 := func(eventData int) {
receivedData = append(receivedData, 1)
}
listener2 := func(eventData int) {
receivedData = append(receivedData, 2)
}
eb.Subscribe("event1", listener1, false, 1, nil)
eb.Subscribe("event1", listener2, false, 2, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
assert.Equal([]int{2, 1}, receivedData)
}
func TestEventBus_Subscribe_async(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_Subscribe_async")
eb := NewEventBus[string]()
var wg sync.WaitGroup
wg.Add(1)
eb.Subscribe("event1", func(eventData string) {
time.Sleep(100 * time.Millisecond)
assert.Equal("hello", eventData)
wg.Done()
}, true, 1, nil)
eb.Publish(Event[string]{Topic: "event1", Payload: "hello"})
wg.Wait()
}
func TestEventBus_ErrorHandler(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_ErrorHandler")
eb := NewEventBus[string]()
eb.SetErrorHandler(func(err error) {
assert.Equal("error", err.Error())
})
eb.Subscribe("event1", func(eventData string) {
panic("error")
}, false, 0, nil)
eb.Publish(Event[string]{Topic: "event1", Payload: "hello"})
}
func TestEventBus_ClearListeners(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_ClearListeners")
eb := NewEventBus[int]()
receivedData1 := 0
receivedData2 := 0
eb.Subscribe("event1", func(eventData int) {
receivedData1 = eventData
}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {
receivedData2 = eventData
}, false, 0, nil)
eb.ClearListeners()
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
assert.Equal(0, receivedData1)
assert.Equal(0, receivedData2)
}
func TestEventBus_ClearListenersByTopic(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_ClearListenersByTopic")
eb := NewEventBus[int]()
receivedData1 := 0
receivedData2 := 0
eb.Subscribe("event1", func(eventData int) {
receivedData1 = eventData
}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {
receivedData2 = eventData
}, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
assert.Equal(0, receivedData1)
assert.Equal(2, receivedData2)
}
func TestEventBus_GetListenersCount(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_GetListenersCount")
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
assert.Equal(2, eb.GetListenersCount("event1"))
assert.Equal(1, eb.GetListenersCount("event2"))
}
func TestEventBus_GetAllListenersCount(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_GetAllListenersCount")
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
assert.Equal(3, eb.GetAllListenersCount())
}
func TestEventBus_GetEvents(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEventBus_GetEvents")
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
events := eb.GetEvents()
sort.Strings(events)
assert.Equal(2, len(events))
assert.Equal([]string{"event1", "event2"}, events)
}

View File

@@ -25,6 +25,8 @@ import (
"sync"
"github.com/duke-git/lancet/v2/validator"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
)
// FileReader is a reader supporting offset seeking and reading one
@@ -170,10 +172,54 @@ func IsDir(path string) bool {
// RemoveFile remove the path file.
// Play: https://go.dev/play/p/P2y0XW8a1SH
func RemoveFile(path string) error {
func RemoveFile(path string, onDelete ...func(path string)) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if info.IsDir() {
return fmt.Errorf("%s is a directory", path)
}
if len(onDelete) > 0 && onDelete[0] != nil {
onDelete[0](path)
}
return os.Remove(path)
}
// RemoveDir remove the path directory.
// Play: https://go.dev/play/p/Oa6KnPek2uy
func RemoveDir(path string, onDelete ...func(path string)) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if !info.IsDir() {
return fmt.Errorf("%s is not a directory", path)
}
var callback func(string)
if len(onDelete) > 0 {
callback = onDelete[0]
}
err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
if err == nil && callback != nil {
callback(p)
}
return nil
})
if err != nil {
return err
}
return os.RemoveAll(path)
}
// CopyFile copy src file to dest file.
// Play: https://go.dev/play/p/Jg9AMJMLrJi
func CopyFile(srcPath string, dstPath string) error {
@@ -422,8 +468,15 @@ func UnZip(zipFile string, destPath string) error {
defer zipReader.Close()
for _, f := range zipReader.File {
decodeName := f.Name
if f.Flags == 0 {
i := bytes.NewReader([]byte(f.Name))
decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder())
content, _ := io.ReadAll(decoder)
decodeName = string(content)
}
// issue#62: fix ZipSlip bug
path, err := safeFilepathJoin(destPath, f.Name)
path, err := safeFilepathJoin(destPath, decodeName)
if err != nil {
return err
}

View File

@@ -85,14 +85,37 @@ func TestIsDir(t *testing.T) {
func TestRemoveFile(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRemoveFile")
f := "./text.txt"
if !IsExist(f) {
CreateFile(f)
err := RemoveFile(f)
assert.IsNil(err)
CreateFile(f)
err := RemoveFile(f)
assert.IsNil(err)
}
func TestRemoveDir(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRemoveDir")
err := os.MkdirAll("./tempdir/a/b", 0755)
if err != nil {
t.Error(err)
t.FailNow()
}
var deletedPaths []string
err = RemoveDir("./tempdir", func(p string) {
deletedPaths = append(deletedPaths, p)
})
if err != nil {
t.Error(err)
t.FailNow()
}
assert.Equal([]string{"./tempdir", "tempdir/a", "tempdir/a/b"}, deletedPaths)
assert.Equal(false, IsExist("./tempdir"))
}
func TestCopyFile(t *testing.T) {

View File

@@ -26,7 +26,7 @@ type tagVS_FIXEDFILEINFO struct {
}
// GetExeOrDllVersion get the version of exe or dll file on windows.
// Play: todo
// Play: https://go.dev/play/p/iLRrDBhE38E
func GetExeOrDllVersion(filePath string) (string, error) {
// 加载系统dll
versionDLL := syscall.NewLazyDLL("version.dll")

View File

@@ -666,3 +666,17 @@ func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
}
return defaultValue
}
// FindValuesBy returns a slice of values from the map that satisfy the given predicate function.
// 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)
for k, v := range m {
if predicate(k, v) {
result = append(result, v)
}
}
return result
}

View File

@@ -829,3 +829,25 @@ func ExampleOrderedMap_UnmarshalJSON() {
// fmt.Println(om.Elements())
}
func ExampleFindValuesBy() {
m := map[int]string{
1: "a",
2: "b",
3: "c",
4: "d",
}
result := FindValuesBy(m, func(k int, v string) bool {
return k%2 == 0
})
// github action will excute this test currently, so sort the result
// to make it deterministic
sort.Strings(result)
fmt.Println(result)
// Output:
// [b d]
}

View File

@@ -888,3 +888,41 @@ func TestGetOrDefault(t *testing.T) {
result2 := GetOrDefault(m1, 5, "123")
assert.Equal("123", result2)
}
func TestFindValuesBy(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestFindValuesBy")
tests := []struct {
name string
inputMap map[string]string
key string
expected []string
}{
{
name: "Key exists",
inputMap: map[string]string{"a": "1", "b": "2", "c": "3"},
key: "b",
expected: []string{"2"},
},
{
name: "Key does not exist",
inputMap: map[string]string{"a": "1", "b": "2", "c": "3"},
key: "d",
expected: []string{},
},
{
name: "Empty map",
inputMap: map[string]string{},
key: "a",
expected: []string{},
},
}
for _, tt := range tests {
result := FindValuesBy(tt.inputMap, func(key string, value string) bool {
return key == tt.key
})
assert.Equal(tt.expected, result)
}
}

View File

@@ -145,10 +145,14 @@ func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string
// Max return max value of numbers.
// Play: https://go.dev/play/p/cN8DHI0rTkH
func Max[T constraints.Integer | constraints.Float](numbers ...T) T {
max := numbers[0]
func Max[T constraints.Ordered](items ...T) T {
if len(items) < 1 {
panic("mathutil.Max: empty list")
}
for _, v := range numbers {
max := items[0]
for _, v := range items {
if max < v {
max = v
}
@@ -181,10 +185,14 @@ func MaxBy[T any](slice []T, comparator func(T, T) bool) T {
// Min return min value of numbers.
// Play: https://go.dev/play/p/21BER_mlGUj
func Min[T constraints.Integer | constraints.Float](numbers ...T) T {
min := numbers[0]
func Min[T constraints.Ordered](items ...T) T {
if len(items) < 1 {
panic("mathutil.min: empty list")
}
for _, v := range numbers {
min := items[0]
for _, v := range items {
if min > v {
min = v
}
@@ -400,7 +408,7 @@ func Div[T constraints.Float | constraints.Integer](x T, y T) float64 {
}
// Variance returns the variance of numbers.
// Play: todo
// Play: https://go.dev/play/p/uHuV4YgXf8F
func Variance[T constraints.Float | constraints.Integer](numbers []T) float64 {
n := len(numbers)
if n == 0 {
@@ -418,13 +426,13 @@ func Variance[T constraints.Float | constraints.Integer](numbers []T) float64 {
}
// StdDev returns the standard deviation of numbers.
// Play: todo
// Play: https://go.dev/play/p/FkNZDXvHD2l
func StdDev[T constraints.Float | constraints.Integer](numbers []T) float64 {
return math.Sqrt(Variance(numbers))
}
// Permutation calculate P(n, k).
// Play: todo
// Play: https://go.dev/play/p/MgobwH_FOxj
func Permutation(n, k uint) uint {
if n < k {
return 0
@@ -437,7 +445,7 @@ func Permutation(n, k uint) uint {
}
// Combination calculate C(n, k).
// Play: todo
// Play: https://go.dev/play/p/ENFQRDQUFi9
func Combination(n, k uint) uint {
if n < k {
return 0

View File

@@ -178,6 +178,7 @@ func TestMax(t *testing.T) {
assert.Equal(0, Max(0, 0))
assert.Equal(3, Max(1, 2, 3))
assert.Equal(1.4, Max(1.2, 1.4, 1.1, 1.4))
assert.Equal("abc", Max("a", "ab", "abc"))
type Integer int
assert.Equal(Integer(1), Max(Integer(1), Integer(0)))
@@ -212,6 +213,8 @@ func TestMin(t *testing.T) {
assert.Equal(0, Min(0, 0))
assert.Equal(1, Min(1, 2, 3))
assert.Equal(1.1, Min(1.2, 1.4, 1.1, 1.4))
assert.Equal("a", Min("a", "ab", "abc"))
}
func TestMinBy(t *testing.T) {

View File

@@ -3,7 +3,6 @@ package netutil
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
@@ -27,8 +26,8 @@ func TestHttpGet(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestHttpPost(t *testing.T) {
@@ -49,8 +48,8 @@ func TestHttpPost(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestHttpPostFormData(t *testing.T) {
@@ -69,8 +68,8 @@ func TestHttpPostFormData(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestHttpPut(t *testing.T) {
@@ -92,8 +91,8 @@ func TestHttpPut(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestHttpPatch(t *testing.T) {
@@ -115,8 +114,8 @@ func TestHttpPatch(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestHttpDelete(t *testing.T) {
@@ -127,8 +126,8 @@ func TestHttpDelete(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestConvertMapToQueryString(t *testing.T) {
@@ -229,8 +228,8 @@ func TestHttpClent_Post(t *testing.T) {
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
defer resp.Body.Close()
t.Log("response status:", resp.StatusCode)
}
func TestStructToUrlValues(t *testing.T) {

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
@@ -11,6 +12,7 @@ import (
"net/url"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"time"
@@ -305,3 +307,97 @@ func IsTelnetConnected(host string, port string) bool {
return true
}
// BuildUrl builds a URL from the given params.
// 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
}
if path != "" {
if !hostRegex.MatchString(host) {
return "", fmt.Errorf("invalid host: '%s' is not a valid host", host)
}
}
parsedUrl := &url.URL{
Scheme: scheme,
Host: host,
}
if path == "" {
parsedUrl.Path = "/"
} else if !strings.HasPrefix(path, "/") {
parsedUrl.Path = "/" + path
} else {
parsedUrl.Path = path
}
queryParams := parsedUrl.Query()
for key, values := range query {
for _, value := range values {
queryParams.Add(key, value)
}
}
parsedUrl.RawQuery = queryParams.Encode()
return parsedUrl.String(), nil
}
var supportedSchemes = map[string]bool{
"http": true,
"https": true,
"ftp": true,
"file": true,
"mailto": true,
"ws": true, // WebSocket
"wss": true, // WebSocket Secure
"data": true, // Data URL
}
func validateScheme(scheme string) error {
if _, exists := supportedSchemes[scheme]; !exists {
return fmt.Errorf("invalid scheme: '%s' is not supported", scheme)
}
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]?)+$`)
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: https://go.dev/play/p/JLXl1hZK7l4
func AddQueryParams(urlStr string, params map[string][]string) (string, error) {
parsedUrl, err := url.Parse(urlStr)
if err != nil {
return "", err
}
queryParams := parsedUrl.Query()
for k, values := range params {
if k == "" {
return "", errors.New("empty key is not allowed")
}
if !alphaNumericRegex.MatchString(k) {
return "", fmt.Errorf("query parameter key %s must be alphanumeric", k)
}
for _, v := range values {
if !alphaNumericRegex.MatchString(v) {
return "", fmt.Errorf("query parameter value %s must be alphanumeric", v)
}
queryParams.Add(k, v)
}
}
parsedUrl.RawQuery = queryParams.Encode()
return parsedUrl.String(), nil
}

View File

@@ -201,3 +201,40 @@ func ExampleIsTelnetConnected() {
// true
// false
}
func ExampleBuildUrl() {
urlStr, err := BuildUrl(
"https",
"example.com",
"query",
map[string][]string{
"a": {"foo", "bar"},
"b": {"baz"},
},
)
fmt.Println(urlStr)
fmt.Println(err)
// Output:
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
func ExampleAddQueryParams() {
urlStr := "https://example.com"
params := map[string][]string{
"a": {"foo", "bar"},
"b": {"baz"},
}
urlStr, err := AddQueryParams(urlStr, params)
fmt.Println(urlStr)
fmt.Println(err)
// Output:
// https://example.com?a=foo&a=bar&b=baz
// <nil>
}

View File

@@ -142,3 +142,118 @@ func TestTelnetConnected(t *testing.T) {
result2 := IsTelnetConnected("www.baidu.com", "123")
assert.Equal(false, result2)
}
func TestBuildUrl(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestBuildUrl")
tests := []struct {
scheme string
host string
path string
query map[string][]string
want string
wantErr bool
}{
{
scheme: "http",
host: "www.test.com",
path: "/path/subpath",
query: map[string][]string{"a": {"1"}, "b": {"2"}},
want: "http://www.test.com/path/subpath?a=1&b=2",
wantErr: false,
},
{
scheme: "http",
host: "www.test.com",
path: "/simple-path",
query: map[string][]string{"a": {"1"}, "b": {"2"}},
want: "http://www.test.com/simple-path?a=1&b=2",
wantErr: false,
},
{
scheme: "http",
host: "www.test.com",
path: "",
query: map[string][]string{"a": {"foo", "bar"}, "b": {"baz"}},
want: "http://www.test.com/?a=foo&a=bar&b=baz",
wantErr: false,
},
{
scheme: "https",
host: "www.test. com",
path: "/path",
query: nil,
want: "",
wantErr: true,
},
{
scheme: "https",
host: "www.test.com",
path: "/path with spaces",
query: nil,
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 {
got, err := BuildUrl(tt.scheme, tt.host, tt.path, tt.query)
assert.Equal(tt.want, got)
assert.Equal(tt.wantErr, err != nil)
}
}
func TestAddQueryParams(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestAddQueryParams")
tests := []struct {
url string
query map[string][]string
want string
wantErr bool
}{
{
url: "http://www.test.com",
query: map[string][]string{"a": {"1"}, "b": {"2"}},
want: "http://www.test.com?a=1&b=2",
wantErr: false,
},
{
url: "http://www.test.com",
query: map[string][]string{"a": {"foo", "bar"}, "b": {"baz"}},
want: "http://www.test.com?a=foo&a=bar&b=baz",
wantErr: false,
},
{
url: "http://www.test.com",
query: map[string][]string{},
want: "http://www.test.com",
wantErr: false,
},
{
url: "http://www.test.com",
query: map[string][]string{"a": {"$%"}},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
got, err := AddQueryParams(tt.url, tt.query)
assert.Equal(tt.want, got)
assert.Equal(tt.wantErr, err != nil)
}
}

View File

@@ -126,12 +126,24 @@ func RandFloat(min, max float64, precision int) float64 {
n := rand.Float64()*(max-min) + min
return mathutil.RoundToFloat(n, precision)
return mathutil.FloorToFloat(n, precision)
}
// RandFloats generate a slice of random float64 numbers of length that do not repeat.
// Play: https://go.dev/play/p/I3yndUQ-rhh
func RandFloats(length int, min, max float64, precision int) []float64 {
if max < min {
min, max = max, min
}
maxLength := int((max - min) * math.Pow10(precision))
if maxLength == 0 {
maxLength = 1
}
if length > maxLength {
length = maxLength
}
nums := make([]float64, length)
used := make(map[float64]struct{}, length)
for i := 0; i < length; {
@@ -335,7 +347,7 @@ func UUIdV4() (string, error) {
}
// RandNumberOfLength 生成一个长度为len的随机数
// Play: todo
// Play: https://go.dev/play/p/oyZbuV7bu7b
func RandNumberOfLength(len int) int {
m := int(math.Pow10(len) - 1)
i := int(math.Pow10(len - 1))

View File

@@ -197,6 +197,14 @@ func TestRandFloats(t *testing.T) {
}
assert.Equal(len(numbers), 5)
numbers2 := RandFloats(10, 3.14, 3.2, 2)
for _, n := range numbers2 {
assert.GreaterOrEqual(n, 3.14)
assert.Less(n, 3.2)
}
assert.Equal(len(numbers2), 6)
}
func TestRandIntSlice(t *testing.T) {

View File

@@ -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.

View File

@@ -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() {

View File

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

View File

@@ -59,16 +59,18 @@ func ContainSubSlice[T comparable](slice, subSlice []T) bool {
return false
}
elementMap := make(map[T]struct{}, len(slice))
elementCount := make(map[T]int, len(slice))
for _, item := range slice {
elementMap[item] = struct{}{}
elementCount[item]++
}
for _, item := range subSlice {
if _, ok := elementMap[item]; !ok {
if elementCount[item] == 0 {
return false
}
elementCount[item]--
}
return true
}
@@ -81,14 +83,18 @@ func Chunk[T any](slice []T, size int) [][]T {
return result
}
for _, item := range slice {
l := len(result)
if l == 0 || len(result[l-1]) == size {
result = append(result, []T{})
l++
}
currentChunk := []T{}
result[l-1] = append(result[l-1], item)
for _, item := range slice {
if len(currentChunk) == size {
result = append(result, currentChunk)
currentChunk = []T{}
}
currentChunk = append(currentChunk, item)
}
if len(currentChunk) > 0 {
result = append(result, currentChunk)
}
return result
@@ -106,6 +112,7 @@ func Compact[T comparable](slice []T) []T {
result = append(result, v)
}
}
return result[:len(result):len(result)]
}
@@ -133,8 +140,17 @@ func Concat[T any](slices ...[]T) []T {
func Difference[T comparable](slice, comparedSlice []T) []T {
result := []T{}
if len(slice) == 0 {
return result
}
comparedMap := make(map[T]struct{}, len(comparedSlice))
for _, v := range comparedSlice {
comparedMap[v] = struct{}{}
}
for _, v := range slice {
if !Contain(comparedSlice, v) {
if _, found := comparedMap[v]; !found {
result = append(result, v)
}
}
@@ -147,13 +163,17 @@ func Difference[T comparable](slice, comparedSlice []T) []T {
// like lodash.js differenceBy: https://lodash.com/docs/4.17.15#differenceBy.
// Play: https://go.dev/play/p/DiivgwM5OnC
func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(index int, item T) T) []T {
orginSliceAfterMap := Map(slice, iteratee)
comparedSliceAfterMap := Map(comparedSlice, iteratee)
result := make([]T, 0)
for i, v := range orginSliceAfterMap {
if !Contain(comparedSliceAfterMap, v) {
result = append(result, slice[i])
comparedMap := make(map[T]struct{}, len(comparedSlice))
for _, item := range comparedSlice {
comparedMap[iteratee(0, item)] = struct{}{}
}
for i, item := range slice {
transformedItem := iteratee(i, item)
if _, found := comparedMap[transformedItem]; !found {
result = append(result, item)
}
}
@@ -165,23 +185,32 @@ func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(inde
// The comparator is invoked with two arguments: (arrVal, othVal).
// Play: https://go.dev/play/p/v2U2deugKuV
func DifferenceWith[T any](slice []T, comparedSlice []T, comparator func(item1, item2 T) bool) []T {
result := make([]T, 0)
getIndex := func(arr []T, item T, comparison func(v1, v2 T) bool) int {
index := -1
for i, v := range arr {
if comparison(item, v) {
index = i
return i
}
}
return -1
}
result := make([]T, 0, len(slice))
comparedMap := make(map[int]T, len(comparedSlice))
for _, v := range comparedSlice {
comparedMap[getIndex(comparedSlice, v, comparator)] = v
}
for _, v := range slice {
found := false
for _, existing := range comparedSlice {
if comparator(v, existing) {
found = true
break
}
}
return index
}
for i, v := range slice {
index := getIndex(comparedSlice, v, comparator)
if index == -1 {
result = append(result, slice[i])
if !found {
result = append(result, v)
}
}
@@ -220,6 +249,28 @@ func EqualWith[T, U any](slice1 []T, slice2 []U, comparator func(T, U) bool) boo
return true
}
// EqualUnordered checks if two slices are equal: the same length and all elements' value are equal (unordered).
// Play: https://go.dev/play/p/n8fSc2w8ZgX
func EqualUnordered[T comparable](slice1, slice2 []T) bool {
if len(slice1) != len(slice2) {
return false
}
seen := make(map[T]int)
for _, v := range slice1 {
seen[v]++
}
for _, v := range slice2 {
if seen[v] == 0 {
return false
}
seen[v]--
}
return true
}
// Every return true if all of the values in the slice pass the predicate function.
// Play: https://go.dev/play/p/R8U6Sl-j8cD
func Every[T any](slice []T, predicate func(index int, item T) bool) bool {
@@ -401,19 +452,20 @@ func FindLastBy[T any](slice []T, predicate func(index int, item T) bool) (v T,
// Flatten flattens slice with one level.
// Play: https://go.dev/play/p/hYa3cBEevtm
func Flatten(slice any) any {
sv := sliceValue(slice)
var result reflect.Value
if sv.Type().Elem().Kind() == reflect.Interface {
result = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, sv.Len())
} else if sv.Type().Elem().Kind() == reflect.Slice {
result = reflect.MakeSlice(sv.Type().Elem(), 0, sv.Len())
} else {
return result
sv := reflect.ValueOf(slice)
if sv.Kind() != reflect.Slice {
panic("Flatten: input must be a slice")
}
elemType := sv.Type().Elem()
if elemType.Kind() == reflect.Slice {
elemType = elemType.Elem()
}
result := reflect.MakeSlice(reflect.SliceOf(elemType), 0, sv.Len())
for i := 0; i < sv.Len(); i++ {
item := reflect.ValueOf(sv.Index(i).Interface())
item := sv.Index(i)
if item.Kind() == reflect.Slice {
for j := 0; j < item.Len(); j++ {
result = reflect.Append(result, item.Index(j))
@@ -585,7 +637,7 @@ func Repeat[T any](item T, n int) []T {
}
// InterfaceSlice convert param to slice of interface.
// This function is deprecated, use generics feature of go1.18+ for replacement.
// deprecated: use generics feature of go1.18+ for replacement.
// Play: https://go.dev/play/p/FdQXF0Vvqs-
func InterfaceSlice(slice any) []any {
sv := sliceValue(slice)
@@ -602,7 +654,7 @@ func InterfaceSlice(slice any) []any {
}
// StringSlice convert param to slice of string.
// This function is deprecated, use generics feature of go1.18+ for replacement.
// deprecated: use generics feature of go1.18+ for replacement.
// Play: https://go.dev/play/p/W0TZDWCPFcI
func StringSlice(slice any) []string {
v := sliceValue(slice)
@@ -620,7 +672,7 @@ func StringSlice(slice any) []string {
}
// IntSlice convert param to slice of int.
// This function is deprecated, use generics feature of go1.18+ for replacement.
// deprecated: use generics feature of go1.18+ for replacement.
// Play: https://go.dev/play/p/UQDj-on9TGN
func IntSlice(slice any) []int {
sv := sliceValue(slice)
@@ -640,15 +692,22 @@ 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 {
if index >= len(slice) {
index = len(slice) - 1
if index < 0 || index >= len(slice) {
return slice[:len(slice)-1]
}
result := make([]T, len(slice)-1)
copy(result, slice[:index])
copy(result[index:], slice[index+1:])
result := append([]T(nil), slice...)
copy(result[index:], result[index+1:])
return result
// Set the last element to zero value, clean up the memory.
result[len(result)-1] = zeroValue[T]()
return result[:len(result)-1]
}
func zeroValue[T any]() T {
var zero T
return zero
}
// DeleteRange delete the element of slice from start index to end indexexclude).
@@ -744,46 +803,54 @@ func InsertAt[T any](slice []T, index int, value any) []T {
return slice
}
if v, ok := value.(T); ok {
slice = append(slice[:index], append([]T{v}, slice[index:]...)...)
switch v := value.(type) {
case T:
result := make([]T, size+1)
copy(result, slice[:index])
result[index] = v
copy(result[index+1:], slice[index:])
return result
case []T:
result := make([]T, size+len(v))
copy(result, slice[:index])
copy(result[index:], v)
copy(result[index+len(v):], slice[index:])
return result
default:
return slice
}
if v, ok := value.([]T); ok {
slice = append(slice[:index], append(v, slice[index:]...)...)
return slice
}
return slice
}
// 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 {
size := len(slice)
if index < 0 || index >= size {
if index < 0 || index >= len(slice) {
return slice
}
slice = append(slice[:index], append([]T{value}, slice[index+1:]...)...)
return slice
result := make([]T, len(slice))
copy(result, slice)
result[index] = value
return result
}
// Unique remove duplicate elements in slice.
// Play: https://go.dev/play/p/AXw0R3ZTE6a
func Unique[T comparable](slice []T) []T {
result := make([]T, 0, len(slice))
if len(slice) == 0 {
return slice
}
seen := make(map[T]struct{}, len(slice))
result := slice[:0]
for i := range slice {
if _, ok := seen[slice[i]]; ok {
continue
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
seen[slice[i]] = struct{}{}
result = append(result, slice[i])
}
return result
@@ -793,18 +860,19 @@ func Unique[T comparable](slice []T) []T {
// The function maintains the order of the elements.
// Play: https://go.dev/play/p/GY7JE4yikrl
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
result := make([]T, 0, len(slice))
if len(slice) == 0 {
return slice
}
seen := make(map[U]struct{}, len(slice))
result := slice[:0]
for i := range slice {
key := iteratee(slice[i])
if _, ok := seen[key]; ok {
continue
for _, item := range slice {
key := iteratee(item)
if _, exists := seen[key]; !exists {
seen[key] = struct{}{}
result = append(result, item)
}
seen[key] = struct{}{}
result = append(result, slice[i])
}
return result
@@ -814,19 +882,20 @@ func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
// The function maintains the order of the elements.
// Play: https://go.dev/play/p/rwSacr-ZHsR
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T {
result := make([]T, 0, len(slice))
seen := make([]T, 0, len(slice))
if len(slice) == 0 {
return slice
}
result := make([]T, 0, len(slice))
for _, item := range slice {
duplicate := false
for _, seenItem := range seen {
if comparator(item, seenItem) {
duplicate = true
isDuplicate := false
for _, existing := range result {
if comparator(item, existing) {
isDuplicate = true
break
}
}
if !duplicate {
seen = append(seen, item)
if !isDuplicate {
result = append(result, item)
}
}
@@ -980,7 +1049,19 @@ func Reverse[T any](slice []T) {
}
}
// Shuffle the slice.
// ReverseCopy return a new slice of element 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))
for i, j := 0, len(slice)-1; i < len(slice); i, j = i+1, j-1 {
result[i] = slice[j]
}
return result
}
// Shuffle return a new slice with elements shuffled.
// Play: https://go.dev/play/p/YHvhnWGU3Ge
func Shuffle[T any](slice []T) []T {
rand.Seed(time.Now().UnixNano())
@@ -992,6 +1073,20 @@ func Shuffle[T any](slice []T) []T {
return slice
}
// ShuffleCopy return a new slice with elements shuffled.
// Play: https://go.dev/play/p/vqDa-Gs1vT0
func ShuffleCopy[T any](slice []T) []T {
result := make([]T, len(slice))
copy(result, slice)
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(result), func(i, j int) {
result[i], result[j] = result[j], result[i]
})
return result
}
// IsAscending checks if a slice is ascending order.
// Play: https://go.dev/play/p/9CtsFjet4SH
func IsAscending[T constraints.Ordered](slice []T) bool {
@@ -1415,7 +1510,7 @@ func Frequency[T comparable](slice []T) map[T]int {
}
// JoinFunc joins the slice elements into a single string with the given separator.
// Play: todo
// Play: https://go.dev/play/p/55ib3SB5fM2
func JoinFunc[T any](slice []T, sep string, transform func(T) T) string {
var buf strings.Builder
for i, v := range slice {
@@ -1428,7 +1523,7 @@ func JoinFunc[T any](slice []T, sep string, transform func(T) T) string {
}
// ConcatBy concats the elements of a slice into a single value using the provided separator and connector function.
// Play: todo
// Play: https://go.dev/play/p/6QcUpcY4UMW
func ConcatBy[T any](slice []T, sep T, connector func(T, T) T) T {
var result T

View File

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

View File

@@ -937,6 +937,41 @@ func ExampleReverse() {
// [d c b a]
}
func ExampleReverseCopy() {
strs := []string{"a", "b", "c", "d"}
reversedStrs := ReverseCopy(strs)
fmt.Println(reversedStrs)
fmt.Println(strs)
// Output:
// [d c b a]
// [a b c d]
}
func ExampleShuffle() {
strs := []string{"a", "b", "c", "d"}
Shuffle(strs)
fmt.Println(len(strs))
// Output:
// 4
}
func ExampleShuffleCopy() {
strs := []string{"a", "b", "c", "d"}
shuffledStrs := ShuffleCopy(strs)
fmt.Println(len(shuffledStrs))
fmt.Println(strs)
// Output:
// 4
// [a b c d]
}
func ExampleIsAscending() {
result1 := IsAscending([]int{1, 2, 3, 4, 5})
@@ -1300,3 +1335,15 @@ func ExampleConcatBy() {
// Alice | Bob | Charlie
// 90
}
func ExampleEqualUnordered() {
result1 := EqualUnordered([]int{1, 2, 3}, []int{3, 2, 1})
result2 := EqualUnordered([]int{1, 2, 3}, []int{4, 5, 6})
fmt.Println(result1)
fmt.Println(result2)
// Output:
// true
// false
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"math"
"reflect"
"sort"
"strconv"
"strings"
"sync"
@@ -1114,6 +1115,17 @@ func TestReverse(t *testing.T) {
assert.Equal([]string{"e", "d", "c", "b", "a"}, s2)
}
func TestReverseCopy(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestReverseCopy")
numbers := []int{1, 2, 3, 4, 5}
reversedNumbers := ReverseCopy(numbers)
assert.Equal([]int{5, 4, 3, 2, 1}, reversedNumbers)
assert.Equal([]int{1, 2, 3, 4, 5}, numbers)
}
func TestDifference(t *testing.T) {
t.Parallel()
@@ -1329,6 +1341,18 @@ func TestShuffle(t *testing.T) {
assert.Equal(5, len(result))
}
func TestShuffleCopy(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestShuffleCopy")
numbers := []int{1, 2, 3, 4, 5}
result := ShuffleCopy(numbers)
assert.Equal(5, len(result))
assert.Equal([]int{1, 2, 3, 4, 5}, numbers)
}
func TestIndexOf(t *testing.T) {
t.Parallel()
@@ -1794,6 +1818,8 @@ func TestFilterConcurrent(t *testing.T) {
nums := []int{1, 2, 3, 4, 5, 6}
expected := []int{4, 5, 6}
actual := FilterConcurrent(nums, func(_, n int) bool { return n > 3 }, 4)
sort.Ints(actual)
sort.Ints(expected)
assert.Equal(expected, actual)
})
}
@@ -1926,3 +1952,25 @@ func TestConcatBy(t *testing.T) {
assert.Equal(90, result.Age)
})
}
func TestEqualUnordered(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEqualUnordered")
tests := []struct {
slice1, slice2 []int
expected bool
}{
{[]int{}, []int{}, true},
{[]int{1, 2, 3}, []int{1, 2, 3}, true},
{[]int{1, 2, 3}, []int{3, 2, 1}, true},
{[]int{1, 2, 3}, []int{1, 2, 3, 4}, false},
{[]int{1, 2, 3}, []int{1, 2}, false},
{[]int{1, 2, 3}, []int{1, 2, 4}, false},
}
for _, test := range tests {
assert.Equal(test.expected, EqualUnordered(test.slice1, test.slice2))
}
}

View File

@@ -130,7 +130,6 @@ func (s Stream[T]) Distinct() Stream[T] {
distinct := map[string]bool{}
for _, v := range s.source {
// todo: performance issue
k := hashKey(v)
if _, ok := distinct[k]; !ok {
distinct[k] = true
@@ -395,6 +394,7 @@ func (s Stream[T]) Min(less func(a, b T) bool) (T, bool) {
}
// IndexOf returns the index of the first occurrence of the specified element in this stream, or -1 if this stream does not contain the element.
// Play: https://go.dev/play/p/tBV5Nc-XDX2
func (s Stream[T]) IndexOf(target T, equal func(a, b T) bool) int {
for i, v := range s.source {
if equal(v, target) {
@@ -405,6 +405,7 @@ func (s Stream[T]) IndexOf(target T, equal func(a, b T) bool) int {
}
// LastIndexOf returns the index of the last occurrence of the specified element in this stream, or -1 if this stream does not contain the element.
// Play: https://go.dev/play/p/CjeoNw2eac_G
func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int {
for i := len(s.source) - 1; i >= 0; i-- {
if equal(s.source[i], target) {
@@ -419,3 +420,12 @@ func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int {
func (s Stream[T]) ToSlice() []T {
return s.source
}
func ToMap[T any, K comparable, V any](s Stream[T], mapper func(item T) (K, V)) map[K]V {
result := map[K]V{}
for _, v := range s.source {
key, value := mapper(v)
result[key] = value
}
return result
}

View File

@@ -412,3 +412,22 @@ func ExampleStream_LastIndexOf() {
// -1
// 3
}
func ExampleToMap() {
type Person struct {
Name string
Age int
}
s := FromSlice([]Person{
{Name: "Tom", Age: 10},
{Name: "Jim", Age: 20},
{Name: "Mike", Age: 30},
})
m := ToMap(s, func(p Person) (string, Person) {
return p.Name, p
})
fmt.Println(m)
// Output:
// map[Jim:{Jim 20} Mike:{Mike 30} Tom:{Tom 10}]
}

View File

@@ -400,3 +400,26 @@ func TestStream_LastIndexOf(t *testing.T) {
assert.Equal(-1, s.LastIndexOf(0, func(a, b int) bool { return a == b }))
assert.Equal(4, s.LastIndexOf(2, func(a, b int) bool { return a == b }))
}
func TestStream_ToMap(t *testing.T) {
assert := internal.NewAssert(t, "TestStream_ToMap")
type Person struct {
Name string
Age int
}
s := FromSlice([]Person{
{Name: "Tom", Age: 10},
{Name: "Jim", Age: 20},
{Name: "Mike", Age: 30},
})
m := ToMap(s, func(p Person) (string, Person) {
return p.Name, p
})
expected := map[string]Person{
"Tom": {Name: "Tom", Age: 10},
"Jim": {Name: "Jim", Age: 20},
"Mike": {Name: "Mike", Age: 30},
}
assert.EqualValues(expected, m)
}

View File

@@ -37,16 +37,17 @@ func CamelCase(s string) string {
// Capitalize converts the first character of a string to upper case and the remaining to lower case.
// Play: https://go.dev/play/p/2OAjgbmAqHZ
func Capitalize(s string) string {
result := make([]rune, len(s))
for i, v := range s {
if i == 0 {
result[i] = unicode.ToUpper(v)
} else {
result[i] = unicode.ToLower(v)
}
if s == "" {
return s
}
return string(result)
runes := []rune(s)
runes[0] = unicode.ToUpper(runes[0])
for i := 1; i < len(runes); i++ {
runes[i] = unicode.ToLower(runes[i])
}
return string(runes)
}
// UpperFirst converts the first character of string to upper case.
@@ -127,49 +128,57 @@ func UpperSnakeCase(s string) string {
// Before returns the substring of the source string up to the first occurrence of the specified string.
// Play: https://go.dev/play/p/JAWTZDS4F5w
func Before(s, char string) string {
i := strings.Index(s, char)
if s == "" || char == "" || i == -1 {
if char == "" {
return s
}
return s[0:i]
if i := strings.Index(s, char); i >= 0 {
return s[:i]
}
return s
}
// BeforeLast returns the substring of the source string up to the last occurrence of the specified string.
// Play: https://go.dev/play/p/pJfXXAoG_Te
func BeforeLast(s, char string) string {
i := strings.LastIndex(s, char)
if s == "" || char == "" || i == -1 {
if char == "" {
return s
}
return s[0:i]
if i := strings.LastIndex(s, char); i >= 0 {
return s[:i]
}
return s
}
// After returns the substring after the first occurrence of a specified string in the source string.
// Play: https://go.dev/play/p/RbCOQqCDA7m
func After(s, char string) string {
i := strings.Index(s, char)
if s == "" || char == "" || i == -1 {
if char == "" {
return s
}
return s[i+len(char):]
if i := strings.Index(s, char); i >= 0 {
return s[i+len(char):]
}
return s
}
// AfterLast returns the substring after the last occurrence of a specified string in the source string.
// Play: https://go.dev/play/p/1TegARrb8Yn
func AfterLast(s, char string) string {
i := strings.LastIndex(s, char)
if s == "" || char == "" || i == -1 {
if char == "" {
return s
}
return s[i+len(char):]
if i := strings.LastIndex(s, char); i >= 0 {
return s[i+len(char):]
}
return s
}
// IsString check if the value data type is string or not.
@@ -213,20 +222,15 @@ func Wrap(str string, wrapWith string) string {
// Unwrap a given string from anther string. will change source string.
// Play: https://go.dev/play/p/Ec2q4BzCpG-
func Unwrap(str string, wrapToken string) string {
if str == "" || wrapToken == "" {
if wrapToken == "" || !strings.HasPrefix(str, wrapToken) || !strings.HasSuffix(str, wrapToken) {
return str
}
firstIndex := strings.Index(str, wrapToken)
lastIndex := strings.LastIndex(str, wrapToken)
if firstIndex == 0 && lastIndex > 0 && lastIndex <= len(str)-1 {
if len(wrapToken) <= lastIndex {
str = str[len(wrapToken):lastIndex]
}
if len(str) < 2*len(wrapToken) {
return str
}
return str
return str[len(wrapToken) : len(str)-len(wrapToken)]
}
// SplitEx split a given string which can control the result slice contains empty string or not.
@@ -286,22 +290,21 @@ func Substring(s string, offset int, length uint) string {
size := len(rs)
if offset < 0 {
offset = size + offset
if offset < 0 {
offset = 0
}
offset += size
}
if offset < 0 {
offset = 0
}
if offset > size {
return ""
}
if length > uint(size)-uint(offset) {
length = uint(size - offset)
end := offset + int(length)
if end > size {
end = size
}
str := string(rs[offset : offset+int(length)])
return strings.Replace(str, "\x00", "", -1)
return strings.ReplaceAll(string(rs[offset:end]), "\x00", "")
}
// SplitWords splits a string into words, word only contains alphabetic characters.
@@ -393,7 +396,10 @@ func RemoveNonPrintable(str string) string {
// StringToBytes converts a string to byte slice without a memory allocation.
// Play: https://go.dev/play/p/7OyFBrf9AxA
func StringToBytes(str string) (b []byte) {
return *(*[]byte)(unsafe.Pointer(&str))
return *(*[]byte)(unsafe.Pointer(&struct {
string
Cap int
}{str, len(str)}))
}
// BytesToString converts a byte slice to string without a memory allocation.
@@ -737,15 +743,15 @@ func RegexMatchAllGroups(pattern, str string) [][]string {
}
// ExtractContent extracts the content between the start and end strings in the source string.
// Play: todo
func ExtractContent(s, start, end string) []string {
// Play: https://go.dev/play/p/Ay9UIk7Rum9
func ExtractContent(str, start, end string) []string {
result := []string{}
for {
if _, after, ok := strings.Cut(s, start); ok {
if _, after, ok := strings.Cut(str, start); ok {
if before, _, ok := strings.Cut(after, end); ok {
result = append(result, before)
s = after
str = after
} else {
break
}
@@ -756,3 +762,20 @@ func ExtractContent(s, start, end string) []string {
return result
}
// FindAllOccurrences returns the positions of all occurrences of a substring in a string.
// Play: https://go.dev/play/p/uvyA6azGLB1
func FindAllOccurrences(str, substr string) []int {
var positions []int
for i := 0; i < len(str); {
index := strings.Index(str[i:], substr)
if index == -1 {
break
}
positions = append(positions, i+index)
i += index + 1
}
return positions
}

View File

@@ -763,5 +763,13 @@ func ExampleExtractContent() {
// Output:
// [content1 content2 content1]
}
func ExampleFindAllOccurrences() {
result := FindAllOccurrences("ababab", "ab")
fmt.Println(result)
// Output:
// [0 2 4]
}

View File

@@ -1,6 +1,7 @@
package strutil
import (
"strings"
"unicode"
)
@@ -111,31 +112,27 @@ func padAtPosition(str string, length int, padStr string, position int) string {
padStr = " "
}
length = length - len(str)
startPadLen := 0
totalPad := length - len(str)
startPad := 0
if position == 0 {
startPadLen = length / 2
startPad = totalPad / 2
} else if position == 1 {
startPadLen = length
startPad = totalPad
} else if position == 2 {
startPad = 0
}
endPadLen := length - startPadLen
endPad := totalPad - startPad
charLen := len(padStr)
leftPad := ""
cur := 0
for cur < startPadLen {
leftPad += string(padStr[cur%charLen])
cur++
repeatPad := func(n int) string {
repeated := strings.Repeat(padStr, (n+len(padStr)-1)/len(padStr))
return repeated[:n]
}
cur = 0
rightPad := ""
for cur < endPadLen {
rightPad += string(padStr[cur%charLen])
cur++
}
left := repeatPad(startPad)
right := repeatPad(endPad)
return leftPad + str + rightPad
return left + str + right
}
// isLetter checks r is a letter but not CJK character.

View File

@@ -307,10 +307,21 @@ func TestBeforeLast(t *testing.T) {
assert := internal.NewAssert(t, "TestBeforeLast")
assert.Equal("lancet", BeforeLast("lancet", ""))
assert.Equal("lancet", BeforeLast("lancet", "abcdef"))
assert.Equal("github.com/test", BeforeLast("github.com/test/lancet", "/"))
assert.Equal("github.com/test/", BeforeLast("github.com/test/test/lancet", "test"))
tests := []struct {
input string
char string
expected string
}{
{"lancet", "", "lancet"},
{"lancet", "lancet", ""},
{"lancet", "abcdef", "lancet"},
{"github.com/test/lancet", "/", "github.com/test"},
{"github.com/test/test/lancet", "test", "github.com/test/"},
}
for _, tt := range tests {
assert.Equal(tt.expected, BeforeLast(tt.input, tt.char))
}
}
func TestAfter(t *testing.T) {
@@ -318,11 +329,21 @@ func TestAfter(t *testing.T) {
assert := internal.NewAssert(t, "TestAfter")
assert.Equal("lancet", After("lancet", ""))
assert.Equal("", After("lancet", "lancet"))
assert.Equal("test/lancet", After("github.com/test/lancet", "/"))
assert.Equal("/lancet", After("github.com/test/lancet", "test"))
assert.Equal("lancet", After("lancet", "abcdef"))
tests := []struct {
input string
char string
expected string
}{
{"lancet", "", "lancet"},
{"lancet", "lancet", ""},
{"lancet", "abcdef", "lancet"},
{"github.com/test/lancet", "/", "test/lancet"},
{"github.com/test/lancet", "test", "/lancet"},
}
for _, tt := range tests {
assert.Equal(tt.expected, After(tt.input, tt.char))
}
}
func TestAfterLast(t *testing.T) {
@@ -330,11 +351,21 @@ func TestAfterLast(t *testing.T) {
assert := internal.NewAssert(t, "TestAfterLast")
assert.Equal("lancet", AfterLast("lancet", ""))
assert.Equal("lancet", AfterLast("github.com/test/lancet", "/"))
assert.Equal("/lancet", AfterLast("github.com/test/lancet", "test"))
assert.Equal("/lancet", AfterLast("github.com/test/test/lancet", "test"))
assert.Equal("lancet", AfterLast("lancet", "abcdef"))
tests := []struct {
input string
char string
expected string
}{
{"lancet", "", "lancet"},
{"lancet", "lancet", ""},
{"lancet", "abcdef", "lancet"},
{"github.com/test/lancet", "/", "lancet"},
{"github.com/test/test/lancet", "test", "/lancet"},
}
for _, tt := range tests {
assert.Equal(tt.expected, AfterLast(tt.input, tt.char))
}
}
func TestIsString(t *testing.T) {
@@ -342,11 +373,20 @@ func TestIsString(t *testing.T) {
assert := internal.NewAssert(t, "TestIsString")
assert.Equal(true, IsString("lancet"))
assert.Equal(true, IsString(""))
assert.Equal(false, IsString(1))
assert.Equal(false, IsString(true))
assert.Equal(false, IsString([]string{}))
tests := []struct {
input interface{}
expected bool
}{
{"lancet", true},
{"", true},
{1, false},
{true, false},
{[]string{}, false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsString(tt.input))
}
}
func TestReverse(t *testing.T) {
@@ -363,11 +403,21 @@ func TestWrap(t *testing.T) {
assert := internal.NewAssert(t, "TestWrap")
assert.Equal("ab", Wrap("ab", ""))
assert.Equal("", Wrap("", "*"))
assert.Equal("*ab*", Wrap("ab", "*"))
assert.Equal("\"ab\"", Wrap("ab", "\""))
assert.Equal("'ab'", Wrap("ab", "'"))
tests := []struct {
input string
wrapper string
expected string
}{
{"", "", ""},
{"ab", "", "ab"},
{"ab", "*", "*ab*"},
{"ab", "\"", "\"ab\""},
{"ab", "'", "'ab'"},
}
for _, tt := range tests {
assert.Equal(tt.expected, Wrap(tt.input, tt.wrapper))
}
}
func TestUnwrap(t *testing.T) {
@@ -468,7 +518,8 @@ func TestStringToBytes(t *testing.T) {
assert := internal.NewAssert(t, "TestStringToBytes")
bytes := StringToBytes("abc")
assert.Equal(bytes, []byte{'a', 'b', 'c'})
assert.Equal([]byte{'a', 'b', 'c'}, bytes)
assert.Equal(3, cap(bytes))
}
func TestBytesToString(t *testing.T) {
@@ -485,10 +536,21 @@ func TestIsBlank(t *testing.T) {
assert := internal.NewAssert(t, "TestIsBlank")
assert.Equal(IsBlank(""), true)
assert.Equal(IsBlank("\t\v\f\n"), true)
tests := []struct {
input string
expected bool
}{
{"", true},
{" ", true},
{"\t\v\f\n", true},
{"\t\v\f\nabc", false},
{"abc", false},
{" 中文", false},
}
assert.Equal(IsBlank(" 中文"), false)
for _, tt := range tests {
assert.Equal(tt.expected, IsBlank(tt.input))
}
}
func TestIsNotBlank(t *testing.T) {
@@ -496,12 +558,22 @@ func TestIsNotBlank(t *testing.T) {
assert := internal.NewAssert(t, "TestIsBlank")
assert.Equal(IsNotBlank(""), false)
assert.Equal(IsNotBlank(" "), false)
assert.Equal(IsNotBlank("\t\v\f\n"), false)
tests := []struct {
input string
expected bool
}{
{"", false},
{" ", false},
{"\t\v\f\n", false},
{"\t\v\f\nabc", true},
{"abc", true},
{" 中文", true},
{" world ", true},
}
assert.Equal(IsNotBlank(" 中文"), true)
assert.Equal(IsNotBlank(" world "), true)
for _, tt := range tests {
assert.Equal(tt.expected, IsNotBlank(tt.input))
}
}
func TestHasPrefixAny(t *testing.T) {
@@ -509,12 +581,19 @@ func TestHasPrefixAny(t *testing.T) {
assert := internal.NewAssert(t, "TestHasPrefixAny")
str := "foo bar"
prefixes := []string{"fo", "xyz", "hello"}
notMatches := []string{"oom", "world"}
tests := []struct {
str string
prefixes []string
expected bool
}{
{"foo bar", []string{"fo", "xyz", "hello"}, true},
{"foo bar", []string{"oom", "world"}, false},
{"foo bar", []string{}, false},
}
assert.Equal(HasPrefixAny(str, prefixes), true)
assert.Equal(HasPrefixAny(str, notMatches), false)
for _, tt := range tests {
assert.Equal(tt.expected, HasPrefixAny(tt.str, tt.prefixes))
}
}
func TestHasSuffixAny(t *testing.T) {
@@ -522,25 +601,44 @@ func TestHasSuffixAny(t *testing.T) {
assert := internal.NewAssert(t, "TestHasSuffixAny")
str := "foo bar"
suffixes := []string{"bar", "xyz", "hello"}
notMatches := []string{"oom", "world"}
tests := []struct {
str string
suffixes []string
expected bool
}{
{"foo bar", []string{"bar", "xyz", "hello"}, true},
{"foo bar", []string{"oom", "world"}, false},
{"foo bar", []string{}, false},
}
assert.Equal(HasSuffixAny(str, suffixes), true)
assert.Equal(HasSuffixAny(str, notMatches), false)
for _, tt := range tests {
assert.Equal(tt.expected, HasSuffixAny(tt.str, tt.suffixes))
}
}
func TestIndexOffset(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIndexOffset")
str := "foo bar hello world"
assert.Equal(IndexOffset(str, "o", 5), 12)
assert.Equal(IndexOffset(str, "o", 0), 1)
assert.Equal(IndexOffset(str, "d", len(str)-1), len(str)-1)
assert.Equal(IndexOffset(str, "d", len(str)), -1)
assert.Equal(IndexOffset(str, "f", -1), -1)
tests := []struct {
str string
substr string
offset int
expected int
}{
{str, "o", 5, 12},
{str, "o", 0, 1},
{str, "d", len(str) - 1, len(str) - 1},
{str, "d", len(str), -1},
{str, "f", -1, -1},
}
for _, tt := range tests {
assert.Equal(tt.expected, IndexOffset(tt.str, tt.substr, tt.offset))
}
}
func TestReplaceWithMap(t *testing.T) {
@@ -648,13 +746,23 @@ func TestSubInBetween(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSubInBetween")
str := "abcde"
tests := []struct {
input string
start string
end string
expected string
}{
{"abcde", "", "", ""},
{"abcde", "a", "d", "bc"},
{"abcde", "a", "e", "bcd"},
{"abcde", "a", "f", ""},
{"abcde", "a", "", ""},
{"abcde", "", "e", "abcd"},
}
assert.Equal("", SubInBetween(str, "", ""))
assert.Equal("ab", SubInBetween(str, "", "c"))
assert.Equal("bc", SubInBetween(str, "a", "d"))
assert.Equal("", SubInBetween(str, "a", ""))
assert.Equal("", SubInBetween(str, "a", "f"))
for _, tt := range tests {
assert.Equal(tt.expected, SubInBetween(tt.input, tt.start, tt.end))
}
}
func TestHammingDistance(t *testing.T) {
@@ -696,13 +804,22 @@ func TestConcat(t *testing.T) {
assert := internal.NewAssert(t, "TestConcat")
assert.Equal("", Concat(0))
assert.Equal("a", Concat(1, "a"))
assert.Equal("ab", Concat(2, "a", "b"))
assert.Equal("abc", Concat(3, "a", "b", "c"))
assert.Equal("abc", Concat(3, "a", "", "b", "c", ""))
assert.Equal("你好,世界!", Concat(0, "你好", "", "", "世界!", ""))
assert.Equal("Hello World!", Concat(0, "Hello", " Wo", "r", "ld!", ""))
tests := []struct {
args []string
expected string
}{
{[]string{}, ""},
{[]string{"a"}, "a"},
{[]string{"a", "b"}, "ab"},
{[]string{"a", "b", "c"}, "abc"},
{[]string{"a", "", "b", "c", ""}, "abc"},
{[]string{"你好", "", "", "世界!", ""}, "你好,世界!"},
{[]string{"Hello", " Wo", "r", "ld!", ""}, "Hello World!"},
}
for _, tt := range tests {
assert.Equal(tt.expected, Concat(0, tt.args...))
}
}
func TestEllipsis(t *testing.T) {
@@ -937,3 +1054,32 @@ func TestExtractContent(t *testing.T) {
})
}
}
func TestFindAllOccurrences(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestFindAllOccurrences")
var empty []int
tests := []struct {
input string
substr string
expected []int
}{
{"", "", empty},
{"", "a", empty},
{"a", "", []int{0}},
{"a", "a", []int{0}},
{"aa", "a", []int{0, 1}},
{"ababab", "ab", []int{0, 2, 4}},
{"ababab", "ba", []int{1, 3}},
{"ababab", "c", empty},
{"ababab", "ababab", []int{0}},
{"ababab", "abababc", empty},
{"ababab", "abababa", empty},
}
for _, tt := range tests {
assert.Equal(tt.expected, FindAllOccurrences(tt.input, tt.substr))
}
}

View File

@@ -21,6 +21,7 @@ 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]*)?$`)
@@ -181,6 +182,12 @@ func IsJSON(str string) bool {
return json.Unmarshal([]byte(str), &js) == nil
}
// IsAlphaNumeric check if the string is alphanumeric.
// Play: https://go.dev/play/p/RHeESLrLg9c
func IsAlphaNumeric(s string) bool {
return alphaNumericMatcher.MatchString(s)
}
// IsNumberStr check if the string can convert to a number.
// Play: https://go.dev/play/p/LzaKocSV79u
func IsNumberStr(s string) bool {
@@ -207,6 +214,18 @@ func IsIp(ipstr string) bool {
return ip != nil
}
// IsIpPort check if the string is ip:port.
// Play: https://go.dev/play/p/xUmls_b9L29
func IsIpPort(str string) bool {
host, port, err := net.SplitHostPort(str)
if err != nil {
return false
}
ip := net.ParseIP(host)
return ip != nil && IsPort(port)
}
// IsIpV4 check if the string is a ipv4 address.
// Play: https://go.dev/play/p/zBGT99EjaIu
func IsIpV4(ipstr string) bool {

View File

@@ -348,6 +348,24 @@ func ExampleIsIp() {
// false
}
func ExampleIsIpPort() {
result1 := IsIpPort("127.0.0.1:8080")
result2 := IsIpPort("[0:0:0:0:0:0:0:1]:8080")
result3 := IsIpPort(":8080")
result4 := IsIpPort("::0:0:0:0:")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// false
// false
}
func ExampleIsIpV4() {
result1 := IsIpV4("127.0.0.1")
result2 := IsIpV4("::0:0:0:0:0:0:1")
@@ -647,3 +665,21 @@ func ExampleIsChinaUnionPay() {
// true
// false
}
func ExampleIsAlphaNumeric() {
result1 := IsAlphaNumeric("ABC")
result2 := IsAlphaNumeric("123")
result3 := IsAlphaNumeric("abc123")
result4 := IsAlphaNumeric("abc123@#$")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}

View File

@@ -15,16 +15,25 @@ func TestIsAllUpper(t *testing.T) {
assert := internal.NewAssert(t, "TestIsAllUpper")
assert.Equal(true, IsAllUpper("ABC"))
assert.Equal(false, IsAllUpper(""))
assert.Equal(false, IsAllUpper("abc"))
assert.Equal(false, IsAllUpper("aBC"))
assert.Equal(false, IsAllUpper("1BC"))
assert.Equal(false, IsAllUpper("1bc"))
assert.Equal(false, IsAllUpper("123"))
assert.Equal(false, IsAllUpper("你好"))
assert.Equal(false, IsAllUpper("A&"))
assert.Equal(false, IsAllUpper("&@#$%^&*"))
tests := []struct {
input string
expected bool
}{
{"ABC", true},
{"", false},
{"abc", false},
{"aBC", false},
{"1BC", false},
{"1bc", false},
{"123", false},
{"你好", false},
{"A&", false},
{"&@#$%^&*", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsAllUpper(tt.input))
}
}
func TestIsAllLower(t *testing.T) {
@@ -32,16 +41,25 @@ func TestIsAllLower(t *testing.T) {
assert := internal.NewAssert(t, "TestIsAllLower")
assert.Equal(true, IsAllLower("abc"))
assert.Equal(false, IsAllLower("ABC"))
assert.Equal(false, IsAllLower(""))
assert.Equal(false, IsAllLower("aBC"))
assert.Equal(false, IsAllLower("1BC"))
assert.Equal(false, IsAllLower("1bc"))
assert.Equal(false, IsAllLower("123"))
assert.Equal(false, IsAllLower("你好"))
assert.Equal(false, IsAllLower("A&"))
assert.Equal(false, IsAllLower("&@#$%^&*"))
tests := []struct {
input string
expected bool
}{
{"abc", true},
{"", false},
{"ABC", false},
{"aBC", false},
{"1BC", false},
{"1bc", false},
{"123", false},
{"你好", false},
{"A&", false},
{"&@#$%^&*", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsAllLower(tt.input))
}
}
func TestContainLower(t *testing.T) {
@@ -49,17 +67,25 @@ func TestContainLower(t *testing.T) {
assert := internal.NewAssert(t, "TestContainLower")
assert.Equal(true, ContainLower("abc"))
assert.Equal(true, ContainLower("aBC"))
assert.Equal(true, ContainLower("1bc"))
assert.Equal(true, ContainLower("a&"))
tests := []struct {
input string
expected bool
}{
{"abc", true},
{"aBC", true},
{"1bc", true},
{"a&", true},
{"ABC", false},
{"", false},
{"1BC", false},
{"123", false},
{"你好", false},
{"&@#$%^&*", false},
}
assert.Equal(false, ContainLower("ABC"))
assert.Equal(false, ContainLower(""))
assert.Equal(false, ContainLower("1BC"))
assert.Equal(false, ContainLower("123"))
assert.Equal(false, ContainLower("你好"))
assert.Equal(false, ContainLower("&@#$%^&*"))
for _, tt := range tests {
assert.Equal(tt.expected, ContainLower(tt.input))
}
}
func TestContainUpper(t *testing.T) {
@@ -67,17 +93,25 @@ func TestContainUpper(t *testing.T) {
assert := internal.NewAssert(t, "TestContainUpper")
assert.Equal(true, ContainUpper("ABC"))
assert.Equal(true, ContainUpper("aBC"))
assert.Equal(true, ContainUpper("1BC"))
assert.Equal(true, ContainUpper("A&"))
tests := []struct {
input string
expected bool
}{
{"ABC", true},
{"aBC", true},
{"1BC", true},
{"A&", true},
{"abc", false},
{"", false},
{"1bc", false},
{"123", false},
{"你好", false},
{"&@#$%^&*", false},
}
assert.Equal(false, ContainUpper("abc"))
assert.Equal(false, ContainUpper(""))
assert.Equal(false, ContainUpper("1bc"))
assert.Equal(false, ContainUpper("123"))
assert.Equal(false, ContainUpper("你好"))
assert.Equal(false, ContainUpper("&@#$%^&*"))
for _, tt := range tests {
assert.Equal(tt.expected, ContainUpper(tt.input))
}
}
func TestContainLetter(t *testing.T) {
@@ -85,15 +119,23 @@ func TestContainLetter(t *testing.T) {
assert := internal.NewAssert(t, "TestContainLetter")
assert.Equal(true, ContainLetter("ABC"))
assert.Equal(true, ContainLetter("1Bc"))
assert.Equal(true, ContainLetter("1ab"))
assert.Equal(true, ContainLetter("A&"))
tests := []struct {
input string
expected bool
}{
{"ABC", true},
{"1Bc", true},
{"1ab", true},
{"A&", true},
{"", false},
{"123", false},
{"你好", false},
{"&@#$%^&*", false},
}
assert.Equal(false, ContainLetter(""))
assert.Equal(false, ContainLetter("123"))
assert.Equal(false, ContainLetter("你好"))
assert.Equal(false, ContainLetter("&@#$%^&*"))
for _, tt := range tests {
assert.Equal(tt.expected, ContainLetter(tt.input))
}
}
func TestContainNumber(t *testing.T) {
@@ -101,18 +143,26 @@ func TestContainNumber(t *testing.T) {
assert := internal.NewAssert(t, "TestContainNumber")
assert.Equal(true, ContainNumber("123"))
assert.Equal(true, ContainNumber("1Bc"))
assert.Equal(true, ContainNumber("a2c"))
assert.Equal(true, ContainNumber("ab3"))
assert.Equal(true, ContainNumber("a23"))
assert.Equal(true, ContainNumber("a23c"))
assert.Equal(true, ContainNumber("1%%%"))
tests := []struct {
input string
expected bool
}{
{"123", true},
{"1Bc", true},
{"a2c", true},
{"ab3", true},
{"a23", true},
{"a23c", true},
{"1%%%", true},
{"ABC", false},
{"", false},
{"你好", false},
{"&@#$%^&*", false},
}
assert.Equal(false, ContainNumber("ABC"))
assert.Equal(false, ContainNumber(""))
assert.Equal(false, ContainNumber("你好"))
assert.Equal(false, ContainNumber("&@#$%^&*"))
for _, tt := range tests {
assert.Equal(tt.expected, ContainNumber(tt.input))
}
}
func TestIsJSON(t *testing.T) {
@@ -120,15 +170,23 @@ func TestIsJSON(t *testing.T) {
assert := internal.NewAssert(t, "TestIsJSON")
assert.Equal(true, IsJSON("{}"))
assert.Equal(true, IsJSON("{\"name\": \"test\"}"))
assert.Equal(true, IsJSON("[]"))
assert.Equal(true, IsJSON("123"))
tests := []struct {
input string
expected bool
}{
{"{}", true},
{"{\"name\": \"test\"}", true},
{"[]", true},
{"123", true},
{"", false},
{"abc", false},
{"你好", false},
{"&@#$%^&*", false},
}
assert.Equal(false, IsJSON(""))
assert.Equal(false, IsJSON("abc"))
assert.Equal(false, IsJSON("你好"))
assert.Equal(false, IsJSON("&@#$%^&*"))
for _, tt := range tests {
assert.Equal(tt.expected, IsJSON(tt.input))
}
}
func TestIsNumber(t *testing.T) {
@@ -136,10 +194,19 @@ func TestIsNumber(t *testing.T) {
assert := internal.NewAssert(t, "TestIsNumber")
assert.Equal(false, IsNumber(""))
assert.Equal(false, IsNumber("3"))
assert.Equal(true, IsNumber(0))
assert.Equal(true, IsNumber(0.1))
tests := []struct {
input interface{}
expected bool
}{
{"", false},
{"3", false},
{0, true},
{0.1, true},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsNumber(tt.input))
}
}
func TestIsFloat(t *testing.T) {
@@ -147,10 +214,19 @@ func TestIsFloat(t *testing.T) {
assert := internal.NewAssert(t, "TestIsFloat")
assert.Equal(false, IsFloat(""))
assert.Equal(false, IsFloat("3"))
assert.Equal(false, IsFloat(0))
assert.Equal(true, IsFloat(0.1))
tests := []struct {
input interface{}
expected bool
}{
{"", false},
{"3", false},
{0, false},
{0.1, true},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsFloat(tt.input))
}
}
func TestIsInt(t *testing.T) {
@@ -158,11 +234,20 @@ func TestIsInt(t *testing.T) {
assert := internal.NewAssert(t, "TestIsInt")
assert.Equal(false, IsInt(""))
assert.Equal(false, IsInt("3"))
assert.Equal(false, IsInt(0.1))
assert.Equal(true, IsInt(0))
assert.Equal(true, IsInt(-1))
tests := []struct {
input interface{}
expected bool
}{
{"", false},
{"3", false},
{0.1, false},
{0, true},
{-1, true},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsInt(tt.input))
}
}
func TestIsNumberStr(t *testing.T) {
@@ -170,11 +255,21 @@ func TestIsNumberStr(t *testing.T) {
assert := internal.NewAssert(t, "TestIsNumberStr")
assert.Equal(true, IsNumberStr("3."))
assert.Equal(true, IsNumberStr("+3."))
assert.Equal(true, IsNumberStr("-3."))
assert.Equal(true, IsNumberStr("+3e2"))
assert.Equal(false, IsNumberStr("abc"))
tests := []struct {
input string
expected bool
}{
{"3", true},
{"3.", true},
{"+3.", true},
{"-3.", true},
{"+3e2", true},
{"abc", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsNumberStr(tt.input))
}
}
func TestIsFloatStr(t *testing.T) {
@@ -182,11 +277,21 @@ func TestIsFloatStr(t *testing.T) {
assert := internal.NewAssert(t, "TestIsFloatStr")
assert.Equal(true, IsFloatStr("3."))
assert.Equal(true, IsFloatStr("+3."))
assert.Equal(true, IsFloatStr("-3."))
assert.Equal(true, IsFloatStr("12"))
assert.Equal(false, IsFloatStr("abc"))
tests := []struct {
input string
expected bool
}{
{"3", true},
{"3.", true},
{"+3.", true},
{"-3.", true},
{"12", true},
{"abc", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsFloatStr(tt.input))
}
}
func TestIsIntStr(t *testing.T) {
@@ -194,10 +299,20 @@ func TestIsIntStr(t *testing.T) {
assert := internal.NewAssert(t, "TestIsIntStr")
assert.Equal(true, IsIntStr("+3"))
assert.Equal(true, IsIntStr("-3"))
assert.Equal(false, IsIntStr("3."))
assert.Equal(false, IsIntStr("abc"))
tests := []struct {
input string
expected bool
}{
{"3", true},
{"3.", false},
{"+3", true},
{"-3", true},
{"abc", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsIntStr(tt.input))
}
}
func TestIsPort(t *testing.T) {
@@ -205,13 +320,22 @@ func TestIsPort(t *testing.T) {
assert := internal.NewAssert(t, "TestIsPort")
assert.Equal(true, IsPort("1"))
assert.Equal(true, IsPort("65535"))
assert.Equal(false, IsPort("abc"))
assert.Equal(false, IsPort("123abc"))
assert.Equal(false, IsPort(""))
assert.Equal(false, IsPort("-1"))
assert.Equal(false, IsPort("65536"))
tests := []struct {
input string
expected bool
}{
{"1", true},
{"65535", true},
{"abc", false},
{"123abc", false},
{"", false},
{"-1", false},
{"65536", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsPort(tt.input))
}
}
func TestIsIp(t *testing.T) {
@@ -219,10 +343,45 @@ func TestIsIp(t *testing.T) {
assert := internal.NewAssert(t, "TestIsIntStr")
assert.Equal(true, IsIp("127.0.0.1"))
assert.Equal(true, IsIp("::0:0:0:0:0:0:1"))
assert.Equal(false, IsIp("127.0.0"))
assert.Equal(false, IsIp("127"))
tests := []struct {
input string
expected bool
}{
{"127.0.0.1", true},
{"::0:0:0:0:0:0:1", true},
{"127.0.0", false},
{"127", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsIp(tt.input))
}
}
func TestIsIpPort(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsIpPort")
tests := []struct {
input string
expected bool
}{
{"[::0:0:0:0:0:0:1]:8080", true},
{"127.0.0.1:8080", true},
{"", false},
{":8080", false},
{"127.0.0.1", false},
{"0:0:0:0:0:0:0:1", false},
{"256.256.256.256:8080", false},
{"256.256.256.256:abc", false},
{"127.0.0.1:70000", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsIpPort(tt.input))
}
}
func TestIsIpV4(t *testing.T) {
@@ -230,13 +389,23 @@ func TestIsIpV4(t *testing.T) {
assert := internal.NewAssert(t, "TestIsIpV4")
assert.Equal(true, IsIpV4("127.0.0.1"))
tests := []struct {
input string
expected bool
}{
{"127.0.0.1", true},
{"::0:0:0:0:0:0:1", false},
assert.Equal(false, IsIpV4("::0:0:0:0:0:0:1"))
assert.Equal(false, IsIpV4("127.0.0.1.1"))
assert.Equal(false, IsIpV4("256.0.0.1"))
assert.Equal(false, IsIpV4("127.0.0.a"))
assert.Equal(false, IsIpV4(""))
{"::0:0:0:0:0:0:1", false},
{"127.0.0.1.1", false},
{"256.0.0.1", false},
{"127.0.0.a", false},
{"", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsIpV4(tt.input))
}
}
func TestIsIpV6(t *testing.T) {
@@ -244,14 +413,23 @@ func TestIsIpV6(t *testing.T) {
assert := internal.NewAssert(t, "TestIsIpV6")
assert.Equal(true, IsIpV6("::0:0:0:0:0:0:1"))
assert.Equal(true, IsIpV6("::1"))
assert.Equal(true, IsIpV6("::"))
tests := []struct {
input string
expected bool
}{
{"::0:0:0:0:0:0:1", true},
{"::1", true},
{"::", true},
{"127.0.0.1", false},
{"2001:db8::8a2e:37023:7334", false},
{"2001::25de::cade", false},
{"", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsIpV6(tt.input))
}
assert.Equal(false, IsIpV6("127.0.0.1"))
assert.Equal(false, IsIpV6("2001:db8::8a2e:37023:7334"))
assert.Equal(false, IsIpV6("2001::25de::cade"))
assert.Equal(false, IsIpV6(""))
}
func TestIsUrl(t *testing.T) {
@@ -270,13 +448,21 @@ func TestIsDns(t *testing.T) {
assert := internal.NewAssert(t, "TestIsDns")
assert.Equal(true, IsDns("abc.com"))
assert.Equal(true, IsDns("123.cn"))
assert.Equal(true, IsDns("a.b.com"))
tests := []struct {
input string
expected bool
}{
{"abc.com", true},
{"123.cn", true},
{"a.b.com", true},
{"a.b.c", false},
{"a@b.com", false},
{"http://abc.com", false},
}
assert.Equal(false, IsDns("a.b.c"))
assert.Equal(false, IsDns("a@b.com"))
assert.Equal(false, IsDns("http://abc.com"))
for _, tt := range tests {
assert.Equal(tt.expected, IsDns(tt.input))
}
}
func TestIsEmail(t *testing.T) {
@@ -313,11 +499,19 @@ func TestIsChinesePhone(t *testing.T) {
assert := internal.NewAssert(t, "TestIsChinesePhone")
assert.Equal(true, IsChinesePhone("010-32116675"))
assert.Equal(true, IsChinesePhone("0464-8756213"))
assert.Equal(true, IsChinesePhone("0731-82251545")) //长沙晚报电话
assert.Equal(false, IsChinesePhone("123-87562"))
tests := []struct {
input string
expected bool
}{
{"010-32116675", true},
{"0464-8756213", true},
{"0731-82251545", true},
{"123-87562", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChinesePhone(tt.input))
}
}
func TestIsChineseIdNum(t *testing.T) {
@@ -325,13 +519,23 @@ func TestIsChineseIdNum(t *testing.T) {
assert := internal.NewAssert(t, "TestIsChineseIdNum")
assert.Equal(true, IsChineseIdNum("210911192105130714"))
assert.Equal(true, IsChineseIdNum("11010519491231002X"))
assert.Equal(true, IsChineseIdNum("11010519491231002x"))
assert.Equal(false, IsChineseIdNum("123456"))
assert.Equal(false, IsChineseIdNum("990911192105130714"))
assert.Equal(false, IsChineseIdNum("990911189905130714"))
assert.Equal(false, IsChineseIdNum("210911222205130714"))
tests := []struct {
input string
expected bool
}{
{"210911192105130714", true},
{"11010519491231002X", true},
{"11010519491231002x", true},
{"123456", false},
{"990911192105130714", false},
{"990911189905130714", false},
{"210911222205130714", false},
{"", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChineseIdNum(tt.input))
}
}
func TestIsCreditCard(t *testing.T) {
@@ -390,13 +594,23 @@ func TestIsStrongPassword(t *testing.T) {
assert := internal.NewAssert(t, "TestIsStrongPassword")
assert.Equal(false, IsStrongPassword("abc", 3))
assert.Equal(false, IsStrongPassword("abc123", 6))
assert.Equal(false, IsStrongPassword("abcABC", 6))
assert.Equal(false, IsStrongPassword("abc123@#$", 9))
assert.Equal(false, IsStrongPassword("abcABC123@#$", 16))
assert.Equal(true, IsStrongPassword("abcABC123@#$", 12))
assert.Equal(true, IsStrongPassword("abcABC123@#$", 10))
tests := []struct {
input string
length int
expected bool
}{
{"abc", 3, false},
{"abc123", 6, false},
{"abcABC", 6, false},
{"abc123@#$", 9, false},
{"abcABC123@#$", 16, false},
{"abcABC123@#$", 12, true},
{"abcABC123@#$", 10, true},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsStrongPassword(tt.input, tt.length))
}
}
func TestIsWeakPassword(t *testing.T) {
@@ -404,11 +618,20 @@ func TestIsWeakPassword(t *testing.T) {
assert := internal.NewAssert(t, "TestIsWeakPassword")
assert.Equal(true, IsWeakPassword("abc"))
assert.Equal(true, IsWeakPassword("123"))
assert.Equal(true, IsWeakPassword("abc123"))
assert.Equal(true, IsWeakPassword("abcABC123"))
assert.Equal(false, IsWeakPassword("abc123@#$"))
tests := []struct {
input string
expected bool
}{
{"abc", true},
{"123", true},
{"abc123", true},
{"abcABC123", true},
{"abc123@#$", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsWeakPassword(tt.input))
}
}
func TestIsZeroValue(t *testing.T) {
@@ -549,50 +772,83 @@ func TestIsPrintable(t *testing.T) {
assert := internal.NewAssert(t, "TestIsPrintable")
assert.Equal(true, IsPrintable("ABC"))
assert.Equal(true, IsPrintable("{id: 123}"))
assert.Equal(true, IsPrintable(""))
assert.Equal(true, IsPrintable("😄"))
assert.Equal(false, IsPrintable("\u0000"))
tests := []struct {
input string
expected bool
}{
{"ABC", true},
{"123", true},
{"你好", true},
{"", true},
{"😄", true},
{"\u0000", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsPrintable(tt.input))
}
}
func TestIsBin(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsBin")
assert.Equal(true, IsBin("0101"))
assert.Equal(true, IsBin("0b1101"))
tests := []struct {
input string
expected bool
}{
{"0101", true},
{"0b1101", true},
{"b1101", false},
{"1201", false},
{"", false},
}
assert.Equal(false, IsBin("b1101"))
assert.Equal(false, IsBin("1201"))
assert.Equal(false, IsBin(""))
for _, tt := range tests {
assert.Equal(tt.expected, IsBin(tt.input))
}
}
func TestIsHex(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsHex")
assert.Equal(true, IsHex("ABCDE"))
assert.Equal(true, IsHex("abcde"))
assert.Equal(true, IsHex("0xabcde"))
assert.Equal(true, IsHex("0Xabcde"))
assert.Equal(true, IsHex("#abcde"))
tests := []struct {
input string
expected bool
}{
{"ABCDE", true},
{"abcde", true},
{"0xabcde", true},
{"0Xabcde", true},
{"#abcde", true},
{"cdfeg", false},
{"0xcdfeg", false},
{"", false},
}
assert.Equal(false, IsHex("cdfeg"))
assert.Equal(false, IsHex("0xcdfeg"))
assert.Equal(false, IsHex(""))
for _, tt := range tests {
assert.Equal(tt.expected, IsHex(tt.input))
}
}
func TestIsBase64URL(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsBase64URL")
assert.Equal(true, IsBase64URL("SAGsbG8sIHdvcmxkIQ"))
assert.Equal(true, IsBase64URL("SAGsbG8sIHdvcmxkIQ=="))
tests := []struct {
input string
expected bool
}{
{"SAGsbG8sIHdvcmxkIQ", true},
{"SAGsbG8sIHdvcmxkIQ==", true},
{"SAGsbG8sIHdvcmxkIQ=", false},
{"SAGsbG8sIHdvcmxkIQ===", false},
}
assert.Equal(false, IsBase64URL("SAGsbG8sIHdvcmxkIQ="))
assert.Equal(false, IsBase64URL("SAGsbG8sIHdvcmxkIQ==="))
// assert.Equal(false, IsBase64URL(""))
for _, tt := range tests {
assert.Equal(tt.expected, IsBase64URL(tt.input))
}
}
func TestIsJWT(t *testing.T) {
@@ -642,3 +898,29 @@ func TestIsChinaUnionPay(t *testing.T) {
assert.Equal(true, IsChinaUnionPay("6250941006528599"))
assert.Equal(false, IsChinaUnionPay("3782822463100007"))
}
func TestIsAlphaNumeric(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsAlphaNumeric")
tests := []struct {
input string
expected bool
}{
{"ABC", true},
{"abc", true},
{"aBC", true},
{"1BC", true},
{"1bc", true},
{"123", true},
{"你好", false},
{"A&", false},
{"&@#$%^&*", false},
{"", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsAlphaNumeric(tt.input))
}
}