diff --git a/README.md b/README.md
index a24aade..105fc30 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@

-[](https://github.com/duke-git/lancet/releases)
+[](https://github.com/duke-git/lancet/releases)
[](https://pkg.go.dev/github.com/duke-git/lancet)
[](https://goreportcard.com/report/github.com/duke-git/lancet)
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -278,6 +278,7 @@ func After(n int, fn interface{}) func(args ...interface{}) []reflect.Value //c
func Before(n int, fn interface{}) func(args ...interface{}) []reflect.Value //creates a function that invokes func once it's called less than n times
func (f Fn) Curry(i interface{}) func(...interface{}) interface{} //make a curryed function
func Compose(fnList ...func(...interface{}) interface{}) func(...interface{}) interface{} //compose the functions from right to left
+func Debounced(fn func(), duration time.Duration) func() //creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.
func Delay(delay time.Duration, fn interface{}, args ...interface{}) //invoke function after delayed time
func Schedule(d time.Duration, fn interface{}, args ...interface{}) chan bool //invoke function every duration time, util close the returned bool chan
func (w *Watcher) Start() //start the watch timer.
@@ -434,7 +435,10 @@ func main() {
func Contain[T comparable](slice []T, value T) bool //check if the value is in the slice or not
func ContainSubSlice[T comparable](slice, subslice []T) bool //check if the slice contain subslice or not
func Chunk[T any](slice []T, size int) [][]T //creates an slice of elements split into groups the length of size.
+func Compact[T any](slice []T) []T //creates an slice with all falsey values removed. The values false, nil, 0, and "" are falsey
+func Concat[T any](slice []T, values ...[]T) []T //creates a new slice concatenating slice with any additional slices and/or values
func Difference[T comparable](slice1, slice2 []T) []T //creates an slice of whose element not included in the other given slice
+func DifferenceBy[T any](slice []T, comparedSlice []T, iteratee func(index int, t T) T) []T //it accepts iteratee which is invoked for each element of slice and values to generate the criterion by which they're compared.
func DeleteByIndex[T any](slice []T, start int, end ...int) []T //delete the element of slice from start index to end index - 1
func Drop[T any](slice []T, n int) []T //creates a slice with `n` elements dropped from the beginning when n > 0, or `n` elements dropped from the ending when n < 0
func Every[T any](slice []T, fn func(index int, t T) bool) bool //return true if all of the values in the slice pass the predicate function
@@ -496,6 +500,7 @@ func Capitalize(s string) string //convert the first character of a string to up
func IsString(v interface{}) bool //check if the value data type is string or not
func KebabCase(s string) string //covert string to kebab-case, "foo_Bar" -> "foo-bar"
func LowerFirst(s string) string //convert the first character of string to lower case
+func UpperFirst(s string) string //converts the first character of string to upper case
func PadEnd(source string, size int, padStr string) string //pads string on the right side if it's shorter than size
func PadStart(source string, size int, padStr string) string//pads string on the left side if it's shorter than size
func ReverseStr(s string) string //return string whose char order is reversed to the given string
@@ -586,6 +591,7 @@ func IsIp(ipstr string) bool //check if the string is a ip address
func IsIpV4(ipstr string) bool //check if the string is a ipv4 address
func IsIpV6(ipstr string) bool //check if the string is a ipv6 address
func IsStrongPassword(password string, length int) bool //check if the string is strong password (alpha(lower+upper) + number + special chars(!@#$%^&*()?><))
+func IsUrl(str string) bool //check if the string is url
func IsWeakPassword(password string) bool //check if the string is weak password(only letter or only number or letter + number)
```
diff --git a/README_zh-CN.md b/README_zh-CN.md
index 81ce7ec..cf74e44 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -4,7 +4,7 @@

-[](https://github.com/duke-git/lancet/releases)
+[](https://github.com/duke-git/lancet/releases)
[](https://pkg.go.dev/github.com/duke-git/lancet)
[](https://goreportcard.com/report/github.com/duke-git/lancet)
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -279,6 +279,7 @@ func Before(n int, fn interface{}) func(args ...interface{}) []reflect.Value //
func (f Fn) Curry(i interface{}) func(...interface{}) interface{} //函数柯里化
func Compose(fnList ...func(...interface{}) interface{}) func(...interface{}) interface{} //从右至左组合函数
func Delay(delay time.Duration, fn interface{}, args ...interface{}) //延迟调用函数
+func Debounced(fn func(), duration time.Duration) func() //go防抖函数,在duration时间内连续调用只会执行一次.
func Schedule(d time.Duration, fn interface{}, args ...interface{}) chan bool //每隔duration时间调用函数, 关闭返回通道可以停止调用
func (w *Watcher) Start() //开时watcher
func (w *Watcher) Stop() //开时watcher
@@ -434,7 +435,10 @@ func main() {
func Contain[T comparable](slice []T, value T) bool //判断slice是否包含value
func ContainSubSlice[T comparable](slice, subslice []T) bool //判断slice是否包含subslice
func Chunk[T any](slice []T, size int) [][]T //均分slice
+func Compact[T any](slice []T) []T //去除slice中的false vule. false values are false, nil, 0, and ""
+func Concat[T any](slice []T, values ...[]T) []T //连接values到slice中
func Difference[T comparable](slice1, slice2 []T) []T //返回切片,其元素在slice1中,不在slice2中
+func DifferenceBy[T any](slice []T, comparedSlice []T, iteratee func(index int, t T) T) []T //将slice 和comparedSlice中每个元素调用iterateeFn后作比较,如果不相等返回slice中的元素。
func DeleteByIndex[T any](slice []T, start int, end ...int) []T //删除切片中start到end位置的值(不包含end)
func Drop[T any](slice []T, n int) []T //创建一个新切片,当n大于0时删除原切片前n个元素,当n小于0时删除原切片后n个元素
func Every[T any](slice []T, fn func(index int, t T) bool) bool //slice中所有元素都符合函数条件时返回true, 否则返回false. 函数签名:func(int, t T) bool
@@ -495,6 +499,7 @@ func CamelCase(s string) string //字符串转为cameCase, "foo bar" -> "fooBar"
func Capitalize(s string) string //字符串转为Capitalize, "fOO" -> "Foo"
func IsString(v interface{}) bool //判断是否是字符串
func KebabCase(s string) string //字符串转为KebabCase, "foo_Bar" -> "foo-bar"
+func UpperFirst(s string) string //字符串的第一个字母转为大写字母
func LowerFirst(s string) string //字符串的第一个字母转为小写字母
func PadEnd(source string, size int, padStr string) string //字符串末尾填充size个字符
func PadStart(source string, size int, padStr string) string//字符串开头填充size个字符
@@ -587,6 +592,7 @@ func IsIp(ipstr string) bool //判断字符串是否是ip
func IsIpV4(ipstr string) bool //判断字符串是否是ipv4
func IsIpV6(ipstr string) bool //判断字符串是否是ipv6
func IsStrongPassword(password string, length int) bool //判断字符串是否是强密码(大小写字母+数字+特殊字符)
+func IsUrl(str string) bool //判断字符串是否是url
func IsWeakPassword(password string) bool //判断字符串是否是弱密码(只有字母或数字)
```
diff --git a/fileutil/file.go b/fileutil/file.go
index f042914..11812a4 100644
--- a/fileutil/file.go
+++ b/fileutil/file.go
@@ -262,7 +262,7 @@ func FileMode(path string) (fs.FileMode, error) {
}
// MiMeType return file mime type
-// file should be string or *os.File
+// param `file` should be string(file path) or *os.File
func MiMeType(file interface{}) string {
var mediatype string
diff --git a/function/function.go b/function/function.go
index 437c34e..9171305 100644
--- a/function/function.go
+++ b/function/function.go
@@ -70,6 +70,23 @@ func Delay(delay time.Duration, fn interface{}, args ...interface{}) {
invokeFunc(fn, args...)
}
+// Debounced creates a debounced function that delays invoking fn until after wait duration have elapsed since the last time the debounced function was invoked.
+func Debounced(fn func(), duration time.Duration) func() {
+ timer := time.NewTimer(duration)
+ timer.Stop()
+
+ go func() {
+ for {
+ select {
+ case <-timer.C:
+ go fn()
+ }
+ }
+ }()
+
+ return func() { timer.Reset(duration) }
+}
+
// Schedule invoke function every duration time, util close the returned bool chan
func Schedule(d time.Duration, fn interface{}, args ...interface{}) chan bool {
// Catch programming error while constructing the closure
diff --git a/function/function_test.go b/function/function_test.go
index 5ab909b..98f53f5 100644
--- a/function/function_test.go
+++ b/function/function_test.go
@@ -93,6 +93,28 @@ func TestDelay(t *testing.T) {
Delay(2*time.Second, print, "test delay")
}
+func TestDebounced(t *testing.T) {
+ assert := internal.NewAssert(t, "TestDebounced")
+
+ count := 0
+ add := func() {
+ count++
+ }
+
+ debouncedAdd := Debounced(add, 50*time.Microsecond)
+ debouncedAdd()
+ debouncedAdd()
+ debouncedAdd()
+ debouncedAdd()
+
+ time.Sleep(100 * time.Millisecond)
+ assert.Equal(1, count)
+
+ debouncedAdd()
+ time.Sleep(100 * time.Millisecond)
+ assert.Equal(2, count)
+}
+
func TestSchedule(t *testing.T) {
assert := internal.NewAssert(t, "TestSchedule")
diff --git a/slice/slice.go b/slice/slice.go
index 4cb12af..bad251e 100644
--- a/slice/slice.go
+++ b/slice/slice.go
@@ -13,9 +13,9 @@ import (
)
// Contain check if the value is in the iterable type or not
-func Contain[T comparable](slice []T, value T) bool {
+func Contain[T any](slice []T, value T) bool {
for _, v := range slice {
- if v == value {
+ if reflect.DeepEqual(v, value) {
return true
}
}
@@ -71,11 +71,14 @@ func Chunk[T any](slice []T, size int) [][]T {
return res
}
-// Difference creates an slice of whose element in slice1 but not in slice2
-func Difference[T comparable](slice1, slice2 []T) []T {
- var res []T
- for _, v := range slice1 {
- if !Contain(slice2, v) {
+// Compact creates an slice with all falsey values removed. The values false, nil, 0, and "" are falsey
+func Compact[T any](slice []T) []T {
+ res := make([]T, 0, 0)
+ for _, v := range slice {
+ if !reflect.DeepEqual(v, nil) &&
+ !reflect.DeepEqual(v, false) &&
+ !reflect.DeepEqual(v, "") &&
+ !reflect.DeepEqual(v, 0) {
res = append(res, v)
}
}
@@ -83,6 +86,46 @@ func Difference[T comparable](slice1, slice2 []T) []T {
return res
}
+// Concat creates a new slice concatenating slice with any additional slices and/or values.
+func Concat[T any](slice []T, values ...[]T) []T {
+ res := append([]T{}, slice...)
+
+ for _, v := range values {
+ res = append(res, v...)
+ }
+
+ return res
+}
+
+// Difference creates an slice of whose element in slice but not in comparedSlice
+func Difference[T comparable](slice, comparedSlice []T) []T {
+ var res []T
+ for _, v := range slice {
+ if !Contain(comparedSlice, v) {
+ res = append(res, v)
+ }
+ }
+
+ return res
+}
+
+// DifferenceBy it accepts iteratee which is invoked for each element of slice
+// and values to generate the criterion by which they're compared.
+// like lodash.js differenceBy: https://lodash.com/docs/4.17.15#differenceBy,
+func DifferenceBy[T any](slice []T, comparedSlice []T, iteratee func(index int, t T) T) []T {
+ orginSliceAfterMap := Map(slice, iteratee)
+ comparedSliceAfterMap := Map(comparedSlice, iteratee)
+
+ res := make([]T, 0, 0)
+ for i, v := range orginSliceAfterMap {
+ if !Contain(comparedSliceAfterMap, v) {
+ res = append(res, slice[i])
+ }
+ }
+
+ return res
+}
+
// Every return true if all of the values in the slice pass the predicate function.
// The fn function signature should be func(int, T) bool .
func Every[T any](slice []T, fn func(index int, t T) bool) bool {
diff --git a/slice/slice_test.go b/slice/slice_test.go
index 66aab40..d914b34 100644
--- a/slice/slice_test.go
+++ b/slice/slice_test.go
@@ -29,6 +29,7 @@ func TestChunk(t *testing.T) {
assert := internal.NewAssert(t, "TestChunk")
arr := []string{"a", "b", "c", "d", "e"}
+
r1 := [][]string{{"a"}, {"b"}, {"c"}, {"d"}, {"e"}}
assert.Equal(r1, Chunk(arr, 1))
@@ -45,6 +46,27 @@ func TestChunk(t *testing.T) {
assert.Equal(r5, Chunk(arr, 5))
}
+func TestCompact(t *testing.T) {
+ assert := internal.NewAssert(t, "TesCompact")
+
+ assert.Equal([]int{}, Compact([]int{0}))
+ assert.Equal([]int{1, 2, 3}, Compact([]int{0, 1, 2, 3}))
+ assert.Equal([]string{}, Compact([]string{""}))
+ assert.Equal([]string{" "}, Compact([]string{" "}))
+ assert.Equal([]string{"a", "b", "0"}, Compact([]string{"", "a", "b", "0"}))
+ assert.Equal([]bool{true, true}, Compact([]bool{false, true, true}))
+}
+
+func TestConcat(t *testing.T) {
+ assert := internal.NewAssert(t, "Concat")
+
+ // assert.Equal([]int{0}, Concat([]int{}, 0))
+ // assert.Equal([]int{1, 2, 3, 4, 5}, Concat([]int{1, 2, 3}, 4, 5))
+ assert.Equal([]int{1, 2, 3, 4, 5}, Concat([]int{1, 2, 3}, []int{4, 5}))
+ assert.Equal([]int{1, 2, 3, 4, 5}, Concat([]int{1, 2, 3}, []int{4}, []int{5}))
+ // assert.Equal([]int{1, 2, 3, 4, 5}, Concat([]int{1, 2, 3}, []int{4}, 5))
+}
+
func TestEvery(t *testing.T) {
nums := []int{1, 2, 3, 5}
isEven := func(i, num int) bool {
@@ -377,6 +399,17 @@ func TestDifference(t *testing.T) {
assert.Equal([]int{1, 2, 3}, Difference(s1, s2))
}
+func TestDifferenceBy(t *testing.T) {
+ assert := internal.NewAssert(t, "TestDifferenceBy")
+
+ s1 := []int{1, 2, 3, 4, 5} //after add one: 2 3 4 5 6
+ s2 := []int{3, 4, 5} //after add one: 4 5 6
+ addOne := func(i int, v int) int {
+ return v + 1
+ }
+ assert.Equal([]int{1, 2}, DifferenceBy(s1, s2, addOne))
+}
+
func TestSortByFielDesc(t *testing.T) {
assert := internal.NewAssert(t, "TestWithout")
diff --git a/strutil/string.go b/strutil/string.go
index f3b1094..268099d 100644
--- a/strutil/string.go
+++ b/strutil/string.go
@@ -54,6 +54,18 @@ func Capitalize(s string) string {
return string(out)
}
+// UpperFirst converts the first character of string to upper case.
+func UpperFirst(s string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ r, size := utf8.DecodeRuneInString(s)
+ r = unicode.ToUpper(r)
+
+ return string(r) + s[size:]
+}
+
// LowerFirst converts the first character of string to lower case.
func LowerFirst(s string) string {
if len(s) == 0 {
diff --git a/strutil/string_test.go b/strutil/string_test.go
index ac7e35c..16432b7 100644
--- a/strutil/string_test.go
+++ b/strutil/string_test.go
@@ -50,6 +50,17 @@ func TestSnakeCase(t *testing.T) {
assert.NotEqual("foo-bar", SnakeCase("foo_Bar"))
}
+func TestUpperFirst(t *testing.T) {
+ assert := internal.NewAssert(t, "TestLowerFirst")
+
+ assert.Equal("Foo", UpperFirst("foo"))
+ assert.Equal("BAR", UpperFirst("bAR"))
+ assert.Equal("FOo", UpperFirst("FOo"))
+ assert.Equal("FOo大", UpperFirst("fOo大"))
+
+ assert.NotEqual("Bar", UpperFirst("BAR"))
+}
+
func TestLowerFirst(t *testing.T) {
assert := internal.NewAssert(t, "TestLowerFirst")
diff --git a/system/os.go b/system/os.go
index f9a290e..91a1cb9 100644
--- a/system/os.go
+++ b/system/os.go
@@ -56,6 +56,9 @@ func ExecCommand(command string) (stdout, stderr string, err error) {
var errout bytes.Buffer
cmd := exec.Command("/bin/bash", "-c", command)
+ if IsWindows() {
+ cmd = exec.Command("cmd")
+ }
cmd.Stdout = &out
cmd.Stderr = &errout
err = cmd.Run()
diff --git a/system/os_test.go b/system/os_test.go
index 1d5ac1f..d70813f 100644
--- a/system/os_test.go
+++ b/system/os_test.go
@@ -56,5 +56,7 @@ func TestExecCommand(t *testing.T) {
t.Logf("error: %v\n", err)
}
- assert.IsNotNil(err)
+ if !IsWindows() {
+ assert.IsNotNil(err)
+ }
}
diff --git a/validator/validator.go b/validator/validator.go
index bb1b8bd..362d8ec 100644
--- a/validator/validator.go
+++ b/validator/validator.go
@@ -7,6 +7,7 @@ package validator
import (
"encoding/json"
"net"
+ "net/url"
"regexp"
"strconv"
"strings"
@@ -123,6 +124,27 @@ func IsPort(str string) bool {
return false
}
+var isUrlRegexMatcher *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]*)?$`)
+
+// IsUrl check if the string is url.
+func IsUrl(str string) bool {
+ if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
+ return false
+ }
+ u, err := url.Parse(str)
+ if err != nil {
+ return false
+ }
+ if strings.HasPrefix(u.Host, ".") {
+ return false
+ }
+ if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
+ return false
+ }
+
+ return isUrlRegexMatcher.MatchString(str)
+}
+
var isDnsRegexMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$`)
// IsDns check if the string is dns.
diff --git a/validator/validator_test.go b/validator/validator_test.go
index 6d07bb6..f9f41f9 100644
--- a/validator/validator_test.go
+++ b/validator/validator_test.go
@@ -160,11 +160,21 @@ func TestIsIpV6(t *testing.T) {
assert.Equal(true, IsIpV6("::0:0:0:0:0:0:1"))
}
+func TestIsUrl(t *testing.T) {
+ assert := internal.NewAssert(t, "TestIsUrl")
+
+ assert.Equal(true, IsUrl("http://abc.com"))
+ assert.Equal(true, IsUrl("abc.com"))
+ assert.Equal(true, IsUrl("a.b.com"))
+ assert.Equal(false, IsUrl("abc"))
+}
+
func TestIsDns(t *testing.T) {
assert := internal.NewAssert(t, "TestIsDns")
assert.Equal(true, IsDns("abc.com"))
assert.Equal(false, IsDns("a.b.com"))
+ assert.Equal(false, IsDns("http://abc.com"))
}
func TestIsEmail(t *testing.T) {