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

Compare commits

..

19 Commits

Author SHA1 Message Date
dudaodong
d7f3354b98 feat: add RWKeyedLocker 2025-04-17 14:25:19 +08:00
dudaodong
f4dee28ebb feat: add KeyedLocker 2025-04-17 14:06:59 +08:00
残念
c841a5b88c Merge pull request #301 from ppd324/main
feat: add ToMap for stream
2025-04-14 10:34:17 +08:00
Peidong Pei
333038634b Update stream.go 2025-04-07 11:15:39 +08:00
Peidong Pei
ef0fed23b2 Update stream_example_test.go 2025-04-07 11:14:42 +08:00
dudaodong
1e0ee1fac1 fix: fix issue #302 2025-04-03 10:23:03 +08:00
Pei PeiDong
4947327ed6 feat: add ToMap for stream 2025-04-02 10:53:46 +08:00
dudaodong
ceb706b874 feat: add FindValuesBy 2025-04-01 11:35:24 +08:00
dudaodong
3849337919 doc: update example link for BeginOfWeek and EndOfWeek 2025-03-31 10:27:09 +08:00
CyJaySong
f4427b9fbc BeginOfWeek,EndOfWeek 的beginFrom和endWith改为必传 (#300) 2025-03-31 10:18:50 +08:00
dudaodong
0ef45b533b fix: fix issue #292, change query params type to map[string][]string 2025-03-28 14:00:11 +08:00
dudaodong
169f280c9c feat: add RemoveDir 2025-03-28 11:06:52 +08:00
dudaodong
e74126a0bd test: format test code 2025-03-27 11:17:07 +08:00
dudaodong
f7be4190f1 test: format test code 2025-03-27 10:49:40 +08:00
dudaodong
8089b71bfd feat: add IsAlphaNumeric 2025-03-15 14:57:21 +08:00
dudaodong
5b9543255a feat: add AddQueryParams 2025-03-15 14:52:49 +08:00
dudaodong
bf859387f4 feat: add BuildUrl 2025-03-15 14:25:03 +08:00
dudaodong
a8a92844f3 doc: update doc for website 2025-03-07 14:37:52 +08:00
dudaodong
6dfdadd34e doc: update for release v2.3.5 2025-03-07 14:19:07 +08:00
47 changed files with 2001 additions and 483 deletions

View File

@@ -508,22 +508,22 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/nT1heB1KUUK)]
- **<big>AddWeek</big>** : add or sub week to time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddWeek)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/M9TqdMiaA2p)]
- **<big>AddMonth</big>** : add or sub months to time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMonth)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/DLoiOnpLvsN)]
- **<big>AddYear</big>** : add or sub year to the time.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddYear)]
[[play](https://go.dev/play/p/MqW2ujnBx10)]
- **<big>AddDaySafe</big>** : add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddDaySafe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/JTohZFpoDJ3)]
- **<big>AddMonthSafe</big>** : add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMonthSafe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/KLw0lo6mbVW)]
- **<big>AddYearSafe</big>** : Add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddYearSafe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/KVGXWZZ54ZH)]
- **<big>BeginOfMinute</big>** : return the date time at the begin of minute of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMinute)]
[[play](https://go.dev/play/p/ieOLVJ9CiFT)]
@@ -535,7 +535,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/94m_UT6cWs9)]
- **<big>BeginOfWeek</big>** : return the date time at the begin of week of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfWeek)]
[[play](https://go.dev/play/p/ynjoJPz7VNV)]
[[play](https://go.dev/play/p/DCHdcL6gnfV)]
- **<big>BeginOfMonth</big>** : return the date time at the begin of month of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMonth)]
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
@@ -553,7 +553,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
- **<big>EndOfWeek</big>** : return the date time at the end of week of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfWeek)]
[[play](https://go.dev/play/p/i08qKXD9flf)]
[[play](https://go.dev/play/p/mGSA162YgX9)]
- **<big>EndOfMonth</big>** : return the date time at the end of month of specific date.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfMonth)]
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
@@ -705,34 +705,34 @@ import "github.com/duke-git/lancet/v2/eventbus"
- **<big>NewEventBus</big>** : Create an EventBus instance.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#NewEventBus)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gHbOPV_NUOJ)]
- **<big>Subscribe</big>** : subscribes to an event with a specific event topic and listener function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Subscribe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/EYGf_8cHei-)]
- **<big>Unsubscribe</big>** : unsubscribes from an event with a specific event topic and listener function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Unsubscribe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/Tmh7Ttfvprf)]
- **<big>Publish</big>** : publishes an event with a specific event topic and data payload.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Publish)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gHTtVexFSH9)]
- **<big>ClearListeners</big>** : clears all the listeners.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#ClearListeners)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/KBfBYlKPgqD)]
- **<big>ClearListenersByTopic</big>** : clears all the listeners by topic.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#ClearListenersByTopic)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gvMljmJOZmU)]
- **<big>GetListenersCount</big>** : returns the number of listeners for a specific event topic.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetListenersCount)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/8VPJsMQgStM)]
- **<big>GetAllListenersCount</big>** : returns the total number of all listeners.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetAllListenersCount)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/PUlr0xcpEOz)]
- **<big>GetEvents</big>** : returns all the events topics.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetEvents)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/etgjjcOtAjX)]
- **<big>SetErrorHandler</big>** : sets the error handler function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#SetErrorHandler)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
<h3 id="fileutil"> 9. Fileutil package implements some basic functions for file operations. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -1479,7 +1479,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/b9iygtgsHI1)]
- **<big>EqualUnordered</big>** : Checks if two slices are equal: the same length and all elements value are equal (unordered).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#EqualUnordered)]
[[play](todo)]
[[play](https://go.dev/play/p/n8fSc2w8ZgX)]
- **<big>Every</big>** : return true if all of the values in the slice pass the predicate function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Every)]
[[play](https://go.dev/play/p/R8U6Sl-j8cD)]
@@ -1581,7 +1581,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
- **<big>ShuffleCopy</big>** : return a new slice with elements shuffled.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ShuffleCopy)]
[[play](todo)]
[[play](https://go.dev/play/p/vqDa-Gs1vT0)]
- **<big>IsAscending</big>** : Checks if a slice is ascending order.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#IsAscending)]
[[play](https://go.dev/play/p/9CtsFjet4SH)]
@@ -1730,7 +1730,7 @@ import "github.com/duke-git/lancet/v2/stream"
[[play](https://go.dev/play/p/A8_zkJnLHm4)]
- **<big>ReverseCopy</big>** : returns a new slice of element order is reversed to the given slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#ReverseCopy)]
[[play](todo)]
[[play](https://go.dev/play/p/c9arEaP7Cg-)]
- **<big>Range</big>** : returns a stream whose elements are in the range from start(included) to end(excluded) original stream.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#Range)]
[[play](https://go.dev/play/p/indZY5V2f4j)]
@@ -1962,7 +1962,7 @@ import "github.com/duke-git/lancet/v2/strutil"
[[play](https://go.dev/play/p/Ay9UIk7Rum9)]
- **<big>FindAllOccurrences</big>** : Returns the positions of all occurrences of a substring in a string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#FindAllOccurrences)]
[[play](todo)]
[[play](https://go.dev/play/p/uvyA6azGLB1)]
<h3 id="system"> 22. System package contain some functions about os, runtime, shell command. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -2218,7 +2218,7 @@ import "github.com/duke-git/lancet/v2/validator"
[[play](https://go.dev/play/p/AHA0r0AzIdC)]
- **<big>IsIpPort</big>** : check if the string is ip:port.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsIpPort)]
[[play](todo)]
[[play](https://go.dev/play/p/xUmls_b9L29)]
- **<big>IsStrongPassword</big>** : check if the string is strong password.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsStrongPassword)]
[[play](https://go.dev/play/p/QHdVcSQ3uDg)]

View File

@@ -509,22 +509,22 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/nT1heB1KUUK)]
- **<big>AddWeek</big>** : 将日期加/减星期数.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddWeek)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/M9TqdMiaA2p)]
- **<big>AddMonth</big>** : 将日期加/减月数.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddMonth)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/DLoiOnpLvsN)]
- **<big>AddYear</big>** : 将日期加/减分年数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddYear)]
[[play](https://go.dev/play/p/MqW2ujnBx10)]
- **<big>AddDaySafe</big>** : 增加/减少指定的天数,并确保日期是有效日期。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddDaySafe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/JTohZFpoDJ3)]
- **<big>AddMonthSafe</big>** : 增加/减少指定的月份,并确保日期是有效日期。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddMonthSafe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/KLw0lo6mbVW)]
- **<big>AddYearSafe</big>** : 增加/减少指定的年份,并确保日期是有效日期。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#AddYearSafe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/KVGXWZZ54ZH)]
- **<big>BeginOfMinute</big>** : 返回指定时间的分钟开始时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfMinute)]
[[play](https://go.dev/play/p/ieOLVJ9CiFT)]
@@ -536,7 +536,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/94m_UT6cWs9)]
- **<big>BeginOfWeek</big>** : 返回指定时间的每周开始时间,默认开始时间星期日。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfWeek)]
[[play](https://go.dev/play/p/ynjoJPz7VNV)]
[[play](https://go.dev/play/p/DCHdcL6gnfV)]
- **<big>BeginOfMonth</big>** : 返回指定时间的当月开始时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfMonth)]
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
@@ -554,7 +554,7 @@ import "github.com/duke-git/lancet/v2/datetime"
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
- **<big>EndOfWeek</big>** : 返回指定时间的星期结束时间,默认结束时间星期六。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfWeek)]
[[play](https://go.dev/play/p/i08qKXD9flf)]
[[play](https://go.dev/play/p/mGSA162YgX9)]
- **<big>EndOfMonth</big>** : 返回指定时间的月份结束时间。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfMonth)]
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
@@ -704,34 +704,34 @@ import "github.com/duke-git/lancet/v2/eventbus"
- **<big>NewEventBus</big>** : 创建EventBus实例。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#NewEventBus)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gHbOPV_NUOJ)]
- **<big>Subscribe</big>** : 订阅具有特定事件主题和监听函数的事件。支持异步,事件优先级,事件过滤器。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#Subscribe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/EYGf_8cHei-)]
- **<big>Unsubscribe</big>** : 取消订阅具有特定事件主题的事件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#Unsubscribe)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/Tmh7Ttfvprf)]
- **<big>Publish</big>** : 发布一个带有特定事件主题和数据负载的事件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#Publish)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gHTtVexFSH9)]
- **<big>ClearListeners</big>** : 清空所有事件监听器。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#ClearListeners)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/KBfBYlKPgqD)]
- **<big>ClearListenersByTopic</big>** : 清空特定事件监听器。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#ClearListenersByTopic)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gvMljmJOZmU)]
- **<big>GetListenersCount</big>** : 获取特定事件的监听器数量。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#GetListenersCount)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/8VPJsMQgStM)]
- **<big>GetAllListenersCount</big>** : 获取所有事件的监听器数量。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#GetAllListenersCount)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/PUlr0xcpEOz)]
- **<big>GetEvents</big>** : 获取所有事件的topic。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#GetEvents)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/etgjjcOtAjX)]
- **<big>SetErrorHandler</big>** : 设置事件的错误处理函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#SetErrorHandler)]
[[play](https://go.dev/play/p/todo)]
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
<h3 id="fileutil"> 10. fileutil 包含文件基本操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -1477,7 +1477,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/b9iygtgsHI1)]
- **<big>EqualUnordered</big>** : 检查两个切片是否相等,元素数量相同,值相等,不考虑元素顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#EqualUnordered)]
[[play](todo)]
[[play](https://go.dev/play/p/n8fSc2w8ZgX)]
- **<big>Every</big>** : 如果切片中的所有值都通过谓词函数,则返回 true。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Every)]
[[play](https://go.dev/play/p/R8U6Sl-j8cD)]
@@ -1555,7 +1555,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/8uI8f1lwNrQ)]
- **<big>ReverseCopy</big>** : 反转切片中的元素顺序, 不改变原slice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#ReverseCopy)]
[[play](todo)]
[[play](https://go.dev/play/p/c9arEaP7Cg-)]
- **<big>Reduce<sup>deprecated</sup></big>** : 将切片中的元素依次运行 iteratee 函数,返回运行结果。(废弃:建议使用 ReduceBy)
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Reduce)]
[[play](https://go.dev/play/p/_RfXJJWIsIm)]
@@ -1582,7 +1582,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
- **<big>ShuffleCopy</big>** : 随机打乱切片中的元素顺序, 不改变原切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ShuffleCopy)]
[[play](todo)]
[[play](https://go.dev/play/p/vqDa-Gs1vT0)]
- **<big>IsAscending</big>** : 检查切片元素是否按升序排列。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#IsAscending)]
[[play](https://go.dev/play/p/9CtsFjet4SH)]
@@ -1962,7 +1962,7 @@ import "github.com/duke-git/lancet/v2/strutil"
[[play](https://go.dev/play/p/Ay9UIk7Rum9)]
- **<big>FindAllOccurrences</big>** : 返回子字符串在字符串中所有出现的位置。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#FindAllOccurrences)]
[[play](todo)]
[[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>
@@ -2220,7 +2220,7 @@ import "github.com/duke-git/lancet/v2/validator"
[[play](https://go.dev/play/p/AHA0r0AzIdC)]
- **<big>IsIpPort</big>** : 检查字符串是否是ip:port格式。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsIpPort)]
[[play](todo)]
[[play](https://go.dev/play/p/xUmls_b9L29)]
- **<big>IsStrongPassword</big>** : 验证字符串是否是强密码:(字母+数字+特殊字符)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsStrongPassword)]
[[play](https://go.dev/play/p/QHdVcSQ3uDg)]

193
concurrency/keyed_locker.go Normal file
View File

@@ -0,0 +1,193 @@
// Copyright 2025 dudaodong@gmail.com. All rights reserved.
// Use of this source code is governed by MIT license
// Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel, locker.
package concurrency
import (
"context"
"sync"
"sync/atomic"
"time"
)
// KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.
type KeyedLocker[K comparable] struct {
locks sync.Map
ttl time.Duration
}
type lockEntry struct {
mu sync.Mutex
ref int32
timer atomic.Pointer[time.Timer]
}
// NewKeyedLocker creates a new KeyedLocker with the specified TTL for lock expiration.
// The TTL is used to automatically release locks that are no longer held.
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.
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(key, entry, key)
done := make(chan struct{})
go func() {
entry.mu.Lock()
defer entry.mu.Unlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// acquire tries to acquire a lock for the specified key.
func (l *KeyedLocker[K]) acquire(key K) *lockEntry {
lock, _ := l.locks.LoadOrStore(key, &lockEntry{})
entry := lock.(*lockEntry)
atomic.AddInt32(&entry.ref, 1)
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
return entry
}
// release releases the lock for the specified key.
func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) {
if atomic.AddInt32(&entry.ref, -1) == 0 {
entry.mu.Lock()
defer entry.mu.Unlock()
if entry.ref == 0 {
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
l.locks.Delete(rawKey)
} else {
if entry.timer.Load() == nil {
t := time.AfterFunc(l.ttl, func() {
l.release(key, entry, rawKey)
})
entry.timer.Store(t)
}
}
}
}
// RWKeyedLocker is a read-write version of KeyedLocker.
type RWKeyedLocker[K comparable] struct {
locks sync.Map
ttl time.Duration
}
type rwLockEntry struct {
mu sync.RWMutex
ref int32
timer atomic.Pointer[time.Timer]
}
// NewRWKeyedLocker creates a new RWKeyedLocker with the specified TTL for lock expiration.
// The TTL is used to automatically release locks that are no longer held.
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.
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(entry, key)
done := make(chan struct{})
go func() {
entry.mu.RLock()
defer entry.mu.RUnlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// Lock acquires a write lock for the specified key and executes the provided function.
// It returns an error if the context is canceled before the function completes.
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(entry, key)
done := make(chan struct{})
go func() {
entry.mu.Lock()
defer entry.mu.Unlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// acquire tries to acquire a read lock for the specified key.
func (l *RWKeyedLocker[K]) acquire(key K) *rwLockEntry {
actual, _ := l.locks.LoadOrStore(key, &rwLockEntry{})
entry := actual.(*rwLockEntry)
atomic.AddInt32(&entry.ref, 1)
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
return entry
}
// release releases the lock for the specified key.
func (l *RWKeyedLocker[K]) release(entry *rwLockEntry, rawKey K) {
if atomic.AddInt32(&entry.ref, -1) == 0 {
timer := time.AfterFunc(l.ttl, func() {
if atomic.LoadInt32(&entry.ref) == 0 {
l.locks.Delete(rawKey)
}
})
entry.timer.Store(timer)
}
}

View File

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

View File

@@ -1,51 +1,51 @@
-----BEGIN rsa private key-----
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
-----END rsa private key-----

View File

@@ -1,14 +1,14 @@
-----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0HaqqGWnNqwRVKoJUUjY
iH/dwkSK61La4RGYkvDfcuo9pDgczHVhFUb/MYWeb3bAlFAGcZLYSBh6WAGxbeMj
SkaLroaXnafhKZ2oXXUy8CzyYxZwpLCDgJLG7Pf0jer3STZW93ZT8UJixbKwbqD6
b8fkpMANwCYrTlXDWBuZVaEKuQNnC4XufQis4fwRxRgfrZuLMvuQVtnyYmssmnp8
JHovYkr87giDjEgvlrC84Lez32ZhEhORRu5NGEDeZ1OewbkXGyQtbcBQYNAxgnOc
O44MTTPhEdz4/A0xwzEe+yIOxWIAWuEM2rclXpZBW/MEK41gZ3C04KcX5R0KRdj2
PTTH7vJ33yRC4hKtWetwMvsUrewrRKVcUgaeFykDAlgIDoSWvG0nzb70AWSoNcj7
krLlwEc2WLGy/kCYWppnfYKafEJXpgblHrDFKlWvte68yrvGdCsWRLQ2uUF0xokQ
Ig4u2bApyWNroxEtjHwUp1VeJuZBQkfg+vIW+t3moQpMtlhy4aai/oGIxI5TIB82
nv8TPVczjHmmE3hwL6nZ5OfbFyWpRFvKLtXcaAKMi2HqOLRkQhqjjJUCO9Wyhu1B
kX8RAeKLl3S7gRyhuOB5JNUGEpQ389MdbUX5S12Qp1xTDS+otbC8tYS1gu0OOOWo
wxbuF5+Ci2SYgSHv/3zXKoMCAwEAAQ==
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==
-----END rsa public key-----

View File

@@ -55,7 +55,7 @@ func (q *ArrayQueue[T]) IsFull() bool {
// Front return front value of queue
func (q *ArrayQueue[T]) Front() T {
return q.data[0]
return q.data[q.head]
}
// Back return back value of queue

View File

@@ -83,13 +83,13 @@ func AddDay(t time.Time, days int64) time.Time {
}
// AddWeek add or sub weeks to the time.
// play: todo
// play: https://go.dev/play/p/M9TqdMiaA2p
func AddWeek(t time.Time, weeks int64) time.Time {
return t.Add(7 * 24 * time.Hour * time.Duration(weeks))
}
// AddMonth add or sub months to the time.
// Play: todo
// Play: https://go.dev/play/p/DLoiOnpLvsN
func AddMonth(t time.Time, months int64) time.Time {
return t.AddDate(0, int(months), 0)
}
@@ -101,7 +101,7 @@ func AddYear(t time.Time, year int64) time.Time {
}
// AddDaySafe add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
// Play: todo
// Play: https://go.dev/play/p/JTohZFpoDJ3
func AddDaySafe(t time.Time, days int) time.Time {
t = t.AddDate(0, 0, days)
year, month, day := t.Date()
@@ -116,7 +116,7 @@ func AddDaySafe(t time.Time, days int) time.Time {
}
// AddMonthSafe add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
// Play: todo
// Play: https://go.dev/play/p/KLw0lo6mbVW
func AddMonthSafe(t time.Time, months int) time.Time {
year := t.Year()
month := int(t.Month()) + months
@@ -141,7 +141,7 @@ func AddMonthSafe(t time.Time, months int) time.Time {
}
// AddYearSafe add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
// Play: todo
// Play: https://go.dev/play/p/KVGXWZZ54ZH
func AddYearSafe(t time.Time, years int) time.Time {
year, month, day := t.Date()
year += years
@@ -280,13 +280,9 @@ func EndOfDay(t time.Time) time.Time {
}
// BeginOfWeek return beginning week, default week begin from Sunday.
// Play: https://go.dev/play/p/ynjoJPz7VNV
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
var beginFromWeekday = time.Sunday
if len(beginFrom) > 0 {
beginFromWeekday = beginFrom[0]
}
y, m, d := t.AddDate(0, 0, int(beginFromWeekday-t.Weekday())).Date()
// Play: https://go.dev/play/p/DCHdcL6gnfV
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time {
y, m, d := t.AddDate(0, 0, int(beginFrom-t.Weekday())).Date()
beginOfWeek := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
if beginOfWeek.After(t) {
return beginOfWeek.AddDate(0, 0, -7)
@@ -295,13 +291,9 @@ func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
}
// EndOfWeek return end week time, default week end with Saturday.
// Play: https://go.dev/play/p/i08qKXD9flf
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time {
var endWithWeekday = time.Saturday
if len(endWith) > 0 {
endWithWeekday = endWith[0]
}
y, m, d := t.AddDate(0, 0, int(endWithWeekday-t.Weekday())).Date()
// Play: https://go.dev/play/p/mGSA162YgX9
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time {
y, m, d := t.AddDate(0, 0, int(endWith-t.Weekday())).Date()
var endWithWeek = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
if endWithWeek.Before(t) {
endWithWeek = endWithWeek.AddDate(0, 0, 7)

View File

@@ -299,23 +299,23 @@ func ExampleEndOfDay() {
func ExampleBeginOfWeek() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := BeginOfWeek(input)
result := BeginOfWeek(input, time.Monday)
fmt.Println(result)
// Output:
// 2023-01-08 00:00:00 +0000 UTC
// 2023-01-02 00:00:00 +0000 UTC
}
func ExampleEndOfWeek() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := EndOfWeek(input)
result := EndOfWeek(input, time.Sunday)
fmt.Println(result)
// Output:
// 2023-01-14 23:59:59.999999999 +0000 UTC
// 2023-01-08 23:59:59.999999999 +0000 UTC
}
func ExampleBeginOfMonth() {

View File

@@ -611,9 +611,9 @@ func TestBeginOfWeek(t *testing.T) {
assert := internal.NewAssert(t, "TestBeginOfWeek")
expected := time.Date(2022, 2, 13, 0, 0, 0, 0, time.Local)
expected := time.Date(2022, 2, 14, 0, 0, 0, 0, time.Local)
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
actual := BeginOfWeek(td)
actual := BeginOfWeek(td, time.Monday)
assert.Equal(expected, actual)
}
@@ -623,9 +623,9 @@ func TestEndOfWeek(t *testing.T) {
assert := internal.NewAssert(t, "TestEndOfWeek")
expected := time.Date(2022, 2, 19, 23, 59, 59, 999999999, time.Local)
expected := time.Date(2022, 2, 20, 23, 59, 59, 999999999, time.Local)
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
actual := EndOfWeek(td)
actual := EndOfWeek(td, time.Sunday)
assert.Equal(expected, actual)
}

View File

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

View File

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

View File

@@ -47,6 +47,7 @@ outline: deep
<div class="package-cell">cryptor</div>
<div class="package-cell">datastructure</div>
<div class="package-cell">datetime</div>
<div class="package-cell">eventbus</div>
<div class="package-cell">fileutil</div>
<div class="package-cell">formatter</div>
<div class="package-cell">function</div>

View File

@@ -153,7 +153,7 @@ func main() {
func AddWeek(t time.Time, weeks int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/M9TqdMiaA2p)</span></b>
```go
package main
@@ -189,7 +189,7 @@ func main() {
func AddMonth(t time.Time, months int64) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/DLoiOnpLvsN)</span></b>
```go
package main
@@ -333,7 +333,7 @@ func main() {
func AddDaySafe(t time.Time, days int) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JTohZFpoDJ3)</span></b>
```go
package main
@@ -380,7 +380,7 @@ func main() {
func AddMonthSafe(t time.Time, months int) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KLw0lo6mbVW)</span></b>
```go
package main
@@ -417,7 +417,7 @@ func main() {
func AddYearSafe(t time.Time, years int) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KVGXWZZ54ZH)</span></b>
```go
package main
@@ -546,7 +546,7 @@ func main() {
<b>函数签名:</b>
```go
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
@@ -562,12 +562,12 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.BeginOfWeek(input)
result := datetime.BeginOfWeek(input, time.Monday)
fmt.Println(result)
// Output:
// 2023-01-08 00:00:00 +0000 UTC
// 2023-01-09 00:00:00 +0000 UTC
}
```
@@ -727,7 +727,7 @@ func main() {
fmt.Println(result)
// Output:
// 2023-01-08 23:59:59.999999999 +0000 UTC
// 2023-01-02 00:00:00 +0000 UTC
}
```
@@ -738,10 +738,10 @@ func main() {
<b>函数签名:</b>
```go
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/i08qKXD9flf)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/mGSA162YgX9)</span></b>
```go
package main
@@ -754,12 +754,12 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.EndOfWeek(input)
result := datetime.EndOfWeek(input, time.Sunday)
fmt.Println(result)
// Output:
// 2023-01-14 23:59:59.999999999 +0000 UTC
// 2023-01-08 23:59:59.999999999 +0000 UTC
}
```

View File

@@ -48,7 +48,7 @@ import (
func NewEventBus[T any]() *EventBus[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gHbOPV_NUOJ)</span></b>
```go
import (
@@ -65,7 +65,7 @@ func main() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
@@ -84,7 +84,7 @@ func main() {
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/EYGf_8cHei-)</span></b>
```go
import (
@@ -106,8 +106,8 @@ func main() {
eb.Subscribe("event1", listener, false, 0, filter)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 2})
fmt.Println(receivedData)
@@ -126,7 +126,7 @@ func main() {
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T))
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Tmh7Ttfvprf)</span></b>
```go
import (
@@ -145,7 +145,7 @@ func main() {
eb.Subscribe("event1", listener, false, 0, nil)
eb.Unsubscribe("event1", listener)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
@@ -161,10 +161,10 @@ func main() {
<b>函数签名:</b>
```go
func (eb *EventBus[T]) Publish(event Event[T])
func (eb *EventBus[T]) Publish(event eventbus.Event[T])
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gHTtVexFSH9)</span></b>
```go
import (
@@ -179,7 +179,7 @@ func main() {
fmt.Println(eventData)
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// 1
@@ -196,7 +196,7 @@ func main() {
func (eb *EventBus[T]) ClearListeners()
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KBfBYlKPgqD)</span></b>
```go
import (
@@ -213,9 +213,12 @@ func main() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListeners()
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
@@ -234,7 +237,7 @@ func main() {
func (eb *EventBus[T]) ClearListenersByTopic(topic string)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gvMljmJOZmU)</span></b>
```go
import (
@@ -251,14 +254,17 @@ func main() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 0
// 2
}
```
@@ -272,7 +278,7 @@ func main() {
func (eb *EventBus[T]) GetListenersCount(topic string) int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/j6yaJ2xAmFz)</span></b>
```go
import (
@@ -284,14 +290,14 @@ func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetListenersCount("event1")
fmt.Println(count)
// Output:
// 2
// 1
}
```
@@ -305,7 +311,7 @@ func main() {
func (eb *EventBus[T]) GetAllListenersCount() int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/PUlr0xcpEOz)</span></b>
```go
import (
@@ -338,7 +344,7 @@ func main() {
func (eb *EventBus[T]) GetEvents() []string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/etgjjcOtAjX)</span></b>
```go
import (
@@ -359,8 +365,8 @@ func main() {
}
// Output:
// event1
// event2
// event1
}
```
@@ -374,7 +380,7 @@ func main() {
func (eb *EventBus[T]) SetErrorHandler(handler func(err 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/gmB0gnFe5mc)</span></b>
```go
import (
@@ -393,7 +399,7 @@ func main() {
panic("error")
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// error

View File

@@ -35,6 +35,7 @@ import (
- [IsDir](#IsDir)
- [ListFileNames](#ListFileNames)
- [RemoveFile](#RemoveFile)
- [RemoveDir](#RemoveDir)
- [ReadFileToString](#ReadFileToString)
- [ReadFileByLine](#ReadFileByLine)
- [Zip](#Zip)
@@ -390,12 +391,12 @@ func main() {
### <span id="RemoveFile">RemoveFile</span>
<p>删除文件</p>
<p>删除文件,支持传入删除前的回调函数。</p>
<b>函数签名:</b>
```go
func RemoveFile(path string) error
func RemoveFile(path string, onDelete ...func(path string)) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/P2y0XW8a1SH)</span></b>
@@ -416,6 +417,41 @@ func main() {
}
```
### <span id="RemoveDir">RemoveDir</span>
<p>删除目录,支持传入删除前的回调函数。</p>
<b>函数签名:</b>
```go
func RemoveDir(path string, onDelete ...func(path string)) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
var deletedPaths []string
err := fileutil.RemoveDir("./tempdir", func(p string) {
deletedPaths = append(deletedPaths, p)
})
if err != nil {
fmt.Println(err)
}
fmt.Println(deletedPaths)
}
```
### <span id="ReadFileToString">ReadFileToString</span>
<p>读取文件内容并返回字符串</p>

View File

@@ -78,7 +78,7 @@ import (
- [GetOrSet](#GetOrSet)
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
<div STYLE="page-break-after: always;"></div>
@@ -2307,4 +2307,43 @@ func main() {
// a
// default
}
```
### <span id="FindValuesBy">FindValuesBy</span>
<p>返回一个切片包含满足给定谓词判断函数的map中的值。</p>
<b>函数签名:</b>
```go
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
2: "b",
3: "c",
4: "d",
}
result := maputil.FindValuesBy(m, func(k int, v string) bool {
return k%2 == 0
})
fmt.Println(result)
// Output:
// [b d]
}
```

View File

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

View File

@@ -887,7 +887,7 @@ func main() {
func EqualUnordered[T comparable](slice1, slice2 []T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/n8fSc2w8ZgX)</span></b>
```go
import (
@@ -1772,7 +1772,7 @@ func main() {
func ReverseCopy[T any](slice []T) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/c9arEaP7Cg-)</span></b>
```go
import (
@@ -2065,7 +2065,7 @@ func main() {
func ShuffleCopy[T any](slice []T) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/vqDa-Gs1vT0)</span></b>
```go
import (

View File

@@ -1772,7 +1772,7 @@ func main() {
func FindAllOccurrences(str, substr string) []int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uvyA6azGLB1)</span></b>
```go
import (

View File

@@ -1001,7 +1001,7 @@ func main() {
func IsIpPort(str string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/xUmls_b9L29)</span></b>
```go
import (

View File

@@ -155,7 +155,7 @@ func main() {
func AddWeek(t time.Time, weeks int64) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/M9TqdMiaA2p)</span></b>
```go
package main
@@ -191,7 +191,7 @@ func main() {
func AddMonth(t time.Time, months int64) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DLoiOnpLvsN)</span></b>
```go
package main
@@ -335,7 +335,7 @@ func main() {
func AddDaySafe(t time.Time, days int) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JTohZFpoDJ3)</span></b>
```go
package main
@@ -382,7 +382,7 @@ func main() {
func AddMonthSafe(t time.Time, months int) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KLw0lo6mbVW)</span></b>
```go
package main
@@ -419,7 +419,7 @@ func main() {
func AddYearSafe(t time.Time, years int) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KVGXWZZ54ZH)</span></b>
```go
package main
@@ -551,7 +551,7 @@ func main() {
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DCHdcL6gnfV)</span></b>
```go
package main
@@ -564,12 +564,13 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.BeginOfWeek(input)
result := datetime.BeginOfWeek(input, time.Monday)
fmt.Println(result)
// Output:
// 2023-01-08 00:00:00 +0000 UTC
// 2023-01-02 00:00:00 +0000 UTC
}
```
@@ -743,7 +744,7 @@ func main() {
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/i08qKXD9flf)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/mGSA162YgX9)</span></b>
```go
package main
@@ -756,12 +757,12 @@ import (
func main() {
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
result := datetime.EndOfWeek(input)
result := datetime.EndOfWeek(input, time.Sunday)
fmt.Println(result)
// Output:
// 2023-01-14 23:59:59.999999999 +0000 UTC
// 2023-01-08 23:59:59.999999999 +0000 UTC
}
```

View File

@@ -48,7 +48,7 @@ import (
func NewEventBus[T any]() *EventBus[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gHbOPV_NUOJ)</span></b>
```go
import (
@@ -65,7 +65,7 @@ func main() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
@@ -84,7 +84,7 @@ func main() {
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/EYGf_8cHei-)</span></b>
```go
import (
@@ -106,8 +106,8 @@ func main() {
eb.Subscribe("event1", listener, false, 0, filter)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 2})
fmt.Println(receivedData)
@@ -126,7 +126,7 @@ func main() {
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T))
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Tmh7Ttfvprf)</span></b>
```go
import (
@@ -145,7 +145,7 @@ func main() {
eb.Subscribe("event1", listener, false, 0, nil)
eb.Unsubscribe("event1", listener)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
fmt.Println(receivedData)
@@ -161,10 +161,10 @@ func main() {
<b>Signature:</b>
```go
func (eb *EventBus[T]) Publish(event Event[T])
func (eb *EventBus[T]) Publish(event eventbus.Event[T])
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gHTtVexFSH9)</span></b>
```go
import (
@@ -179,7 +179,7 @@ func main() {
fmt.Println(eventData)
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// 1
@@ -196,7 +196,7 @@ func main() {
func (eb *EventBus[T]) ClearListeners()
```
<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/KBfBYlKPgqD)</span></b>
```go
import (
@@ -213,9 +213,12 @@ func main() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListeners()
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
@@ -234,7 +237,7 @@ func main() {
func (eb *EventBus[T]) ClearListenersByTopic(topic string)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gvMljmJOZmU)</span></b>
```go
import (
@@ -251,14 +254,17 @@ func main() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 0
// 2
}
```
@@ -272,7 +278,7 @@ func main() {
func (eb *EventBus[T]) GetListenersCount(topic string) int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/j6yaJ2xAmFz)</span></b>
```go
import (
@@ -284,14 +290,14 @@ func main() {
eb := eventbus.NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetListenersCount("event1")
fmt.Println(count)
// Output:
// 2
// 1
}
```
@@ -305,7 +311,7 @@ func main() {
func (eb *EventBus[T]) GetAllListenersCount() int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/PUlr0xcpEOz)</span></b>
```go
import (
@@ -338,7 +344,7 @@ func main() {
func (eb *EventBus[T]) GetEvents() []string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/etgjjcOtAjX)</span></b>
```go
import (
@@ -359,8 +365,8 @@ func main() {
}
// Output:
// event1
// event2
// event1
}
```
@@ -374,7 +380,7 @@ func main() {
func (eb *EventBus[T]) SetErrorHandler(handler func(err 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/gmB0gnFe5mc)</span></b>
```go
import (
@@ -393,7 +399,7 @@ func main() {
panic("error")
}, false, 0, nil)
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
// Output:
// error

View File

@@ -35,6 +35,7 @@ import (
- [IsDir](#IsDir)
- [ListFileNames](#ListFileNames)
- [RemoveFile](#RemoveFile)
- [RemoveDir](#RemoveDir)
- [ReadFileToString](#ReadFileToString)
- [ReadFileByLine](#ReadFileByLine)
- [Zip](#Zip)
@@ -395,7 +396,7 @@ func main() {
<b>Signature:</b>
```go
func RemoveFile(path string) error
func RemoveFile(path string, onDelete ...func(path string)) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/P2y0XW8a1SH)</span></b>
@@ -416,6 +417,41 @@ func main() {
}
```
### <span id="RemoveDir">RemoveDir</span>
<p>Delete directory.</p>
<b>Signature:</b>
```go
func RemoveDir(path string, onDelete ...func(path string)) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
var deletedPaths []string
err := fileutil.RemoveDir("./tempdir", func(p string) {
deletedPaths = append(deletedPaths, p)
})
if err != nil {
fmt.Println(err)
}
fmt.Println(deletedPaths)
}
```
### <span id="ReadFileToString">ReadFileToString</span>
<p>Return string of file content.</p>

View File

@@ -79,6 +79,7 @@ import (
- [GetOrSet](#GetOrSet)
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
<div STYLE="page-break-after: always;"></div>
@@ -2276,8 +2277,8 @@ func main() {
}
result := maputil.SortByKey(m, func(a, b int) bool {
return a < b
})
return a < b
})
fmt.Println(result)
@@ -2288,7 +2289,7 @@ func main() {
### <span id="GetOrDefault">GetOrDefault</span>
<p>returns the value of the given key or a default value if the key is not present.</p>
<p>Returns the value of the given key or a default value if the key is not present.</p>
<b>Signature:</b>
@@ -2296,7 +2297,7 @@ func main() {
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
```
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/99QjSYSBdiM)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/99QjSYSBdiM)</span></b>
```go
package main
@@ -2324,4 +2325,43 @@ func main() {
// a
// default
}
```
### <span id="FindValuesBy">FindValuesBy</span>
<p>Returns a slice of values from the map that satisfy the given predicate function.</p>
<b>Signature:</b>
```go
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
2: "b",
3: "c",
4: "d",
}
result := maputil.FindValuesBy(m, func(k int, v string) bool {
return k%2 == 0
})
fmt.Println(result)
// Output:
// [b d]
}
```

View File

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

View File

@@ -852,7 +852,7 @@ func main() {
func EqualUnordered[T comparable](slice1, slice2 []T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/n8fSc2w8ZgX)</span></b>
```go
import (
@@ -1768,7 +1768,7 @@ func main() {
func ReverseCopy[T any](slice []T) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/c9arEaP7Cg-)</span></b>
```go
import (
@@ -2062,7 +2062,7 @@ func main() {
func ShuffleCopy[T any](slice []T) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/vqDa-Gs1vT0)</span></b>
```go
import (

View File

@@ -1774,7 +1774,7 @@ func main() {
func FindAllOccurrences(str, substr string) []int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uvyA6azGLB1)</span></b>
```go
import (

View File

@@ -1003,7 +1003,7 @@ func main() {
func IsIpPort(str string) bool
```
<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/xUmls_b9L29)</span></b>
```go
import (

View File

@@ -33,7 +33,7 @@ type EventListener[T any] struct {
}
// NewEventBus creates a new EventBus.
// Play: todo
// Play: https://go.dev/play/p/gHbOPV_NUOJ
func NewEventBus[T any]() *EventBus[T] {
return &EventBus[T]{
listeners: sync.Map{},
@@ -41,7 +41,7 @@ func NewEventBus[T any]() *EventBus[T] {
}
// Subscribe subscribes to an event with a specific event topic and listener function.
// Play: todo
// Play: https://go.dev/play/p/EYGf_8cHei-
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool) {
eb.mu.Lock()
defer eb.mu.Unlock()
@@ -65,7 +65,7 @@ func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async
}
// Unsubscribe unsubscribes from an event with a specific event topic and listener function.
// Play: todo
// Play: https://go.dev/play/p/Tmh7Ttfvprf
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T)) {
eb.mu.Lock()
defer eb.mu.Unlock()
@@ -89,7 +89,7 @@ func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T)) {
}
// Publish publishes an event with a specific event topic and data payload.
// Play: todo
// Play: https://go.dev/play/p/gHTtVexFSH9
func (eb *EventBus[T]) Publish(event Event[T]) {
eb.mu.RLock()
defer eb.mu.RUnlock()
@@ -125,12 +125,13 @@ 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)) {
eb.errorHandler = handler
}
// ClearListeners clears all the listeners.
// Play: todo
// Play: https://go.dev/play/p/KBfBYlKPgqD
func (eb *EventBus[T]) ClearListeners() {
eb.mu.Lock()
defer eb.mu.Unlock()
@@ -139,7 +140,7 @@ func (eb *EventBus[T]) ClearListeners() {
}
// ClearListenersByTopic clears all the listeners by topic.
// Play: todo
// Play: https://go.dev/play/p/gvMljmJOZmU
func (eb *EventBus[T]) ClearListenersByTopic(topic string) {
eb.mu.Lock()
defer eb.mu.Unlock()
@@ -148,7 +149,7 @@ func (eb *EventBus[T]) ClearListenersByTopic(topic string) {
}
// GetListenersCount returns the number of listeners for a specific event topic.
// Play: todo
// Play: https://go.dev/play/p/8VPJsMQgStM
func (eb *EventBus[T]) GetListenersCount(topic string) int {
eb.mu.RLock()
defer eb.mu.RUnlock()
@@ -163,7 +164,7 @@ func (eb *EventBus[T]) GetListenersCount(topic string) int {
}
// GetAllListenersCount returns the total number of listeners.
// Play: todo
// Play: https://go.dev/play/p/PUlr0xcpEOz
func (eb *EventBus[T]) GetAllListenersCount() int {
eb.mu.RLock()
defer eb.mu.RUnlock()
@@ -178,7 +179,7 @@ func (eb *EventBus[T]) GetAllListenersCount() int {
}
// GetEvents returns all the events topics.
// Play: todo
// Play: https://go.dev/play/p/etgjjcOtAjX
func (eb *EventBus[T]) GetEvents() []string {
eb.mu.RLock()
defer eb.mu.RUnlock()

View File

@@ -136,9 +136,12 @@ func ExampleEventBus_ClearListeners() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListeners()
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
@@ -155,28 +158,31 @@ func ExampleEventBus_ClearListenersByTopic() {
}
eb.Subscribe("event1", listener, false, 0, nil)
eb.Subscribe("event2", listener, false, 0, nil)
eb.ClearListenersByTopic("event1")
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
fmt.Println(receivedData)
// Output:
// 0
// 2
}
func ExampleEventBus_GetListenersCount() {
eb := NewEventBus[int]()
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
count := eb.GetListenersCount("event1")
fmt.Println(count)
// Output:
// 2
// 1
}
func ExampleEventBus_SetErrorHandler() {

View File

@@ -172,10 +172,54 @@ func IsDir(path string) bool {
// RemoveFile remove the path file.
// Play: https://go.dev/play/p/P2y0XW8a1SH
func RemoveFile(path string) error {
func RemoveFile(path string, onDelete ...func(path string)) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if info.IsDir() {
return fmt.Errorf("%s is a directory", path)
}
if len(onDelete) > 0 && onDelete[0] != nil {
onDelete[0](path)
}
return os.Remove(path)
}
// RemoveDir remove the path directory.
// Play: todo
func RemoveDir(path string, onDelete ...func(path string)) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if !info.IsDir() {
return fmt.Errorf("%s is not a directory", path)
}
var callback func(string)
if len(onDelete) > 0 {
callback = onDelete[0]
}
err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
if err == nil && callback != nil {
callback(p)
}
return nil
})
if err != nil {
return err
}
return os.RemoveAll(path)
}
// CopyFile copy src file to dest file.
// Play: https://go.dev/play/p/Jg9AMJMLrJi
func CopyFile(srcPath string, dstPath string) error {

View File

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

View File

@@ -666,3 +666,17 @@ func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
}
return defaultValue
}
// FindValuesBy returns a slice of values from the map that satisfy the given predicate function.
// Play: todo
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V {
result := make([]V, 0)
for k, v := range m {
if predicate(k, v) {
result = append(result, v)
}
}
return result
}

View File

@@ -829,3 +829,21 @@ func ExampleOrderedMap_UnmarshalJSON() {
// fmt.Println(om.Elements())
}
func ExampleFindValuesBy() {
m := map[int]string{
1: "a",
2: "b",
3: "c",
4: "d",
}
result := FindValuesBy(m, func(k int, v string) bool {
return k%2 == 0
})
fmt.Println(result)
// Output:
// [b d]
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -221,7 +221,7 @@ func EqualWith[T, U any](slice1 []T, slice2 []U, comparator func(T, U) bool) boo
}
// EqualUnordered checks if two slices are equal: the same length and all elements' value are equal (unordered).
// Play: todo
// Play: https://go.dev/play/p/n8fSc2w8ZgX
func EqualUnordered[T comparable](slice1, slice2 []T) bool {
if len(slice1) != len(slice2) {
return false
@@ -1003,7 +1003,7 @@ func Reverse[T any](slice []T) {
}
// ReverseCopy return a new slice of element order is reversed to the given slice.
// Play: todo
// Play: https://go.dev/play/p/c9arEaP7Cg-
func ReverseCopy[T any](slice []T) []T {
result := make([]T, len(slice))
@@ -1027,7 +1027,7 @@ func Shuffle[T any](slice []T) []T {
}
// ShuffleCopy return a new slice with elements shuffled.
// Play: todo
// Play: https://go.dev/play/p/vqDa-Gs1vT0
func ShuffleCopy[T any](slice []T) []T {
result := make([]T, len(slice))
copy(result, slice)

View File

@@ -420,3 +420,12 @@ func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int {
func (s Stream[T]) ToSlice() []T {
return s.source
}
func ToMap[T any, K comparable, V any](s Stream[T], mapper func(item T) (K, V)) map[K]V {
result := map[K]V{}
for _, v := range s.source {
key, value := mapper(v)
result[key] = value
}
return result
}

View File

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

View File

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

View File

@@ -758,7 +758,7 @@ func ExtractContent(str, start, end string) []string {
}
// FindAllOccurrences returns the positions of all occurrences of a substring in a string.
// Play: todo
// Play: https://go.dev/play/p/uvyA6azGLB1
func FindAllOccurrences(str, substr string) []int {
var positions []int

View File

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

View File

@@ -21,6 +21,7 @@ import (
var (
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
@@ -181,6 +182,12 @@ func IsJSON(str string) bool {
return json.Unmarshal([]byte(str), &js) == nil
}
// IsAlphaNumericStr check if the string is alphanumeric.
// Play: todo
func IsAlphaNumeric(s string) bool {
return alphaNumericMatcher.MatchString(s)
}
// IsNumberStr check if the string can convert to a number.
// Play: https://go.dev/play/p/LzaKocSV79u
func IsNumberStr(s string) bool {
@@ -208,7 +215,7 @@ func IsIp(ipstr string) bool {
}
// IsIpPort check if the string is ip:port.
// Play:
// Play: https://go.dev/play/p/xUmls_b9L29
func IsIpPort(str string) bool {
host, port, err := net.SplitHostPort(str)
if err != nil {

View File

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