From 3ca096b4acb0f2036d8dbf209178b76f6c2ed376 Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Fri, 21 Jan 2022 14:54:55 +0800
Subject: [PATCH 1/9] update MiMeType func comment
---
fileutil/file.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
From a3399503f767fdb18a342cabea9ee982e31caa0a Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Fri, 21 Jan 2022 17:13:31 +0800
Subject: [PATCH 2/9] feat: add Debounced func
---
function/function.go | 17 +++++++++++++++++
function/function_test.go | 22 ++++++++++++++++++++++
2 files changed, 39 insertions(+)
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")
From aa64bf5bee28230ad88b78f577613b3112229bf3 Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Sat, 22 Jan 2022 18:11:52 +0800
Subject: [PATCH 3/9] feat: add IsUrl func
---
validator/validator.go | 22 ++++++++++++++++++++++
validator/validator_test.go | 10 ++++++++++
2 files changed, 32 insertions(+)
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) {
From ca88687f3de2912ee0980d9942fcefc650cbb0d4 Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Sat, 22 Jan 2022 18:55:41 +0800
Subject: [PATCH 4/9] feat: add UpperFirst func
---
strutil/string.go | 12 ++++++++++++
strutil/string_test.go | 11 +++++++++++
2 files changed, 23 insertions(+)
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")
From 6a1a0b8677e132fc53a828d715256833533fbf48 Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Sat, 22 Jan 2022 21:16:34 +0800
Subject: [PATCH 5/9] feat: add Concat func
---
slice/slice.go | 59 +++++++++++++++++++++++++++++++++++++++------
slice/slice_test.go | 53 ++++++++++++++++++++--------------------
2 files changed, 77 insertions(+), 35 deletions(-)
diff --git a/slice/slice.go b/slice/slice.go
index ea9e1f8..0e978a6 100644
--- a/slice/slice.go
+++ b/slice/slice.go
@@ -102,21 +102,64 @@ func Chunk(slice []interface{}, size int) [][]interface{} {
return res
}
-// Difference creates an slice of whose element not included in the other given slice
-func Difference(slice1, slice2 interface{}) interface{} {
- v := sliceValue(slice1)
+// Compact creates an slice with all falsey values removed. The values false, nil, 0, and "" are falsey
+func Compact(slice interface{}) interface{} {
+ sv := sliceValue(slice)
var indexes []int
- for i := 0; i < v.Len(); i++ {
- vi := v.Index(i).Interface()
- if !Contain(slice2, vi) {
+ for i := 0; i < sv.Len(); i++ {
+ item := sv.Index(i).Interface()
+ if item != nil && item != false && item != "" && item != 0 {
+ indexes = append(indexes, i)
+ }
+ }
+ res := reflect.MakeSlice(sv.Type(), len(indexes), len(indexes))
+ for i := range indexes {
+ res.Index(i).Set(sv.Index(indexes[i]))
+ }
+
+ return res.Interface()
+}
+
+// Concat creates a new slice concatenating slice with any additional slices and/or values.
+func Concat(slice interface{}, values ...interface{}) interface{} {
+ sv := sliceValue(slice)
+ size := sv.Len()
+
+ res := reflect.MakeSlice(sv.Type(), size, size)
+ for i := 0; i < size; i++ {
+ res.Index(i).Set(sv.Index(i))
+ }
+
+ for _, v := range values {
+ if reflect.TypeOf(v).Kind() == reflect.Slice {
+ vv := reflect.ValueOf(v)
+ for i := 0; i < vv.Len(); i++ {
+ res = reflect.Append(res, vv.Index(i))
+ }
+ } else {
+ res = reflect.Append(res, reflect.ValueOf(v))
+ }
+ }
+
+ return res.Interface()
+}
+
+// Difference creates an slice of whose element in slice1, not in slice2
+func Difference(slice1, slice2 interface{}) interface{} {
+ sv := sliceValue(slice1)
+
+ var indexes []int
+ for i := 0; i < sv.Len(); i++ {
+ item := sv.Index(i).Interface()
+ if !Contain(slice2, item) {
indexes = append(indexes, i)
}
}
- res := reflect.MakeSlice(v.Type(), len(indexes), len(indexes))
+ res := reflect.MakeSlice(sv.Type(), len(indexes), len(indexes))
for i := range indexes {
- res.Index(i).Set(v.Index(indexes[i]))
+ res.Index(i).Set(sv.Index(indexes[i]))
}
return res.Interface()
}
diff --git a/slice/slice_test.go b/slice/slice_test.go
index ea452d8..9bd5b51 100644
--- a/slice/slice_test.go
+++ b/slice/slice_test.go
@@ -36,44 +36,43 @@ func TestChunk(t *testing.T) {
assert := internal.NewAssert(t, "TestChunk")
arr := []string{"a", "b", "c", "d", "e"}
- r1 := [][]interface{}{
- {"a"},
- {"b"},
- {"c"},
- {"d"},
- {"e"},
- }
+ r1 := [][]interface{}{{"a"}, {"b"}, {"c"}, {"d"}, {"e"}}
assert.Equal(r1, Chunk(InterfaceSlice(arr), 1))
- r2 := [][]interface{}{
- {"a", "b"},
- {"c", "d"},
- {"e"},
- }
+ r2 := [][]interface{}{{"a", "b"}, {"c", "d"}, {"e"}}
assert.Equal(r2, Chunk(InterfaceSlice(arr), 2))
- r3 := [][]interface{}{
- {"a", "b", "c"},
- {"d", "e"},
- }
+ r3 := [][]interface{}{{"a", "b", "c"}, {"d", "e"}}
assert.Equal(r3, Chunk(InterfaceSlice(arr), 3))
- r4 := [][]interface{}{
- {"a", "b", "c", "d"},
- {"e"},
- }
+ r4 := [][]interface{}{{"a", "b", "c", "d"}, {"e"}}
assert.Equal(r4, Chunk(InterfaceSlice(arr), 4))
- r5 := [][]interface{}{
- {"a"},
- {"b"},
- {"c"},
- {"d"},
- {"e"},
- }
+ r5 := [][]interface{}{{"a"}, {"b"}, {"c"}, {"d"}, {"e"}}
assert.Equal(r5, Chunk(InterfaceSlice(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 {
From 92967e0add1cb3670bb5cc196a0dcf28bdd4cef8 Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Sat, 22 Jan 2022 21:21:12 +0800
Subject: [PATCH 6/9] fix: TestCompact for blank string case
---
slice/slice_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/slice/slice_test.go b/slice/slice_test.go
index 9bd5b51..82dbb61 100644
--- a/slice/slice_test.go
+++ b/slice/slice_test.go
@@ -58,7 +58,7 @@ func TestCompact(t *testing.T) {
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{" "}, Compact([]string{" "}))
assert.Equal([]string{"a", "b", "0"}, Compact([]string{"", "a", "b", "0"}))
assert.Equal([]bool{true, true}, Compact([]bool{false, true, true}))
}
From 6f035f710ee52e8f0311ea4574bb6191320cfd2f Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Sun, 23 Jan 2022 14:27:37 +0800
Subject: [PATCH 7/9] feat: add DifferenceBy func
---
slice/slice.go | 39 +++++++++++++++++++++++++++++++++++++++
slice/slice_test.go | 11 +++++++++++
2 files changed, 50 insertions(+)
diff --git a/slice/slice.go b/slice/slice.go
index 0e978a6..9f0b7fb 100644
--- a/slice/slice.go
+++ b/slice/slice.go
@@ -164,6 +164,45 @@ func Difference(slice1, slice2 interface{}) interface{} {
return res.Interface()
}
+// 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,
+// the iterateeFn function signature should be func(index int, value interface{}) interface{}.
+func DifferenceBy(slice interface{}, comparedSlice interface{}, iterateeFn interface{}) interface{} {
+ sv := sliceValue(slice)
+ smv := sliceValue(comparedSlice)
+ fn := functionValue(iterateeFn)
+
+ elemType := sv.Type().Elem()
+ if checkSliceCallbackFuncSignature(fn, elemType, nil) {
+ panic("function param should be of type func(" + elemType.String() + ")" + elemType.String())
+ }
+
+ slice1 := reflect.MakeSlice(sv.Type(), sv.Len(), sv.Len())
+ for i := 0; i < sv.Len(); i++ {
+ slice1.Index(i).Set(fn.Call([]reflect.Value{reflect.ValueOf(i), sv.Index(i)})[0])
+ }
+
+ slice2 := reflect.MakeSlice(smv.Type(), smv.Len(), smv.Len())
+ for i := 0; i < smv.Len(); i++ {
+ slice2.Index(i).Set(fn.Call([]reflect.Value{reflect.ValueOf(i), smv.Index(i)})[0])
+ }
+
+ sliceAfterMap := slice1.Interface()
+ comparedSliceAfterMap := slice2.Interface()
+
+ res := reflect.MakeSlice(sv.Type(), 0, 0)
+ sm := sliceValue(sliceAfterMap)
+ for i := 0; i < sm.Len(); i++ {
+ item := sm.Index(i).Interface()
+ if !Contain(comparedSliceAfterMap, item) {
+ res = reflect.Append(res, sv.Index(i))
+ }
+ }
+
+ return res.Interface()
+}
+
// Every return true if all of the values in the slice pass the predicate function.
// The function signature should be func(index int, value interface{}) bool .
func Every(slice, function interface{}) bool {
diff --git a/slice/slice_test.go b/slice/slice_test.go
index 82dbb61..eec492d 100644
--- a/slice/slice_test.go
+++ b/slice/slice_test.go
@@ -439,6 +439,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")
From e07d54d1daaa6209025a55c841689080f271bd44 Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Sun, 23 Jan 2022 14:40:59 +0800
Subject: [PATCH 8/9] fix: fix exec windows command test failed
---
system/os.go | 3 +++
system/os_test.go | 4 +++-
2 files changed, 6 insertions(+), 1 deletion(-)
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)
+ }
}
From 8bb102cb6e2046ab9ca98ee1f9295e1c2db9445f Mon Sep 17 00:00:00 2001
From: dudaodong
Date: Mon, 24 Jan 2022 10:10:38 +0800
Subject: [PATCH 9/9] release v1.2.2
---
README.md | 8 +++++++-
README_zh-CN.md | 8 +++++++-
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 3dfee18..1d10615 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(slice interface{}, value interface{}) bool //check if the value is in the slice or not
func ContainSubSlice(slice interface{}, subslice interface{}) bool //check if the slice contain subslice or not
func Chunk(slice []interface{}, size int) [][]interface{} //creates an slice of elements split into groups the length of `size`
+func Compact(slice interface{}) interface{} //creates an slice with all falsey values removed. The values false, nil, 0, and "" are falsey
+func Concat(slice interface{}, values ...interface{}) interface{} //creates a new slice concatenating slice with any additional slices and/or values
func Difference(slice1, slice2 interface{}) interface{} //creates an slice of whose element not included in the other given slice
+func DifferenceBy(slice interface{}, comparedSlice interface{}, iterateeFn interface{}) interface{} //it accepts iteratee which is invoked for each element of slice and values to generate the criterion by which they're compared.
func DeleteByIndex(slice interface{}, start int, end ...int) (interface{}, error) //delete the element of slice from start index to end index - 1
func Drop(slice interface{}, n int) interface{} //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(slice, function interface{}) bool //return true if all of the values in the slice pass the predicate function, function signature should be func(index int, value interface{}) bool
@@ -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,5 +591,6 @@ 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 b9a653b..6c65093 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(slice interface{}, value interface{}) bool //判断slice是否包含value
func ContainSubSlice(slice interface{}, subslice interface{}) bool //判断slice是否包含subslice
func Chunk(slice []interface{}, size int) [][]interface{} //均分slice
+func Compact(slice interface{}) interface{} //去除slice中的false vule. false values are false, nil, 0, and ""
+func Concat(slice interface{}, values ...interface{}) interface{} //连接values到slice中
func Difference(slice1, slice2 interface{}) interface{} //返回切片,其元素在slice1中,不在slice2中
+func DifferenceBy(slice interface{}, comparedSlice interface{}, iterateeFn interface{}) interface{} //将slice 和comparedSlice中每个元素调用iterateeFn后作比较,如果不相等返回slice中的元素。
func DeleteByIndex(slice interface{}, start int, end ...int) (interface{}, error) //删除切片中start到end位置的值
func Drop(slice interface{}, n int) interface{} //创建一个新切片,当n大于0时删除原切片前n个元素,当n小于0时删除原切片后n个元素
func Every(slice, function interface{}) bool //slice中所有元素都符合函数条件时返回true, 否则返回false. 函数签名:func(index int, value interface{}) 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,5 +592,6 @@ 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 //判断字符串是否是弱密码(只有字母或数字)
```