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

Compare commits

..

62 Commits

Author SHA1 Message Date
dudaodong
5d3964d81a release v2.3.8 2025-11-01 19:39:46 +08:00
dudaodong
6c3dc3e7d6 Merge branch 'rc' of github.com:duke-git/lancet into rc 2025-10-31 14:59:25 +08:00
dudaodong
2a2e1ca551 doc: add enum and update overview index 2025-10-31 14:57:54 +08:00
dudaodong
e1821eed2c doc: add docment for enum package 2025-10-31 14:48:32 +08:00
dudaodong
62f0a96d91 doc: add docment for enum package 2025-10-31 14:48:22 +08:00
dudaodong
cbdc3971dd doc: add docment for new or fixed function 2025-10-31 13:37:41 +08:00
Javen
350450bb67 feat: add ContainAny (#338)
Co-authored-by: Jiawen <im@linjiawen.com>
2025-10-30 19:24:45 +08:00
dudaodong
f407e51b24 fix: fix issue #335 2025-10-30 17:09:48 +08:00
dudaodong
3c6c3a14cf Merge branch 'rc' into v2 2025-10-29 21:10:54 +08:00
dudaodong
41bafdef92 refact: remove unused code 2025-10-29 21:10:30 +08:00
wangxc
fc624195c7 feat:add map to markdown conversion in map.go (#336)
* feat(file): add map to markdown conversion in map.go

* feat(file): add map to markdown conversion in map.go
2025-10-29 21:07:18 +08:00
残念
5b3a59e785 Merge pull request #334 from duke-git/v2
Add enum package
2025-10-25 12:32:12 +08:00
chentong
74abb2d3f1 feat(struct): add struct name function (#328)
* feat(struct): add struct name function

- Add Name() method to Struct type to return the struct name
- Implement unit tests for the new Name() method

* refactor(structs): rename Struct.Name to Struct.TypeName

- Rename method Name to TypeName for clarity and accuracy
- Update corresponding test cases
2025-10-23 11:20:43 +08:00
dudaodong
3f12b34eea fix: fix issue for PR #334, usge Pair struct to instance enum items 2025-10-16 18:43:24 +08:00
dudaodong
cd43004a91 feat: add example for enum package 2025-10-14 16:37:02 +08:00
dudaodong
3ac9461c00 Merge branch 'rc' into v2 2025-09-30 14:57:01 +08:00
dudaodong
309b07ae8a feat: a simple enum implementation 2025-09-30 14:56:25 +08:00
chentong
8fe56b6dc7 fix(eventbus): update error handler to include topic information (#333)
* fix(eventbus): update error handler to include topic information

- Modified error handler signature to accept topic string and error
- Updated panic recovery logic to pass event topic to error handler
- Adjusted SetErrorHandler method parameter signature
- Updated example test to demonstrate new error handler usage
- Enhanced error handling test to verify topic information

* fix(eventbus): unit test
2025-09-28 20:10:12 +08:00
残念
15a0dad0d8 Merge pull request #330 from zoulux/main
fix: return 0 when Average is called with empty slice
2025-09-23 11:22:30 +08:00
jake
93c777a418 Update mathutil.go
fix: return 0 when Average is called with empty slice
2025-09-22 10:04:23 +08:00
jake
5ff1c6578f Merge branch 'duke-git:main' into main 2025-09-22 09:53:09 +08:00
dudaodong
7d4b9510a2 feat: add IsChineseHMPassport 2025-08-21 14:13:43 +08:00
dudaodong
9f0ad2354a feat: update some test fucntions 2025-08-21 10:52:44 +08:00
dudaodong
55b66dee99 Merge branch 'rc' into v2 2025-08-21 10:08:21 +08:00
Idichekop
4c64a16204 fix(package): [slice] functions with inconsistent return behaviour (#326)
* Some functions modified

* Fixes in all the functions of slice.go

* Re-use of swap function internal.
2025-08-19 10:50:25 +08:00
dudaodong
385e64cc52 feat: add IsPassport 2025-08-13 14:01:24 +08:00
dudaodong
be45a259db Merge branch 'v2' into rc 2025-08-13 11:24:27 +08:00
Idichekop
6f703fe577 fix(package): [slice] Fix RigthPadding and LeftPadding (#322)
* Fixes in  RightPadding and LeftPadding

* Tests cover padding empty, nil, and negative lenght

* Implemented repeat, concat, and grow functionalities as internal.
2025-08-04 10:45:29 +08:00
Idichekop
cb8d93c499 Simple refactor of ForEach functions (#323) 2025-07-30 14:28:27 +08:00
idichekop
ae1014c572 fix(package):[function] Corrected behaviour of Nand predicate (#319) 2025-07-22 10:08:50 +08:00
dudaodong
d5b9e67330 fix: fix go lint issue 2025-07-07 11:18:34 +08:00
dudaodong
a81403766f feat: add ToPointers, FromPointer, FromPointers 2025-07-07 11:16:44 +08:00
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
jake
83c069e234 Update slice_concurrent.go 2025-06-24 19:14:37 +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
78 changed files with 6748 additions and 1103 deletions

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.5-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.8-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)
@@ -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,7 +355,7 @@ 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/X3itkCxwB_x)]
@@ -693,7 +717,6 @@ 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>
@@ -778,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)]
@@ -926,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
@@ -1097,7 +1122,12 @@ 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)]
- **<big>ToMarkdownTable</big>** : Convert a map slice data to a Markdown table string. It supports custom header display names and column display order.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToMarkdownTable)]
[[play](https://go.dev/play/p/todo)]
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1282,6 +1312,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>
@@ -1407,7 +1443,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>
@@ -1429,6 +1464,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>ContainSubSlice</big>** : check if the slice contain a given subslice or not.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainSubSlice)]
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
- **<big>ContainAny</big>** : check if the slice contains any element from the targets slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainAny)]
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
- **<big>Chunk</big>** : creates a slice of elements split into groups the length of size.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Chunk)]
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
@@ -1654,7 +1692,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.
@@ -1787,30 +1825,49 @@ import "github.com/duke-git/lancet/v2/structs"
- **<big>New</big>** : creates a `Struct` instance.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#New)]
[[play](https://go.dev/play/p/O29l8kk-Z17)]
- **<big>ToMap</big>** : converts a valid struct to a map.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#ToMap)]
[[play](https://go.dev/play/p/qQbLySBgerZ)]
- **<big>Fields</big>** : get all fields of a given struct, that the fields are abstract struct field.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Fields)]
[[play](https://go.dev/play/p/w3Kk_CyVY7D)]
- **<big>Field</big>** : get an abstract field of a struct by given field name
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Field)]
[[play](https://go.dev/play/p/KocZMSYarza)]
- **<big>IsStruct</big>** : check if the struct is valid.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsStruct)]
[[play](https://go.dev/play/p/bU2FSdkbK1C)]
- **<big>Tag</big>** : get a `Tag` of the `Field`, `Tag` is a abstract struct field tag.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Tag)]
[[play](https://go.dev/play/p/DVrx5HvvUJr)]
- **<big>Name</big>** : get the field name.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Name)]
[[play](https://go.dev/play/p/zfIGlqsatee)]
- **<big>Value</big>** : get the `Field` underlying value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Value)]
[[play](https://go.dev/play/p/qufYEU2o4Oi)]
- **<big>Kind</big>** : get the field's kind.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Kind)]
[[play](https://go.dev/play/p/wg4NlcUNG5o)]
- **<big>IsEmbedded</big>** : check if the field is an embedded field.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsEmbedded)]
[[play](https://go.dev/play/p/wV2PrbYm3Ec)]
- **<big>IsExported</big>** : check if the field is exported.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsExported)]
[[play](https://go.dev/play/p/csK4AXYaNbJ)]
- **<big>IsZero</big>** : check if the field is zero value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsZero)]
[[play](https://go.dev/play/p/RzqpGISf87r)]
- **<big>IsSlice</big>** : check if the field is a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsSlice)]
[[play](https://go.dev/play/p/MKz4CgBIUrU)]
- **<big>IsTargetType</big>** : check if the field is target type.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsTargetType)]
[[play](https://go.dev/play/p/Ig75P-agN39)]
- **<big>TypeName</big>** : Return struct type name.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#TypeName)]
[[play](https://go.dev/play/p/todo)]
<h3 id="strutil"> 21. Strutil package contains some functions to manipulate string. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -2012,7 +2069,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
@@ -2195,6 +2251,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)]
@@ -2267,6 +2326,12 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsChinaUnionPay</big>** : check if a give string is a valid china union pay number or not.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsChinaUnionPay)]
[[play](https://go.dev/play/p/yafpdxLiymu)]
- **<big>IsPassport</big>** : Passport validation(using regex).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsPassport)]
[[play](https://go.dev/play/p/todo)]
- **<big>IsChineseHMPassport</big>** : Mainland travel permit for Hong Kong, Macao validation (using regex).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsChineseHMPassport)]
[[play](https://go.dev/play/p/todo)]
<h3 id="xerror"> 25. Xerror package implements helpers for errors. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -2315,7 +2380,7 @@ 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/D5Mdb0mRj0P)]
@@ -2324,6 +2389,7 @@ import "github.com/duke-git/lancet/v2/xerror"
#### [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.5-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.8-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)
@@ -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,19 +342,19 @@ 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/X3itkCxwB_x)]
@@ -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)]
@@ -474,19 +509,19 @@ import "github.com/duke-git/lancet/v2/cryptor"
- **<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/7_zo6mrx-eX)]
- **<big>GenerateRsaKeyPair</big>** : 创建rsa公钥私钥和key。
- **<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/qhsbf8BJ6Mf)]
- **<big>RsaVerifySign</big>** : 验证数据的签名是否符合RSA算法。
- **<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/qhsbf8BJ6Mf)]
@@ -645,7 +680,7 @@ 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>** : 返回最早时间。
@@ -658,7 +693,6 @@ import "github.com/duke-git/lancet/v2/datetime"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#MaxMin)]
[[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>
```go
@@ -702,7 +736,7 @@ import "github.com/duke-git/lancet/v2/eventbus"
#### 函数列表:
- **<big>NewEventBus</big>** : 创建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>** : 订阅具有特定事件主题和监听函数的事件。支持异步,事件优先级,事件过滤器。
@@ -726,7 +760,7 @@ import "github.com/duke-git/lancet/v2/eventbus"
- **<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。
- **<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>** : 设置事件的错误处理函数。
@@ -777,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)]
@@ -809,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 写入文件。
@@ -820,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)]
@@ -828,11 +865,10 @@ 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/iLRrDBhE38E)]
<h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -901,33 +937,31 @@ 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"> 13. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -1005,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>** : 创建有序映射。有序映射是键值对的集合,其中键是唯一的,并且保留键插入的顺序。
@@ -1026,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>** : 返回第一个键值对。
@@ -1050,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>** : 返回按顺序产生键值对的通道。
@@ -1059,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 结构。
@@ -1092,12 +1126,18 @@ 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)]
- **<big>ToMarkdownTable</big>** : 将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToMarkdownTable)]
[[play](https://go.dev/play/p/todo)]
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1143,16 +1183,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>** : 根据指定的起始值和数量,创建一个数字切片。
@@ -1203,10 +1243,10 @@ import "github.com/duke-git/lancet/v2/mathutil"
- **<big>StdDev</big>** : 计算标准差。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#StdDev)]
[[play](https://go.dev/play/p/FkNZDXvHD2l)]
- **<big>Permutation</big>** : 计算排列数P(n, k)。
- **<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/MgobwH_FOxj)]
- **<big>Combination</big>** : 计算组合数C(n, k)。
- **<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/ENFQRDQUFi9)]
@@ -1282,6 +1322,12 @@ 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"> 16. pointer 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1339,28 +1385,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>** : 从给定切片中随机生成元素。
@@ -1390,7 +1436,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>** : 定义计算退避间隔的方法的接口。
@@ -1405,8 +1451,6 @@ 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"> 19. slice 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <a href="#index">回到目录</a></h3>
```go
@@ -1427,6 +1471,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>ContainSubSlice</big>** : 判断 slice 是否包含 subslice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainSubSlice)]
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
- **<big>ContainAny</big>** : 判断 slice 是否包含 targets 切片中的任意一个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainAny)]
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
- **<big>Chunk</big>** : 按照 size 参数均分 slice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Chunk)]
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
@@ -1511,7 +1558,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 时,终止遍历。
@@ -1544,7 +1591,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>** : 合并多个切片(不会消除重复元素)。
@@ -1553,7 +1600,7 @@ 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。
- **<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)
@@ -1565,7 +1612,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。
@@ -1628,7 +1675,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>** : 并发的从输入切片中移除重复元素,结果保持元素的顺序。
@@ -1651,13 +1698,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>** : 根据判断函数将切片分成两部分。它开始附加到与函数匹配的第一个元素之后的第二个切片。第一个匹配之后的所有元素都包含在第二个切片中,无论它们是否与函数匹配。
@@ -1704,13 +1751,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 元素的元素组成。
@@ -1767,10 +1814,10 @@ 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/tBV5Nc-XDX2)]
- **<big>LastIndexOf</big>** : 返回在stream中找到值的最后一个匹配项的索引如果找不到值则返回-1。
- **<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/CjeoNw2eac_G)]
@@ -1784,32 +1831,49 @@ import "github.com/duke-git/lancet/v2/structs"
- **<big>New</big>** : `Struct`结构体的构造函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#New)]
[[play](https://go.dev/play/p/O29l8kk-Z17)]
- **<big>ToMap</big>** : 将一个合法的 struct 对象转换为 map[string]any。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#ToMap)]
[[play](https://go.dev/play/p/qQbLySBgerZ)]
- **<big>Fields</big>** : 获取一个 struct 对象的属性列表。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Fields)]
[[play](https://go.dev/play/p/w3Kk_CyVY7D)]
- **<big>Field</big>** : 根据属性名获取一个 struct 对象的属性。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Fields)]
[[play](https://go.dev/play/p/KocZMSYarza)]
- **<big>IsStruct</big>** : 判断是否为一个合法的 struct 对象。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsStruct)]
[[play](https://go.dev/play/p/bU2FSdkbK1C)]
- **<big>Tag</big>** : 获取`Field``Tag`,默认的 tag key 是 json。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Tag)]
[[play](https://go.dev/play/p/DVrx5HvvUJr)]
- **<big>Name</big>** : 获取属性名。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Name)]
[[play](https://go.dev/play/p/zfIGlqsatee)]
- **<big>Value</big>** : 获取`Field`属性的值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Value)]
[[play](https://go.dev/play/p/qufYEU2o4Oi)]
- **<big>Kind</big>** : 获取属性 Kind。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Kind)]
[[play](https://go.dev/play/p/wg4NlcUNG5o)]
- **<big>IsEmbedded</big>** : 判断属性是否为嵌入。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsEmbedded)]
[[play](https://go.dev/play/p/wV2PrbYm3Ec)]
- **<big>IsExported</big>** : 判断属性是否导出。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsExported)]
[[play](https://go.dev/play/p/csK4AXYaNbJ)]
- **<big>IsZero</big>** : 判断属性是否为零值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsZero)]
[[play](https://go.dev/play/p/RzqpGISf87r)]
- **<big>IsSlice</big>** : 判断属性是否是切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsSlice)]
[[play](https://go.dev/play/p/MKz4CgBIUrU)]
- **<big>IsTargetType</big>** : 判断属性是否是目标类型。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsTargetType)]
[[play](https://go.dev/play/p/Ig75P-agN39)]
- **<big>TypeName</big>** : 获取结构体类型名。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#TypeName)]
[[play](https://go.dev/play/p/todo)]
<h3 id="strutil"> 22. strutil 包含字符串处理的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1933,7 +1997,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>** : 计算两个字符串之间的汉明距离。
@@ -1951,7 +2015,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>** : 使用正则表达式匹配字符串中的所有子组并返回结果。
@@ -1964,7 +2028,6 @@ import "github.com/duke-git/lancet/v2/strutil"
[[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"> 23. system 包含 os, runtime, shell command 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -2009,12 +2072,10 @@ 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"> 24. Tuple 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -2176,7 +2237,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>** : 验证字符串是否是有效电子邮件地址。
@@ -2197,6 +2258,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)]
@@ -2212,13 +2276,13 @@ 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格式。
- **<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>** : 验证字符串是否是强密码:(字母+数字+特殊字符)。
@@ -2233,10 +2297,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>** : 检查字符串是否全部为可打印字符。
@@ -2251,13 +2315,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 卡号。
@@ -2269,7 +2333,12 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsChinaUnionPay</big>** : 检查字符串是否是有效的中国银联卡号。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChinaUnionPay)]
[[play](https://go.dev/play/p/yafpdxLiymu)]
- **<big>IsPassport</big>** : 判断护照(正则判断)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsPassport)]
[[play](https://go.dev/play/p/todo)]
- **<big>IsChineseHMPassport</big>** : 判断港澳台通行证(正则判断)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChineseHMPassport)]
[[play](https://go.dev/play/p/todo)]
<h3 id="xerror"> 26. xerror 包实现一些错误处理函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
@@ -2293,31 +2362,31 @@ 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/D5Mdb0mRj0P)]
@@ -2327,7 +2396,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" />
@@ -2335,4 +2404,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

@@ -3,6 +3,7 @@ package concurrency
import (
"context"
"fmt"
"log"
"time"
)
@@ -199,3 +200,159 @@ func ExampleChannel_Bridge() {
// 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

@@ -2,7 +2,6 @@
// 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 (
@@ -26,12 +25,14 @@ type lockEntry struct {
// NewKeyedLocker creates a new KeyedLocker with the specified TTL for lock expiration.
// The TTL is used to automatically release locks that are no longer held.
// Play: https://go.dev/play/p/GzeyC33T5rw
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K] {
return &KeyedLocker[K]{ttl: ttl}
}
// Do acquires a lock for the specified key and executes the provided function.
// It returns an error if the context is canceled before the function completes.
// Play: https://go.dev/play/p/GzeyC33T5rw
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(key, entry, key)
@@ -108,12 +109,14 @@ type rwLockEntry struct {
// NewRWKeyedLocker creates a new RWKeyedLocker with the specified TTL for lock expiration.
// The TTL is used to automatically release locks that are no longer held.
// Play: https://go.dev/play/p/CkaJWWwZm9
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K] {
return &RWKeyedLocker[K]{ttl: ttl}
}
// RLock acquires a read lock for the specified key and executes the provided function.
// It returns an error if the context is canceled before the function completes.
// Play: https://go.dev/play/p/ZrCr8sMo77T
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(entry, key)
@@ -142,6 +145,7 @@ func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
// Lock acquires a write lock for the specified key and executes the provided function.
// It returns an error if the context is canceled before the function completes.
// Play: https://go.dev/play/p/WgAcXbOPKGk
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(entry, key)
@@ -191,3 +195,63 @@ func (l *RWKeyedLocker[K]) release(entry *rwLockEntry, rawKey K) {
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

@@ -175,3 +175,56 @@ func TestRWKeyedLocker_LockTimeout(t *testing.T) {
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

@@ -228,6 +228,42 @@ func ToPointer[T any](value T) *T {
return &value
}
// ToPointers convert a slice of values to a slice of pointers.
// Play: todo
func ToPointers[T any](values []T) []*T {
result := make([]*T, len(values))
for i := range values {
result[i] = &values[i]
}
return result
}
// FromPointer returns the value pointed to by the pointer.
// Play: todo
func FromPointer[T any](ptr *T) T {
if ptr == nil {
var zeroValue T
return zeroValue
}
return *ptr
}
// FromPointers convert a slice of pointers to a slice of values.
// Play: todo
func FromPointers[T any](pointers []*T) []T {
result := make([]T, len(pointers))
for i, ptr := range pointers {
if ptr == nil {
var zeroValue T
result[i] = zeroValue
} else {
result[i] = *ptr
}
}
return result
}
// ToMap convert a slice of structs to a map based on iteratee function.
// Play: https://go.dev/play/p/tVFy7E-t24l
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V {
@@ -406,15 +442,15 @@ func ToStdBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.StdEncoding.EncodeToString(value.([]byte))
return base64.StdEncoding.EncodeToString(v)
case string:
return base64.StdEncoding.EncodeToString([]byte(value.(string)))
return base64.StdEncoding.EncodeToString([]byte(v))
case error:
return base64.StdEncoding.EncodeToString([]byte(value.(error).Error()))
return base64.StdEncoding.EncodeToString([]byte(v.Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}
@@ -428,15 +464,15 @@ func ToUrlBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.URLEncoding.EncodeToString(value.([]byte))
return base64.URLEncoding.EncodeToString(v)
case string:
return base64.URLEncoding.EncodeToString([]byte(value.(string)))
return base64.URLEncoding.EncodeToString([]byte(v))
case error:
return base64.URLEncoding.EncodeToString([]byte(value.(error).Error()))
return base64.URLEncoding.EncodeToString([]byte(v.Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}
@@ -450,7 +486,7 @@ func ToRawStdBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.RawStdEncoding.EncodeToString(value.([]byte))
case string:
@@ -458,7 +494,7 @@ func ToRawStdBase64(value any) string {
case error:
return base64.RawStdEncoding.EncodeToString([]byte(value.(error).Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}
@@ -472,7 +508,7 @@ func ToRawUrlBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.RawURLEncoding.EncodeToString(value.([]byte))
case string:
@@ -480,7 +516,7 @@ func ToRawUrlBase64(value any) string {
case error:
return base64.RawURLEncoding.EncodeToString([]byte(value.(error).Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}

View File

@@ -302,6 +302,75 @@ func TestToPointer(t *testing.T) {
assert.Equal(*result, 123)
}
func TestToPointers(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestToPointers")
intVals := []int{1, 2, 3}
result := ToPointers(intVals)
assert.Equal(3, len(result))
assert.Equal(1, *result[0])
assert.Equal(2, *result[1])
assert.Equal(3, *result[2])
stringVals := []string{"a", "b", "c"}
resultStr := ToPointers(stringVals)
assert.Equal(3, len(resultStr))
assert.Equal("a", *resultStr[0])
assert.Equal("b", *resultStr[1])
assert.Equal("c", *resultStr[2])
}
func TestFromPointer(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestFromPointer")
intVal := 123
pointer := &intVal
result := FromPointer(pointer)
assert.Equal(123, result)
stringVal := "abc"
stringPointer := &stringVal
resultStr := FromPointer(stringPointer)
assert.Equal("abc", resultStr)
}
func TestFromPointers(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestFromPointers")
intPointers := []*int{new(int), new(int), new(int)}
*intPointers[0] = 1
*intPointers[1] = 2
*intPointers[2] = 3
result := FromPointers(intPointers)
assert.Equal(3, len(result))
assert.Equal(1, result[0])
assert.Equal(2, result[1])
assert.Equal(3, result[2])
stringPointers := []*string{new(string), new(string), new(string)}
*stringPointers[0] = "a"
*stringPointers[1] = "b"
*stringPointers[2] = "c"
resultStr := FromPointers(stringPointers)
assert.Equal(3, len(resultStr))
assert.Equal("a", resultStr[0])
assert.Equal("b", resultStr[1])
assert.Equal("c", resultStr[2])
}
func TestEncodeByte(t *testing.T) {
t.Parallel()

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)
@@ -693,117 +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 {
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-----
MIIJKQIBAAKCAgEAtKEukXkdl8ggX5I5Us0MBcsrFzbTgwOn8u72IhJzruvsHcjl
GGF5L1eXh3C0zbcoWq+Bxf8NnXvSXASZlZHTiSVLFerxFw2LwTNcyfOz/Y/wIbtr
Yu4ZdttLKdJS607UvnZ57O7SQni5rxYAP5j+veKHEJgAO7Sn0lEEWwkeudsfVrQm
Qx6JHYtCtx78p2GHXPj9wXWHxWHKMkczKtmBFDLlYocPhQfWzx87sYKjGmTMGzVd
dsrllAqVozlxXTQndOdozx8BWHSBiYWguOvWiYjvryrIf0B7wn4wt3o2Yre3w4Bk
wrTUE73JWG2q1NLuPoT27m7WcdVFGGcKAygAdMMSwS530v8tnAw876/w1SfUmtkI
cqknysMjsvxHx1fbEfkl3b0yBnzcUrT1qxzmNEqJSQXBtFYFyGgyeL8J1ul/T5hP
SNz0we5TO/xRkX5Y1HNJ+a2r8EnE1PU8zgQGBxYSIub4ajSUccyn5yHQ/k2Flsol
Ziu/UzfnzMgdg55svgf5x1ntKYBoVHuYe84Ab3WRAeVlUi0OphvFlBzfIQvrG2sR
biPZRZZ0E8kBH94zPmrFmyX+7TwlSqVHtNFc+Ynz+hrHcd4z6N7fIb+D1gSYuukk
cRqr4ANO0ugZk0YYKaWDf86H7OZOXna8ObIAi+3gx+Wcp1lm7Ffk9sn+dGkCAwEA
AQKCAgAdp/kFWWVKbkkiZ9eRiKjYqqrAfPftIsSIVkODBJSJu6JgoYM7pYVICJGQ
YyjMPa4adYZRA7cwjAvVn8u3iuG4Oq9BQfmjV04CwnQRlDmQ//jlEOhorb7wjMCi
dS24BJFZVApgpDVRRJD39hzEVFI+ytpyFwKyys5i8XpNmAm7agaTLbC6hGDuwNaL
SkMhGBopYZgIE0vfVFbmOlpkRqGyt0iCDLq3lLnn97DNTC2LP9FjBjf6MQXQcIxw
6BV8v/tabkP+/ZAy/a3m7lGdCtuGaT0w/U0911B6dk15Uk9rlc5OAt6IOTg4pYhR
RHAv0RHcoegI7Zm3xtQ5VXGHYyHvwwXA/e5pAqOOO/GZ+85OcilphnSLGoP71rqJ
h22GXTj4uCWiiPA8yLwAdtLKhN/KygzH3gkrsD5bH5R9ZmnBiIk192X2v1flND8M
ikBx6ADCphQnJ2zhCv6teesro9A0GIHVI0NjpZayvKjHMHpMpHpN8ZLMTmbafIH3
tq3g9H17ncWal2qDKco7J+6VgJx1NRGlQY/52J25BX2UzB3rrFW21w01TCtpKH3d
o4sPl5IQS5RsrlPEIVAOo7tYHsB5pgfWUzMN3u9hONnES4NHwgDgYJlRl9B/unPh
/kbz/lEJq3Cw2F3zhYb/8neM49lgmWq+hpUshejiYABaXpMGoQKCAQEA4audTQEt
cH89v7NGWWUplfIgb2ndXUW8IrJQbuLbFkCdlXbSfsOA6GsA8bMNI9IUqAfCpvNg
qmCdJ7b8kfHDIilW2SPDA9wW3Fd30ofJEIkfRet87DeT541E27hUHTZ5GSiKvzqn
j+IXu2mpcASgeOJnIp6KmyTOIwj9DJZPexnDRYwnbjpLUdWCCkGcXbIMggrvjaTj
QEWW8RWt+GPyhDACh1KFa2RSON5p3HasPKZJmJz6Zgk9rJ7xI3MSdnGjGMl4PviB
LyrXjf5URqrDmpUf+Xtl3ysNiGDzRBn05SjpmsS2yOjQlK5CiK/o+LTIsNUtdzay
5PLoxR74b6uTtwKCAQEAzOfmshWZ7AbaaLYPCdV3aDRsti4O8U400cc4QFTMBjwZ
AI8A/++fcTZJgtmFPMhg4C9BNj1w8sgcQ7otTv5iJFgZ2bktBp5dgWsX4SbyrC16
xapOa1PNcgV+YmZ2HnzC4lCfE42hsTP2QI2E2Yr1vyeZ9YvbWJHvBJYA0s5lwkpp
dO7abJ/F6DYzpvjomAobutTQmd0XvNTSLuhfudRDadC4RtBQ/bjF+Rx3EjXeWJzy
l83VBsDnlqhrsGpV+yq1pRePfY9XzDjWfz+oOzCMSvl1+eXmFxl7PJqXc9JD2CeW
6xfzQEXxB7czHaTJ4tSB4yC7U9qOXLkZQBpyGg143wKCAQA9raH4gfHhZWWDF4SK
ulN7YAntaYnPDFg3Q3UoWWh31IE9cJRngReiblx7suxMdgafRj+1UZ+B8ZYCXMj7
OpCSranG/zc1vtmgr2dYazRRCKk7evlRtn7+MmY3h1G2CkVe0u3ZBjb15F2II4Dj
1N/nKjn2BE7tyElu2e4PmqVuh8QPJhdA0T30x94a34PVN+yjPknq9L4Huv1eNwat
dOO7rUODqNI+X9T5JhDY6LZ6fRhwVbc6XBw3KdnOTo0lQjnJdIcg7tqgAZ2YeYKf
Ldz4SvnKPifBrwqr05OpcU61s1DltA4hK0CW4mnc4fdSwlZ3vkwG4TRTzvA/sA9G
tiZRAoIBAQCuVmCiBF8BwpLxpHUHGOiPcItONcHg7XljQu1JTtyIMXnUT9e56lbu
LBI/knMaVCKYm5wQWhZPepMRzMXf/+/gnFTiOftlNji4dDXNCyZN+CQNKemux451
BNeTQToelmf5xj6SlF6ONne+VKpDrUeJbFhB4sytfvyuGjJ5KcLKnCU9qDuPUCFC
gVtRJVZAhdkyDP+u6b3Ym/p4jp1jroXs8fjXx0YhmaRXXzCv/cU//8kn/6jQJjDk
rkdxwgeFu8Dwxir/2YYJ7BIUEkVAlv3GjJkkFca+wJ9p4N4bXTr8HjL5s1bzyI5a
0jRbdGmQ5N3eMWsw3TNjENm7AMU0BWJhAoIBAQCdW0LoiFahv/zrqM/gBE88C1fS
w4/BrSl/bhLGZQqSbcr9SxM/2Y5xyE4RS52glSDfx4zlseI93SqQDKLfGP+bjI9d
HG07T3SELvs816SFRuGC+ktw3mFjFaGn4KlLtOsGcPRMZofsm/XdUNpWj5Ic4d5/
4YWwBQn9VaODDvrhCcv+biA0vRBPlH67gubCy8RMb7J2GspMU9ekIjQxHJkVLY/a
k9gBVdAtzeQY9VHLsc5/zTXReXUCyvrUw+h2hVtGx3CgncKH4WWELBWHXZqQJ6kJ
sZhZ+KH9b6XGnSY29wPX1GKCTIXXIfN5xzFo65yw2b8WE0u0idvEsv1oKqtC
MIIJKQIBAAKCAgEAuzBz+aC+e7Lvny2zYlcyAfG6AAtPkxZqJ9JkYkM+0CP87pe0
xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUoj/TWY/xpoPNXXzz/5dz3u6r/A5Tu
mJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgfm4AZOXd/plzBkTrnu6lKG/rH9cnr
2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzvjLJWmJ08VrnF9PFtWhL939xSAIic
FzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5YlWmuFJ8YKvwR0+4XIRLX9qsMy7q
PTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoCLHLqNKf/dD+4vXxgS+f1ObfOcJpD
qBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8jIvt2FutT4hsNSd0L6c2mfo5mTno
DOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a+kTiW8BedNqcRnVVGT9/OS/ggyKF
wxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30znguCdaTTViA9UbrjtcE2bZtnqOMAU2s
08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg5hf7ft6FA+bXB9DHwGdv/UyrGr88
nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UWDcHAyc+Zv+te0DqPUNcAW5ECAwEA
AQKCAgBLJ0Do0Cip8UVTWz3SFb/2F97dda0VGMK2CjpTWTw2xLwf7ric9MesIi3k
fBgLhzUduaiGqxD7gSuIcc8/na4TXfFVY1nlTM2fZxY2a2jq59RK09iXXcwanM9y
8YPAgpfPI4Jq6Sm5D+aGGKvAlzvZaqy17cxKNqNgc43mQimG4kC15cPTfaIFmkXl
doJIbJoWlkzVzNWKuzDp06jBhmeGzXMHAtne1+cqWGPW7hkPb51cqXxBs/gOtkiH
QAmliMG9HCvHDnoXbk1K/XolD3aWjFzLVBKrnVxyxQb33gWFDn5kbkmNGshaVDuC
EqYsMYJ9U4HLNGTdJXlaY4izGe+UyExET5p2KYKC9S34jMvR5k9Hf4pATSUYRhjL
t/EV8EZlWCJGvGRAdtlKNLjRuIAiMTofUZca+sCHDvdcv8+/imOnKCXOETtcOHzw
I7MRdIi2JigcBKuKaua9H77cEuvwG9Bb7aLbqQ3XM5JhoBEBe2jHG39GYDAAjEWz
XWo2ri8rkU0nhixN26x7DXCfMewxZ/zc4czBTU2giM0Yrh4BMpRpHnw14QXRb58y
eTD7GVrC9g1/6HXsAzzBfKyTMZhZhmfjcgSuYMUbzwIvvttJQtw5Ic/LJmR1Eg2F
YZ3mUmwJwDEPyVlV2mXNUYYa64v7O3h+NsjXukWXw080fWdsoQKCAQEA8ZcVn863
wXQVex7RcXs5frdnKEtHx6V6tXXq4tvK71Jbkny3gOmPqwwEF0fk4m2Fo07CmJkX
t5o0tbPxfVbxeWGRGubAstjd5oWgt6nMAgcEkRbAzYM8qLGAGekS4g5+2/SjrQhG
oR2phBv+T9w/Oglf6mVzc8YDNP9B0PB0CTICTYhej16Qhc/jpFmXkjXfslGlUp0F
WVkNE7BZEk/fNgCbmAV1hCcDt7MwoOYBqGBoWb3tRKNhtBIDfJY1LVPjB6Jo3FWl
nolJ1v1In9MhsNudZ6QlYbO8uMadsx3a1Flsu/w69TT+sPjmw+GoSzGuMlH15cFY
qZZ6k75WmwyRGwKCAQEAxlq0SEK86+5zAIvVRQI5pQk0HEGy0dtcUgwBhpy13Bga
sCezorJwS1tEHXfWYMtwmHytMXbySnFQEx5jJLFaQhPfOHybHV94fqq+qcC+NuEt
z2KMoQG+zlupH5LwZv3RzzMSng0AuxNaiPx/tXfXM+5O19wb8VKu7X+hkgOW+psu
wGnofT1zYTCWEbRPZENSL6Mi8BShwu3UIMFhKhVZJZH6MOSU/AoULv49ije39Z58
B06IERBIGpM6FE6L73BHphbUh9Osr/I9vbi7zCzt/utQ1uzMzzxxJjjadYf1K7xa
MYsmtKJ85+dG2/WOw6bRSGk1Dw9KqUBqHQ7bwXq8wwKCAQEAtrCwuotg69qzz8oL
SgyL+uYIDTF4U2Iwu/4ypGDfQkD+XHURc1uruAY7JbvJOuzlbQxHHYxPohjrmSg9
CrJvooGEcFplCBn1G7ibQ6gUTMgvzOPu4rpGaa7oly9ohye9COojx9qFRpsesHdW
xd9gtKuYK7GSL89iZ3ZLuAvNQ5LcqPLhxvsUwQvnMkZJ11gEFF2nbiStgdZUjDoD
8VQTEEw/XSNrrYavSgAoWtP0FvbokkyMmyYN4VTp7BHOnrtb6E8Jiuz9dDiPbRNW
Ev5e8NXyXwiC+DIqGXSglm2SKJiDIFjp4Lm1i/B82U3QrSQhfY37LEYcnQndIdKC
vXcwVwKCAQA0K2UhYFQ6JYQfz6dvOA+bRZlsGSeEJJLajYfVNOBsG/bhAAAyOYZp
e36l1YAQA1IA+UHAMc22IKlz7dkbrH3VxU4/mB5gEl0py5TMJwKggoc+9WeRbVkX
A2qvAEG0hOuq+H7cDQV1LrjwMKESRIvYf8RC6AR9a0bQ9nGzarhJ/4jDWNeqIQB4
voOp8mezMjWqi9jDllmZYF4bo2D/5Y+F3ygTtfstcyUt2vaqpM8AjgeHEHOfMU4V
l0V+U85gUoK1v2l0tArGWAs/HBhgsiyCkLe5X5zaoMYNzIRAx1qHf0mloDi058u8
Xsr3TVWYRgbjabBn3pi/fU6rh93qvHJrAoIBAQCHNkNclB3UMBoZv2Dnd2ZwUZnP
BzN6pq/NjG4GnjSCxPYwYnvB1TakTVLTYOjMS/7TCsFqPFSi93mjDsZ+0ZHBUZOH
076AdxzFD+WxxYZ2+vl3iRhgERY3LgqHOBTt7O1/OYcNraJ6Pk2ppU/PqYaVb389
2dsgqEEbO0np59I2+BP6giIFr3L2xKk2CRdVLKqCQ8FNx2xpi+1kcJlUW+ZxPiwu
JaOA/mNHOz3Kq4DDE+1XjvKq602zm7D1oG67xM/gE5UV/KT7auI9M+zQcTPWZ3T6
cl5A3z4tP9KdWJInadUQyOrVIHCbqxIlZCQjYFo2m9HzFr6fWbgtWC+IyGJQ
-----END rsa private key-----

View File

@@ -1,14 +1,14 @@
-----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtKEukXkdl8ggX5I5Us0M
BcsrFzbTgwOn8u72IhJzruvsHcjlGGF5L1eXh3C0zbcoWq+Bxf8NnXvSXASZlZHT
iSVLFerxFw2LwTNcyfOz/Y/wIbtrYu4ZdttLKdJS607UvnZ57O7SQni5rxYAP5j+
veKHEJgAO7Sn0lEEWwkeudsfVrQmQx6JHYtCtx78p2GHXPj9wXWHxWHKMkczKtmB
FDLlYocPhQfWzx87sYKjGmTMGzVddsrllAqVozlxXTQndOdozx8BWHSBiYWguOvW
iYjvryrIf0B7wn4wt3o2Yre3w4BkwrTUE73JWG2q1NLuPoT27m7WcdVFGGcKAygA
dMMSwS530v8tnAw876/w1SfUmtkIcqknysMjsvxHx1fbEfkl3b0yBnzcUrT1qxzm
NEqJSQXBtFYFyGgyeL8J1ul/T5hPSNz0we5TO/xRkX5Y1HNJ+a2r8EnE1PU8zgQG
BxYSIub4ajSUccyn5yHQ/k2FlsolZiu/UzfnzMgdg55svgf5x1ntKYBoVHuYe84A
b3WRAeVlUi0OphvFlBzfIQvrG2sRbiPZRZZ0E8kBH94zPmrFmyX+7TwlSqVHtNFc
+Ynz+hrHcd4z6N7fIb+D1gSYuukkcRqr4ANO0ugZk0YYKaWDf86H7OZOXna8ObIA
i+3gx+Wcp1lm7Ffk9sn+dGkCAwEAAQ==
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuzBz+aC+e7Lvny2zYlcy
AfG6AAtPkxZqJ9JkYkM+0CP87pe0xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUo
j/TWY/xpoPNXXzz/5dz3u6r/A5TumJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgf
m4AZOXd/plzBkTrnu6lKG/rH9cnr2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzv
jLJWmJ08VrnF9PFtWhL939xSAIicFzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5
YlWmuFJ8YKvwR0+4XIRLX9qsMy7qPTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoC
LHLqNKf/dD+4vXxgS+f1ObfOcJpDqBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8
jIvt2FutT4hsNSd0L6c2mfo5mTnoDOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a
+kTiW8BedNqcRnVVGT9/OS/ggyKFwxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30zngu
CdaTTViA9UbrjtcE2bZtnqOMAU2s08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg
5hf7ft6FA+bXB9DHwGdv/UyrGr88nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UW
DcHAyc+Zv+te0DqPUNcAW5ECAwEAAQ==
-----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

@@ -60,7 +60,7 @@ func (q *ArrayQueue[T]) Front() T {
// Back return back value of queue
func (q *ArrayQueue[T]) Back() T {
return q.data[q.size-1]
return q.data[q.tail-1]
}
// EnQueue put element into queue

View File

@@ -114,6 +114,7 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
],
},
{ text: 'datetime', link: '/en/api/packages/datetime' },
{ text: 'enum', link: '/en/api/packages/enum' },
{ text: 'eventbus', link: '/en/api/packages/eventbus' },
{ text: 'fileutil', link: '/en/api/packages/fileutil' },
{ text: 'formatter', link: '/en/api/packages/formatter' },
@@ -128,9 +129,9 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: 'stream', link: '/en/api/packages/stream' },
{ text: 'struct', link: '/en/api/packages/struct' },
{ text: 'strutil', link: '/en/api/packages/strutil' },
{ text: 'system', link: '/en/api/packages/system' },
{ text: 'tuple', link: '/en/api/packages/tuple' },
{ text: 'validator', link: '/en/api/packages/validator' },
{ text: 'system', link: '/en/api/packages/system' },
{ text: 'xerror', link: '/en/api/packages/xerror' },
],
},

View File

@@ -128,7 +128,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/fileutil' },
{ text: '格式化工具', link: '/api/packages/formatter' },
{ text: '函数', link: '/api/packages/function' },
{ text: '数学工具', link: '/api/packages/mathutil' },
@@ -141,9 +141,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: '流', link: '/api/packages/stream' },
{ text: '结构体', link: '/api/packages/struct' },
{ text: '字符串', link: '/api/packages/strutil' },
{ text: '系统', link: '/api/packages/system' },
{ text: '枚举', link: '/api/packages/enum' },
{ text: '元组', link: '/api/packages/tuple' },
{ text: '验证器', link: '/api/packages/validator' },
{ text: '系统工具函数', link: '/api/packages/system' },
{ text: '错误处理', link: '/api/packages/xerror' },
],
},

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">enum</div>
<div class="package-cell">eventbus</div>
<div class="package-cell">fileutil</div>
<div class="package-cell">formatter</div>
@@ -67,4 +67,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

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

View File

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

850
docs/api/packages/enum.md Normal file
View File

@@ -0,0 +1,850 @@
# Enum
Enum实现一个简单枚举工具包。
<div STYLE="page-break-after: always;"></div>
## 源码:
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
<div STYLE="page-break-after: always;"></div>
## 用法:
```go
import (
"github.com/duke-git/lancet/v2/enum"
)
```
<div STYLE="page-break-after: always;"></div>
## 目录
- [NewItem](#NewItem)
- [NewItemsFromPairs](#NewItemsFromPairs)
- [Value](#Value)
- [Name](#Name)
- [Valid](#Valid)
- [MarshalJSON](#MarshalJSON)
- [NewRegistry](#NewRegistry)
- [Add](#Add)
- [Remove](#Remove)
- [Update](#Update)
- [GetByValue](#GetByValue)
- [GetByName](#GetByName)
- [Items](#Items)
- [Contains](#Contains)
- [Size](#Size)
- [Range](#Range)
- [SortedItems](#SortedItems)
- [Filter](#Filter)
<div STYLE="page-break-after: always;"></div>
## 文档
### <span id="NewItem">NewItem</span>
<p>创建枚举项。</p>
<b>函数签名:</b>
```go
func NewItem[T comparable](value T, name string) *Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
fmt.Println(item1.Name(), item1.Value())
fmt.Println(item2.Name(), item2.Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="NewItemsFromPairs">NewItemsFromPairs</span>
<p>从Pair结构体的切片创建枚举项。</p>
<b>函数签名:</b>
```go
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Value">Value</span>
<p>返回枚举项的值。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) Value() T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Name">Name</span>
<p>返回枚举项的名称。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) Name() string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Valid">Valid</span>
<p>检查枚举项是否有效。如果提供了自定义检查函数,将使用该函数验证值。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) Valid(checker ...func(T) bool) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
fmt.Println(item.Valid())
invalidItem := enum.NewItem(Unknown, "")
fmt.Println(invalidItem.Valid())
// Output:
// true
// false
}
```
### <span id="MarshalJSON">MarshalJSON</span>
<p>枚举项实现json.Marshaler 接口。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) MarshalJSON() ([]byte, error)
func (it *Item[T]) UnmarshalJSON(data []byte) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
data, _ := item.MarshalJSON()
fmt.Println(string(data))
var unmarshaledItem Item[Status]
_ = unmarshaledItem.UnmarshalJSON(data)
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
// Output:
// {"name":"Active","value":1}
// Active 1
}
```
### <span id="NewRegistry">NewRegistry</span>
<p>Registry 定义了一个通用的枚举注册表结构体。</p>
<b>函数签名:</b>
```go
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Add">Add</span>
<p>向枚举注册表添加枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Add(items ...*Item[T])
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Remove">Remove</span>
<p>在枚举注册表中删除枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Remove(value T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
fmt.Println("Size before removal:", registry.Size())
removed := registry.Remove(Active)
fmt.Println("Removed:", removed)
fmt.Println("Size after removal:", registry.Size())
// Output:
// Size before removal: 1
// Removed: true
// Size after removal: 0
}
```
### <span id="Update">Update</span>
<p>在枚举注册表中更新枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Update(value T, newName string) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
fmt.Println("Updated:", updated)
if item, found := registry.GetByValue(Active); found {
fmt.Println("New name:", item.Name())
}
// Output:
// Updated: true
// New name: Activated
}
```
### <span id="GetByValue">GetByValue</span>
<p>在枚举注册表中通过值获取枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found name by value:", item.Name())
}
// Output:
// Found name by value: Active
}
```
### <span id="GetByName">GetByName</span>
<p>在枚举注册表中通过名称获取枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
if item, found := registry.GetByName("Active"); found {
fmt.Println("Found value by name:", item.Value())
}
// Output:
// Found value by name: 1
}
```
### <span id="Items">Items</span>
<p>返回枚举注册表中的枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Items() []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
for _, item := range registry.Items() {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Contains">Contains</span>
<p>检查注册表中是否存在具有给定值的枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Contains(value T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
fmt.Println(registry.Contains(Active))
fmt.Println(registry.Contains(Inactive))
// Output:
// true
// false
}
```
### <span id="Size">Size</span>
<p>返回注册表中枚举项的数目。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Size() int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
fmt.Println("Initial size:", registry.Size())
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
fmt.Println("Size after adding items:", registry.Size())
registry.Remove(Active)
fmt.Println("Size after removing an item:", registry.Size())
// Output:
// Initial size: 0
// Size after adding items: 2
// Size after removing an item: 1
}
```
### <span id="Range">Range</span>
<p>遍历注册表中的所有枚举项,并应用给定的函数。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
registry.Range(func(item *Item[Status]) bool {
fmt.Println(item.Name(), item.Value())
return true // continue iteration
})
// Output:
// Active 1
// Inactive 2
}
```
### <span id="SortedItems">SortedItems</span>
<p>返回按给定比较函数排序的所有枚举项的切片。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Inactive, "Inactive")
item2 := enum.NewItem(Active, "Active")
registry.Add(item1, item2)
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
return i1.value < i2.value
}) {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Filter">Filter</span>
<p>返回满足给定谓词函数的枚举项切片。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
activeItems := registry.Filter(func(item *Item[Status]) bool {
return item.Value() == Active
})
for _, item := range activeItems {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
}
```

View File

@@ -427,7 +427,7 @@ func main() {
func RemoveDir(path string, onDelete ...func(path string)) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
```go
package main
@@ -970,7 +970,7 @@ func main() {
<b>函数签名:</b>
```go
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uNep3Tr8fqF)</span></b>
@@ -994,9 +994,9 @@ func main() {
if err != nil {
return
}
fmt.Println(string(dat))
// Output:
// User-agent: *
// Disallow: /deny
@@ -1025,7 +1025,7 @@ import (
func main() {
const mb = 1024 * 1024
const defaultChunkSizeMB = 100
const defaultChunkSizeMB = 100
// test1.csv file content:
// Lili,22,female
@@ -1089,7 +1089,7 @@ func main() {
numParsers := runtime.NumCPU()
linesCh := make(chan []string, numParsers)
// test1.csv file content:
// Lili,22,female
// Jim,21,male
@@ -1114,6 +1114,7 @@ func main() {
// 2
}
```
### <span id="GetExeOrDllVersion">GetExeOrDllVersion</span>
<p>返回exe,dll文件版本号(仅Window平台).</p>
@@ -1143,4 +1144,4 @@ func main() {
// Output:
// 3.9.10.19
}
```
```

View File

@@ -79,6 +79,7 @@ import (
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
- [ToMarkdownTable](#ToMarkdownTable)
<div STYLE="page-break-after: always;"></div>
@@ -1095,7 +1096,7 @@ func main() {
<b>函数签名:</b>
```go
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
@@ -1259,7 +1260,6 @@ func main() {
}
```
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
<p>删除给定键的键值对。</p>
@@ -2192,7 +2192,6 @@ func main() {
}
```
### <span id="GetOrSet">GetOrSet</span>
<p>返回给定键的值,如果不存在则设置该值。</p>
@@ -2276,7 +2275,7 @@ func main() {
<b>函数签名:</b>
```go
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/99QjSYSBdiM)</span></b>
@@ -2319,7 +2318,7 @@ func main() {
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
```go
package main
@@ -2346,4 +2345,69 @@ func main() {
// Output:
// [b d]
}
```
### <span id="ToMarkdownTable">ToMarkdownTable</span>
<p>将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。</p>
<b>函数签名:</b>
```go
编辑
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
// 基本用法:自动从数据中提取列名作为表头
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
{"name": "Bob", "age": 30, "salary": 60000},
}
result := maputil.ToMarkdownTable(data, nil, nil)
fmt.Println(result)
// 输出:
// |name|age|salary|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// 自定义表头显示名称
headerMap := map[string]string{
"name": "姓名",
"age": "年龄",
"salary": "薪资",
}
result = maputil.ToMarkdownTable(data, headerMap, nil)
fmt.Println(result)
// 输出:
// |姓名|年龄|薪资|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// 自定义列顺序
columnOrder := []string{"salary", "name"}
result = maputil.ToMarkdownTable(data, nil, columnOrder)
fmt.Println(result)
// 输出:
// |salary|name|
// |---|---|
// |50000|Alice|
// |60000|Bob|
}
```

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)

View File

@@ -48,7 +48,6 @@ import (
- [UploadFile](#UploadFile)
- [IsPingConnected](#IsPingConnected)
- [IsTelnetConnected](#IsTelnetConnected)
- [IsTelnetConnected](#IsTelnetConnected)
- [BuildUrl](#BuildUrl)
- [AddQueryParams](#AddQueryParams)
@@ -1045,7 +1044,7 @@ func main() {
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
@@ -1085,7 +1084,7 @@ func main() {
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
@@ -1113,4 +1112,4 @@ func main() {
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
```
```

View File

@@ -27,6 +27,7 @@ import (
- [Contain](#Contain)
- [ContainBy](#ContainBy)
- [ContainSubSlice](#ContainSubSlice)
- [ContainAny](#ContainAny)
- [Chunk](#Chunk)
- [Compact](#Compact)
- [Concat](#Concat)
@@ -256,6 +257,43 @@ func main() {
}
```
### <span id="ContainAny">ContainAny</span>
<p>判断slice是否包含targets切片中的任意一个元素</p>
<b>函数签名:</b>
```go
func ContainAny[T comparable](slice []T, targets []T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// false
// true
// false
}
```
### <span id="Chunk">Chunk</span>
<p>按照size参数均分slice</p>

View File

@@ -31,6 +31,7 @@ import (
- [IsStruct](#IsStruct)
- [Tag](#Tag)
- [Name](#Name)
- [TypeName](#TypeName)
- [Value](#Value)
- [Kind](#Kind)
- [IsEmbedded](#IsEmbedded)
@@ -53,7 +54,7 @@ import (
func New(value any, tagName ...string) *Struct
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/O29l8kk-Z17)</span></b>
```go
package main
@@ -68,7 +69,11 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
// to do something
fmt.Println(s.ToMap())
// Output:
// map[name:11] <nil>
}
```
@@ -88,7 +93,7 @@ func (s *Struct) ToMap() (map[string]any, error)
func ToMap(v any) (map[string]any, error)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qQbLySBgerZ)</span></b>
```go
package main
@@ -129,7 +134,7 @@ func main() {
func (s *Struct) Fields() []*Field
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/w3Kk_CyVY7D)</span></b>
```go
package main
@@ -161,10 +166,10 @@ func main() {
<b>函数签名:</b>
```go
func (s *Struct) Field(name string) *Field
func (s *Struct) Field(name string) (*Field, bool)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KocZMSYarza)</span></b>
```go
package main
@@ -180,12 +185,14 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
f := s.Field("Name")
f, found := s.Field("Name")
fmt.Println(f.Value())
fmt.Println(found)
// Output:
// 11
// true
}
```
@@ -199,7 +206,7 @@ func main() {
func (s *Struct) IsStruct() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bU2FSdkbK1C)</span></b>
```go
package main
@@ -233,7 +240,7 @@ func main() {
func (f *Field) Tag() *Tag
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/DVrx5HvvUJr)</span></b>
```go
package main
@@ -270,7 +277,7 @@ func main() {
func (f *Field) Value() any
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qufYEU2o4Oi)</span></b>
```go
package main
@@ -306,7 +313,7 @@ func main() {
func (f *Field) IsEmbedded() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wV2PrbYm3Ec)</span></b>
```go
package main
@@ -351,7 +358,7 @@ func main() {
func (f *Field) IsExported() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/csK4AXYaNbJ)</span></b>
```go
package main
@@ -390,7 +397,7 @@ func main() {
func (f *Field) IsZero() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/RzqpGISf87r)</span></b>
```go
package main
@@ -429,7 +436,7 @@ func main() {
func (f *Field) Name() string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zfIGlqsatee)</span></b>
```go
package main
@@ -468,7 +475,7 @@ func main() {
func (f *Field) Kind() reflect.Kind
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wg4NlcUNG5o)</span></b>
```go
package main
@@ -497,6 +504,42 @@ func main() {
}
```
### <span id="TypeName">TypeName</span>
<p>获取结构体类型名。</p>
<b>函数签名:</b>
```go
func (s *Struct) TypeName() string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/structs"
)
func main() {
type Parent struct {
Name string
Age int
}
p := &Parent{Age: 11}
s := structs.New(p1)
fmt.Println(s.TypeName())
// Output:
// Parent
}
```
### <span id="IsSlice">IsSlice</span>
<p>判断属性是否是切片</p>
@@ -507,7 +550,7 @@ func main() {
func (f *Field) IsSlice() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/MKz4CgBIUrU)</span></b>
```go
package main
@@ -544,7 +587,7 @@ func main() {
func (f *Field) IsTargetType(targetType reflect.Kind) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ig75P-agN39)</span></b>
```go
package main

View File

@@ -65,6 +65,8 @@ import (
- [IsAmericanExpress](#IsAmericanExpress)
- [IsUnionPay](#IsUnionPay)
- [IsChinaUnionPay](#IsChinaUnionPay)
- [IsPassport](#IsPassport)
- [IsChineseHMPassport](#IsChineseHMPassport)
<div STYLE="page-break-after: always;"></div>
@@ -824,6 +826,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>
@@ -1530,3 +1569,80 @@ func main() {
// false
}
```
### <span id="IsPassport">IsPassport</span>
<p>判断护照(正则判断)。</p>
<b>函数签名:</b>
```go
func IsPassport(passport, country string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsPassport("P123456789", "CN")
result2 := validator.IsPassport("123456789", "US")
result3 := validator.IsPassport("AB1234567", "RU")
result4 := validator.IsPassport("123456789", "CN")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
```
### <span id="IsChineseHMPassport">IsChineseHMPassport</span>
<p>判断港澳台通行证(正则判断)。</p>
<b>函数签名:</b>
```go
func IsChineseHMPassport(hmPassport string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsChineseHMPassport("C12345678")
result2 := validator.IsChineseHMPassport("C00000000")
result3 := validator.IsChineseHMPassport("M12345678")
result4 := validator.IsChineseHMPassport("c12345678")
result5 := validator.IsChineseHMPassport("C1234567")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// true
// true
// true
// false
// false
}
```

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,8 @@ outline: deep
<div class="package-cell">cryptor</div>
<div class="package-cell">datastructure</div>
<div class="package-cell">datetime</div>
<div class="package-cell">enum</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 +68,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

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

View File

@@ -0,0 +1,850 @@
# Enum
Package enum provides a simple enum implementation.
<div STYLE="page-break-after: always;"></div>
## Source:
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
<div STYLE="page-break-after: always;"></div>
## Usage:
```go
import (
"github.com/duke-git/lancet/v2/enum"
)
```
<div STYLE="page-break-after: always;"></div>
## Index
- [NewItem](#NewItem)
- [NewItemsFromPairs](#NewItemsFromPairs)
- [Value](#Value)
- [Name](#Name)
- [Valid](#Valid)
- [MarshalJSON](#MarshalJSON)
- [NewRegistry](#NewRegistry)
- [Add](#Add)
- [Remove](#Remove)
- [Update](#Update)
- [GetByValue](#GetByValue)
- [GetByName](#GetByName)
- [Items](#Items)
- [Contains](#Contains)
- [Size](#Size)
- [Range](#Range)
- [SortedItems](#SortedItems)
- [Filter](#Filter)
<div STYLE="page-break-after: always;"></div>
## Documentation
### <span id="NewItem">NewItem</span>
<p>Creates a new enum item.</p>
<b>Signature:</b>
```go
func NewItem[T comparable](value T, name string) *Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
fmt.Println(item1.Name(), item1.Value())
fmt.Println(item2.Name(), item2.Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="NewItemsFromPairs">NewItemsFromPairs</span>
<p>Creates enum items from a slice of Pair structs.</p>
<b>Signature:</b>
```go
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Value">Value</span>
<p>Returns the value of the enum item.</p>
<b>Signature:</b>
```go
func (it *Item[T]) Value() T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Name">Name</span>
<p>Returns the name of the enum item.</p>
<b>Signature:</b>
```go
func (it *Item[T]) Name() string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Valid">Valid</span>
<p>Checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.</p>
<b>Signature:</b>
```go
func (it *Item[T]) Valid(checker ...func(T) bool) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
fmt.Println(item.Valid())
invalidItem := enum.NewItem(Unknown, "")
fmt.Println(invalidItem.Valid())
// Output:
// true
// false
}
```
### <span id="MarshalJSON">MarshalJSON</span>
<p>Implementation of json.Marshaler interface.</p>
<b>Signature:</b>
```go
func (it *Item[T]) MarshalJSON() ([]byte, error)
func (it *Item[T]) UnmarshalJSON(data []byte) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
data, _ := item.MarshalJSON()
fmt.Println(string(data))
var unmarshaledItem Item[Status]
_ = unmarshaledItem.UnmarshalJSON(data)
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
// Output:
// {"name":"Active","value":1}
// Active 1
}
```
### <span id="NewRegistry">NewRegistry</span>
<p>Creates a new enum registry.</p>
<b>Signature:</b>
```go
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Add">Add</span>
<p>Adds enum items to the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Add(items ...*Item[T])
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Remove">Remove</span>
<p>Removes an enum item from the registry by its value.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Remove(value T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
fmt.Println("Size before removal:", registry.Size())
removed := registry.Remove(Active)
fmt.Println("Removed:", removed)
fmt.Println("Size after removal:", registry.Size())
// Output:
// Size before removal: 1
// Removed: true
// Size after removal: 0
}
```
### <span id="Update">Update</span>
<p>Updates the name of an enum item in the registry by its value.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Update(value T, newName string) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
fmt.Println("Updated:", updated)
if item, found := registry.GetByValue(Active); found {
fmt.Println("New name:", item.Name())
}
// Output:
// Updated: true
// New name: Activated
}
```
### <span id="GetByValue">GetByValue</span>
<p>Retrieves an enum item by its value.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found name by value:", item.Name())
}
// Output:
// Found name by value: Active
}
```
### <span id="GetByName">GetByName</span>
<p>Retrieves an enum item by its name.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
if item, found := registry.GetByName("Active"); found {
fmt.Println("Found value by name:", item.Value())
}
// Output:
// Found value by name: 1
}
```
### <span id="Items">Items</span>
<p>Returns a slice of all enum items in the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Items() []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
for _, item := range registry.Items() {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Contains">Contains</span>
<p>Checks if an enum item with the given value exists in the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Contains(value T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
fmt.Println(registry.Contains(Active))
fmt.Println(registry.Contains(Inactive))
// Output:
// true
// false
}
```
### <span id="Size">Size</span>
<p>Returns the number of enum items in the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Size() int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
fmt.Println("Initial size:", registry.Size())
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
fmt.Println("Size after adding items:", registry.Size())
registry.Remove(Active)
fmt.Println("Size after removing an item:", registry.Size())
// Output:
// Initial size: 0
// Size after adding items: 2
// Size after removing an item: 1
}
```
### <span id="Range">Range</span>
<p>Iterates over all enum items in the registry and applies the given function.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
registry.Range(func(item *Item[Status]) bool {
fmt.Println(item.Name(), item.Value())
return true // continue iteration
})
// Output:
// Active 1
// Inactive 2
}
```
### <span id="SortedItems">SortedItems</span>
<p>Returns a slice of all enum items sorted by the given less function.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Inactive, "Inactive")
item2 := enum.NewItem(Active, "Active")
registry.Add(item1, item2)
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
return i1.value < i2.value
}) {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Filter">Filter</span>
<p>Returns a slice of enum items that satisfy the given predicate function.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
activeItems := registry.Filter(func(item *Item[Status]) bool {
return item.Value() == Active
})
for _, item := range activeItems {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
}
```

View File

@@ -427,7 +427,7 @@ func main() {
func RemoveDir(path string, onDelete ...func(path string)) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
```go
package main
@@ -970,7 +970,7 @@ func main() {
<b>Signature:</b>
```go
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uNep3Tr8fqF)</span></b>
@@ -1023,7 +1023,7 @@ import (
func main() {
const mb = 1024 * 1024
const defaultChunkSizeMB = 100
const defaultChunkSizeMB = 100
// test1.csv file content:
// Lili,22,female
@@ -1087,7 +1087,7 @@ func main() {
numParsers := runtime.NumCPU()
linesCh := make(chan []string, numParsers)
// test1.csv file content:
// Lili,22,female
// Jim,21,male
@@ -1140,8 +1140,8 @@ func main() {
}
fmt.Println(v)
// Output:
// 3.9.10.19
}
```
```

View File

@@ -620,7 +620,7 @@ func main() {
### <span id="Nand">Nand</span>
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to true only if all predicates evaluate to false for the given value.</p>
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to false only if all predicates evaluate to true for the given value.</p>
<b>Signature:</b>
@@ -650,7 +650,7 @@ func main() {
// Output:
// false
// false
// true
// true
}
```

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:
@@ -80,6 +79,8 @@ import (
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
- [ToMarkdownTable](#ToMarkdownTable)
<div STYLE="page-break-after: always;"></div>
@@ -1111,7 +1112,7 @@ Translate the key and value of the map into two slices that are sorted according
<b>Signature:</b>
```go
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
@@ -1275,7 +1276,6 @@ func main() {
}
```
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
<p>Deletes the key-value pair for the given key.</p>
@@ -1838,7 +1838,7 @@ func main() {
fmt.Println(om.Elements())
// Output:
// [{a 1} {b 2} {c 3}]
// [{a 1} {b 2} {c 3}]
}
```
@@ -2294,7 +2294,7 @@ func main() {
<b>Signature:</b>
```go
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/99QjSYSBdiM)</span></b>
@@ -2337,7 +2337,7 @@ func main() {
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
```go
package main
@@ -2364,4 +2364,70 @@ func main() {
// Output:
// [b d]
}
```
### <span id="ToMarkdownTable">ToMarkdownTable</span>
<p>Convert a map slice data to a Markdown table string. It supports custom header display names and column display order.</p>
<b>Signature:</b>
```go
编辑
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
// basic usage: automatically extract column names from data as table headers
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
{"name": "Bob", "age": 30, "salary": 60000},
}
result := maputil.ToMarkdownTable(data, nil, nil)
fmt.Println(result)
// output:
// |name|age|salary|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// custom header name
headerMap := map[string]string{
"name": "n",
"age": "a",
"salary": "s",
}
result = maputil.ToMarkdownTable(data, headerMap, nil)
fmt.Println(result)
// ouput:
// |m|a|s|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// custom column display order
columnOrder := []string{"salary", "name"}
result = maputil.ToMarkdownTable(data, nil, columnOrder)
fmt.Println(result)
// 输出:
// |salary|name|
// |---|---|
// |50000|Alice|
// |60000|Bob|
}
```

View File

@@ -51,7 +51,6 @@ import (
- [BuildUrl](#BuildUrl)
- [AddQueryParams](#AddQueryParams)
<div STYLE="page-break-after: always;"></div>
<link rel="stylesheet" type="text/css" href="/styles/api_doc.css">
@@ -1045,7 +1044,7 @@ func main() {
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
@@ -1085,7 +1084,7 @@ func main() {
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
```go
package main
@@ -1113,4 +1112,4 @@ func main() {
// https://example.com/query?a=foo&a=bar&b=baz
// <nil>
}
```
```

View File

@@ -27,6 +27,7 @@ import (
- [Contain](#Contain)
- [ContainBy](#ContainBy)
- [ContainSubSlice](#ContainSubSlice)
- [ContainAny](#ContainAny)
- [Chunk](#Chunk)
- [Compact](#Compact)
- [Concat](#Concat)
@@ -256,6 +257,43 @@ func main() {
}
```
### <span id="ContainAny">ContainAny</span>
<p>Check if the slice contains any element from the targets slice.</p>
<b>Signature:</b>
```go
func ContainAny[T comparable](slice []T, targets []T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// false
// true
// false
}
```
### <span id="Chunk">Chunk</span>
<p>Creates an slice of elements split into groups the length of `size`.</p>

View File

@@ -31,6 +31,7 @@ import (
- [IsStruct](#IsStruct)
- [Tag](#Tag)
- [Name](#Name)
- [TypeName](#TypeName)
- [Value](#Value)
- [Kind](#Kind)
- [IsEmbedded](#IsEmbedded)
@@ -53,12 +54,13 @@ import (
func New(value any, tagName ...string) *Struct
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/O29l8kk-Z17)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/structs"
)
@@ -68,7 +70,11 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
// to do something
fmt.Println(s.ToMap())
// Output:
// map[name:11] <nil>
}
```
@@ -88,7 +94,7 @@ func (s *Struct) ToMap() (map[string]any, error)
func ToMap(v any) (map[string]any, error)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qQbLySBgerZ)</span></b>
```go
package main
@@ -130,7 +136,7 @@ func main() {
func (s *Struct) Fields() []*Field
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/w3Kk_CyVY7D)</span></b>
```go
package main
@@ -162,10 +168,10 @@ func main() {
<b>Signature:</b>
```go
func (s *Struct) Field(name string) *Field
func (s *Struct) Field(name string) (*Field, bool)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KocZMSYarza)</span></b>
```go
package main
@@ -181,12 +187,14 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
f := s.Field("Name")
f, found := s.Field("Name")
fmt.Println(f.Value())
fmt.Println(found)
// Output:
// 11
// true
}
```
@@ -200,7 +208,7 @@ func main() {
func (s *Struct) IsStruct() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bU2FSdkbK1C)</span></b>
```go
package main
@@ -234,7 +242,7 @@ func main() {
func (f *Field) Tag() *Tag
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DVrx5HvvUJr)</span></b>
```go
package main
@@ -271,7 +279,7 @@ func main() {
func (f *Field) Value() any
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qufYEU2o4Oi)</span></b>
```go
package main
@@ -307,7 +315,7 @@ func main() {
func (f *Field) IsEmbedded() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wV2PrbYm3Ec)</span></b>
```go
package main
@@ -352,7 +360,7 @@ func main() {
func (f *Field) IsExported() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/csK4AXYaNbJ)</span></b>
```go
package main
@@ -391,7 +399,7 @@ func main() {
func (f *Field) IsZero() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/RzqpGISf87r)</span></b>
```go
package main
@@ -430,7 +438,7 @@ func main() {
func (f *Field) Name() string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zfIGlqsatee)</span></b>
```go
package main
@@ -469,7 +477,7 @@ func main() {
func (f *Field) Kind() reflect.Kind
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wg4NlcUNG5o)</span></b>
```go
package main
@@ -498,6 +506,42 @@ func main() {
}
```
### <span id="TypeName">TypeName</span>
<p>Return struct type name.</p>
<b>Signature:</b>
```go
func (s *Struct) TypeName() string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/structs"
)
func main() {
type Parent struct {
Name string
Age int
}
p := &Parent{Age: 11}
s := structs.New(p1)
fmt.Println(s.TypeName())
// Output:
// Parent
}
```
### <span id="IsSlice">IsSlice</span>
<p>Check if the field is a slice</p>
@@ -508,7 +552,7 @@ func main() {
func (f *Field) IsSlice() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/MKz4CgBIUrU)</span></b>
```go
package main
@@ -545,7 +589,7 @@ func main() {
func (f *Field) IsTargetType(targetType reflect.Kind) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Ig75P-agN39)</span></b>
```go
package main

View File

@@ -65,6 +65,8 @@ import (
- [IsAmericanExpress](#IsAmericanExpress)
- [IsUnionPay](#IsUnionPay)
- [IsChinaUnionPay](#IsChinaUnionPay)
- [IsPassport](#IsPassport)
- [IsChineseHMPassport](#IsChineseHMPassport)
<div STYLE="page-break-after: always;"></div>
@@ -826,6 +828,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>
@@ -1532,3 +1571,80 @@ func main() {
// false
}
```
### <span id="IsPassport">IsPassport</span>
<p>Passport validation(using regex).</p>
<b>Signature:</b>
```go
func IsPassport(passport, country string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsPassport("P123456789", "CN")
result2 := validator.IsPassport("123456789", "US")
result3 := validator.IsPassport("AB1234567", "RU")
result4 := validator.IsPassport("123456789", "CN")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
```
### <span id="IsChineseHMPassport">IsChineseHMPassport</span>
<p>Mainland travel permit for Hong Kong, Macao validation (using regex). </p>
<b>Signature:</b>
```go
func IsChineseHMPassport(hmPassport string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsChineseHMPassport("C12345678")
result2 := validator.IsChineseHMPassport("C00000000")
result3 := validator.IsChineseHMPassport("M12345678")
result4 := validator.IsChineseHMPassport("c12345678")
result5 := validator.IsChineseHMPassport("C1234567")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// true
// true
// true
// false
// false
}
```

526
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

298
enum/enum.go Normal file
View File

@@ -0,0 +1,298 @@
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
// Use of this source code is governed by MIT license
// Package enum provides a simple enum implementation.
package enum
import (
"encoding/json"
"fmt"
"reflect"
"sort"
"sync"
)
// Enum defines a common enum interface.
type Enum[T comparable] interface {
Value() T
String() string
Name() string
Valid(checker ...func(T) bool) bool
}
// Item defines a common enum item struct implement Enum interface.
type Item[T comparable] struct {
value T
name string
}
// NewItem creates a new enum item.
func NewItem[T comparable](value T, name string) *Item[T] {
return &Item[T]{value: value, name: name}
}
// Pair represents a value-name pair for creating enum items
type Pair[T comparable] struct {
Value T
Name string
}
// NewItemsFromPairs creates enum items from a slice of Pair structs.
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T] {
if len(pairs) == 0 {
return []*Item[T]{}
}
items := make([]*Item[T], 0, len(pairs))
for _, pair := range pairs {
items = append(items, &Item[T]{value: pair.Value, name: pair.Name})
}
return items
}
// Value returns the value of the enum item.
func (it *Item[T]) Value() T {
return it.value
}
// Name returns the name of the enum item.
func (it *Item[T]) Name() string {
return it.name
}
// String returns the string representation of the enum item.
func (it *Item[T]) String() string {
return it.name
}
// Valid checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
func (it *Item[T]) Valid(checker ...func(T) bool) bool {
if len(checker) > 0 {
return checker[0](it.value) && it.name != ""
}
var zero T
return it.value != zero && it.name != ""
}
// MarshalJSON implements the json.Marshaler interface.
func (it *Item[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"value": it.value,
"name": it.name,
})
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (it *Item[T]) UnmarshalJSON(data []byte) error {
type alias struct {
Value any `json:"value"`
Name string `json:"name"`
}
var temp alias
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
var v T
rv := reflect.TypeOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, ok := temp.Value.(float64)
if !ok {
return fmt.Errorf("invalid type for value, want int family")
}
converted := reflect.ValueOf(int64(val)).Convert(rv)
it.value = converted.Interface().(T)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, ok := temp.Value.(float64)
if !ok {
return fmt.Errorf("invalid type for value, want uint family")
}
converted := reflect.ValueOf(uint64(val)).Convert(rv)
it.value = converted.Interface().(T)
case reflect.Float32, reflect.Float64:
val, ok := temp.Value.(float64)
if !ok {
return fmt.Errorf("invalid type for value, want float family")
}
converted := reflect.ValueOf(val).Convert(rv)
it.value = converted.Interface().(T)
case reflect.String:
val, ok := temp.Value.(string)
if !ok {
return fmt.Errorf("invalid type for value, want string")
}
it.value = any(val).(T)
case reflect.Bool:
val, ok := temp.Value.(bool)
if !ok {
return fmt.Errorf("invalid type for value, want bool")
}
it.value = any(val).(T)
default:
val, ok := temp.Value.(float64)
if ok {
converted := reflect.ValueOf(int64(val)).Convert(rv)
it.value = converted.Interface().(T)
} else {
val2, ok2 := temp.Value.(T)
if !ok2 {
return fmt.Errorf("invalid type for value")
}
it.value = val2
}
}
it.name = temp.Name
return nil
}
// Registry defines a common enum registry struct.
type Registry[T comparable] struct {
mu sync.RWMutex
values map[T]*Item[T]
names map[string]*Item[T]
items []*Item[T]
}
// NewRegistry creates a new enum registry.
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T] {
r := &Registry[T]{
values: make(map[T]*Item[T]),
names: make(map[string]*Item[T]),
items: make([]*Item[T], 0, len(items)),
}
r.Add(items...)
return r
}
// Add adds enum items to the registry.
func (r *Registry[T]) Add(items ...*Item[T]) {
r.mu.Lock()
defer r.mu.Unlock()
for _, item := range items {
if _, exists := r.values[item.value]; exists {
continue
}
if _, exists := r.names[item.name]; exists {
continue
}
r.values[item.value] = item
r.names[item.name] = item
r.items = append(r.items, item)
}
}
// Remove removes an enum item from the registry by its value.
func (r *Registry[T]) Remove(value T) bool {
r.mu.Lock()
defer r.mu.Unlock()
item, ok := r.values[value]
if !ok {
return false
}
delete(r.values, value)
delete(r.names, item.name)
for i, it := range r.items {
if it.value == value {
r.items = append(r.items[:i], r.items[i+1:]...)
break
}
}
return true
}
// Update updates the name of an enum item in the registry by its value.
func (r *Registry[T]) Update(value T, newName string) bool {
r.mu.Lock()
defer r.mu.Unlock()
item, ok := r.values[value]
if !ok {
return false
}
delete(r.names, item.name)
item.name = newName
r.names[newName] = item
return true
}
// GetByValue retrieves an enum item by its value.
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool) {
r.mu.RLock()
defer r.mu.RUnlock()
item, ok := r.values[value]
return item, ok
}
// GetByName retrieves an enum item by its name.
func (r *Registry[T]) GetByName(name string) (*Item[T], bool) {
r.mu.RLock()
defer r.mu.RUnlock()
item, ok := r.names[name]
return item, ok
}
// Items returns a slice of all enum items in the registry.
func (r *Registry[T]) Items() []*Item[T] {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]*Item[T], len(r.items))
copy(result, r.items)
return result
}
// Contains checks if an enum item with the given value exists in the registry.
func (r *Registry[T]) Contains(value T) bool {
_, ok := r.GetByValue(value)
return ok
}
// Size returns the number of enum items in the registry.
func (r *Registry[T]) Size() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.items)
}
// Range iterates over all enum items in the registry and applies the given function.
func (r *Registry[T]) Range(fn func(*Item[T]) bool) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, item := range r.items {
if !fn(item) {
break
}
}
}
// SortedItems returns a slice of all enum items sorted by the given less function.
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T] {
items := r.Items()
sort.Slice(items, func(i, j int) bool {
return less(items[i], items[j])
})
return items
}
// Filter returns a slice of enum items that satisfy the given predicate function.
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T] {
r.mu.RLock()
defer r.mu.RUnlock()
var result []*Item[T]
for _, item := range r.items {
if predicate(item) {
result = append(result, item)
}
}
return result
}

208
enum/enum_example_test.go Normal file
View File

@@ -0,0 +1,208 @@
package enum
import "fmt"
func ExampleNewItem() {
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
fmt.Println(item1.Name(), item1.Value())
fmt.Println(item2.Name(), item2.Value())
// Output:
// Active 1
// Inactive 2
}
func ExampleNewItemsFromPairs() {
items := NewItemsFromPairs(
Pair[Status]{Value: Active, Name: "Active"},
Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
func ExampleItem_Valid() {
item := NewItem(Active, "Active")
fmt.Println(item.Valid())
invalidItem := NewItem(Unknown, "")
fmt.Println(invalidItem.Valid())
// Output:
// true
// false
}
func ExampleItem_MarshalJSON() {
item := NewItem(Active, "Active")
data, _ := item.MarshalJSON()
fmt.Println(string(data))
var unmarshaledItem Item[Status]
_ = unmarshaledItem.UnmarshalJSON(data)
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
// Output:
// {"name":"Active","value":1}
// Active 1
}
func ExampleRegistry_Add() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
func ExampleRegistry_Remove() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
fmt.Println("Size before removal:", registry.Size())
removed := registry.Remove(Active)
fmt.Println("Removed:", removed)
fmt.Println("Size after removal:", registry.Size())
// Output:
// Size before removal: 1
// Removed: true
// Size after removal: 0
}
func ExampleRegistry_Update() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
fmt.Println("Updated:", updated)
if item, found := registry.GetByValue(Active); found {
fmt.Println("New name:", item.Name())
}
// Output:
// Updated: true
// New name: Activated
}
func ExampleRegistry_Items() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
for _, item := range registry.Items() {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
func ExampleRegistry_Contains() {
registry := NewRegistry[Status]()
item := NewItem(Active, "Active")
registry.Add(item)
fmt.Println(registry.Contains(Active))
fmt.Println(registry.Contains(Inactive))
// Output:
// true
// false
}
func ExampleRegistry_Size() {
registry := NewRegistry[Status]()
fmt.Println("Initial size:", registry.Size())
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
fmt.Println("Size after adding items:", registry.Size())
registry.Remove(Active)
fmt.Println("Size after removing an item:", registry.Size())
// Output:
// Initial size: 0
// Size after adding items: 2
// Size after removing an item: 1
}
func ExampleRegistry_Range() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
registry.Range(func(item *Item[Status]) bool {
fmt.Println(item.Name(), item.Value())
return true // continue iteration
})
// Output:
// Active 1
// Inactive 2
}
func ExampleRegistry_SortedItems() {
registry := NewRegistry[Status]()
item1 := NewItem(Inactive, "Inactive")
item2 := NewItem(Active, "Active")
registry.Add(item1, item2)
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
return i1.value < i2.value
}) {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
func ExampleRegistry_Filter() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
activeItems := registry.Filter(func(item *Item[Status]) bool {
return item.Value() == Active
})
for _, item := range activeItems {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
}

184
enum/enum_test.go Normal file
View File

@@ -0,0 +1,184 @@
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
// Use of this source code is governed by MIT license
package enum
import (
"testing"
"github.com/duke-git/lancet/v2/internal"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func TestNewItemsFromPairs(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestNewItemsFromPairs")
items := NewItemsFromPairs(
Pair[Status]{Value: Active, Name: "Active"},
Pair[Status]{Value: Inactive, Name: "Inactive"},
)
assert.Equal(2, len(items))
assert.Equal(Active, items[0].Value())
assert.Equal("Active", items[0].Name())
assert.Equal(Inactive, items[1].Value())
assert.Equal("Inactive", items[1].Name())
}
func TestItem_Valid(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestItem_Valid")
item := NewItem(Active, "Active")
assert.Equal(true, item.Valid())
invalidItem := NewItem(Unknown, "")
assert.Equal(false, invalidItem.Valid())
}
func TestItem_MarshalJSON(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestItem_MarshalJSON")
item := NewItem(Active, "Active")
data, err := item.MarshalJSON()
assert.IsNil(err)
assert.Equal("{\"name\":\"Active\",\"value\":1}", string(data))
var unmarshaledItem Item[Status]
err = unmarshaledItem.UnmarshalJSON(data)
assert.IsNil(err)
assert.Equal(item.Value(), unmarshaledItem.Value())
assert.Equal(item.Name(), unmarshaledItem.Name())
}
func TestRegistry_AddAndGet(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_AddAndGet")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
assert.Equal(2, registry.Size())
item, ok := registry.GetByValue(Active)
assert.Equal(true, ok)
assert.Equal("Active", item.Name())
item, ok = registry.GetByName("Inactive")
assert.Equal(true, ok)
assert.Equal(Inactive, item.Value())
}
func TestRegistry_Remove(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Remove")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
assert.Equal(2, registry.Size())
removed := registry.Remove(Active)
assert.Equal(true, removed)
assert.Equal(1, registry.Size())
_, ok := registry.GetByValue(Active)
assert.Equal(false, ok)
}
func TestRegistry_Update(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Update")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
assert.Equal(true, updated)
item, ok := registry.GetByValue(Active)
assert.Equal(true, ok)
assert.Equal("Activated", item.Name())
}
func TestRegistry_Contains(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Contains")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
assert.Equal(true, registry.Contains(Active))
assert.Equal(false, registry.Contains(Inactive))
}
func TestRegistry_Range(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Range")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
var values []Status
registry.Range(func(item *Item[Status]) bool {
values = append(values, item.Value())
return true
})
assert.Equal(2, len(values))
assert.Equal(Active, values[0])
assert.Equal(Inactive, values[1])
}
func TestRegistry_SortedItems(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_SortedItems")
registry := NewRegistry[Status]()
item1 := NewItem(Inactive, "Inactive")
item2 := NewItem(Active, "Active")
registry.Add(item1, item2)
sortedItems := registry.SortedItems(func(i1, i2 *Item[Status]) bool {
return i1.value < i2.value
})
assert.Equal(2, len(sortedItems))
assert.Equal(Active, sortedItems[0].Value())
assert.Equal(Inactive, sortedItems[1].Value())
}
func TestRegistry_Filter(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Filter")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
filteredItems := registry.Filter(func(item *Item[Status]) bool {
return item.Value() == Active
})
assert.Equal(1, len(filteredItems))
assert.Equal(Active, filteredItems[0].Value())
}

View File

@@ -21,7 +21,7 @@ type EventBus[T any] struct {
// listeners map[string][]*EventListener[T]
listeners sync.Map
mu sync.RWMutex
errorHandler func(err error)
errorHandler func(topic string, err error)
}
// EventListener is the struct that holds the listener function and its priority.
@@ -117,7 +117,7 @@ func (eb *EventBus[T]) Publish(event Event[T]) {
func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event[T]) {
defer func() {
if r := recover(); r != nil && eb.errorHandler != nil {
eb.errorHandler(fmt.Errorf("%v", r))
eb.errorHandler(event.Topic, fmt.Errorf("%v", r))
}
}()
@@ -126,7 +126,7 @@ func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event
// SetErrorHandler sets the error handler function.
// Play: https://go.dev/play/p/gmB0gnFe5mc
func (eb *EventBus[T]) SetErrorHandler(handler func(err error)) {
func (eb *EventBus[T]) SetErrorHandler(handler func(topic string, err error)) {
eb.errorHandler = handler
}

View File

@@ -2,6 +2,7 @@ package eventbus
import (
"fmt"
"sort"
"sync"
"time"
)
@@ -188,8 +189,8 @@ func ExampleEventBus_GetListenersCount() {
func ExampleEventBus_SetErrorHandler() {
eb := NewEventBus[int]()
eb.SetErrorHandler(func(err error) {
fmt.Println(err)
eb.SetErrorHandler(func(topic string, err error) {
fmt.Println(topic, err)
})
eb.Subscribe("event1", func(eventData int) {
@@ -199,7 +200,7 @@ func ExampleEventBus_SetErrorHandler() {
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
// Output:
// error
// event1 error
}
func ExampleEventBus_GetAllListenersCount() {
@@ -224,6 +225,7 @@ func ExampleEventBus_GetEvents() {
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
events := eb.GetEvents()
sort.Strings(events)
for _, event := range events {
fmt.Println(event)

View File

@@ -1,6 +1,7 @@
package eventbus
import (
"sort"
"sync"
"testing"
"time"
@@ -113,7 +114,8 @@ func TestEventBus_ErrorHandler(t *testing.T) {
eb := NewEventBus[string]()
eb.SetErrorHandler(func(err error) {
eb.SetErrorHandler(func(topic string, err error) {
assert.Equal("event1", topic)
assert.Equal("error", err.Error())
})
@@ -213,7 +215,8 @@ func TestEventBus_GetEvents(t *testing.T) {
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
events := eb.GetEvents()
sort.Strings(events)
assert.Equal(2, len(events))
assert.EqualValues([]string{"event1", "event2"}, events)
assert.Equal([]string{"event1", "event2"}, events)
}

View File

@@ -190,7 +190,7 @@ func RemoveFile(path string, onDelete ...func(path string)) error {
}
// RemoveDir remove the path directory.
// Play: todo
// Play: https://go.dev/play/p/Oa6KnPek2uy
func RemoveDir(path string, onDelete ...func(path string)) error {
info, err := os.Stat(path)
if err != nil {

View File

@@ -18,7 +18,7 @@ func And[T any](predicates ...func(T) bool) func(T) bool {
}
// Nand returns a composed predicate that represents the logical NAND of a list of predicates.
// It evaluates to true only if all predicates evaluate to false for the given value.
// It evaluates to false only if all predicates evaluate to true for the given value.
// Play: https://go.dev/play/p/Rb-FdNGpgSO
func Nand[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
@@ -26,11 +26,11 @@ func Nand[T any](predicates ...func(T) bool) func(T) bool {
}
return func(value T) bool {
for _, predicate := range predicates {
if predicate(value) {
return false // Short-circuit on the first true predicate
if !predicate(value) {
return true // Short-circuit on the first false predicate
}
}
return true // True if all predicates are false
return false // False if all predicates are true
}
}

View File

@@ -65,7 +65,7 @@ func ExampleNand() {
// Output:
// false
// false
// true
// true
}

View File

@@ -65,7 +65,7 @@ func TestPredicatesNandPure(t *testing.T) {
)
assert.ShouldBeFalse(isNumericAndLength5("12345"))
assert.ShouldBeFalse(isNumericAndLength5("1234"))
assert.ShouldBeTrue(isNumericAndLength5("1234"))
assert.ShouldBeTrue(isNumericAndLength5("abcdef"))
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"golang.org/x/exp/constraints"
@@ -668,7 +669,7 @@ func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
}
// FindValuesBy returns a slice of values from the map that satisfy the given predicate function.
// Play: todo
// Play: https://go.dev/play/p/bvNwNBZDm6v
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V {
result := make([]V, 0)
@@ -680,3 +681,150 @@ func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V)
return result
}
// ToMarkdownTable converts a slice of maps to a Markdown table.
// Play: ttps://go.dev/play/p/todo
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string {
if len(data) == 0 {
return "| |\n|---|\n"
}
var headers []string
// 如果提供了columnOrder则按指定顺序排列
if len(columnOrder) > 0 {
headers = make([]string, len(columnOrder))
copy(headers, columnOrder)
} else {
// 否则按自然顺序提取headers
seen := make(map[string]bool)
for _, row := range data {
for k := range row {
if !seen[k] {
seen[k] = true
headers = append(headers, k)
}
}
}
}
var builder strings.Builder
// Header row - 使用映射的中文标题
builder.WriteString("| ")
for i, h := range headers {
// 如果有映射则使用中文标题,否则使用原字段名
displayHeader := h
if headerMap != nil && headerMap[h] != "" {
displayHeader = headerMap[h]
}
builder.WriteString(displayHeader)
if i < len(headers)-1 {
builder.WriteString(" | ")
}
}
builder.WriteString(" |\n")
// Separator
builder.WriteString("|")
for i := range headers {
if i > 0 {
builder.WriteString("|")
}
builder.WriteString("---")
}
builder.WriteString("|\n")
// Data rows
for _, row := range data {
builder.WriteString("| ")
for i, h := range headers {
val, exists := row[h]
var cell string
if !exists {
cell = ""
} else {
cell = formatValue(val)
}
builder.WriteString(cell)
if i < len(headers)-1 {
builder.WriteString(" | ")
}
}
builder.WriteString(" |\n")
}
return builder.String()
}
// formatValue formats any value for display in Markdown table
func formatValue(v interface{}) string {
switch val := v.(type) {
case int:
return commaInt64(int64(val))
case int8:
return commaInt64(int64(val))
case int16:
return commaInt64(int64(val))
case int32:
return commaInt64(int64(val))
case int64:
return commaInt64(val)
case uint:
return commaUint64(uint64(val))
case uint8:
return commaUint64(uint64(val))
case uint16:
return commaUint64(uint64(val))
case uint32:
return commaUint64(uint64(val))
case uint64:
return commaUint64(val)
case float32:
return fmt.Sprintf("%.2f", val)
case float64:
return fmt.Sprintf("%.2f", val)
case string:
return val
case bool:
return fmt.Sprintf("%t", val)
case nil:
return ""
default:
return fmt.Sprintf("%v", val)
}
}
// commaInt64 adds comma separators to int64
func commaInt64(n int64) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = -n
}
s := strconv.FormatInt(n, 10)
return addCommas(s)
}
// commaUint64 adds comma separators to uint64
func commaUint64(n uint64) string {
if n == 0 {
return "0"
}
s := strconv.FormatUint(n, 10)
return addCommas(s)
}
// addCommas inserts commas every 3 digits
func addCommas(s string) string {
var result strings.Builder
for i, c := range s {
if i > 0 && (len(s)-i)%3 == 0 {
result.WriteRune(',')
}
result.WriteRune(c)
}
return result.String()
}

View File

@@ -842,6 +842,10 @@ func ExampleFindValuesBy() {
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:

View File

@@ -1,6 +1,7 @@
package maputil
import (
"fmt"
"math/cmplx"
"sort"
"strconv"
@@ -926,3 +927,46 @@ func TestFindValuesBy(t *testing.T) {
assert.Equal(tt.expected, result)
}
}
func TestToMarkdownTable(t *testing.T) {
// 测试空数据
emptyResult := ToMarkdownTable([]map[string]interface{}{}, nil, nil)
expectedEmpty := "| |\n|---|\n"
if emptyResult != expectedEmpty {
t.Errorf("Expected empty table, got: %s", emptyResult)
}
// 测试基本数据
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
{"name": "Bob", "age": 30, "salary": 60000},
}
result := ToMarkdownTable(data, nil, nil)
fmt.Printf("%s", result)
// 验证结果包含预期的表头和数据行
}
// 测试自定义列顺序
func TestToMarkdownTableWithColumnOrder(t *testing.T) {
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
}
columnOrder := []string{"salary", "name", "age"}
result := ToMarkdownTable(data, nil, columnOrder)
fmt.Printf("%s", result)
// 验证列顺序是否正确
}
// 测试自定义表头
func TestToMarkdownTableWithHeaderMap(t *testing.T) {
data := []map[string]interface{}{
{"name": "Alice", "age": 25},
}
headerMap := map[string]string{
"name": "姓名",
"age": "年龄",
}
result := ToMarkdownTable(data, headerMap, nil)
fmt.Printf("%s", result)
// 验证中文表头是否正确显示
}

View File

@@ -238,6 +238,9 @@ func Sum[T constraints.Integer | constraints.Float](numbers ...T) T {
// Average return average value of numbers.
// Play: https://go.dev/play/p/Vv7LBwER-pz
func Average[T constraints.Integer | constraints.Float](numbers ...T) float64 {
if len(numbers) == 0 {
return 0
}
var sum float64
for _, num := range numbers {
sum += float64(num)

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

@@ -309,7 +309,7 @@ func IsTelnetConnected(host string, port string) bool {
}
// BuildUrl builds a URL from the given params.
// Play: todo
// Play: https://go.dev/play/p/JLXl1hZK7l4
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error) {
if err := validateScheme(scheme); err != nil {
return "", err
@@ -365,13 +365,13 @@ func validateScheme(scheme string) error {
return nil
}
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])(\.[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])*$`)
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)(\.[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)+$`)
var pathRegex = regexp.MustCompile(`^\/([a-zA-Z0-9%_-]+(?:\/[a-zA-Z0-9%_-]+)*)$`)
var alphaNumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
// AddQueryParams adds query parameters to the given URL.
// Play: todoå
// Play: https://go.dev/play/p/JLXl1hZK7l4
func AddQueryParams(urlStr string, params map[string][]string) (string, error) {
parsedUrl, err := url.Parse(urlStr)
if err != nil {

View File

@@ -196,6 +196,14 @@ func TestBuildUrl(t *testing.T) {
want: "https://www.test.com/path%20with%20spaces",
wantErr: false,
},
{
scheme: "https",
host: "my.api.edu.cn",
path: "/api",
query: nil,
want: "https://my.api.edu.cn/api",
wantErr: false,
},
}
for _, tt := range tests {

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,19 +59,42 @@ 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
}
// ContainAny check if the slice contains any element from the targets slice.
// Play: https://go.dev/play/p/4xoxhc9XSSw
func ContainAny[T comparable](slice []T, targets []T) bool {
if len(targets) == 0 {
return false
}
sliceMap := make(map[T]struct{}, len(slice))
for _, item := range slice {
sliceMap[item] = struct{}{}
}
for _, target := range targets {
if _, exists := sliceMap[target]; exists {
return true
}
}
return false
}
// Chunk creates a slice of elements split into groups the length of size.
// Play: https://go.dev/play/p/b4Pou5j2L_C
func Chunk[T any](slice []T, size int) [][]T {
@@ -81,14 +104,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 +133,7 @@ func Compact[T comparable](slice []T) []T {
result = append(result, v)
}
}
return result[:len(result):len(result)]
}
@@ -133,8 +161,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 +184,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 +206,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)
}
}
@@ -423,19 +473,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))
@@ -477,19 +528,17 @@ func flattenRecursive(value reflect.Value, result reflect.Value) reflect.Value {
}
// ForEach iterates over elements of slice and invokes function for each element.
// Play: https://go.dev/play/p/DrPaa4YsHRF
func ForEach[T any](slice []T, iteratee func(index int, item T)) {
for i := 0; i < len(slice); i++ {
iteratee(i, slice[i])
for idx, elem := range slice {
iteratee(idx, elem)
}
}
// ForEachWithBreak iterates over elements of slice and invokes function for each element,
// when iteratee return false, will break the for each loop.
// Play: https://go.dev/play/p/qScs39f3D9W
// when function return false, will break the for each loop.
func ForEachWithBreak[T any](slice []T, iteratee func(index int, item T) bool) {
for i := 0; i < len(slice); i++ {
if !iteratee(i, slice[i]) {
for idx, elem := range slice {
if !iteratee(idx, elem) {
break
}
}
@@ -607,7 +656,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)
@@ -624,7 +673,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)
@@ -642,7 +691,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)
@@ -662,15 +711,23 @@ 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
result := append([]T(nil), slice...)
if index < 0 || index >= len(slice) {
return result[:len(slice)-1]
}
result := make([]T, len(slice)-1)
copy(result, slice[:index])
copy(result[index:], slice[index+1:])
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).
@@ -699,7 +756,8 @@ func Drop[T any](slice []T, n int) []T {
}
if n <= 0 {
return slice
result := make([]T, 0, size)
return append(result, slice...)
}
result := make([]T, 0, size-n)
@@ -717,7 +775,8 @@ func DropRight[T any](slice []T, n int) []T {
}
if n <= 0 {
return slice
result := make([]T, 0, size)
return append(result, slice...)
}
result := make([]T, 0, size-n)
@@ -763,49 +822,59 @@ func InsertAt[T any](slice []T, index int, value any) []T {
size := len(slice)
if index < 0 || index > size {
return slice
result := make([]T, size)
copy(result, slice)
return result
}
if v, ok := value.(T); ok {
slice = append(slice[:index], append([]T{v}, slice[index:]...)...)
return slice
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:
result := make([]T, size)
copy(result, slice)
return result
}
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)
result := make([]T, len(slice))
copy(result, slice)
if index < 0 || index >= size {
return slice
if index >= 0 && index < len(slice) {
result[index] = value
}
slice = append(slice[:index], append([]T{value}, slice[index+1:]...)...)
return slice
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
@@ -815,18 +884,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
@@ -836,19 +906,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)
}
}
@@ -974,7 +1045,9 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
return []T{}
}
if len(slices) == 1 {
return Unique(slices[0])
result := make([]T, len(slices[0]))
copy(result, slices[0])
return Unique(result)
}
result := make([]T, 0)
@@ -995,6 +1068,7 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
}
// Reverse return slice of element order is reversed to the given slice.
// Reverse modifies the slice in place.
// Play: https://go.dev/play/p/8uI8f1lwNrQ
func Reverse[T any](slice []T) {
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
@@ -1002,7 +1076,8 @@ func Reverse[T any](slice []T) {
}
}
// ReverseCopy return a new slice of element order is reversed to the given slice.
// ReverseCopy return a new slice of element where the order is reversed to the given
// slice.
// Play: https://go.dev/play/p/c9arEaP7Cg-
func ReverseCopy[T any](slice []T) []T {
result := make([]T, len(slice))
@@ -1020,7 +1095,7 @@ func Shuffle[T any](slice []T) []T {
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(slice), func(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
swap(slice, i, j)
})
return slice
@@ -1193,11 +1268,12 @@ func SortByField[T any](slice []T, field string, sortType ...string) error {
// Without creates a slice excluding all given items.
// Play: https://go.dev/play/p/bwhEXEypThg
func Without[T comparable](slice []T, items ...T) []T {
result := make([]T, 0, len(slice))
if len(items) == 0 || len(slice) == 0 {
return slice
return append(result, slice...)
}
result := make([]T, 0, len(slice))
for _, v := range slice {
if !Contain(items, v) {
result = append(result, v)
@@ -1418,36 +1494,28 @@ func Random[T any](slice []T) (val T, idx int) {
return slice[idx], idx
}
// RightPadding adds padding to the right end of a slice.
// RightPadding returns a copy of the slice padding the given value to the right end of a slice.
// If paddingLength is zero or less, the function returns a copy of the slice.
// Play: https://go.dev/play/p/0_2rlLEMBXL
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
if paddingLength == 0 {
return slice
suffix := []T{}
if paddingLength > 0 {
suffix = repeat([]T{paddingValue}, paddingLength)
}
for i := 0; i < paddingLength; i++ {
slice = append(slice, paddingValue)
}
return slice
padded := concat(slice, suffix)
return padded
}
// LeftPadding adds padding to the left begin of a slice.
// LeftPadding returns a copy of the slice padding the given value to the left begin of a slice.
// If paddingLength is zero or less, the function returns a copy of the slice.
// Play: https://go.dev/play/p/jlQVoelLl2k
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
if paddingLength == 0 {
return slice
prefix := []T{}
if paddingLength > 0 {
prefix = repeat([]T{paddingValue}, paddingLength)
}
paddedSlice := make([]T, len(slice)+paddingLength)
i := 0
for ; i < paddingLength; i++ {
paddedSlice[i] = paddingValue
}
for j := 0; j < len(slice); j++ {
paddedSlice[i] = slice[j]
i++
}
return paddedSlice
padded := concat(prefix, slice)
return padded
}
// Frequency counts the frequency of each element in the slice.

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

@@ -58,6 +58,24 @@ func ExampleContainSubSlice() {
// false
}
func ExampleContainAny() {
result1 := ContainAny([]string{"a", "b", "c"}, []string{"a"})
result2 := ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
result3 := ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
result4 := ContainAny([]string{"a", "b", "c"}, []string{})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// false
// true
// false
}
func ExampleChunk() {
arr := []string{"a", "b", "c", "d", "e"}
@@ -831,7 +849,7 @@ func ExampleUniqueByComparator() {
})
caseInsensitiveStrings := UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
return strings.ToLower(item) == strings.ToLower(other)
return strings.EqualFold(item, other)
})
fmt.Println(uniqueNums)

View File

@@ -2,6 +2,7 @@ package slice
import (
"fmt"
"math/bits"
"reflect"
"golang.org/x/exp/constraints"
@@ -96,3 +97,71 @@ func partitionAnySlice[T any](slice []T, lowIndex, highIndex int, less func(a, b
func swap[T any](slice []T, i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
// `repeat` returns a new slice that repeats the provided slice the given number of
// times. The result has length and capacity (len(x) * count). The result is never nil.
// Repeat panics if count is negative or if the result of (len(x) * count) overflows.
//
// repeat has been provided in the standard lib within the package `slices` under the
// name Repeat since GO version 1.21 onwards. As lancet commits to compatibility with GO
// 1.18 onwards, we implement the functionality as an internal function.
func repeat[S ~[]E, E any](x S, count int) S {
if count < 0 {
panic("count cannot be negative")
}
const maxInt = ^uint(0) >> 1
hi, lo := bits.Mul(uint(len(x)), uint(count))
if hi > 0 || lo > maxInt {
panic("the result of (len(x) * count) overflows")
}
newslice := make(S, int(lo)) // lo = len(x) * count
n := copy(newslice, x)
for n < len(newslice) {
n += copy(newslice[n:], newslice[:n])
}
return newslice
}
// concat returns a new slice concatenating the passed in slices.
//
// concat has been provided in the standard lib within the package `slices` under the
// name Concat since GO version 1.21 onwards. As lancet commits to compatibility with GO
// 1.18 onwards, we implement the functionality as an internal function.
func concat[S ~[]E, E any](slices ...S) S {
size := 0
for _, s := range slices {
size += len(s)
if size < 0 {
panic("len out of range")
}
}
// Use Grow, not make, to round up to the size class:
// the extra space is otherwise unused and helps
// callers that append a few elements to the result.
newslice := grow[S](nil, size)
for _, s := range slices {
newslice = append(newslice, s...)
}
return newslice
}
// grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After grow(n), at least n elements can be appended
// to the slice without another allocation. If n is negative or too large to
// allocate the memory, grow panics.
//
// grow has been provided in the standard lib within the package `slices` under the
// name Grow since GO version 1.21 onwards. As lancet commits to compatibility with GO
// 1.18 onwards, we implement the functionality as an internal function.
func grow[S ~[]E, E any](s S, n int) S {
if n < 0 {
panic("cannot be negative")
}
if n -= cap(s) - len(s); n > 0 {
// This expression allocates only once.
s = append(s[:cap(s)], make([]E, n)...)[:len(s)]
}
return s
}

View File

@@ -89,6 +89,46 @@ func TestContainSubSlice(t *testing.T) {
}
}
func TestContainAny(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestContainAny")
tests := []struct {
slice []string
targets []string
want bool
}{
{[]string{"a", "b", "c"}, []string{"a"}, true},
{[]string{"a", "b", "c"}, []string{"a", "b"}, true},
{[]string{"a", "b", "c"}, []string{"d", "e"}, false},
{[]string{"a", "b", "c"}, []string{"d", "a"}, true},
{[]string{"a", "b", "c"}, []string{}, false},
{[]string{}, []string{"a"}, false},
{[]string{}, []string{}, false},
{[]string{"a", "b", "c"}, []string{"c", "d", "e"}, true},
}
for _, tt := range tests {
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
}
intTests := []struct {
slice []int
targets []int
want bool
}{
{[]int{1, 2, 3, 4, 5}, []int{3}, true},
{[]int{1, 2, 3, 4, 5}, []int{6, 7}, false},
{[]int{1, 2, 3, 4, 5}, []int{5, 6, 7}, true},
{[]int{1, 2, 3, 4, 5}, []int{}, false},
}
for _, tt := range intTests {
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
}
}
func TestChunk(t *testing.T) {
t.Parallel()
@@ -1008,7 +1048,7 @@ func TestUniqueByComparator(t *testing.T) {
t.Run("case-insensitive string comparison", func(t *testing.T) {
stringSlice := []string{"apple", "banana", "Apple", "cherry", "Banana", "date"}
caseInsensitiveComparator := func(item, other string) bool {
return strings.ToLower(item) == strings.ToLower(other)
return strings.EqualFold(item, other)
}
result := UniqueByComparator(stringSlice, caseInsensitiveComparator)
@@ -1756,6 +1796,20 @@ func TestRightPaddingAndLeftPadding(t *testing.T) {
padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3)
assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded)
// Test with negative padding length
paddedNegative := LeftPadding(RightPadding(nums, 0, -3), 0, -3)
assert.Equal([]int{1, 2, 3, 4, 5}, paddedNegative)
// Test with empty slice
empty := []int{}
paddedEmpty := LeftPadding(RightPadding(empty, 0, 3), 0, 3)
assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedEmpty)
// Test with nil
nilSlice := []int(nil)
paddedNil := LeftPadding(RightPadding(nilSlice, 0, 3), 0, 3)
assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedNil)
}
func TestUniqueByConcurrent(t *testing.T) {

View File

@@ -26,26 +26,31 @@ func newField(v reflect.Value, f reflect.StructField, tagName string) *Field {
}
// Tag returns the value that the key in the tag string.
// Play: https://go.dev/play/p/DVrx5HvvUJr
func (f *Field) Tag() *Tag {
return f.tag
}
// Value returns the underlying value of the field.
// Play: https://go.dev/play/p/qufYEU2o4Oi
func (f *Field) Value() any {
return f.rvalue.Interface()
}
// IsEmbedded returns true if the given field is an embedded field.
// Play: https://go.dev/play/p/wV2PrbYm3Ec
func (f *Field) IsEmbedded() bool {
return len(f.field.Index) > 1
}
// IsExported returns true if the given field is exported.
// Play: https://go.dev/play/p/csK4AXYaNbJ
func (f *Field) IsExported() bool {
return f.field.IsExported()
}
// IsZero returns true if the given field is zero value.
// Play: https://go.dev/play/p/RzqpGISf87r
func (f *Field) IsZero() bool {
z := reflect.Zero(f.rvalue.Type()).Interface()
v := f.Value()
@@ -63,22 +68,26 @@ func (f *Field) IsNil() bool {
}
// Name returns the name of the given field
// Play: https://go.dev/play/p/zfIGlqsatee
func (f *Field) Name() string {
return f.field.Name
}
// Kind returns the field's kind
// Play: https://go.dev/play/p/wg4NlcUNG5o
func (f *Field) Kind() reflect.Kind {
return f.rvalue.Kind()
}
// IsSlice check if a struct field type is slice or not
// Play: https://go.dev/play/p/MKz4CgBIUrU
func (f *Field) IsSlice() bool {
k := f.rvalue.Kind()
return k == reflect.Slice
}
// IsTargetType check if a struct field type is target type or not
// Play: https://go.dev/play/p/Ig75P-agN39
func (f *Field) IsTargetType(targetType reflect.Kind) bool {
return f.rvalue.Kind() == targetType
}

View File

@@ -20,6 +20,7 @@ type Struct struct {
}
// New returns a new *Struct
// Play: https://go.dev/play/p/O29l8kk-Z17
func New(value any, tagName ...string) *Struct {
value = pointer.ExtractPointer(value)
v := reflect.ValueOf(value)
@@ -60,6 +61,7 @@ func New(value any, tagName ...string) *Struct {
// Name string `json:"myName"`
//
// ToMap convert the exported fields of a struct to map.
// Play: https://go.dev/play/p/qQbLySBgerZ
func (s *Struct) ToMap() (map[string]any, error) {
if !s.IsStruct() {
return nil, fmt.Errorf("invalid struct %v", s)
@@ -87,6 +89,7 @@ func (s *Struct) ToMap() (map[string]any, error) {
}
// Fields returns all the struct fields within a slice
// Play: https://go.dev/play/p/w3Kk_CyVY7D
func (s *Struct) Fields() []*Field {
fieldNum := s.rvalue.NumField()
fields := make([]*Field, 0, fieldNum)
@@ -100,6 +103,7 @@ func (s *Struct) Fields() []*Field {
}
// Field returns a Field if the given field name was found
// Play: https://go.dev/play/p/KocZMSYarza
func (s *Struct) Field(name string) (*Field, bool) {
f, ok := s.rtype.FieldByName(name)
if !ok {
@@ -109,6 +113,7 @@ func (s *Struct) Field(name string) (*Field, bool) {
}
// IsStruct returns true if the given rvalue is a struct
// Play: https://go.dev/play/p/bU2FSdkbK1C
func (s *Struct) IsStruct() bool {
k := s.rvalue.Kind()
if k == reflect.Invalid {
@@ -119,6 +124,13 @@ func (s *Struct) IsStruct() bool {
// ToMap convert struct to map, only convert exported struct field
// map key is specified same as struct field tag `json` value.
// Play: https://go.dev/play/p/qQbLySBgerZ
func ToMap(v any) (map[string]any, error) {
return New(v).ToMap()
}
// TypeName return struct type name
// Play: https://go.dev/play/p/todo
func (s *Struct) TypeName() string {
return s.rtype.Name()
}

View File

@@ -177,3 +177,21 @@ func TestStruct_IsStruct(t *testing.T) {
assert.Equal(true, s1.IsStruct())
assert.Equal(false, s2.IsStruct())
}
func TestStruct_TypeName(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestStruct_TypeName")
type Test1 struct{}
t1 := &Test1{}
s1 := New(t1)
assert.Equal("Test1", s1.TypeName())
type Test2 struct{}
t2 := Test2{}
s2 := New(t2)
assert.Equal("Test2", s2.TypeName())
}

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.

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

@@ -518,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) {

View File

@@ -19,30 +19,45 @@ import (
)
var (
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^(\d{17})([0-9]|X|x)$`)
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
unionPay *regexp.Regexp = regexp.MustCompile("^62[0-5]\\d{13,16}$")
chinaUnionPay *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
// dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*(?:xn--[a-zA-Z0-9\-]{1,59}|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)$`)
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`)
)
var passportMatcher = map[string]*regexp.Regexp{
"CN": regexp.MustCompile(`^P\d{9}$`),
"US": regexp.MustCompile(`^\d{9}$`),
"GB": regexp.MustCompile(`^[A-Z0-9]{9}$`),
"RU": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
"DE": regexp.MustCompile(`^\d{9}$`),
"FR": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
"JP": regexp.MustCompile(`^\d{8}$`),
"IT": regexp.MustCompile(`^\d{8}$`),
"AU": regexp.MustCompile(`^[A-Z]{1}\d{8}$`),
"BR": regexp.MustCompile(`^\d{9}$`),
"IN": regexp.MustCompile(`^[A-Z]{1,2}\d{7}$`),
"HK": regexp.MustCompile(`^M\d{8}$`),
"MO": regexp.MustCompile(`^[A-Z]\d{8}$`),
}
var (
// Identity card formula
factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
@@ -182,8 +197,8 @@ func IsJSON(str string) bool {
return json.Unmarshal([]byte(str), &js) == nil
}
// IsAlphaNumericStr check if the string is alphanumeric.
// Play: todo
// IsAlphaNumeric check if the string is alphanumeric.
// Play: https://go.dev/play/p/RHeESLrLg9c
func IsAlphaNumeric(s string) bool {
return alphaNumericMatcher.MatchString(s)
}
@@ -258,21 +273,45 @@ func IsPort(str string) bool {
// IsUrl check if the string is url.
// Play: https://go.dev/play/p/pbJGa7F98Ka
func IsUrl(str string) bool {
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
return false
}
u, err := url.Parse(str)
if err != nil {
return false
}
if strings.HasPrefix(u.Host, ".") {
return false
}
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
if str == "" {
return false
}
return urlMatcher.MatchString(str)
u, err := url.Parse(str)
if err != nil || u.Scheme == "" || u.Host == "" {
return false
}
allowedSchemes := map[string]struct{}{
"http": {},
"https": {},
"ftp": {},
"ws": {},
"wss": {},
"file": {},
"mailto": {},
"data": {},
}
if _, ok := allowedSchemes[u.Scheme]; !ok {
return false
}
if u.Scheme == "file" || u.Scheme == "mailto" || u.Scheme == "data" {
return true
}
host := u.Hostname()
if !strings.Contains(host, ".") || strings.HasSuffix(host, ".") {
return false
}
// domainRegexp := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$`)
if !dnsMatcher.MatchString(host) {
return false
}
return true
}
// IsDns check if the string is dns.
@@ -286,8 +325,6 @@ func IsDns(dns string) bool {
func IsEmail(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
// return emailMatcher.MatchString(email)
}
// IsChineseMobile check if the string is chinese mobile number.
@@ -460,12 +497,13 @@ func IsZeroValue(value any) bool {
func IsGBK(data []byte) bool {
i := 0
for i < len(data) {
if data[i] <= 0xff {
if data[i] < 0x81 {
i++
continue
} else {
if data[i] >= 0x81 &&
data[i] <= 0xfe &&
i+1 < len(data) &&
data[i+1] >= 0x40 &&
data[i+1] <= 0xfe &&
data[i+1] != 0xf7 {
@@ -561,12 +599,53 @@ func IsAmericanExpress(v string) bool {
// IsUnionPay check if a give string is a valid union pay nubmer or not.
// Play: https://go.dev/play/p/CUHPEwEITDf
func IsUnionPay(v string) bool {
return unionPay.MatchString(v)
func IsUnionPay(cardNo string) bool {
if len(cardNo) < 16 || len(cardNo) > 19 {
return false
}
matched, _ := regexp.MatchString(`^\d+$`, cardNo)
if !matched {
return false
}
if len(cardNo) < 3 {
return false
}
prefix := cardNo[:3]
prefixNum, err := strconv.Atoi(prefix)
if err != nil {
return false
}
if prefixNum < 620 || prefixNum > 625 {
return false
}
return true
}
// IsChinaUnionPay check if a give string is a valid china union pay nubmer or not.
// Play: https://go.dev/play/p/yafpdxLiymu
func IsChinaUnionPay(v string) bool {
return chinaUnionPay.MatchString(v)
func IsChinaUnionPay(cardNo string) bool {
return chinaUnionPayMatcher.MatchString(cardNo)
}
// IsPassport checks if the passport number is valid for a given country.
// country is a two-letter country code (ISO 3166-1 alpha-2).
// Play: todo
func IsPassport(passport, country string) bool {
if matcher, ok := passportMatcher[country]; ok {
return matcher.MatchString(passport)
}
return false
}
// IsChineseHMPassport checks if the string is a valid Chinese Hong Kong and Macau Travel Permit number.
// Chinese Hong Kong and Macau Travel Permit format: C or M + 8 digits (e.g., C12345678, M12345678).
// Play: https://go.dev/play/p/TODO
func IsChineseHMPassport(hmPassport string) bool {
return chineseHMPassportMatcher.MatchString(hmPassport)
}

View File

@@ -214,7 +214,7 @@ func ExampleIsUrl() {
fmt.Println(result3)
// Output:
// true
// false
// true
// false
}
@@ -665,3 +665,60 @@ 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
}
func ExampleIsPassport() {
result1 := IsPassport("P123456789", "CN")
result2 := IsPassport("123456789", "US")
result3 := IsPassport("AB1234567", "RU")
result4 := IsPassport("123456789", "CN")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
func ExampleIsChineseHMPassport() {
result1 := IsChineseHMPassport("C12345678")
result2 := IsChineseHMPassport("C00000000")
result3 := IsChineseHMPassport("M12345678")
result4 := IsChineseHMPassport("c12345678")
result5 := IsChineseHMPassport("C1234567")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// true
// true
// true
// false
// false
}

View File

@@ -437,10 +437,32 @@ func TestIsUrl(t *testing.T) {
assert := internal.NewAssert(t, "TestIsUrl")
assert.Equal(true, IsUrl("http://abc.com"))
assert.Equal(true, IsUrl("abc.com"))
assert.Equal(true, IsUrl("a.b.com"))
assert.Equal(false, IsUrl("abc"))
tests := []struct {
input string
expected bool
}{
{"http://abc.com", true},
{"https://abc.com", true},
{"ftp://abc.com", true},
{"http://abc.com/path?query=123", true},
{"https://abc.com/path/to/resource", true},
{"ws://abc.com", true},
{"wss://abc.com", true},
{"mailto://abc.com", true},
{"file://path/to/file", true},
{"data://text/plain;base64,SGVsbG8sIFdvcmxkIQ==", true},
{"http://abc.com/path/to/resource?query=123#fragment", true},
{"abc", false},
{"http://", false},
{"http://abc", false},
{"http://abc:8080", false},
{"http://abc:99999999", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsUrl(tt.input))
}
}
func TestIsDns(t *testing.T) {
@@ -455,7 +477,8 @@ func TestIsDns(t *testing.T) {
{"abc.com", true},
{"123.cn", true},
{"a.b.com", true},
{"a.b.c", false},
{"a.b.c", true},
{"www.xn--6qq986b3xl.xn--fiqs8s.com", true},
{"a@b.com", false},
{"http://abc.com", false},
}
@@ -477,12 +500,24 @@ func TestIsEmail(t *testing.T) {
func TestContainChinese(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestContainChinese")
assert.Equal(true, ContainChinese("你好"))
assert.Equal(true, ContainChinese("你好hello"))
assert.Equal(false, ContainChinese("hello"))
tests := []struct {
input string
expected bool
}{
{"你好", true},
{"hello", false},
{"你好hello", true},
{"hello你好", true},
{"", false},
{"123", false},
{"!@#$%^&*()", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, ContainChinese(tt.input))
}
}
func TestIsChineseMobile(t *testing.T) {
@@ -490,8 +525,20 @@ func TestIsChineseMobile(t *testing.T) {
assert := internal.NewAssert(t, "TestIsChineseMobile")
assert.Equal(true, IsChineseMobile("13263527980"))
assert.Equal(false, IsChineseMobile("434324324"))
tests := []struct {
input string
expected bool
}{
{"13263527980", true},
{"1326352798", false},
{"132635279801", false},
{"1326352798a", false},
{"1326352798@", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChineseMobile(tt.input))
}
}
func TestIsChinesePhone(t *testing.T) {
@@ -887,7 +934,7 @@ func TestIsUnionPay(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsUnionPay")
assert.Equal(true, IsUnionPay("6221263430109903"))
assert.Equal(true, IsUnionPay("6228480402564890"))
assert.Equal(false, IsUnionPay("3782822463100007"))
}
@@ -895,8 +942,25 @@ func TestIsChinaUnionPay(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsChinaUnionPay")
assert.Equal(true, IsChinaUnionPay("6250941006528599"))
assert.Equal(false, IsChinaUnionPay("3782822463100007"))
tests := []struct {
cardNumber string
expected bool
}{
{"6228480420000000000", true},
{"6214830000000000", true},
{"6230580000000000000", true},
{"6259640000000000000", true},
{"6260000000000000000", true},
{"6288888888888888", true},
// 非银联前缀
{"4123456789012345", false},
{"3528000000000000", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChinaUnionPay(tt.cardNumber))
}
}
func TestIsAlphaNumeric(t *testing.T) {
@@ -924,3 +988,72 @@ func TestIsAlphaNumeric(t *testing.T) {
assert.Equal(tt.expected, IsAlphaNumeric(tt.input))
}
}
func TestIsPassport(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsPassport")
tests := []struct {
passport string
countryCode string
expected bool
}{
{"P123456789", "CN", true},
{"123456789", "US", true},
{"A12345678", "GB", true},
{"AB1234567", "FR", true},
{"12345678", "JP", true},
{"M12345678", "HK", true},
{"A12345678", "MO", true},
{"A1234567", "IN", true},
{"12345678", "IT", true},
{"A12345678", "AU", true},
{"123456789", "BR", true},
{"AB1234567", "RU", true},
{"123456789", "CN", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsPassport(tt.passport, tt.countryCode))
}
}
func TestIsChineseHMPassport(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsChineseHMPassport")
tests := []struct {
input string
expected bool
}{
{"C12345678", true},
{"C00000000", true},
{"C99999999", true},
{"M12345678", true}, // M prefix
{"M00000000", true}, // M prefix
{"M99999999", true}, // M prefix
{"c12345678", false}, // lowercase c
{"m12345678", false}, // lowercase m
{"C1234567", false}, // 7 digits
{"M1234567", false}, // 7 digits with M
{"C123456789", false}, // 9 digits
{"M123456789", false}, // 9 digits with M
{"C1234567a", false}, // contains letter
{"M1234567a", false}, // contains letter with M
{"D12345678", false}, // starts with D
{"12345678", false}, // no prefix
{"CC12345678", false}, // double C
{"MM12345678", false}, // double M
{"C 12345678", false}, // contains space
{"M 12345678", false}, // contains space with M
{"C12345-678", false}, // contains dash
{"M12345-678", false}, // contains dash with M
{"", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChineseHMPassport(tt.input))
}
}