diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1e71714
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.idea/*
+.vscode/*
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..049fa37
--- /dev/null
+++ b/README.md
@@ -0,0 +1,444 @@
+
+
Lancet
+
+ Lancet is a comprehensive, efficient, and reusable util function library of go. Inspired by the java apache common package and lodash.js.
+
+
+
+
+
+
+English | [简体中文](./README_zh-CN.md)
+
+
+
+### Feature
+
+- 👏 Comprehensive, efficient and reusable.
+- 💪 100+ common go util functions, support string, slice, datetime, net, crypt...
+- 💅 Only depend on the go standard library.
+- 🌍 Unit test for exery exported function.
+
+### Installation
+
+```go
+go get github.com/duke-git/lancet
+```
+
+### Usage
+Lancet organizes the code into package structure, and you need to import the corresponding package name when use it. For example, if you use string-related functions,import the strutil package like below:
+
+```go
+import "github.com/duke-git/lancet/strutil"
+```
+
+### Example
+
+Here takes the string function ReverseStr (reverse order string) as an example, and the strutil package needs to be imported.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/strutil"
+)
+
+func main() {
+ s := "hello"
+ rs := strutil.ReverseStr(s)
+ fmt.Println(rs) //olleh
+}
+```
+
+### API Documentation
+
+#### 1. convertor contains some functions for data convertion.
+
+- Support conversion between commonly used data types.
+- Usage: import "github.com/duke-git/lancet/cryptor"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/convertor"
+)
+
+func main() {
+ s := "12.3"
+ f, err := convertor.ToFloat(s)
+ if err != nil {
+ fmt.Errorf("error is %s", err.Error())
+ }
+ fmt.Println(f) // 12.3
+}
+```
+
+- Function list:
+
+```go
+func ColorHexToRGB(colorHex string) (red, green, blue int) //convert color hex to color rgb
+func ColorRGBToHex(red, green, blue int) string //convert color rgb to color hex
+func ToBool(s string) (bool, error) //convert string to a boolean
+func ToBytes(data interface{}) ([]byte, error) //convert interface to bytes
+func ToChar(s string) []string //convert string to char slice
+func ToFloat(value interface{}) (float64, error) //convert value to float64, if input is not a float return 0.0 and error
+func ToInt(value interface{}) (int64, error) //convert value to int64, if input is not a numeric format return 0 and error
+func ToJson(value interface{}) (string, error) //convert value to a valid json string
+func ToString(value interface{}) string //convert value to string
+func StructToMap(value interface{}) (map[string]interface{}, error) //convert struct to map, only convert exported field, tag `json` should be set
+```
+
+#### 2. cryptor is for data encryption and decryption.
+
+- Support md5, hmac, aes, des, ras.
+- Usage: import "github.com/duke-git/lancet/cryptor"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/cryptor"
+)
+
+func main() {
+ data := "hello"
+ key := "abcdefghijklmnop"
+
+ encrypted := cryptor.AesCbcEncrypt([]byte(data), []byte(key))
+ decrypted := cryptor.AesCbcDecrypt(encrypted, []byte(key))
+ fmt.Println(string(decrypted)) // hello
+}
+```
+
+- Function list:
+
+```go
+func AesEcbEncrypt(data, key []byte) []byte //AES ECB encrypt
+func AesEcbDecrypt(encrypted, key []byte) []byte //AES ECB decrypt
+func AesCbcEncrypt(data, key []byte) []byte //AES CBC encrypt
+func AesCbcDecrypt(encrypted, key []byte) []byte //AES CBC decrypt
+func AesCtrCrypt(data, key []byte) []byte //AES CTR encrypt / decrypt
+func AesCfbEncrypt(data, key []byte) []byte //AES CFB encrypt
+func AesCfbDecrypt(encrypted, key []byte) []byte //AES CFB decrypt
+func AesOfbEncrypt(data, key []byte) []byte //AES OFB encrypt
+func AesOfbDecrypt(data, key []byte) []byte //AES OFB decrypt
+func Base64StdEncode(s string) string //base64 encode
+func Base64StdDecode(s string) string //base64 decode
+func DesCbcEncrypt(data, key []byte) []byte //DES CBC encrypt
+func DesCbcDecrypt(encrypted, key []byte) []byte //DES CBC decrypt
+func DesCtrCrypt(data, key []byte) []byte //DES CTR encrypt/decrypt
+func DesCfbEncrypt(data, key []byte) []byte //DES CFB encrypt
+func DesCfbDecrypt(encrypted, key []byte) []byte //DES CFB decrypt
+func DesOfbEncrypt(data, key []byte) []byte //DES OFB encrypt
+func DesOfbDecrypt(data, key []byte) []byte //DES OFB decrypt
+func HmacMd5(data, key string) string //get hmac md5 value
+func HmacSha1(data, key string) string //get hmac sha1 value
+func HmacSha256(data, key string) string //get hmac sha256 value
+func HmacSha512(data, key string) string //get hmac sha512 value
+func Sha1(data string) string //get sha1 value
+func Sha256(data string) string //getsha256 value
+func Sha512(data string) string //get sha512 value
+func GenerateRsaKey(keySize int, priKeyFile, pubKeyFile string) //generate RSA pem file
+func RsaEncrypt(data []byte, pubKeyFileName string) []byte //RSA encrypt
+func RsaDecrypt(data []byte, privateKeyFileName string) []byte //RSA decrypt
+
+```
+
+#### 3. datetime parse and format datetime
+
+- Parse and format datetime
+- Usage: import "github.com/duke-git/lancet/datetime"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/datetime"
+)
+
+func main() {
+ now := time.Now()
+ s := datetime.FormatTimeToStr(now, "yyyy-mm-dd hh:mm:ss")
+ fmt.Println(s) // 2021-11-24 11:16:55
+}
+```
+
+- Function list:
+
+```go
+func AddDay(t time.Time, day int64) time.Time //add or sub days to time
+func AddHour(t time.Time, hour int64) time.Time //add or sub hours to time
+func AddMinute(t time.Time, minute int64) time.Time //add or sub minutes to time
+func GetNowDate() string //get current date, format is yyyy-mm-dd
+func GetNowTime() string //get current time, format is hh:mm:ss
+func GetNowDateTime() string //get current date and time, format is yyyy-mm-dd hh:mm:ss
+func GetZeroHourTimestamp() int64 //return timestamp of zero hour (timestamp of 00:00)
+func GetNightTimestamp() int64 //return timestamp of zero hour (timestamp of 23:59)
+func FormatTimeToStr(t time.Time, format string) string //convert time to string
+func FormatStrToTime(str, format string) time.Time //convert string to time
+```
+
+#### 4. fileutil basic functions for file operations
+
+- Basic functions for file operations.
+- Usage: import "github.com/duke-git/lancet/fileutil"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/fileutil"
+)
+
+func main() {
+ fmt.Println(fileutil.IsDir("./")) // true
+}
+```
+
+- 函数列表:
+
+```go
+func CreateFile(path string) bool // create a file in path
+func CopyFile(srcFilePath string, dstFilePath string) error //copy src file to dst file
+func IsExist(path string) bool //checks if a file or directory exists
+func IsDir(path string) bool //checks if the path is directy or not
+func ListFileNames(path string) ([]string, error) //return all file names in the path
+func RemoveFile(path string) error //remove the path file
+```
+
+#### 5. formatter is for data format
+
+- Contain some formatting function
+- Usage: import "github.com/duke-git/lancet/formatter"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/formatter"
+)
+
+func main() {
+ fmt.Println(formatter.Comma("12345", "")) // "12,345"
+ fmt.Println(formatter.Comma(12345.67, "¥")) // "¥12,345.67"
+}
+```
+
+- Function list:
+
+```go
+func Comma(v interface{}, symbol string) string //add comma to number by every 3 numbers from right. ahead by symbol char
+```
+
+#### 6. netutil is for net process
+
+- Ip and http request method.
+- Usage: import "github.com/duke-git/lancet/netutil".
+- The Http function params order:url, header, query string, body, httpclient.
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/netutil"
+)
+
+func main() {
+ url := "https://gutendex.com/books?"
+ header := make(map[string]string)
+ header["Content-Type"] = "application/json"
+ queryParams := make(map[string]interface{})
+ queryParams["ids"] = "1"
+
+ resp, err := netutil.HttpGet(url, header, queryParams)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println("response: ", resp.StatusCode, string(body))
+}
+```
+
+- Function list:
+
+```go
+func GetInternalIp() string //get internal ip
+func GetPublicIpInfo() (*PublicIpInfo, error) //get public ip info: country, region, isp, city, lat, lon, ip
+func IsPublicIP(IP net.IP) bool //判断ip是否为公共ip
+func HttpGet(url string, params ...interface{}) (*http.Response, error) //http get request
+func HttpPost(url string, params ...interface{}) (*http.Response, error) //http post request
+func HttpPut(url string, params ...interface{}) (*http.Response, error) //http put request
+func HttpDelete(url string, params ...interface{}) (*http.Response, error) //http delete request
+func HttpPatch(url string, params ...interface{}) (*http.Response, error) //http patch request
+func ConvertMapToQueryString(param map[string]interface{}) string //convert map to url query string
+```
+
+#### 7. random is for rand string and int generation.
+
+- Generate random string and int.
+- Usage: import "github.com/duke-git/lancet/random".
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/random"
+)
+
+func main() {
+ randStr := random.RandString(6)
+ fmt.Println(randStr)
+}
+```
+
+- Function list:
+
+```go
+func RandBytes(length int) []byte //generate random []byte
+func RandInt(min, max int) int //generate random int
+func RandString(length int) string //generate random string
+```
+
+#### 8. slice is for process slice
+
+- Contain function for process slice.
+- Usage: import "github.com/duke-git/lancet/slice"
+- Due to the unstable support of generic, most of the slice processing function parameter and return value is interface {}. After go generic is stable, the related functions will be refactored.
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/slice"
+)
+
+func main() {
+ nums := []int{1, 4, 3, 4, 6, 7, 3}
+ uniqueNums, _ := slice.IntSlice(slice.Unique(nums))
+ fmt.Println(uniqueNums) //[1 4 3 6 7]
+}
+```
+
+- Function list:
+
+```go
+func Contain(slice interface{}, value interface{}) bool //check if the value is in the slice or not
+func Chunk(slice []interface{}, size int) [][]interface{} //creates an slice of elements split into groups the length of `size`.
+func ConvertSlice(originalSlice interface{}, newSliceType reflect.Type) interface{} //convert originalSlice to newSliceType
+func Difference(slice1, slice2 interface{}) interface{} //creates an slice of whose element not included in the other given slice
+func DeleteByIndex(slice interface{}, start int, end ...int) (interface{}, error) //delete the element of slice from start index to end index - 1
+func Filter(slice, function interface{}) interface{} //filter slice, function signature should be func(index int, value interface{}) bool
+func IntSlice(slice interface{}) ([]int, error) //convert value to int slice
+func InterfaceSlice(slice interface{}) []interface{} //convert value to interface{} slice
+func InsertByIndex(slice interface{}, index int, value interface{}) (interface{}, error) //insert the element into slice at index.
+func Map(slice, function interface{}) interface{} //map lisce, function signature should be func(index int, value interface{}) interface{}
+func ReverseSlice(slice interface{}) //revere slice
+func Reduce(slice, function, zero interface{}) interface{} //reduce slice, function signature should be func(index int, value1, value2 interface{}) interface{}
+func SortByField(slice interface{}, field string, sortType ...string) error //sort struct slice by field
+func StringSlice(slice interface{}) []string //convert value to string slice
+func Unique(slice interface{}) interface{} //remove duplicate elements in slice
+func UpdateByIndex(slice interface{}, index int, value interface{}) (interface{}, error) //update the slice element at index.
+```
+
+#### 9. strutil is for processing string
+
+- Contain functions to precess string
+- Usage: import "github.com/duke-git/lancet/strutil"
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/strutil"
+)
+
+func main() {
+ str := "Foo-Bar"
+ camelCaseStr := strutil.CamelCase(str)
+ fmt.Println(camelCaseStr) //fooBar
+}
+```
+
+- Function list:
+
+```go
+func After(s, char string) string //create substring in source string after position when char first appear
+func AfterLast(s, char string) string //create substring in source string after position when char last appear
+func Before(s, char string) string //create substring in source string before position when char first appear
+func BeforeLast(s, char string) string //create substring in source string before position when char last appear
+func CamelCase(s string) string //covert string to camelCase string. "foo bar" -> "fooBar"
+func Capitalize(s string) string //convert the first character of a string to upper case, "fOO" -> "Foo"
+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 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
+func SnakeCase(s string) string //covert string to snake_case "fooBar" -> "foo_bar"
+```
+
+#### 10. validator is for data validation
+
+- Contain function for data validation.
+- Usage: import "github.com/duke-git/lancet/validator".
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/validator"
+)
+
+func main() {
+ str := "Foo-Bar"
+ isAlpha := validator.IsAlpha(str)
+ fmt.Println(isAlpha) //false
+}
+```
+
+- Function list:
+
+```go
+func ContainChinese(s string) bool //check if the string contain mandarin chinese
+func IsAlpha(s string) bool //checks if the string contains only letters (a-zA-Z)
+func IsBase64(base64 string) bool //check if the string is base64 string
+func IsChineseMobile(mobileNum string) bool //check if the string is chinese mobile number
+func IsChineseIdNum(id string) bool //check if the string is chinese id number
+func IsChinesePhone(phone string) bool //check if the string is chinese phone number
+func IsCreditCard(creditCart string) bool //check if the string is credit card
+func IsDns(dns string) bool //check if the string is dns
+func IsEmail(email string) bool //check if the string is a email address
+func IsEmptyString(s string) bool //check if the string is empty
+func IsFloatStr(s string) bool //check if the string can convert to a float
+func IsNumberStr(s string) bool //check if the string can convert to a number
+func IsRegexMatch(s, regex string) bool //check if the string match the regexp
+func IsIntStr(s string) bool //check if the string can convert to a integer
+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 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
new file mode 100644
index 0000000..4d7e0a7
--- /dev/null
+++ b/README_zh-CN.md
@@ -0,0 +1,445 @@
+
+
Lancet
+
+ lancet(柳叶刀)是一个全面、高效、可复用的go语言工具函数库。 lancet受到了java apache common包和lodash.js的启发。
+
+
+
+
+
+
+简体中文 | [English](./README.md)
+
+
+
+### 特性
+
+- 👏 全面、高效、可复用
+- 💪 100+常用go工具函数,支持string、slice、datetime、net、crypt...
+- 💅 只依赖go标准库
+- 🌍 所有导出函数单测试覆盖率100%
+
+### 安装
+
+```go
+go get github.com/duke-git/lancet
+```
+
+### 用法
+
+lancet是以包的结构组织代码的,使用时需要导入相应的包名。例如:如果使用字符串相关函数,需要导入strutil包:
+
+```go
+import "github.com/duke-git/lancet/strutil"
+```
+
+### 例子
+
+此处以字符串工具函数ReverseStr(逆序字符串)为例,需要导入strutil包:
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/strutil"
+)
+
+func main() {
+ s := "hello"
+ rs := strutil.ReverseStr(s)
+ fmt.Println(rs) //olleh
+}
+```
+
+### API文档
+
+#### 1. convertor数据转换包
+
+- 转换函数支持常用数据类型之间的转换
+- 导入包:import "github.com/duke-git/lancet/cryptor"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/convertor"
+)
+
+func main() {
+ s := "12.3"
+ f, err := convertor.ToFloat(s)
+ if err != nil {
+ fmt.Errorf("error is %s", err.Error())
+ }
+ fmt.Println(f) // 12.3
+}
+```
+
+- 函数列表:
+
+```go
+func ColorHexToRGB(colorHex string) (red, green, blue int) //颜色值16进制转rgb
+func ColorRGBToHex(red, green, blue int) string //颜色值rgb转16进制
+func ToBool(s string) (bool, error) //字符串转成Bool
+func ToBytes(data interface{}) ([]byte, error) //interface转成byte slice
+func ToChar(s string) []string //字符串转成字符slice
+func ToFloat(value interface{}) (float64, error) //interface转成float64
+func ToInt(value interface{}) (int64, error) //interface转成int64
+func ToJson(value interface{}) (string, error) //interface转成json string
+func ToString(value interface{}) string //interface转成string
+func StructToMap(value interface{}) (map[string]interface{}, error) //struct串转成map, 需要设置struct tag `json`
+```
+
+#### 2. cryptor加解密包
+
+- 加密函数是支持md5, hmac, aes, des, ras
+- 导入包:import "github.com/duke-git/lancet/cryptor"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/cryptor"
+)
+
+func main() {
+ data := "hello"
+ key := "abcdefghijklmnop"
+
+ encrypted := cryptor.AesCbcEncrypt([]byte(data), []byte(key))
+ decrypted := cryptor.AesCbcDecrypt(encrypted, []byte(key))
+ fmt.Println(string(decrypted)) // hello
+}
+```
+
+- 函数列表:
+
+```go
+func AesEcbEncrypt(data, key []byte) []byte //AES ECB模式加密
+func AesEcbDecrypt(encrypted, key []byte) []byte //AES ECB模式解密
+func AesCbcEncrypt(data, key []byte) []byte //AES CBC模式加密
+func AesCbcDecrypt(encrypted, key []byte) []byte //AES CBC模式解密
+func AesCtrCrypt(data, key []byte) []byte //AES CTR模式加密/解密
+func AesCfbEncrypt(data, key []byte) []byte //AES CFB模式加密
+func AesCfbDecrypt(encrypted, key []byte) []byte //AES CFB模式解密
+func AesOfbEncrypt(data, key []byte) []byte //AES OFB模式加密
+func AesOfbDecrypt(data, key []byte) []byte //AES OFB模式解密
+func Base64StdEncode(s string) string //base64编码
+func Base64StdDecode(s string) string //base64解码
+func DesCbcEncrypt(data, key []byte) []byte //DES CBC模式加密
+func DesCbcDecrypt(encrypted, key []byte) []byte //DES CBC模式解密
+func DesCtrCrypt(data, key []byte) []byte //DES CTR模式加密/解密
+func DesCfbEncrypt(data, key []byte) []byte //DES CFB模式加密
+func DesCfbDecrypt(encrypted, key []byte) []byte //DES CFB模式解密
+func DesOfbEncrypt(data, key []byte) []byte //DES OFB模式加密
+func DesOfbDecrypt(data, key []byte) []byte //DES OFB模式解密
+func HmacMd5(data, key string) string //获取hmac md5值
+func HmacSha1(data, key string) string //获取hmac sha1值
+func HmacSha256(data, key string) string //获取hmac sha256值
+func HmacSha512(data, key string) string //获取hmac sha512值
+func Sha1(data string) string //获取sha1值
+func Sha256(data string) string //获取sha256值
+func Sha512(data string) string //获取sha512值
+func GenerateRsaKey(keySize int, priKeyFile, pubKeyFile string) //生成RSA私钥文件
+func RsaEncrypt(data []byte, pubKeyFileName string) []byte //RSA加密
+func RsaDecrypt(data []byte, privateKeyFileName string) []byte //RSA解密
+
+```
+
+#### 3. datetime日期时间处理包
+
+- 处理日期时间
+- 导入包:import "github.com/duke-git/lancet/datetime"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/datetime"
+)
+
+func main() {
+ now := time.Now()
+ s := datetime.FormatTimeToStr(now, "yyyy-mm-dd hh:mm:ss")
+ fmt.Println(s) // 2021-11-24 11:16:55
+}
+```
+
+- 函数列表:
+
+```go
+func AddDay(t time.Time, day int64) time.Time //加减天数
+func AddHour(t time.Time, hour int64) time.Time //加减小时数
+func AddMinute(t time.Time, minute int64) time.Time //加减分钟数
+func GetNowDate() string //获取当天日期 格式yyyy-mm-dd
+func GetNowTime() string //获取当前时间 格式hh:mm:ss
+func GetNowDateTime() string //获取当前日期时间 格式yyyy-mm-dd hh:mm:ss
+func GetZeroHourTimestamp() int64 //获取当天零时时间戳(00:00)
+func GetNightTimestamp() int64 //获取当天23时时间戳(23:59)
+func FormatTimeToStr(t time.Time, format string) string //时间格式化字符串
+func FormatStrToTime(str, format string) time.Time //字符串转换成时间
+```
+
+#### 4. fileutil文件处理包
+
+- 文件处理常用函数
+- 导入包:import "github.com/duke-git/lancet/fileutil"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/fileutil"
+)
+
+func main() {
+ fmt.Println(fileutil.IsDir("./")) // true
+}
+```
+
+- 函数列表:
+
+```go
+func IsExist(path string) bool //判断文件/目录是否存在
+func CreateFile(path string) bool //创建文件
+func IsDir(path string) bool //判断是否为目录
+func RemoveFile(path string) error //删除文件
+func CopyFile(srcFilePath string, dstFilePath string) error //复制文件
+func ListFileNames(path string) ([]string, error) //列出目录下所有文件名称
+```
+
+#### 5. formatter格式化处理包
+
+- 格式化相关处理函数
+- 导入包:import "github.com/duke-git/lancet/formatter"
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/formatter"
+)
+
+func main() {
+ fmt.Println(formatter.Comma("12345", "")) // "12,345"
+ fmt.Println(formatter.Comma(12345.67, "¥")) // "¥12,345.67"
+}
+```
+
+- 函数列表:
+
+```go
+func Comma(v interface{}, symbol string) string //用逗号每隔3位分割数字/字符串
+```
+
+#### 6. netutil网络处理包
+
+- 处理ip, http请求相关函数
+- 导入包:import "github.com/duke-git/lancet/netutil"
+- http方法params参数顺序:header, query string, body, httpclient
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/netutil"
+)
+
+func main() {
+ url := "https://gutendex.com/books?"
+ header := make(map[string]string)
+ header["Content-Type"] = "application/json"
+ queryParams := make(map[string]interface{})
+ queryParams["ids"] = "1"
+
+ resp, err := netutil.HttpGet(url, header, queryParams)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println("response: ", resp.StatusCode, string(body))
+}
+```
+
+- 函数列表:
+
+```go
+func GetInternalIp() string //获取内部ip
+func GetPublicIpInfo() (*PublicIpInfo, error) //获取公共ip信息: country, region, isp, city, lat, lon, ip
+func IsPublicIP(IP net.IP) bool //判断ip是否为公共ip
+func HttpGet(url string, params ...interface{}) (*http.Response, error) //http get请求
+func HttpPost(url string, params ...interface{}) (*http.Response, error) //http post请求
+func HttpPut(url string, params ...interface{}) (*http.Response, error) //http put请求
+func HttpDelete(url string, params ...interface{}) (*http.Response, error) //http delete请求
+func HttpPatch(url string, params ...interface{}) (*http.Response, error) //http patch请求
+func ConvertMapToQueryString(param map[string]interface{}) string //将map转换成url query string
+```
+
+#### 7. random随机数处理包
+
+- 生成和处理随机数
+- 导入包:import "github.com/duke-git/lancet/random"
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/random"
+)
+
+func main() {
+ randStr := random.RandString(6)
+ fmt.Println(randStr)
+}
+```
+
+- 函数列表:
+
+```go
+func RandBytes(length int) []byte //生成随机[]byte
+func RandInt(min, max int) int //生成随机int
+func RandString(length int) string //生成随机string
+```
+
+#### 8. slice切片操作包
+
+- 切片操作相关函数
+- 导入包:import "github.com/duke-git/lancet/slice"
+- 由于go目前对范型支持不稳定,slice处理函数参数和返回值大部分为interface{}, 待范型特性稳定后,会重构相关函数
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/slice"
+)
+
+func main() {
+ nums := []int{1, 4, 3, 4, 6, 7, 3}
+ uniqueNums, _ := slice.IntSlice(slice.Unique(nums))
+ fmt.Println(uniqueNums) //[1 4 3 6 7]
+}
+```
+
+- 函数列表:
+
+```go
+func Contain(slice interface{}, value interface{}) bool //判断slice是否包含value
+func Chunk(slice []interface{}, size int) [][]interface{} //均分slice
+func ConvertSlice(originalSlice interface{}, newSliceType reflect.Type) interface{} //将originalSlice转换为 newSliceType
+func Difference(slice1, slice2 interface{}) interface{} //返回
+func DeleteByIndex(slice interface{}, start int, end ...int) (interface{}, error) //删除切片中start到end位置的值
+func Filter(slice, function interface{}) interface{} //过滤slice, 函数签名:func(index int, value interface{}) bool
+func IntSlice(slice interface{}) ([]int, error) //转成int切片
+func InterfaceSlice(slice interface{}) []interface{} //转成interface{}切片
+func InsertByIndex(slice interface{}, index int, value interface{}) (interface{}, error) //在切片中index位置插入value
+func Map(slice, function interface{}) interface{} //遍历切片, 函数签名:func(index int, value interface{}) interface{}
+func ReverseSlice(slice interface{}) //反转切片
+func Reduce(slice, function, zero interface{}) interface{} //切片reduce操作, 函数签名:func(index int, value1, value2 interface{}) interface{}
+func SortByField(slice interface{}, field string, sortType ...string) error //对struct切片进行排序
+func StringSlice(slice interface{}) []string //转为string切片
+func Unique(slice interface{}) interface{} //去重切片
+func UpdateByIndex(slice interface{}, index int, value interface{}) (interface{}, error) //在切片中index位置更新value
+```
+
+#### 9. strutil字符串处理包
+
+- 字符串操作相关函数
+- 导入包:import "github.com/duke-git/lancet/strutil"
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/strutil"
+)
+
+func main() {
+ str := "Foo-Bar"
+ camelCaseStr := strutil.CamelCase(str)
+ fmt.Println(camelCaseStr) //fooBar
+}
+```
+
+- 函数列表:
+
+```go
+func After(s, char string) string //截取字符串中char第一次出现之后的字符串
+func AfterLast(s, char string) string //截取字符串中char最后一次出现之后的字符串
+func Before(s, char string) string //截取字符串中char第一次出现之前的字符串
+func BeforeLast(s, char string) string //截取字符串中char最后一次出现之前的字符串
+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 LowerFirst(s string) string //字符串的第一个字母转为小写字母
+func PadEnd(source string, size int, padStr string) string //字符串末尾填充size个字符
+func PadStart(source string, size int, padStr string) string//字符串开头填充size个字符
+func ReverseStr(s string) string //字符串逆袭
+func SnakeCase(s string) string //字符串转为SnakeCase, "fooBar" -> "foo_bar"
+```
+
+#### 10. validator验证器包
+
+- 数据校验相关函数
+- 导入包:import "github.com/duke-git/lancet/validator"
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "github.com/duke-git/lancet/validator"
+)
+
+func main() {
+ str := "Foo-Bar"
+ isAlpha := validator.IsAlpha(str)
+ fmt.Println(isAlpha) //false
+}
+```
+
+- 函数列表:
+
+```go
+func ContainChinese(s string) bool //判断字符串中是否含有中文字符
+func IsAlpha(s string) bool //判断字符串是否只含有字母
+func IsBase64(base64 string) bool //判断字符串是base64
+func IsChineseMobile(mobileNum string) bool //判断字符串是否是手机号
+func IsChineseIdNum(id string) bool //判断字符串是否是身份证号
+func IsChinesePhone(phone string) bool //判断字符串是否是座机电话号码
+func IsCreditCard(creditCart string) bool //判断字符串是否是信用卡
+func IsDns(dns string) bool //判断字符串是否是DNS
+func IsEmail(email string) bool //判断字符串是否是邮箱
+func IsEmptyString(s string) bool //判断字符串是否为空
+func IsFloatStr(s string) bool //判断字符串是否可以转成float
+func IsNumberStr(s string) bool //判断字符串是否可以转成数字
+func IsRegexMatch(s, regex string) bool //判断字符串是否match正则表达式
+func IsIntStr(s string) bool //判断字符串是否可以转成整数
+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 IsWeakPassword(password string) bool //判断字符串是否是弱密码(只有字母或数字)
+```
diff --git a/convertor/convertor.go b/convertor/convertor.go
new file mode 100644
index 0000000..5de9203
--- /dev/null
+++ b/convertor/convertor.go
@@ -0,0 +1,210 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package convertor implements some functions to convert data.
+package convertor
+
+import (
+ "bytes"
+ "encoding/gob"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// ToBool convert string to a boolean
+func ToBool(s string) (bool, error) {
+ return strconv.ParseBool(s)
+}
+
+// ToBool convert interface to bytes
+func ToBytes(data interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ enc := gob.NewEncoder(&buf)
+ err := enc.Encode(data)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+// ToChar convert string to char slice
+func ToChar(s string) []string {
+ c := make([]string, 0)
+ if len(s) == 0 {
+ c = append(c, "")
+ }
+ for _, v := range s {
+ c = append(c, string(v))
+ }
+ return c
+}
+
+// ToString convert value to string
+func ToString(value interface{}) string {
+ var res string
+ if value == nil {
+ return res
+ }
+ switch v := value.(type) {
+ case float64:
+ res = strconv.FormatFloat(v, 'f', -1, 64)
+ case float32:
+ res = strconv.FormatFloat(float64(v), 'f', -1, 64)
+ case int:
+ res = strconv.Itoa(v)
+ case uint:
+ res = strconv.Itoa(int(v))
+ case int8:
+ res = strconv.Itoa(int(v))
+ case uint8:
+ res = strconv.Itoa(int(v))
+ case int16:
+ res = strconv.Itoa(int(v))
+ case uint16:
+ res = strconv.Itoa(int(v))
+ case int32:
+ res = strconv.Itoa(int(v))
+ case uint32:
+ res = strconv.Itoa(int(v))
+ case int64:
+ res = strconv.FormatInt(v, 10)
+ case uint64:
+ res = strconv.FormatUint(v, 10)
+ case string:
+ res = value.(string)
+ case []byte:
+ res = string(value.([]byte))
+ default:
+ newValue, _ := json.Marshal(value)
+ res = string(newValue)
+ }
+ return res
+}
+
+// ToJson convert value to a valid json string
+func ToJson(value interface{}) (string, error) {
+ res, err := json.Marshal(value)
+ if err != nil {
+ res = []byte("")
+ }
+
+ return string(res), err
+}
+
+// ToFloat convert value to a float64, if input is not a float return 0.0 and error
+func ToFloat(value interface{}) (float64, error) {
+ v := reflect.ValueOf(value)
+
+ res := 0.0
+ err := fmt.Errorf("ToInt: unvalid interface type %T", value)
+ switch value.(type) {
+ case int, int8, int16, int32, int64:
+ res = float64(v.Int())
+ return res, nil
+ case uint, uint8, uint16, uint32, uint64:
+ res = float64(v.Uint())
+ return res, nil
+ case float32, float64:
+ res = v.Float()
+ return res, nil
+ case string:
+ res, err = strconv.ParseFloat(v.String(), 64)
+ if err != nil {
+ res = 0.0
+ }
+ return res, err
+ default:
+ return res, err
+ }
+}
+
+// ToInt convert value to a int64, if input is not a numeric format return 0 and error
+func ToInt(value interface{}) (int64, error) {
+ v := reflect.ValueOf(value)
+
+ var res int64
+ err := fmt.Errorf("ToInt: invalid interface type %T", value)
+ switch value.(type) {
+ case int, int8, int16, int32, int64:
+ res = v.Int()
+ return res, nil
+ case uint, uint8, uint16, uint32, uint64:
+ res = int64(v.Uint())
+ return res, nil
+ case float32, float64:
+ res = int64(v.Float())
+ return res, nil
+ case string:
+ res, err = strconv.ParseInt(v.String(), 0, 64)
+ if err != nil {
+ res = 0
+ }
+ return res, err
+ default:
+ return res, err
+ }
+}
+
+// StructToMap convert struct to map, only convert exported struct field
+// map key is specified same as struct field tag `json` value
+func StructToMap(value interface{}) (map[string]interface{}, error) {
+ v := reflect.ValueOf(value)
+ t := reflect.TypeOf(value)
+
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if t.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", value)
+ }
+
+ res := make(map[string]interface{})
+
+ fieldNum := t.NumField()
+ pattern := `^[A-Z]`
+ regex := regexp.MustCompile(pattern)
+ for i := 0; i < fieldNum; i++ {
+ name := t.Field(i).Name
+ tag := t.Field(i).Tag.Get("json")
+ if regex.MatchString(name) && tag != "" {
+ //res[name] = v.Field(i).Interface()
+ res[tag] = v.Field(i).Interface()
+ }
+ }
+
+ return res, nil
+}
+
+// ColorHexToRGB convert hex color to rgb color
+func ColorHexToRGB(colorHex string) (red, green, blue int) {
+ colorHex = strings.TrimPrefix(colorHex, "#")
+ color64, err := strconv.ParseInt(colorHex, 16, 32)
+ if err != nil {
+ return
+ }
+ color := int(color64)
+ return color >> 16, (color & 0x00FF00) >> 8, color & 0x0000FF
+}
+
+// ColorRGBToHex convert rgb color to hex color
+func ColorRGBToHex(red, green, blue int) string {
+ r := strconv.FormatInt(int64(red), 16)
+ g := strconv.FormatInt(int64(green), 16)
+ b := strconv.FormatInt(int64(blue), 16)
+
+ if len(r) == 1 {
+ r = "0" + r
+ }
+ if len(g) == 1 {
+ g = "0" + g
+ }
+ if len(b) == 1 {
+ b = "0" + b
+ }
+
+ return "#" + r + g + b
+}
diff --git a/convertor/convertor_test.go b/convertor/convertor_test.go
new file mode 100644
index 0000000..7c84a9e
--- /dev/null
+++ b/convertor/convertor_test.go
@@ -0,0 +1,210 @@
+package convertor
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/utils"
+ "reflect"
+ "testing"
+)
+
+func TestToChar(t *testing.T) {
+ cases := []string{"", "abc", "1 2#3"}
+ expected := [][]string{
+ {""},
+ {"a", "b", "c"},
+ {"1", " ", "2", "#", "3"},
+ }
+ for i := 0; i < len(cases); i++ {
+ res := ToChar(cases[i])
+ if !reflect.DeepEqual(res, expected[i]) {
+ utils.LogFailedTestInfo(t, "ToChar", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestToBool(t *testing.T) {
+ cases := []string{"true", "True", "false", "False", "0", "1", "123"}
+ expected := []bool{true, true, false, false, false, true, false}
+
+ for i := 0; i < len(cases); i++ {
+ res, _ := ToBool(cases[i])
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "ToBool", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestToBytes(t *testing.T) {
+ cases := []interface{}{
+ 0,
+ false,
+ "1",
+ }
+ expected := [][]byte{
+ {3, 4, 0, 0},
+ {3, 2, 0, 0},
+ {4, 12, 0, 1, 49},
+ }
+ for i := 0; i < len(cases); i++ {
+ res, _ := ToBytes(cases[i])
+ fmt.Println(res)
+ if !reflect.DeepEqual(res, expected[i]) {
+ utils.LogFailedTestInfo(t, "ToBytes", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestToInt(t *testing.T) {
+ cases := []interface{}{"123", "-123", 123, "abc", false, "111111111111111111111111111111111111111"}
+ expected := []int64{123, -123, 123, 0, 0, 0}
+
+ for i := 0; i < len(cases); i++ {
+ res, _ := ToInt(cases[i])
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "ToInt", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestToFloat(t *testing.T) {
+ cases := []interface{}{"", "-1", "-.11", "1.23e3", ".123e10", "abc"}
+ expected := []float64{0, -1, -0.11, 1230, 0.123e10, 0}
+
+ for i := 0; i < len(cases); i++ {
+ res, _ := ToFloat(cases[i])
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "ToFloat", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestToString(t *testing.T) {
+ // basic type
+ toString(t, "a1", "a1")
+ toString(t, 111, "111")
+ toString(t, 111.01, "111.01")
+ toString(t, true, "true")
+ //toString(t, 1.5+10i, "(1.5+10i)")
+
+ // slice
+ aSlice := []int{1, 2, 3}
+ toString(t, aSlice, "[1,2,3]")
+
+ // map
+ aMap := make(map[string]int)
+ aMap["a"] = 1
+ aMap["b"] = 2
+ aMap["c"] = 3
+
+ toString(t, aMap, "{\"a\":1,\"b\":2,\"c\":3}")
+
+ // struct
+ type TestStruct struct {
+ Name string
+ }
+ aStruct := TestStruct{Name: "TestStruct"}
+ toString(t, aStruct, "{\"Name\":\"TestStruct\"}")
+}
+
+func toString(t *testing.T, test interface{}, expected string) {
+ res := ToString(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "ToString", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestToJson(t *testing.T) {
+ // map
+ aMap := make(map[string]int)
+ aMap["a"] = 1
+ aMap["b"] = 2
+ aMap["c"] = 3
+
+ mapJson := "{\"a\":1,\"b\":2,\"c\":3}"
+ r1, _ := ToJson(aMap)
+ if r1 != mapJson {
+ utils.LogFailedTestInfo(t, "ToJson", aMap, mapJson, r1)
+ t.FailNow()
+ }
+
+ // struct
+ type TestStruct struct {
+ Name string
+ }
+ aStruct := TestStruct{Name: "TestStruct"}
+ structJson := "{\"Name\":\"TestStruct\"}"
+ r2, _ := ToJson(aStruct)
+ if r2 != structJson {
+ utils.LogFailedTestInfo(t, "ToJson", aMap, mapJson, r1)
+ t.FailNow()
+ }
+}
+
+func TestStructToMap(t *testing.T) {
+ type People struct {
+ Name string `json:"name"`
+ age int
+ }
+
+ p1 := People{
+ "test",
+ 100,
+ }
+
+ pm1, _ := StructToMap(p1)
+ m1 := make(map[string]interface{})
+ m1["name"] = "test"
+ //exp1["100"] = 100
+
+ if !reflect.DeepEqual(pm1, m1) {
+ utils.LogFailedTestInfo(t, "StructToMap", p1, m1, pm1)
+ t.FailNow()
+ }
+
+ p2 := People{
+ "test",
+ 100,
+ }
+
+ pm2, _ := StructToMap(p1)
+ m2 := make(map[string]interface{})
+ m2["name"] = "test"
+ m2["100"] = 100
+
+ if reflect.DeepEqual(pm2, m2) {
+ utils.LogFailedTestInfo(t, "StructToMap", p2, m2, pm2)
+ t.FailNow()
+ }
+}
+
+func TestColorHexToRGB(t *testing.T) {
+ colorHex := "#003366"
+ r, g, b := ColorHexToRGB(colorHex)
+ colorRGB := fmt.Sprintf("%d,%d,%d", r, g, b)
+ expected := "0,51,102"
+
+ if colorRGB != expected {
+ utils.LogFailedTestInfo(t, "ColorHexToRGB", colorHex, expected, colorRGB)
+ t.FailNow()
+ }
+}
+
+func TestColorRGBToHex(t *testing.T) {
+ r := 0
+ g := 51
+ b := 102
+ colorRGB := fmt.Sprintf("%d,%d,%d", r, g, b)
+ colorHex := ColorRGBToHex(r, g, b)
+ expected := "#003366"
+
+ if colorHex != expected {
+ utils.LogFailedTestInfo(t, "ColorHexToRGB", colorRGB, expected, colorHex)
+ t.FailNow()
+ }
+}
diff --git a/cryptor/aes.go b/cryptor/aes.go
new file mode 100644
index 0000000..ef7016a
--- /dev/null
+++ b/cryptor/aes.go
@@ -0,0 +1,168 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package cryptor implements some util functions to encrypt and decrypt.
+// Note:
+// 1. for aes crypt function, the `key` param length should be 16, 24 or 32. if not, will panic.
+package cryptor
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "io"
+)
+
+// AesEcbEncrypt encrypt data with key use AES ECB algorithm
+// len(key) should be 16, 24 or 32
+func AesEcbEncrypt(data, key []byte) []byte {
+ cipher, _ := aes.NewCipher(generateAesKey(key))
+ length := (len(data) + aes.BlockSize) / aes.BlockSize
+ plain := make([]byte, length*aes.BlockSize)
+ copy(plain, data)
+ pad := byte(len(plain) - len(data))
+ for i := len(data); i < len(plain); i++ {
+ plain[i] = pad
+ }
+ encrypted := make([]byte, len(plain))
+ for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
+ cipher.Encrypt(encrypted[bs:be], plain[bs:be])
+ }
+
+ return encrypted
+}
+
+// AesEcbDecrypt decrypt data with key use AES ECB algorithm
+// len(key) should be 16, 24 or 32
+func AesEcbDecrypt(encrypted, key []byte) []byte {
+ cipher, _ := aes.NewCipher(generateAesKey(key))
+ decrypted := make([]byte, len(encrypted))
+ //
+ for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
+ cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
+ }
+
+ trim := 0
+ if len(decrypted) > 0 {
+ trim = len(decrypted) - int(decrypted[len(decrypted)-1])
+ }
+
+ return decrypted[:trim]
+}
+
+// AesCbcEncrypt encrypt data with key use AES CBC algorithm
+// len(key) should be 16, 24 or 32
+func AesCbcEncrypt(data, key []byte) []byte {
+ // len(key) should be 16, 24 or 32
+ block, _ := aes.NewCipher(key)
+ blockSize := block.BlockSize()
+ data = pkcs7Padding(data, blockSize)
+ blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
+
+ encrypted := make([]byte, len(data))
+ blockMode.CryptBlocks(encrypted, data)
+ return encrypted
+}
+
+// AesEcbDecrypt decrypt data with key use AES CBC algorithm
+// len(key) should be 16, 24 or 32
+func AesCbcDecrypt(encrypted, key []byte) []byte {
+ block, _ := aes.NewCipher(key)
+ blockSize := block.BlockSize()
+ blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
+
+ decrypted := make([]byte, len(encrypted))
+ blockMode.CryptBlocks(decrypted, encrypted)
+ decrypted = pkcs7UnPadding(decrypted)
+ return decrypted
+}
+
+// AesCtrCrypt encrypt data with key use AES CTR algorithm
+// len(key) should be 16, 24 or 32
+func AesCtrCrypt(data, key []byte) []byte {
+ block, _ := aes.NewCipher(key)
+
+ iv := bytes.Repeat([]byte("1"), block.BlockSize())
+ stream := cipher.NewCTR(block, iv)
+
+ dst := make([]byte, len(data))
+ stream.XORKeyStream(dst, data)
+
+ return dst
+}
+
+// AesCfbEncrypt encrypt data with key use AES CFB algorithm
+// len(key) should be 16, 24 or 32
+func AesCfbEncrypt(data, key []byte) []byte {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ panic(err)
+ }
+
+ encrypted := make([]byte, aes.BlockSize+len(data))
+ iv := encrypted[:aes.BlockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ panic(err)
+ }
+
+ stream := cipher.NewCFBEncrypter(block, iv)
+ stream.XORKeyStream(encrypted[aes.BlockSize:], data)
+ return encrypted
+}
+
+// AesCfbDecrypt decrypt data with key use AES CFB algorithm
+// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32
+func AesCfbDecrypt(encrypted, key []byte) []byte {
+ block, _ := aes.NewCipher(key)
+ if len(encrypted) < aes.BlockSize {
+ panic("encrypted data is too short")
+ }
+ iv := encrypted[:aes.BlockSize]
+ encrypted = encrypted[aes.BlockSize:]
+
+ stream := cipher.NewCFBDecrypter(block, iv)
+ stream.XORKeyStream(encrypted, encrypted)
+ return encrypted
+}
+
+// AesOfbEncrypt encrypt data with key use AES OFB algorithm
+// len(key) should be 16, 24 or 32
+func AesOfbEncrypt(data, key []byte) []byte {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ panic(err)
+ }
+ data = pkcs7Padding(data, aes.BlockSize)
+ encrypted := make([]byte, aes.BlockSize+len(data))
+ iv := encrypted[:aes.BlockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ panic(err)
+ }
+
+ stream := cipher.NewOFB(block, iv)
+ stream.XORKeyStream(encrypted[aes.BlockSize:], data)
+ return encrypted
+}
+
+// AesOfbDecrypt decrypt data with key use AES OFB algorithm
+// len(key) should be 16, 24 or 32
+func AesOfbDecrypt(data, key []byte) []byte {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ panic(err)
+ }
+
+ iv := data[:aes.BlockSize]
+ data = data[aes.BlockSize:]
+ if len(data)%aes.BlockSize != 0 {
+ return nil
+ }
+
+ decrypted := make([]byte, len(data))
+ mode := cipher.NewOFB(block, iv)
+ mode.XORKeyStream(decrypted, data)
+
+ decrypted = pkcs7UnPadding(decrypted)
+ return decrypted
+}
diff --git a/cryptor/aes_test.go b/cryptor/aes_test.go
new file mode 100644
index 0000000..09e6b3b
--- /dev/null
+++ b/cryptor/aes_test.go
@@ -0,0 +1,72 @@
+package cryptor
+
+import (
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestAesEcbEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefghijklmnop"
+
+ aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
+ aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
+
+ if string(aesEcbDecrypt) != data {
+ utils.LogFailedTestInfo(t, "AesEcbEncrypt/AesEcbDecrypt", data, data, string(aesEcbDecrypt))
+ t.FailNow()
+ }
+}
+
+func TestAesCbcEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefghijklmnop"
+
+ aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
+ aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
+
+ if string(aesCbcDecrypt) != data {
+ utils.LogFailedTestInfo(t, "AesCbcEncrypt/AesCbcDecrypt", data, data, string(aesCbcDecrypt))
+ t.FailNow()
+ }
+}
+
+func TestAesCtrCrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefghijklmnop"
+
+ aesCtrCrypt := AesCtrCrypt([]byte(data), []byte(key))
+ aesCtrDeCrypt := AesCtrCrypt(aesCtrCrypt, []byte(key))
+
+ if string(aesCtrDeCrypt) != data {
+ utils.LogFailedTestInfo(t, "AesCtrCrypt", data, data, string(aesCtrDeCrypt))
+ t.FailNow()
+ }
+}
+
+func TestAesCfbEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefghijklmnop"
+
+ aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
+ aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
+
+ if string(aesCfbDecrypt) != data {
+ utils.LogFailedTestInfo(t, "AesCfbEncrypt/AesCfbDecrypt", data, data, string(aesCfbDecrypt))
+ t.FailNow()
+ }
+}
+
+func TestAesOfbEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefghijklmnop"
+
+ aesOfbEncrypt := AesOfbEncrypt([]byte(data), []byte(key))
+ aesOfbDecrypt := AesOfbDecrypt(aesOfbEncrypt, []byte(key))
+
+ if string(aesOfbDecrypt) != data {
+ utils.LogFailedTestInfo(t, "AesOfbEncrypt/AesOfbDecrypt", data, data, string(aesOfbDecrypt))
+ t.FailNow()
+ }
+}
diff --git a/cryptor/basic.go b/cryptor/basic.go
new file mode 100644
index 0000000..e0a4d3e
--- /dev/null
+++ b/cryptor/basic.go
@@ -0,0 +1,96 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package cryptor implements some util functions to encrypt and decrypt.
+// Contain base64, hmac, sha, aes, des, and rsa
+package cryptor
+
+import (
+ "crypto/hmac"
+ "crypto/md5"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/base64"
+ "encoding/hex"
+ "io/ioutil"
+)
+
+// Base64StdEncode encode string with base64 encoding
+func Base64StdEncode(s string) string {
+ return base64.StdEncoding.EncodeToString([]byte(s))
+}
+
+// Base64StdEncode decode a base64 encoded string
+func Base64StdDecode(s string) string {
+ b, _ := base64.StdEncoding.DecodeString(s)
+ return string(b)
+}
+
+// Md5Str return the md5 value of string
+func Md5String(s string) string {
+ h := md5.New()
+ h.Write([]byte(s))
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+// Md5File return the md5 value of file
+func Md5File(filename string) (string, error) {
+ f, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return "", err
+ }
+
+ h := md5.New()
+ h.Write(f)
+ return hex.EncodeToString(h.Sum(nil)), nil
+}
+
+// HmacMd5 return the hmac hash of string use md5
+func HmacMd5(data, key string) string {
+ h := hmac.New(md5.New, []byte(key))
+ h.Write([]byte(data))
+ return hex.EncodeToString(h.Sum([]byte("")))
+}
+
+// HmacSha1 return the hmac hash of string use sha1
+func HmacSha1(data, key string) string {
+ h := hmac.New(sha1.New, []byte(key))
+ h.Write([]byte(data))
+ return hex.EncodeToString(h.Sum([]byte("")))
+}
+
+// HmacSha256 return the hmac hash of string use sha256
+func HmacSha256(data, key string) string {
+ h := hmac.New(sha256.New, []byte(key))
+ h.Write([]byte(data))
+ return hex.EncodeToString(h.Sum([]byte("")))
+}
+
+// HmacSha512 return the hmac hash of string use sha512
+func HmacSha512(data, key string) string {
+ h := hmac.New(sha512.New, []byte(key))
+ h.Write([]byte(data))
+ return hex.EncodeToString(h.Sum([]byte("")))
+}
+
+// Sha1 return the sha1 value (SHA-1 hash algorithm) of string
+func Sha1(data string) string {
+ sha1 := sha1.New()
+ sha1.Write([]byte(data))
+ return hex.EncodeToString(sha1.Sum([]byte("")))
+}
+
+// Sha256 return the sha256 value (SHA256 hash algorithm) of string
+func Sha256(data string) string {
+ sha256 := sha256.New()
+ sha256.Write([]byte(data))
+ return hex.EncodeToString(sha256.Sum([]byte("")))
+}
+
+// Sha512 return the sha512 value (SHA512 hash algorithm) of string
+func Sha512(data string) string {
+ sha512 := sha512.New()
+ sha512.Write([]byte(data))
+ return hex.EncodeToString(sha512.Sum([]byte("")))
+}
diff --git a/cryptor/basic_test.go b/cryptor/basic_test.go
new file mode 100644
index 0000000..d763167
--- /dev/null
+++ b/cryptor/basic_test.go
@@ -0,0 +1,133 @@
+package cryptor
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestBase64StdEncode(t *testing.T) {
+ s := "hello world"
+ bs := Base64StdEncode(s)
+
+ if bs != "aGVsbG8gd29ybGQ=" {
+ utils.LogFailedTestInfo(t, "Base64StdEncode", s, "aGVsbG8gd29ybGQ=", bs)
+ t.FailNow()
+ }
+}
+
+func TestBase64StdDecode(t *testing.T) {
+ bs := "aGVsbG8gd29ybGQ="
+ s := Base64StdDecode(bs)
+
+ if s != "hello world" {
+ utils.LogFailedTestInfo(t, "Base64StdDecode", bs, "hello world=", s)
+ t.FailNow()
+ }
+}
+
+func TestMd5String(t *testing.T) {
+ s := "hello"
+ smd5 := Md5String(s)
+ expected := "5d41402abc4b2a76b9719d911017c592"
+
+ if smd5 != expected {
+ utils.LogFailedTestInfo(t, "Md5String", s, expected, smd5)
+ t.FailNow()
+ }
+}
+
+func TestMd5File(t *testing.T) {
+ file, _ := os.Create("./hello.txt")
+ defer file.Close()
+ file.WriteString("hello\n")
+
+ fileMd5, err := Md5File("./hello.txt")
+ if err != nil {
+ t.FailNow()
+ }
+ fmt.Println(fileMd5)
+}
+
+func TestHmacMd5(t *testing.T) {
+ s := "hello world"
+ key := "12345"
+ hmacMd5 := HmacMd5(s, key)
+ expected := "5f4c9faaff0a1ad3007d9ddc06abe36d"
+
+ if hmacMd5 != expected {
+ utils.LogFailedTestInfo(t, "HmacMd5", s, expected, hmacMd5)
+ t.FailNow()
+ }
+}
+
+func TestHmacSha1(t *testing.T) {
+ s := "hello world"
+ key := "12345"
+ hmacSha1 := HmacSha1(s, key)
+ expected := "3826f812255d8683f051ee97346d1359234d5dbd"
+
+ if hmacSha1 != expected {
+ utils.LogFailedTestInfo(t, "HmacSha1", s, expected, hmacSha1)
+ t.FailNow()
+ }
+}
+
+func TestHmacSha256(t *testing.T) {
+ s := "hello world"
+ key := "12345"
+ hmacSha256 := HmacSha256(s, key)
+ expected := "9dce2609f2d67d41f74c7f9efc8ccd44370d41ad2de52982627588dfe7289ab8"
+
+ if hmacSha256 != expected {
+ utils.LogFailedTestInfo(t, "HmacSha256", s, expected, hmacSha256)
+ t.FailNow()
+ }
+}
+
+func TestHmacSha512(t *testing.T) {
+ s := "hello world"
+ key := "12345"
+ hmacSha512 := HmacSha512(s, key)
+ expected := "5b1563ac4e9b49c9ada8ccb232588fc4f0c30fd12f756b3a0b95af4985c236ca60925253bae10ce2c6bf9af1c1679b51e5395ff3d2826c0a2c7c0d72225d4175"
+
+ if hmacSha512 != expected {
+ utils.LogFailedTestInfo(t, "HmacSha512", s, expected, hmacSha512)
+ t.FailNow()
+ }
+}
+
+func TestSha1(t *testing.T) {
+ s := "hello world"
+ sha1 := Sha1(s)
+ expected := "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"
+
+ if sha1 != expected {
+ utils.LogFailedTestInfo(t, "Sha1", s, expected, sha1)
+ t.FailNow()
+ }
+}
+
+func TestSha256(t *testing.T) {
+ s := "hello world"
+ sha256 := Sha256(s)
+ expected := "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
+
+ if sha256 != expected {
+ utils.LogFailedTestInfo(t, "Sha256", s, expected, sha256)
+ t.FailNow()
+ }
+}
+
+func TestSha512(t *testing.T) {
+ s := "hello world"
+ sha512 := Sha512(s)
+ expected := "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f"
+
+ if sha512 != expected {
+ utils.LogFailedTestInfo(t, "Sha512", s, expected, sha512)
+ t.FailNow()
+ }
+}
diff --git a/cryptor/crypt_util.go b/cryptor/crypt_util.go
new file mode 100644
index 0000000..6005346
--- /dev/null
+++ b/cryptor/crypt_util.go
@@ -0,0 +1,37 @@
+package cryptor
+
+import "bytes"
+
+func generateAesKey(key []byte) []byte {
+ genKey := make([]byte, 16)
+ copy(genKey, key)
+ for i := 16; i < len(key); {
+ for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
+ genKey[j] ^= key[i]
+ }
+ }
+ return genKey
+}
+
+func generateDesKey(key []byte) []byte {
+ genKey := make([]byte, 8)
+ copy(genKey, key)
+ for i := 8; i < len(key); {
+ for j := 0; j < 8 && i < len(key); j, i = j+1, i+1 {
+ genKey[j] ^= key[i]
+ }
+ }
+ return genKey
+}
+
+func pkcs7Padding(src []byte, blockSize int) []byte {
+ padding := blockSize - len(src)%blockSize
+ padText := bytes.Repeat([]byte{byte(padding)}, padding)
+ return append(src, padText...)
+}
+
+func pkcs7UnPadding(src []byte) []byte {
+ length := len(src)
+ unPadding := int(src[length-1])
+ return src[:(length - unPadding)]
+}
diff --git a/cryptor/des.go b/cryptor/des.go
new file mode 100644
index 0000000..bb537fd
--- /dev/null
+++ b/cryptor/des.go
@@ -0,0 +1,166 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package cryptor implements some util functions to encrypt and decrypt.
+package cryptor
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "crypto/des"
+ "crypto/rand"
+ "io"
+)
+
+// DesEcbEncrypt encrypt data with key use DES ECB algorithm
+// len(key) should be 8
+func DesEcbEncrypt(data, key []byte) []byte {
+ cipher, _ := des.NewCipher(generateDesKey(key))
+ length := (len(data) + des.BlockSize) / des.BlockSize
+ plain := make([]byte, length*des.BlockSize)
+ copy(plain, data)
+ pad := byte(len(plain) - len(data))
+ for i := len(data); i < len(plain); i++ {
+ plain[i] = pad
+ }
+ encrypted := make([]byte, len(plain))
+ for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
+ cipher.Encrypt(encrypted[bs:be], plain[bs:be])
+ }
+
+ return encrypted
+}
+
+// DesEcbDecrypt decrypt data with key use DES ECB algorithm
+// len(key) should be 8
+func DesEcbDecrypt(encrypted, key []byte) []byte {
+ cipher, _ := des.NewCipher(generateDesKey(key))
+ decrypted := make([]byte, len(encrypted))
+ //
+ for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
+ cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
+ }
+
+ trim := 0
+ if len(decrypted) > 0 {
+ trim = len(decrypted) - int(decrypted[len(decrypted)-1])
+ }
+
+ return decrypted[:trim]
+}
+
+// DesCbcEncrypt encrypt data with key use DES CBC algorithm
+// len(key) should be 8
+func DesCbcEncrypt(data, key []byte) []byte {
+ block, _ := des.NewCipher(key)
+ blockSize := block.BlockSize()
+ data = pkcs7Padding(data, blockSize)
+ blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
+
+ encrypted := make([]byte, len(data))
+ blockMode.CryptBlocks(encrypted, data)
+ return encrypted
+}
+
+// DesCbcDecrypt decrypt data with key use DES CBC algorithm
+// len(key) should be 8
+func DesCbcDecrypt(encrypted, key []byte) []byte {
+ block, _ := des.NewCipher(key)
+ blockSize := block.BlockSize()
+ blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
+
+ decrypted := make([]byte, len(encrypted))
+ blockMode.CryptBlocks(decrypted, encrypted)
+ decrypted = pkcs7UnPadding(decrypted)
+ return decrypted
+}
+
+// DesCtrCrypt encrypt data with key use DES CTR algorithm
+// len(key) should be 8
+func DesCtrCrypt(data, key []byte) []byte {
+ block, _ := des.NewCipher(key)
+
+ iv := bytes.Repeat([]byte("1"), block.BlockSize())
+ stream := cipher.NewCTR(block, iv)
+
+ dst := make([]byte, len(data))
+ stream.XORKeyStream(dst, data)
+
+ return dst
+}
+
+// DesCfbEncrypt encrypt data with key use DES CFB algorithm
+// len(key) should be 8
+func DesCfbEncrypt(data, key []byte) []byte {
+ block, err := des.NewCipher(key)
+ if err != nil {
+ panic(err)
+ }
+
+ encrypted := make([]byte, des.BlockSize+len(data))
+ iv := encrypted[:des.BlockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ panic(err)
+ }
+
+ stream := cipher.NewCFBEncrypter(block, iv)
+ stream.XORKeyStream(encrypted[des.BlockSize:], data)
+ return encrypted
+}
+
+// DesCfbDecrypt decrypt data with key use DES CFB algorithm
+// len(encrypted) should be great than 16, len(key) should be 8
+func DesCfbDecrypt(encrypted, key []byte) []byte {
+ block, _ := des.NewCipher(key)
+ if len(encrypted) < des.BlockSize {
+ panic("encrypted data is too short")
+ }
+ iv := encrypted[:des.BlockSize]
+ encrypted = encrypted[des.BlockSize:]
+
+ stream := cipher.NewCFBDecrypter(block, iv)
+ stream.XORKeyStream(encrypted, encrypted)
+ return encrypted
+}
+
+// DesOfbEncrypt encrypt data with key use DES OFB algorithm
+// len(key) should be 16, 24 or 32
+func DesOfbEncrypt(data, key []byte) []byte {
+ block, err := des.NewCipher(key)
+ if err != nil {
+ panic(err)
+ }
+ data = pkcs7Padding(data, des.BlockSize)
+ encrypted := make([]byte, des.BlockSize+len(data))
+ iv := encrypted[:des.BlockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ panic(err)
+ }
+
+ stream := cipher.NewOFB(block, iv)
+ stream.XORKeyStream(encrypted[des.BlockSize:], data)
+ return encrypted
+}
+
+// DesOfbDecrypt decrypt data with key use DES OFB algorithm
+// len(key) should be 8
+func DesOfbDecrypt(data, key []byte) []byte {
+ block, err := des.NewCipher(key)
+ if err != nil {
+ panic(err)
+ }
+
+ iv := data[:des.BlockSize]
+ data = data[des.BlockSize:]
+ if len(data)%des.BlockSize != 0 {
+ return nil
+ }
+
+ decrypted := make([]byte, len(data))
+ mode := cipher.NewOFB(block, iv)
+ mode.XORKeyStream(decrypted, data)
+
+ decrypted = pkcs7UnPadding(decrypted)
+ return decrypted
+
+}
diff --git a/cryptor/des_test.go b/cryptor/des_test.go
new file mode 100644
index 0000000..5365b09
--- /dev/null
+++ b/cryptor/des_test.go
@@ -0,0 +1,72 @@
+package cryptor
+
+import (
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestDesEcbEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefgh"
+
+ desEcbEncrypt := DesEcbEncrypt([]byte(data), []byte(key))
+ desEcbDecrypt := DesEcbDecrypt(desEcbEncrypt, []byte(key))
+
+ if string(desEcbDecrypt) != data {
+ utils.LogFailedTestInfo(t, "DesEcbEncrypt/DesEcbDecrypt", data, data, string(desEcbDecrypt))
+ t.FailNow()
+ }
+}
+
+func TestDesCbcEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefgh"
+
+ desCbcEncrypt := DesCbcEncrypt([]byte(data), []byte(key))
+ desCbcDecrypt := DesCbcDecrypt(desCbcEncrypt, []byte(key))
+
+ if string(desCbcDecrypt) != data {
+ utils.LogFailedTestInfo(t, "DesCbcEncrypt/DesCbcDecrypt", data, data, string(desCbcDecrypt))
+ t.FailNow()
+ }
+}
+
+func TestDesCtrCrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefgh"
+
+ desCtrCrypt := DesCtrCrypt([]byte(data), []byte(key))
+ desCtrDeCrypt := DesCtrCrypt(desCtrCrypt, []byte(key))
+
+ if string(desCtrDeCrypt) != data {
+ utils.LogFailedTestInfo(t, "DesCtrCrypt", data, data, string(desCtrDeCrypt))
+ t.FailNow()
+ }
+}
+
+func TestDesCfbEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefgh"
+
+ desCfbEncrypt := DesCfbEncrypt([]byte(data), []byte(key))
+ desCfbDecrypt := DesCfbDecrypt(desCfbEncrypt, []byte(key))
+
+ if string(desCfbDecrypt) != data {
+ utils.LogFailedTestInfo(t, "DesCfbEncrypt/DesCfbDecrypt", data, data, string(desCfbDecrypt))
+ t.FailNow()
+ }
+}
+
+func TestDesOfbEncrypt(t *testing.T) {
+ data := "hello world"
+ key := "abcdefgh"
+
+ desOfbEncrypt := DesOfbEncrypt([]byte(data), []byte(key))
+ desOfbDecrypt := DesOfbDecrypt(desOfbEncrypt, []byte(key))
+
+ if string(desOfbDecrypt) != data {
+ utils.LogFailedTestInfo(t, "DesOfbEncrypt/DesOfbDecrypt", data, data, string(desOfbDecrypt))
+ t.FailNow()
+ }
+}
diff --git a/cryptor/rsa.go b/cryptor/rsa.go
new file mode 100644
index 0000000..ae3e22c
--- /dev/null
+++ b/cryptor/rsa.go
@@ -0,0 +1,116 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package cryptor implements some util functions to encrypt and decrypt.
+package cryptor
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "os"
+)
+
+// GenerateRsaKey make a rsa private key, and return key file name
+// Generated key file is `rsa_private.pem` and `rsa_public.pem` in current path
+func GenerateRsaKey(keySize int, priKeyFile, pubKeyFile string) {
+ // private key
+ privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
+ if err != nil {
+ panic(err)
+ }
+
+ derText := x509.MarshalPKCS1PrivateKey(privateKey)
+
+ block := pem.Block{
+ Type: "rsa private key",
+ Bytes: derText,
+ }
+
+ //file,err := os.Create("rsa_private.pem")
+ file, err := os.Create(priKeyFile)
+ if err != nil {
+ panic(err)
+ }
+ pem.Encode(file, &block)
+ file.Close()
+
+ // public key
+ publicKey := privateKey.PublicKey
+
+ derpText, err := x509.MarshalPKIXPublicKey(&publicKey)
+ if err != nil {
+ panic(err)
+ }
+
+ block = pem.Block{
+ Type: "rsa public key",
+ Bytes: derpText,
+ }
+
+ //file,err = os.Create("rsa_public.pem")
+ file, err = os.Create(pubKeyFile)
+ if err != nil {
+ panic(err)
+ }
+ pem.Encode(file, &block)
+ file.Close()
+}
+
+// RsaEncrypt encrypt data with ras algorithm
+func RsaEncrypt(data []byte, pubKeyFileName string) []byte {
+ file, err := os.Open(pubKeyFileName)
+ if err != nil {
+ panic(err)
+ }
+ fileInfo, err := file.Stat()
+ if err != nil {
+ panic(err)
+ }
+ defer file.Close()
+ buf := make([]byte, fileInfo.Size())
+ file.Read(buf)
+
+ block, _ := pem.Decode(buf)
+
+ pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+ pubKey := pubInterface.(*rsa.PublicKey)
+
+ cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, data)
+ if err != nil {
+ panic(err)
+ }
+ return cipherText
+}
+
+// RsaDecrypt decrypt data with ras algorithm
+func RsaDecrypt(data []byte, privateKeyFileName string) []byte {
+ file, err := os.Open(privateKeyFileName)
+ if err != nil {
+ panic(err)
+ }
+ fileInfo, err := file.Stat()
+ if err != nil {
+ panic(err)
+ }
+ buf := make([]byte, fileInfo.Size())
+ defer file.Close()
+ file.Read(buf)
+
+ block, _ := pem.Decode(buf)
+
+ priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ plainText, err := rsa.DecryptPKCS1v15(rand.Reader, priKey, data)
+ if err != nil {
+ panic(err)
+ }
+ return plainText
+}
diff --git a/cryptor/rsa_test.go b/cryptor/rsa_test.go
new file mode 100644
index 0000000..b1f8ab5
--- /dev/null
+++ b/cryptor/rsa_test.go
@@ -0,0 +1,19 @@
+package cryptor
+
+import (
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestRsaEncrypt(t *testing.T) {
+ GenerateRsaKey(4096, "rsa_private.pem", "rsa_public.pem")
+ data := []byte("hello world")
+ encrypted := RsaEncrypt(data, "rsa_public.pem")
+ decrypted := RsaDecrypt(encrypted, "rsa_private.pem")
+
+ if string(data) != string(decrypted) {
+ utils.LogFailedTestInfo(t, "RsaEncrypt/RsaDecrypt", string(data), string(data), string(decrypted))
+ t.FailNow()
+ }
+}
diff --git a/datetime/datetime.go b/datetime/datetime.go
new file mode 100644
index 0000000..8e12621
--- /dev/null
+++ b/datetime/datetime.go
@@ -0,0 +1,117 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license.
+
+// Package datetime implements some functions to format date and time.
+
+// Note:
+// 1. `format` param in FormatTimeToStr function should be as flow:
+//"yyyy-mm-dd hh:mm:ss"
+//"yyyy-mm-dd hh:mm"
+//"yyyy-mm-dd hh"
+//"yyyy-mm-dd"
+//"yyyy-mm"
+//"mm-dd"
+//"dd-mm-yy hh:mm:ss"
+//"yyyy/mm/dd hh:mm:ss"
+//"yyyy/mm/dd hh:mm"
+//"yyyy/mm/dd hh"
+//"yyyy/mm/dd"
+//"yyyy/mm"
+//"mm/dd"
+//"dd/mm/yy hh:mm:ss"
+//"yyyy"
+//"mm"
+//"hh:mm:ss"
+//"mm:ss"
+
+package datetime
+
+import (
+ "strconv"
+ "time"
+)
+
+var timeFormat map[string]string
+
+func init() {
+ timeFormat = map[string]string{
+ "yyyy-mm-dd hh:mm:ss": "2006-01-02 15:04:05",
+ "yyyy-mm-dd hh:mm": "2006-01-02 15:04",
+ "yyyy-mm-dd hh": "2006-01-02 15:04",
+ "yyyy-mm-dd": "2006-01-02",
+ "yyyy-mm": "2006-01",
+ "mm-dd": "01-02",
+ "dd-mm-yy hh:mm:ss": "02-01-06 15:04:05",
+ "yyyy/mm/dd hh:mm:ss": "2006/01/02 15:04:05",
+ "yyyy/mm/dd hh:mm": "2006/01/02 15:04",
+ "yyyy/mm/dd hh": "2006/01/02 15",
+ "yyyy/mm/dd": "2006/01/02",
+ "yyyy/mm": "2006/01",
+ "mm/dd": "01/02",
+ "dd/mm/yy hh:mm:ss": "02/01/06 15:04:05",
+ "yyyy": "2006",
+ "mm": "01",
+ "hh:mm:ss": "15:04:05",
+ "mm:ss": "04:05",
+ }
+}
+
+// AddMinute add or sub minute to the time
+func AddMinute(t time.Time, minute int64) time.Time {
+ s := strconv.FormatInt(minute, 10)
+ m, _ := time.ParseDuration(s + "m")
+ return t.Add(m)
+}
+
+// AddHour add or sub hour to the time
+func AddHour(t time.Time, hour int64) time.Time {
+ s := strconv.FormatInt(hour, 10)
+ h, _ := time.ParseDuration(s + "h")
+ return t.Add(h)
+}
+
+// AddDay add or sub day to the time
+func AddDay(t time.Time, day int64) time.Time {
+ dayHours := day * 24
+ d := strconv.FormatInt(dayHours, 10)
+ h, _ := time.ParseDuration(d + "h")
+ return t.Add(h)
+}
+
+// GetNowDate return format yyyy-mm-dd of current date
+func GetNowDate() string {
+ return time.Now().Format("2006-01-02")
+}
+
+// GetNowTime return format hh-mm-ss of current time
+func GetNowTime() string {
+ return time.Now().Format("15:04:05")
+}
+
+// GetNowDateTime return format yyyy-mm-dd hh-mm-ss of current datetime
+func GetNowDateTime() string {
+ return time.Now().Format("2006-01-02 15:04:05")
+}
+
+// GetZeroHourTimestamp return timestamp of zero hour (timestamp of 00:00)
+func GetZeroHourTimestamp() int64 {
+ ts := time.Now().Format("2006-01-02")
+ t, _ := time.Parse("2006-01-02", ts)
+ return t.UTC().Unix() - 8*3600
+}
+
+// GetNightTimestamp return timestamp of zero hour (timestamp of 23:59)
+func GetNightTimestamp() int64 {
+ return GetZeroHourTimestamp() + 86400 - 1
+}
+
+// FormatTimeToStr convert time to string
+func FormatTimeToStr(t time.Time, format string) string {
+ return t.Format(timeFormat[format])
+}
+
+// FormatStrToTime convert string to time
+func FormatStrToTime(str, format string) time.Time {
+ t, _ := time.Parse(timeFormat[format], str)
+ return t
+}
diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go
new file mode 100644
index 0000000..4a6e0cc
--- /dev/null
+++ b/datetime/datetime_test.go
@@ -0,0 +1,145 @@
+package datetime
+
+import (
+ "github.com/duke-git/lancet/utils"
+ "testing"
+ "time"
+)
+
+func TestAddDay(t *testing.T) {
+ now := time.Now()
+
+ after2Days := AddDay(now, 2)
+ diff1 := after2Days.Sub(now)
+ if diff1.Hours() != 48 {
+ utils.LogFailedTestInfo(t, "AddDay", now, 48, diff1.Hours())
+ t.FailNow()
+ }
+
+ before2Days := AddDay(now, -2)
+ diff2 := before2Days.Sub(now)
+ if diff2.Hours() != -48 {
+ utils.LogFailedTestInfo(t, "AddDay", now, -48, diff2.Hours())
+ t.FailNow()
+ }
+
+}
+func TestAddHour(t *testing.T) {
+ now := time.Now()
+
+ after2Hours := AddHour(now, 2)
+ diff1 := after2Hours.Sub(now)
+ if diff1.Hours() != 2 {
+ utils.LogFailedTestInfo(t, "AddHour", now, 2, diff1.Hours())
+ t.FailNow()
+ }
+
+ before2Hours := AddHour(now, -2)
+ diff2 := before2Hours.Sub(now)
+ if diff2.Hours() != -2 {
+ utils.LogFailedTestInfo(t, "AddHour", now, -2, diff2.Hours())
+ t.FailNow()
+ }
+}
+
+func TestAddMinute(t *testing.T) {
+ now := time.Now()
+
+ after2Minutes := AddMinute(now, 2)
+ diff1 := after2Minutes.Sub(now)
+ if diff1.Minutes() != 2 {
+ utils.LogFailedTestInfo(t, "AddMinute", now, 2, diff1.Minutes())
+ t.FailNow()
+ }
+
+ before2Minutes := AddMinute(now, -2)
+ diff2 := before2Minutes.Sub(now)
+ if diff2.Minutes() != -2 {
+ utils.LogFailedTestInfo(t, "AddMinute", now, -2, diff2.Minutes())
+ t.FailNow()
+ }
+}
+
+func TestGetNowDate(t *testing.T) {
+ date := GetNowDate()
+ expected := time.Now().Format("2006-01-02")
+ if date != expected {
+ utils.LogFailedTestInfo(t, "GetNowDate", "", expected, date)
+ t.FailNow()
+ }
+}
+
+func TestGetNotTime(t *testing.T) {
+ ts := GetNowTime()
+ expected := time.Now().Format("15:04:05")
+ if ts != expected {
+ utils.LogFailedTestInfo(t, "GetNowTime", "", expected, ts)
+ t.FailNow()
+ }
+}
+
+func TestGetNowDateTime(t *testing.T) {
+ ts := GetNowDateTime()
+ expected := time.Now().Format("2006-01-02 15:04:05")
+ if ts != expected {
+ utils.LogFailedTestInfo(t, "GetNowDateTime", "", expected, ts)
+ t.FailNow()
+ }
+}
+
+//todo
+//func TestGetZeroHourTimestamp(t *testing.T) {
+// ts := GetZeroHourTimestamp()
+// expected := time.Now().UTC().Unix() - 8*3600
+// if ts != expected {
+// utils.LogFailedTestInfo(t, "GetZeroHourTimestamp", "", expected, ts)
+// t.FailNow()
+// }
+//}
+
+func TestFormatTimeToStr(t *testing.T) {
+ datetime, _ := time.Parse("2006-01-02 15:04:05", "2021-01-02 16:04:08")
+ cases := []string{
+ "yyyy-mm-dd hh:mm:ss", "yyyy-mm-dd",
+ "dd-mm-yy hh:mm:ss", "yyyy/mm/dd hh:mm:ss",
+ "hh:mm:ss", "yyyy/mm"}
+
+ expected := []string{
+ "2021-01-02 16:04:08", "2021-01-02",
+ "02-01-21 16:04:08", "2021/01/02 16:04:08",
+ "16:04:08", "2021/01"}
+
+ for i := 0; i < len(cases); i++ {
+ res := FormatTimeToStr(datetime, cases[i])
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "FormatTimeToStr", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+
+}
+
+func TestFormatStrToTime(t *testing.T) {
+ formats := []string{
+ "2006-01-02 15:04:05", "2006-01-02",
+ "02-01-06 15:04:05", "2006/01/02 15:04:05",
+ "2006/01"}
+ cases := []string{
+ "yyyy-mm-dd hh:mm:ss", "yyyy-mm-dd",
+ "dd-mm-yy hh:mm:ss", "yyyy/mm/dd hh:mm:ss",
+ "yyyy/mm"}
+
+ datetimeStr := []string{
+ "2021-01-02 16:04:08", "2021-01-02",
+ "02-01-21 16:04:08", "2021/01/02 16:04:08",
+ "2021/01"}
+
+ for i := 0; i < len(cases); i++ {
+ res := FormatStrToTime(datetimeStr[i], cases[i])
+ expected, _ := time.Parse(formats[i], datetimeStr[i])
+ if res != expected {
+ utils.LogFailedTestInfo(t, "FormatTimeToStr", cases[i], expected, res)
+ t.FailNow()
+ }
+ }
+}
diff --git a/fileutil/file.go b/fileutil/file.go
new file mode 100644
index 0000000..d057551
--- /dev/null
+++ b/fileutil/file.go
@@ -0,0 +1,104 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license.
+
+// Package fileutil implements some basic functions for file operations
+
+package fileutil
+
+import (
+ "errors"
+ "io"
+ "io/ioutil"
+ "os"
+)
+
+// IsFileExists checks if a file or directory exists
+func IsExist(path string) bool {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true
+ }
+ if errors.Is(err, os.ErrExist) {
+ return false
+ }
+ return false
+}
+
+// CreateFile create a file in path
+func CreateFile(path string) bool {
+ file, err := os.Create(path)
+ if err != nil {
+ return false
+ }
+
+ defer file.Close()
+ return true
+}
+
+// IsFileExists checks if the path is directy or not
+func IsDir(path string) bool {
+ file, err := os.Stat(path)
+ if err != nil {
+ return false
+ }
+ return file.IsDir()
+}
+
+// RemoveFile remove the path file
+func RemoveFile(path string) error {
+ return os.Remove(path)
+}
+
+// CopyFile copy src file to dest file
+func CopyFile(srcFilePath string, dstFilePath string) error {
+ srcFile, err := os.Open(srcFilePath)
+ if err != nil {
+ return err
+ }
+ defer srcFile.Close()
+
+ distFile, err := os.Create(dstFilePath)
+ if err != nil {
+ return err
+ }
+ defer distFile.Close()
+
+ var tmp = make([]byte, 1024*4)
+ for {
+ n, err := srcFile.Read(tmp)
+ distFile.Write(tmp[:n])
+ if err != nil {
+ if err == io.EOF {
+ return nil
+ } else {
+ return err
+ }
+ }
+ }
+}
+
+// ListFileNames return all file names in the path
+func ListFileNames(path string) ([]string, error) {
+ if !IsExist(path) {
+ return []string{}, nil
+ }
+
+ fs, err := ioutil.ReadDir(path)
+ if err != nil {
+ return []string{}, err
+ }
+
+ sz := len(fs)
+ if sz == 0 {
+ return []string{}, nil
+ }
+
+ res := []string{}
+ for i := 0; i < sz; i++ {
+ if !fs[i].IsDir() {
+ res = append(res, fs[i].Name())
+ }
+ }
+
+ return res, nil
+}
diff --git a/fileutil/file_test.go b/fileutil/file_test.go
new file mode 100644
index 0000000..edc8ca9
--- /dev/null
+++ b/fileutil/file_test.go
@@ -0,0 +1,99 @@
+package fileutil
+
+import (
+ "github.com/duke-git/lancet/utils"
+ "os"
+ "reflect"
+ "testing"
+)
+
+func TestIsExist(t *testing.T) {
+ cases := []string{"./", "./a.txt"}
+ expected := []bool{true, false}
+
+ for i := 0; i < len(cases); i++ {
+ res := IsExist(cases[i])
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "IsExist", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestCreateFile(t *testing.T) {
+ f := "./text.txt"
+ if CreateFile(f) {
+ file, err := os.Open(f)
+ if err != nil {
+ utils.LogFailedTestInfo(t, "CreateFile", f, f, "create file error: "+err.Error())
+ t.FailNow()
+ }
+ if file.Name() != f {
+ utils.LogFailedTestInfo(t, "CreateFile", f, f, file.Name())
+ t.FailNow()
+ }
+ } else {
+ utils.LogFailedTestInfo(t, "CreateFile", f, f, "create file error")
+ t.FailNow()
+ }
+}
+
+func TestIsDir(t *testing.T) {
+ cases := []string{"./", "./a.txt"}
+ expected := []bool{true, false}
+
+ for i := 0; i < len(cases); i++ {
+ res := IsDir(cases[i])
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "IsDir", cases[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
+
+func TestRemoveFile(t *testing.T) {
+ f := "./text.txt"
+ if CreateFile(f) {
+ err := RemoveFile(f)
+ if err != nil {
+ utils.LogFailedTestInfo(t, "RemoveFile", f, f, err.Error())
+ t.FailNow()
+ }
+ } else {
+ utils.LogFailedTestInfo(t, "RemoveFile", f, f, "create file error")
+ t.FailNow()
+ }
+}
+
+func TestCopyFile(t *testing.T) {
+ srcFile := "./text.txt"
+ CreateFile(srcFile)
+
+ dstFile := "./text_copy.txt"
+
+ err := CopyFile(srcFile, dstFile)
+ if err != nil {
+ file, err := os.Open(dstFile)
+ if err != nil {
+ utils.LogFailedTestInfo(t, "CopyFile", srcFile, dstFile, "create file error: "+err.Error())
+ t.FailNow()
+ }
+ if file.Name() != dstFile {
+ utils.LogFailedTestInfo(t, "CopyFile", srcFile, dstFile, file.Name())
+ t.FailNow()
+ }
+ }
+}
+
+func TestListFileNames(t *testing.T) {
+ filesInCurrentPath, err := ListFileNames("./")
+ if err != nil {
+ t.FailNow()
+ }
+ expected := []string{"file.go", "file_test.go"}
+ if !reflect.DeepEqual(filesInCurrentPath, expected) {
+ utils.LogFailedTestInfo(t, "ToChar", "./", expected, filesInCurrentPath)
+ t.FailNow()
+ }
+
+}
diff --git a/formatter/formatter.go b/formatter/formatter.go
new file mode 100644
index 0000000..a9bf1a2
--- /dev/null
+++ b/formatter/formatter.go
@@ -0,0 +1,17 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package formatter implements some functions to format string, struct.
+package formatter
+
+import "strings"
+
+// Comma add comma to number by every 3 numbers from right. ahead by symbol char
+func Comma(v interface{}, symbol string) string {
+ s := numString(v)
+ dotIndex := strings.Index(s, ".")
+ if dotIndex != -1 {
+ return symbol + commaString(s[:dotIndex]) + s[dotIndex:]
+ }
+ return symbol + commaString(s)
+}
\ No newline at end of file
diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go
new file mode 100644
index 0000000..2dee50d
--- /dev/null
+++ b/formatter/formatter_test.go
@@ -0,0 +1,25 @@
+package formatter
+
+import (
+ "github.com/duke-git/lancet/utils"
+ "testing"
+)
+
+func TestComma(t *testing.T) {
+ comma(t, "", "","")
+ comma(t, "aa", "","")
+ comma(t, "123", "","123")
+ comma(t, "12345", "","12,345")
+ comma(t, 12345, "","12,345")
+ comma(t, 12345, "$","$12,345")
+ comma(t, 12345, "¥","¥12,345")
+ comma(t, 12345.6789, "","12,345.6789")
+}
+
+func comma(t *testing.T, test interface{}, symbol string, expected interface{}) {
+ res:= Comma(test, symbol)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "Comma", test, expected, res)
+ t.FailNow()
+ }
+}
\ No newline at end of file
diff --git a/formatter/formatter_util.go b/formatter/formatter_util.go
new file mode 100644
index 0000000..ff7f6ff
--- /dev/null
+++ b/formatter/formatter_util.go
@@ -0,0 +1,39 @@
+package formatter
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+func commaString(s string) string {
+ if len(s) <= 3 {
+ return s
+ }
+ return commaString(s[:len(s)-3]) + "," + commaString(s[len(s)-3:])
+}
+
+func numString(value interface{}) string {
+ switch reflect.TypeOf(value).Kind() {
+ case reflect.Int, reflect.Int64, reflect.Float32, reflect.Float64:
+ return fmt.Sprintf("%v", value)
+ case reflect.String: {
+ sv := fmt.Sprintf("%v", value)
+ if strings.Contains(sv, ".") {
+ _, err := strconv.ParseFloat(sv, 64)
+ if err == nil {
+ return sv
+ }
+ }else {
+ _, err := strconv.ParseInt(sv, 10, 64)
+ if err == nil {
+ return sv
+ }
+ }
+ }
+ default:
+ return ""
+ }
+ return ""
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..02610a3
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/duke-git/lancet
+
+go 1.16
diff --git a/netutil/net.go b/netutil/net.go
new file mode 100644
index 0000000..132d9b4
--- /dev/null
+++ b/netutil/net.go
@@ -0,0 +1,83 @@
+package netutil
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net"
+ "net/http"
+)
+
+// GetInternalIp return internal ipv4
+func GetInternalIp() string {
+ addr, err := net.InterfaceAddrs()
+ if err != nil {
+ panic(err.Error())
+ }
+ for _, a := range addr {
+ if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
+ if ipNet.IP.To4() != nil {
+ return ipNet.IP.String()
+ }
+ }
+ }
+
+ return ""
+}
+
+// GetPublicIpInfo return public ip information
+// return the PublicIpInfo struct
+func GetPublicIpInfo() (*PublicIpInfo, error) {
+ resp, err := http.Get("http://ip-api.com/json/")
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ var ip PublicIpInfo
+ err = json.Unmarshal(body, &ip)
+ if err != nil {
+ return nil, err
+ }
+
+ return &ip, nil
+}
+
+type PublicIpInfo struct {
+ Status string `json:"status"`
+ Country string `json:"country"`
+ CountryCode string `json:"countryCode"`
+ Region string `json:"region"`
+ RegionName string `json:"regionName"`
+ City string `json:"city"`
+ Lat float64 `json:"lat"`
+ Lon float64 `json:"lon"`
+ Isp string `json:"isp"`
+ Org string `json:"org"`
+ As string `json:"as"`
+ Ip string `json:"query"`
+}
+
+// IsPublicIP verify a ip is public or not
+func IsPublicIP(IP net.IP) bool {
+ if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
+ return false
+ }
+ if ip4 := IP.To4(); ip4 != nil {
+ switch {
+ case ip4[0] == 10:
+ return false
+ case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
+ return false
+ case ip4[0] == 192 && ip4[1] == 168:
+ return false
+ default:
+ return true
+ }
+ }
+ return false
+}
diff --git a/netutil/net_test.go b/netutil/net_test.go
new file mode 100644
index 0000000..b8faec9
--- /dev/null
+++ b/netutil/net_test.go
@@ -0,0 +1,46 @@
+package netutil
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/utils"
+ "net"
+ "testing"
+)
+
+func TestGetInternalIp(t *testing.T) {
+ internalIp := GetInternalIp()
+ ip := net.ParseIP(internalIp)
+ if ip == nil {
+ utils.LogFailedTestInfo(t, "GetInternalIp", "GetInternalIp", "", ip)
+ t.FailNow()
+ }
+}
+
+func TestGetPublicIpInfo(t *testing.T) {
+ publicIpInfo, err := GetPublicIpInfo()
+ if err != nil {
+ t.FailNow()
+ }
+ fmt.Printf("public ip info is: %+v \n", *publicIpInfo)
+}
+
+func TestIsPublicIP(t *testing.T) {
+ ips := []net.IP{
+ net.ParseIP("127.0.0.1"),
+ net.ParseIP("192.168.0.1"),
+ net.ParseIP("10.91.210.131"),
+ net.ParseIP("172.20.16.1"),
+ net.ParseIP("36.112.24.10"),
+ }
+
+ expected := []bool{false, false, false, false, true}
+
+ for i := 0; i < len(ips); i++ {
+ res := IsPublicIP(ips[i])
+
+ if res != expected[i] {
+ utils.LogFailedTestInfo(t, "IsPublicIP", ips[i], expected[i], res)
+ t.FailNow()
+ }
+ }
+}
diff --git a/netutil/request.go b/netutil/request.go
new file mode 100644
index 0000000..b438687
--- /dev/null
+++ b/netutil/request.go
@@ -0,0 +1,67 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license.
+
+// Package netutil implements some basic functions to send http request and get ip info.
+// Note:
+// HttpGet, HttpPost, HttpDelete, HttpPut, HttpPatch, function param `url` is required.
+// HttpGet, HttpPost, HttpDelete, HttpPut, HttpPatch, function param `params` is variable, the order is:
+// params[0] is header which type should be http.Header or map[string]string,
+// params[1] is query param which type should be url.Values or map[string]interface{},
+// params[2] is post body which type should be []byte.
+// params[3] is http client which type should be http.Client.
+package netutil
+
+import (
+ "fmt"
+ "net/http"
+ "sort"
+ "strings"
+)
+
+//HttpGet send get http request
+func HttpGet(url string, params ...interface{}) (*http.Response, error) {
+ return request(http.MethodGet, url, params...)
+}
+
+//HttpPost send post http request
+func HttpPost(url string, params ...interface{}) (*http.Response, error) {
+ return request(http.MethodPost, url, params...)
+}
+
+//HttpPut send put http request
+func HttpPut(url string, params ...interface{}) (*http.Response, error) {
+ return request(http.MethodPut, url, params...)
+}
+
+//HttpDelete send delete http request
+func HttpDelete(url string, params ...interface{}) (*http.Response, error) {
+ return request(http.MethodDelete, url, params...)
+}
+
+// HttpPatch send patch http request
+func HttpPatch(url string, params ...interface{}) (*http.Response, error) {
+ return request(http.MethodPatch, url, params...)
+}
+
+// ConvertMapToQueryString convert map to sorted url query string
+func ConvertMapToQueryString(param map[string]interface{}) string {
+ if param == nil {
+ return ""
+ }
+ var keys []string
+ for key := range param {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+
+ var build strings.Builder
+ for i, v := range keys {
+ build.WriteString(v)
+ build.WriteString("=")
+ build.WriteString(fmt.Sprintf("%v", param[v]))
+ if i != len(keys)-1 {
+ build.WriteString("&")
+ }
+ }
+ return build.String()
+}
diff --git a/netutil/request_test.go b/netutil/request_test.go
new file mode 100644
index 0000000..9527200
--- /dev/null
+++ b/netutil/request_test.go
@@ -0,0 +1,98 @@
+package netutil
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "github.com/duke-git/lancet/utils"
+ "log"
+ "testing"
+)
+
+func TestHttpGet(t *testing.T) {
+ url := "https://gutendex.com/books?"
+ queryParams := make(map[string]interface{})
+ queryParams["ids"] = "1"
+
+ resp, err := HttpGet(url, nil, queryParams)
+ if err != nil {
+ log.Fatal(err)
+ t.FailNow()
+ }
+
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println("response: ", resp.StatusCode, string(body))
+
+}
+
+func TestHttpPost(t *testing.T) {
+ url := "http://public-api-v1.aspirantzhang.com/users"
+ type User struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ }
+ user := User{
+ "test",
+ "test@test.com",
+ }
+ bodyParams, _ := json.Marshal(user)
+ header := map[string]string{
+ "Content-Type": "application/json",
+ }
+ resp, err := HttpPost(url, header, nil, bodyParams)
+ if err != nil {
+ log.Fatal(err)
+ t.FailNow()
+ }
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println("response: ", resp.StatusCode, string(body))
+}
+
+func TestHttpPut(t *testing.T) {
+ url := "http://public-api-v1.aspirantzhang.com/users/10420"
+ type User struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+ }
+ user := User{
+ "test",
+ "test@test.com",
+ }
+ bodyParams, _ := json.Marshal(user)
+ header := map[string]string{
+ "Content-Type": "application/json",
+ }
+ resp, err := HttpPut(url, header, nil, bodyParams)
+ if err != nil {
+ log.Fatal(err)
+ t.FailNow()
+ }
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println("response: ", resp.StatusCode, string(body))
+}
+
+func TestHttpDelete(t *testing.T) {
+ url := "http://public-api-v1.aspirantzhang.com/users/10420"
+ resp, err := HttpDelete(url)
+ if err != nil {
+ log.Fatal(err)
+ t.FailNow()
+ }
+ body, _ := ioutil.ReadAll(resp.Body)
+ fmt.Println("response: ", resp.StatusCode, string(body))
+}
+
+func TestConvertMapToQueryString(t *testing.T) {
+ var m = map[string]interface{}{
+ "c": 3,
+ "a": 1,
+ "b": 2,
+ }
+
+ expected := "a=1&b=2&c=3"
+ r := ConvertMapToQueryString(m)
+ if r != expected {
+ utils.LogFailedTestInfo(t, "ConvertMapToQueryString", m, expected, r)
+ t.FailNow()
+ }
+}
diff --git a/netutil/request_util.go b/netutil/request_util.go
new file mode 100644
index 0000000..58d0d8b
--- /dev/null
+++ b/netutil/request_util.go
@@ -0,0 +1,181 @@
+package netutil
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+func request(method, reqUrl string, params ...interface{}) (*http.Response, error) {
+ if len(reqUrl) == 0 {
+ return nil, errors.New("url should be specified")
+ }
+
+ req := &http.Request{
+ Method: method,
+ Header: make(http.Header),
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ }
+
+ client := &http.Client{}
+ switch len(params) {
+ case 0:
+ err := setUrl(req, reqUrl)
+ if err != nil {
+ return nil, err
+ }
+ case 1:
+ err := setUrl(req, reqUrl)
+ if err != nil {
+ return nil, err
+ }
+ err = setHeader(req, params[0])
+ if err != nil {
+ return nil, err
+ }
+ case 2:
+ err := setHeader(req, params[0])
+ if err != nil {
+ return nil, err
+ }
+ err = setQueryParam(req, reqUrl, params[1])
+ if err != nil {
+ return nil, err
+ }
+ case 3:
+ err := setHeader(req, params[0])
+ if err != nil {
+ return nil, err
+ }
+ err = setQueryParam(req, reqUrl, params[1])
+ if err != nil {
+ return nil, err
+ }
+ err = setBodyByte(req, params[2])
+ if err != nil {
+ return nil, err
+ }
+ case 4:
+ err := setHeader(req, params[0])
+ if err != nil {
+ return nil, err
+ }
+ err = setQueryParam(req, reqUrl, params[1])
+ if err != nil {
+ return nil, err
+ }
+ err = setBodyByte(req, params[2])
+ if err != nil {
+ return nil, err
+ }
+ client, err = getClient(params[3])
+ if err != nil {
+ return nil, err
+ }
+
+ }
+
+ resp, e := client.Do(req)
+ return resp, e
+}
+
+func setHeader(req *http.Request, header interface{}) error {
+ if header != nil {
+ switch v := header.(type) {
+ case map[string]string:
+ for k := range v {
+ req.Header.Add(k, v[k])
+ }
+ case http.Header:
+ for k, vv := range v {
+ for _, vvv := range vv {
+ req.Header.Add(k, vvv)
+ }
+ }
+ default:
+ return errors.New("header params type should be http.Header or map[string]string")
+ }
+ }
+
+ if host := req.Header.Get("Host"); host != "" {
+ req.Host = host
+ }
+
+ return nil
+}
+func setUrl(req *http.Request, reqUrl string) error {
+ u, err := url.Parse(reqUrl)
+ if err != nil {
+ return err
+ }
+ req.URL = u
+ return nil
+}
+func setQueryParam(req *http.Request, reqUrl string, queryParam interface{}) error {
+ var values url.Values
+ if queryParam != nil {
+ switch v := queryParam.(type) {
+ case map[string]interface{}:
+ values = url.Values{}
+ for k := range v {
+ values.Set(k, fmt.Sprintf("%s", v[k]))
+ }
+ case url.Values:
+ values = v
+ default:
+ return errors.New("query params type should be url.Values or map[string]interface{}")
+ }
+ }
+
+ // set url
+ if values != nil {
+ if !strings.Contains(reqUrl, "?") {
+ reqUrl = reqUrl + "?" + values.Encode()
+ } else {
+ reqUrl = reqUrl + "&" + values.Encode()
+ }
+ }
+ u, err := url.Parse(reqUrl)
+ if err != nil {
+ return err
+ }
+ req.URL = u
+
+ return nil
+}
+
+func setBodyByte(req *http.Request, body interface{}) error {
+ if body != nil {
+ var bodyByte []byte
+ if body != nil {
+ switch v := body.(type) {
+ case []byte:
+ bodyByte = v
+ default:
+ return errors.New("body type should be []byte")
+ }
+ }
+ req.Body = ioutil.NopCloser(bytes.NewReader(bodyByte))
+ }
+ return nil
+}
+
+func getClient(client interface{}) (*http.Client, error) {
+ c := http.Client{}
+ if client != nil {
+ switch v := client.(type) {
+ case http.Client:
+ c = v
+ default:
+ return nil, errors.New("client type should be http.Client")
+ }
+ }
+
+ return &c, nil
+}
diff --git a/random/random.go b/random/random.go
new file mode 100644
index 0000000..c015e57
--- /dev/null
+++ b/random/random.go
@@ -0,0 +1,50 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license.
+
+// Package random implements some basic functions to generate random int and string.
+package random
+
+import (
+ crand "crypto/rand"
+ "io"
+ "math/rand"
+ "time"
+)
+
+// RandString generate random string
+// see https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
+func RandString(length int) string {
+ const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+ b := make([]byte, length)
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ for i := range b {
+ b[i] = letters[r.Int63()%int64(len(letters))]
+ }
+ return string(b)
+}
+
+// RandInt generate random int between min and max, maybe min, not be max
+func RandInt(min, max int) int {
+ if min == max {
+ return min
+ }
+ if max < min {
+ min, max = max, min
+ }
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ return r.Intn(max-min) + min
+}
+
+// RandBytes generate random byte slice
+func RandBytes(length int) []byte {
+ if length < 1 {
+ return nil
+ }
+ b := make([]byte, length)
+
+ if _, err := io.ReadFull(crand.Reader, b); err != nil {
+ return nil
+ }
+ return b
+}
diff --git a/random/random_test.go b/random/random_test.go
new file mode 100644
index 0000000..1528233
--- /dev/null
+++ b/random/random_test.go
@@ -0,0 +1,46 @@
+package random
+
+import (
+ "fmt"
+ "github.com/duke-git/lancet/utils"
+ "reflect"
+ "regexp"
+ "testing"
+)
+
+func TestRandString(t *testing.T) {
+ randStr := RandString(6)
+ fmt.Println(randStr)
+ pattern := `^[a-zA-Z]+$`
+ reg := regexp.MustCompile(pattern)
+
+ if len(randStr) != 6 || !reg.MatchString(randStr) {
+ utils.LogFailedTestInfo(t, "RandString", "RandString(6)", "RandString(6) should be 6 letters ", randStr)
+ t.FailNow()
+ }
+}
+
+func TestRandInt(t *testing.T) {
+ randInt := RandInt(1, 10)
+
+ if randInt < 1 || randInt >= 10 {
+ utils.LogFailedTestInfo(t, "RandInt", "RandInt(1, 10)", "RandInt(1, 10) should between [1, 10) ", randInt)
+ t.FailNow()
+ }
+}
+
+func TestRandBytes(t *testing.T) {
+ randBytes := RandBytes(4)
+
+ if len(randBytes) != 4 {
+ utils.LogFailedTestInfo(t, "RandBytes", "RandBytes(4)", "RandBytes(4) should return 4 element of []bytes", randBytes)
+ t.FailNow()
+ }
+
+ v := reflect.ValueOf(randBytes)
+ et := v.Type().Elem()
+ if v.Kind() != reflect.Slice || et.Kind() != reflect.Uint8 {
+ utils.LogFailedTestInfo(t, "RandBytes", "RandBytes(4)", "RandBytes(4) should return 4 element of []bytes", randBytes)
+ t.FailNow()
+ }
+}
diff --git a/slice/slice.go b/slice/slice.go
new file mode 100644
index 0000000..8bedb49
--- /dev/null
+++ b/slice/slice.go
@@ -0,0 +1,411 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package slice implements some functions to manipulate slice.
+package slice
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "sort"
+ "strings"
+ "unsafe"
+)
+
+// Contain check if the value is in the slice or not
+func Contain(slice interface{}, value interface{}) bool {
+ v := reflect.ValueOf(slice)
+ switch reflect.TypeOf(slice).Kind() {
+ case reflect.Slice, reflect.Array:
+ for i := 0; i < v.Len(); i++ {
+ if v.Index(i).Interface() == value {
+ return true
+ }
+ }
+
+ case reflect.Map:
+ if v.MapIndex(reflect.ValueOf(value)).IsValid() {
+ return true
+ }
+ case reflect.String:
+ s := fmt.Sprintf("%v", slice)
+ ss := fmt.Sprintf("%v", value)
+ return strings.Contains(s, ss)
+ }
+
+ return false
+}
+
+// Chunk creates an slice of elements split into groups the length of `size`.
+func Chunk(slice []interface{}, size int) [][]interface{} {
+ var res [][]interface{}
+
+ if len(slice) == 0 || size <= 0 {
+ return res
+ }
+
+ length := len(slice)
+ if size == 1 || size >= length {
+ for _, v := range slice {
+ var tmp []interface{}
+ tmp = append(tmp, v)
+ res = append(res, tmp)
+ }
+ return res
+ }
+
+ // divide slice equally
+ divideNum := length/size + 1
+ for i := 0; i < divideNum; i++ {
+ if i == divideNum-1 {
+ if len(slice[i*size:]) > 0 {
+ res = append(res, slice[i*size:])
+ }
+ } else {
+ res = append(res, slice[i*size:(i+1)*size])
+ }
+ }
+
+ 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)
+
+ var indexes []int
+ for i := 0; i < v.Len(); i++ {
+ vi := v.Index(i).Interface()
+ if !Contain(slice2, vi) {
+ indexes = append(indexes, i)
+ }
+ }
+
+ res := reflect.MakeSlice(v.Type(), len(indexes), len(indexes))
+ for i := range indexes {
+ res.Index(i).Set(v.Index(indexes[i]))
+ }
+ return res.Interface()
+}
+
+// Filter iterates over elements of slice, returning an slice of all elements `signature` returns truthy for.
+// The function signature should be func(index int, value interface{}) bool .
+func Filter(slice, function interface{}) interface{} {
+ sv := sliceValue(slice)
+ fn := functionValue(function)
+
+ elemType := sv.Type().Elem()
+ if checkSliceCallbackFuncSignature(fn, elemType, reflect.ValueOf(true).Type()) {
+ panic("Filter function must be of type func(int, " + elemType.String() + ")" + reflect.ValueOf(true).Type().String())
+ }
+
+ var indexes []int
+ for i := 0; i < sv.Len(); i++ {
+ flag := fn.Call([]reflect.Value{reflect.ValueOf(i), sv.Index(i)})[0]
+ if flag.Bool() {
+ 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()
+}
+
+// Map creates an slice of values by running each element of `slice` thru `function`.
+// The function signature should be func(index int, value interface{}) interface{}.
+func Map(slice, function interface{}) interface{} {
+ sv := sliceValue(slice)
+ fn := functionValue(function)
+
+ elemType := sv.Type().Elem()
+ if checkSliceCallbackFuncSignature(fn, elemType, nil) {
+ panic("Map function must be of type func(int, " + elemType.String() + ")" + elemType.String())
+ }
+
+ res := reflect.MakeSlice(sv.Type(), sv.Len(), sv.Len())
+ for i := 0; i < sv.Len(); i++ {
+ res.Index(i).Set(fn.Call([]reflect.Value{reflect.ValueOf(i), sv.Index(i)})[0])
+ }
+ return res.Interface()
+}
+
+// Reduce creates an slice of values by running each element of `slice` thru `function`.
+// The function signature should be func(index int, value1, value2 interface{}) interface{} .
+func Reduce(slice, function, zero interface{}) interface{} {
+ sv := sliceValue(slice)
+
+ len := sv.Len()
+ if len == 0 {
+ return zero
+ } else if len == 1 {
+ return sv.Index(0)
+ }
+
+ elementType := sv.Type().Elem()
+ fn := functionValue(function)
+
+ if checkSliceCallbackFuncSignature(fn, elementType, elementType, elementType) {
+ t := elementType.String()
+ panic("Reduce function must be of type func(int, " + t + ", " + t + ")" + t)
+ }
+
+ var params [3]reflect.Value
+ params[0] = reflect.ValueOf(0)
+ params[1] = sv.Index(0)
+ params[2] = sv.Index(1)
+
+ res := fn.Call(params[:])[0]
+
+ for i := 2; i < len; i++ {
+ params[0] = reflect.ValueOf(i)
+ params[1] = res
+ params[2] = sv.Index(i)
+ res = fn.Call(params[:])[0]
+ }
+
+ return res.Interface()
+}
+
+// InterfaceSlice convert param to slice of interface.
+func InterfaceSlice(slice interface{}) []interface{} {
+ sv := sliceValue(slice)
+ if sv.IsNil() {
+ return nil
+ }
+
+ res := make([]interface{}, sv.Len())
+ for i := 0; i < sv.Len(); i++ {
+ res[i] = sv.Index(i).Interface()
+ }
+
+ return res
+}
+
+// StringSlice convert param to slice of string.
+func StringSlice(slice interface{}) []string {
+ var res []string
+
+ v := sliceValue(slice)
+ for i := 0; i < v.Len(); i++ {
+ res = append(res, fmt.Sprint(v.Index(i)))
+ }
+
+ return res
+}
+
+// IntSlice convert param to slice of int.
+func IntSlice(slice interface{}) ([]int, error) {
+ var res []int
+
+ sv := sliceValue(slice)
+ for i := 0; i < sv.Len(); i++ {
+ v := sv.Index(i).Interface()
+ switch v := v.(type) {
+ case int:
+ res = append(res, v)
+ default:
+ return nil, errors.New("InvalidSliceElementType")
+ }
+ }
+
+ return res, nil
+}
+
+// ConvertSlice convert original slice to new data type element of slice.
+func ConvertSlice(originalSlice interface{}, newSliceType reflect.Type) interface{} {
+ sv := sliceValue(originalSlice)
+ if newSliceType.Kind() != reflect.Slice {
+ panic(fmt.Sprintf("Invalid newSliceType(non-slice type of type %T)", newSliceType))
+ }
+
+ newSlice := reflect.New(newSliceType)
+
+ hdr := (*reflect.SliceHeader)(unsafe.Pointer(newSlice.Pointer()))
+
+ var newElemSize = int(sv.Type().Elem().Size()) / int(newSliceType.Elem().Size())
+
+ hdr.Cap = sv.Cap() * newElemSize
+ hdr.Len = sv.Len() * newElemSize
+ hdr.Data = sv.Pointer()
+
+ return newSlice.Elem().Interface()
+}
+
+// DeleteByIndex delete the element of slice from start index to end index - 1.
+// Delete i: s = append(s[:i], s[i+1:]...)
+// Delete i to j: s = append(s[:i], s[j:]...)
+func DeleteByIndex(slice interface{}, start int, end ...int) (interface{}, error) {
+ v := sliceValue(slice)
+ i := start
+ if v.Len() == 0 || i < 0 || i > v.Len() {
+ return nil, errors.New("InvalidStartIndex")
+ }
+ if len(end) > 0 {
+ j := end[0]
+ if j <= i || j > v.Len() {
+ return nil, errors.New("InvalidEndIndex")
+ }
+ v = reflect.AppendSlice(v.Slice(0, i), v.Slice(j, v.Len()))
+ } else {
+ v = reflect.AppendSlice(v.Slice(0, i), v.Slice(i+1, v.Len()))
+ }
+
+ return v.Interface(), nil
+}
+
+// InsertByIndex insert the element into slice at index.
+// Insert value: s = append(s[:i], append([]T{x}, s[i:]...)...)
+// Insert slice: a = append(a[:i], append(b, a[i:]...)...)
+func InsertByIndex(slice interface{}, index int, value interface{}) (interface{}, error) {
+ v := sliceValue(slice)
+
+ if index < 0 || index > v.Len() {
+ return slice, errors.New("InvalidSliceIndex")
+ }
+
+ // value is slice
+ vv := reflect.ValueOf(value)
+ if vv.Kind() == reflect.Slice {
+ if reflect.TypeOf(slice).Elem() != reflect.TypeOf(value).Elem() {
+ return slice, errors.New("InvalidValueType")
+ }
+ v = reflect.AppendSlice(v.Slice(0, index), reflect.AppendSlice(vv.Slice(0, vv.Len()), v.Slice(index, v.Len())))
+ return v.Interface(), nil
+ }
+
+ // value is not slice
+ if reflect.TypeOf(slice).Elem() != reflect.TypeOf(value) {
+ return slice, errors.New("InvalidValueType")
+ }
+ if index == v.Len() {
+ return reflect.Append(v, reflect.ValueOf(value)).Interface(), nil
+ }
+
+ v = reflect.AppendSlice(v.Slice(0, index+1), v.Slice(index, v.Len()))
+ v.Index(index).Set(reflect.ValueOf(value))
+
+ return v.Interface(), nil
+}
+
+// UpdateByIndex update the slice element at index.
+func UpdateByIndex(slice interface{}, index int, value interface{}) (interface{}, error) {
+ v := sliceValue(slice)
+
+ if index < 0 || index >= v.Len() {
+ return slice, errors.New("InvalidSliceIndex")
+ }
+ if reflect.TypeOf(slice).Elem() != reflect.TypeOf(value) {
+ return slice, errors.New("InvalidValueType")
+ }
+
+ v.Index(index).Set(reflect.ValueOf(value))
+
+ return v.Interface(), nil
+}
+
+// Unique remove duplicate elements in slice.
+func Unique(slice interface{}) interface{} {
+ sv := sliceValue(slice)
+ if sv.Len() == 0 {
+ return slice
+ }
+
+ var res []interface{}
+ for i := 0; i < sv.Len(); i++ {
+ v := sv.Index(i).Interface()
+ flag := true
+ for j := range res {
+ if v == res[j] {
+ flag = false
+ break
+ }
+ }
+ if flag {
+ res = append(res, v)
+ }
+ }
+
+ return res
+
+ // if use map filter, the result slice element order is random, not same as origin slice
+ //mp := make(map[interface{}]bool)
+ //for i := 0; i < sv.Len(); i++ {
+ // v := sv.Index(i).Interface()
+ // mp[v] = true
+ //}
+ //
+ //var res []interface{}
+ //for k := range mp {
+ // res = append(res, mp[k])
+ //}
+ //return res
+
+}
+
+// ReverseSlice return slice of element order is reversed to the given slice
+func ReverseSlice(slice interface{}) {
+ v := sliceValue(slice)
+ swp := reflect.Swapper(v.Interface())
+ for i, j := 0, v.Len()-1; i < j; i, j = i+1, j-1 {
+ swp(i, j)
+ }
+}
+
+// SortByField return sorted slice by field
+// Slice element should be struct, field type should be int, uint, string, or bool
+// default sortType is ascending (asc), if descending order, set sortType to desc
+func SortByField(slice interface{}, field string, sortType ...string) error {
+ v := sliceValue(slice)
+ t := v.Type().Elem()
+
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if t.Kind() != reflect.Struct {
+ return fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", slice)
+ }
+
+ // Find the field.
+ sf, ok := t.FieldByName(field)
+ if !ok {
+ return fmt.Errorf("field name %s not found", field)
+ }
+
+ // Create a less function based on the field's kind.
+ var less func(a, b reflect.Value) bool
+ switch sf.Type.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
+ case reflect.Float32, reflect.Float64:
+ less = func(a, b reflect.Value) bool { return a.Float() < b.Float() }
+ case reflect.String:
+ less = func(a, b reflect.Value) bool { return a.String() < b.String() }
+ case reflect.Bool:
+ less = func(a, b reflect.Value) bool { return !a.Bool() && b.Bool() }
+ default:
+ return fmt.Errorf("field type %s not supported", sf.Type)
+ }
+
+ sort.Slice(slice, func(i, j int) bool {
+ a := v.Index(i)
+ b := v.Index(j)
+ if t.Kind() == reflect.Ptr {
+ a = a.Elem()
+ b = b.Elem()
+ }
+ a = a.FieldByIndex(sf.Index)
+ b = b.FieldByIndex(sf.Index)
+ return less(a, b)
+ })
+
+ if sortType[0] == "desc" {
+ ReverseSlice(slice)
+ }
+ return nil
+}
diff --git a/slice/slice_test.go b/slice/slice_test.go
new file mode 100644
index 0000000..c9cc1e4
--- /dev/null
+++ b/slice/slice_test.go
@@ -0,0 +1,438 @@
+package slice
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestContain(t *testing.T) {
+ t1 := []string{"a", "b", "c", "d"}
+ contain(t, t1, "a", true)
+ contain(t, t1, "e", false)
+
+ var t2 []string
+ contain(t, t2, "1", false)
+}
+
+func contain(t *testing.T, test interface{}, value interface{}, expected bool) {
+ res := Contain(test, value)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "Contain", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestChunk(t *testing.T) {
+ t1 := []string{"a", "b", "c", "d", "e"}
+
+ r1 := [][]interface{}{
+ {"a"},
+ {"b"},
+ {"c"},
+ {"d"},
+ {"e"},
+ }
+ chunk(t, InterfaceSlice(t1), 1, r1)
+
+ r2 := [][]interface{}{
+ {"a", "b"},
+ {"c", "d"},
+ {"e"},
+ }
+ chunk(t, InterfaceSlice(t1), 2, r2)
+
+ r3 := [][]interface{}{
+ {"a", "b", "c"},
+ {"d", "e"},
+ }
+ chunk(t, InterfaceSlice(t1), 3, r3)
+
+ r4 := [][]interface{}{
+ {"a", "b", "c", "d"},
+ {"e"},
+ }
+ chunk(t, InterfaceSlice(t1), 4, r4)
+
+ r5 := [][]interface{}{
+ {"a"},
+ {"b"},
+ {"c"},
+ {"d"},
+ {"e"},
+ }
+ chunk(t, InterfaceSlice(t1), 5, r5)
+
+}
+
+func chunk(t *testing.T, test []interface{}, num int, expected [][]interface{}) {
+ res := Chunk(test, num)
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "Chunk", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestConvertSlice(t *testing.T) {
+ //t1 := []string{"1","2"}
+ //aInt, _ := strconv.ParseInt("1", 10, 64)
+ //bInt, _ := strconv.ParseInt("2", 10, 64)
+ //expected :=[]int64{aInt, bInt}
+ //
+ //a := ConvertSlice(t1, reflect.TypeOf(expected))
+ //if !reflect.DeepEqual(a, expected) {
+ // utils.LogFailedTestInfo(t, "ConvertSlice", t1, expected, a)
+ // t.FailNow()
+ //}
+}
+
+func TestFilter(t *testing.T) {
+ s1 := []int{1, 2, 3, 4, 5}
+ even := func(i, num int) bool {
+ return num%2 == 0
+ }
+ e1 := []int{2, 4}
+ r1 := Filter(s1, even)
+ if !reflect.DeepEqual(r1, e1) {
+ utils.LogFailedTestInfo(t, "Filter", s1, e1, r1)
+ t.FailNow()
+ }
+
+ type student struct {
+ name string
+ age int
+ }
+
+ students := []student{
+ {"a", 10},
+ {"b", 11},
+ {"c", 12},
+ {"d", 13},
+ {"e", 14},
+ }
+
+ e2 := []student{
+ {"d", 13},
+ {"e", 14},
+ }
+ filterFunc := func(i int, s student) bool {
+ return s.age > 12
+ }
+
+ r2 := Filter(students, filterFunc)
+ if !reflect.DeepEqual(r2, e2) {
+ utils.LogFailedTestInfo(t, "Filter", students, e2, r2)
+ t.FailNow()
+ }
+
+}
+
+func TestMap(t *testing.T) {
+ s1 := []int{1, 2, 3, 4}
+ multiplyTwo := func(i, num int) int {
+ return num * 2
+ }
+ e1 := []int{2, 4, 6, 8}
+ r1 := Map(s1, multiplyTwo)
+ if !reflect.DeepEqual(r1, e1) {
+ utils.LogFailedTestInfo(t, "Map", s1, e1, r1)
+ t.FailNow()
+ }
+
+ type student struct {
+ name string
+ age int
+ }
+ students := []student{
+ {"a", 1},
+ {"b", 2},
+ {"c", 3},
+ }
+
+ e2 := []student{
+ {"a", 11},
+ {"b", 12},
+ {"c", 13},
+ }
+ mapFunc := func(i int, s student) student {
+ s.age += 10
+ return s
+ }
+ r2 := Map(students, mapFunc)
+ if !reflect.DeepEqual(r2, e2) {
+ utils.LogFailedTestInfo(t, "Filter", students, e2, r2)
+ t.FailNow()
+ }
+}
+
+func TestReduce(t *testing.T) {
+ s1 := []int{1, 2, 3, 4}
+ f1 := func(i, v1, v2 int) int {
+ return v1 + v2
+ }
+ e1 := 10
+ r1 := Reduce(s1, f1, 0)
+ if e1 != r1 {
+ utils.LogFailedTestInfo(t, "Reduce", s1, e1, r1)
+ t.FailNow()
+ }
+
+ // failed Reduce function should be func(i int, v1, v2 int) int
+ //s1 := []int{1, 2, 3, 4}
+ //f1 := func(i string, v1, v2 int) int { //i should be int
+ // return v1+v2
+ //}
+ //e1 := 10
+ //r1 := Reduce(s1, f1, 0)
+ //if e1 != r1 {
+ // utils.LogFailedTestInfo(t, "Reduce", s1, e1, r1)
+ // t.FailNow()
+ //}
+}
+
+func TestIntSlice(t *testing.T) {
+ var t1 []interface{}
+ t1 = append(t1, 1, 2, 3, 4, 5)
+ expect := []int{1, 2, 3, 4, 5}
+ intSlice(t, t1, expect)
+}
+
+func intSlice(t *testing.T, test interface{}, expected []int) {
+ res, err := IntSlice(test)
+ if err != nil {
+ t.Error("IntSlice Error: " + err.Error())
+ }
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "IntSlice", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestStringSlice(t *testing.T) {
+ var t1 []interface{}
+ t1 = append(t1, "a", "b", "c", "d", "e")
+ expect := []string{"a", "b", "c", "d", "e"}
+ stringSlice(t, t1, expect)
+}
+
+func stringSlice(t *testing.T, test interface{}, expected []string) {
+ res := StringSlice(test)
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "StringSlice", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestInterfaceSlice(t *testing.T) {
+ t1 := []string{"a", "b", "c", "d", "e"}
+ expect := []interface{}{"a", "b", "c", "d", "e"}
+ interfaceSlice(t, t1, expect)
+}
+
+func interfaceSlice(t *testing.T, test interface{}, expected []interface{}) {
+ res := InterfaceSlice(test)
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "InterfaceSlice", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestDeleteByIndex(t *testing.T) {
+ origin := []string{"a", "b", "c", "d", "e"}
+
+ t1 := []string{"a", "b", "c", "d", "e"}
+ r1 := []string{"b", "c", "d", "e"}
+ deleteByIndex(t, origin, t1, 0, 0, r1)
+
+ t1 = []string{"a", "b", "c", "d", "e"}
+ r2 := []string{"a", "b", "c", "e"}
+ deleteByIndex(t, origin, t1, 3, 0, r2)
+
+ t1 = []string{"a", "b", "c", "d", "e"}
+ r3 := []string{"a", "b", "c", "d"}
+ deleteByIndex(t, origin, t1, 4, 0, r3)
+
+ t1 = []string{"a", "b", "c", "d", "e"}
+ r4 := []string{"c", "d", "e"}
+ deleteByIndex(t, origin, t1, 0, 2, r4)
+
+ t1 = []string{"a", "b", "c", "d", "e"}
+ r5 := []string{} // var r5 []string{} failed
+ deleteByIndex(t, origin, t1, 0, 5, r5)
+
+ // failed
+ //t1 = []string{"a", "b", "c", "d","e"}
+ //r6 := []string{"a", "c", "d","e"}
+ //deleteByIndex(t, origin, t1, 1, 1, r6)
+
+ // failed
+ //t1 = []string{"a", "b", "c", "d","e"}
+ //r7 := []string{}
+ //deleteByIndex(t, origin, t1, 0, 6, r7)
+}
+
+func deleteByIndex(t *testing.T, origin, test interface{}, start, end int, expected interface{}) {
+ var res interface{}
+ var err error
+ if end != 0 {
+ res, err = DeleteByIndex(test, start, end)
+ } else {
+ res, err = DeleteByIndex(test, start)
+ }
+ if err != nil {
+ t.Error("DeleteByIndex Error: " + err.Error())
+ }
+
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "DeleteByIndex", origin, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestInsertByIndex(t *testing.T) {
+ t1 := []string{"a", "b", "c"}
+
+ r1 := []string{"1", "a", "b", "c"}
+ insertByIndex(t, t1, 0, "1", r1)
+
+ r2 := []string{"a", "1", "b", "c"}
+ insertByIndex(t, t1, 1, "1", r2)
+
+ r3 := []string{"a", "b", "c", "1"}
+ insertByIndex(t, t1, 3, "1", r3)
+
+ r4 := []string{"1", "2", "3", "a", "b", "c"}
+ insertByIndex(t, t1, 0, []string{"1", "2", "3"}, r4)
+
+ r5 := []string{"a", "1", "2", "3", "b", "c"}
+ insertByIndex(t, t1, 1, []string{"1", "2", "3"}, r5)
+
+ r6 := []string{"a", "b", "1", "2", "3", "c"}
+ insertByIndex(t, t1, 2, []string{"1", "2", "3"}, r6)
+
+ r7 := []string{"a", "b", "c", "1", "2", "3"}
+ insertByIndex(t, t1, 3, []string{"1", "2", "3"}, r7)
+}
+
+func insertByIndex(t *testing.T, test interface{}, index int, value, expected interface{}) {
+ res, err := InsertByIndex(test, index, value)
+ if err != nil {
+ t.Error("InsertByIndex Error: " + err.Error())
+ }
+
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "InsertByIndex", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestUpdateByIndex(t *testing.T) {
+ t1 := []string{"a", "b", "c"}
+ r1 := []string{"1", "b", "c"}
+ updateByIndex(t, t1, 0, "1", r1)
+
+ t1 = []string{"a", "b", "c"}
+ r2 := []string{"a", "1", "c"}
+ updateByIndex(t, t1, 1, "1", r2)
+
+ t1 = []string{"a", "b", "c"}
+ r3 := []string{"a", "b", "1"}
+ updateByIndex(t, t1, 2, "1", r3)
+
+ //failed
+ //t1 = []string{"a","b","c"}
+ //r4 := []string{"a", "b", "1"}
+ //updateByIndex(t, t1, 3, "1", r4)
+
+}
+
+func updateByIndex(t *testing.T, test interface{}, index int, value, expected interface{}) {
+ res, err := UpdateByIndex(test, index, value)
+ if err != nil {
+ t.Error("UpdateByIndex Error: " + err.Error())
+ }
+
+ if !reflect.DeepEqual(res, expected) {
+ utils.LogFailedTestInfo(t, "UpdateByIndex", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestUnique(t *testing.T) {
+ t1 := []int{1, 2, 2, 3}
+ e1 := []int{1, 2, 3}
+ r1, _ := IntSlice(Unique(t1))
+ if !reflect.DeepEqual(r1, e1) {
+ utils.LogFailedTestInfo(t, "Unique", t1, e1, r1)
+ t.FailNow()
+ }
+
+ t2 := []string{"a", "a", "b", "c"}
+ e2 := []string{"a", "b", "c"}
+ r2 := StringSlice(Unique(t2))
+ if !reflect.DeepEqual(r2, e2) {
+ utils.LogFailedTestInfo(t, "Unique", t2, e2, r2)
+ t.FailNow()
+ }
+}
+
+func TestReverseSlice(t *testing.T) {
+ s1 := []int{1, 2, 3, 4, 5}
+ e1 := []int{5, 4, 3, 2, 1}
+ ReverseSlice(s1)
+ if !reflect.DeepEqual(s1, e1) {
+ utils.LogFailedTestInfo(t, "ReverseSlice", s1, e1, s1)
+ t.FailNow()
+ }
+
+ s2 := []string{"a", "b", "c", "d", "e"}
+ e2 := []string{"e", "d", "c", "b", "a"}
+ ReverseSlice(s2)
+ if !reflect.DeepEqual(s2, e2) {
+ utils.LogFailedTestInfo(t, "ReverseSlice", s2, e2, s2)
+ t.FailNow()
+ }
+}
+
+func TestDifference(t *testing.T) {
+ s1 := []int{1, 2, 3, 4, 5}
+ s2 := []int{4, 5, 6}
+ e1 := []int{1, 2, 3}
+ r1 := Difference(s1, s2)
+ if !reflect.DeepEqual(r1, e1) {
+ utils.LogFailedTestInfo(t, "Difference", s1, e1, r1)
+ t.FailNow()
+ }
+}
+
+func TestSortByField(t *testing.T) {
+ type student struct {
+ name string
+ age int
+ }
+ students := []student{
+ {"a", 10},
+ {"b", 15},
+ {"c", 5},
+ {"d", 6},
+ }
+
+ sortByAge := []student{
+ {"b", 15},
+ {"a", 10},
+ {"d", 6},
+ {"c", 5},
+ }
+
+ err := SortByField(students, "age", "desc")
+ if err != nil {
+ t.Error("IntSlice Error: " + err.Error())
+ }
+
+ if !reflect.DeepEqual(students, sortByAge) {
+ utils.LogFailedTestInfo(t, "SortByField", students, sortByAge, students)
+ t.FailNow()
+ }
+
+}
diff --git a/slice/slice_util.go b/slice/slice_util.go
new file mode 100644
index 0000000..e18f55e
--- /dev/null
+++ b/slice/slice_util.go
@@ -0,0 +1,54 @@
+package slice
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// sliceValue return the reflect value of a slice
+func sliceValue(slice interface{}) reflect.Value {
+ v := reflect.ValueOf(slice)
+ if v.Kind() != reflect.Slice {
+ panic(fmt.Sprintf("Invalid slice type, value of type %T", slice))
+ }
+ return v
+}
+
+// functionValue return the reflect value of a function
+func functionValue(function interface{}) reflect.Value {
+ v := reflect.ValueOf(function)
+ if v.Kind() != reflect.Func {
+ panic(fmt.Sprintf("Invalid function type, value of type %T", function))
+ }
+ return v
+}
+
+// checkSliceCallbackFuncSignature Check func sign : s :[]type1{} -> func(i int, data type1) type2
+// see https://coolshell.cn/articles/21164.html#%E6%B3%9B%E5%9E%8BMap-Reduce
+func checkSliceCallbackFuncSignature(fn reflect.Value, types ...reflect.Type) bool {
+ //Check it is a function
+ if fn.Kind() != reflect.Func {
+ return false
+ }
+ // NumIn() - returns a function type's input parameter count.
+ // NumOut() - returns a function type's output parameter count.
+ if (fn.Type().NumIn() != len(types)-1) || (fn.Type().NumOut() != 1) {
+ return false
+ }
+ // In() - returns the type of a function type's i'th input parameter.
+ // first input param type should be int
+ if fn.Type().In(0) != reflect.TypeOf(1) {
+ return false
+ }
+ for i := 0; i < len(types)-1; i++ {
+ if fn.Type().In(i) != types[i] {
+ return false
+ }
+ }
+ // Out() - returns the type of a function type's i'th output parameter.
+ outType := types[len(types)-1]
+ if outType != nil && fn.Type().Out(0) != outType {
+ return false
+ }
+ return true
+}
diff --git a/strutil/string.go b/strutil/string.go
new file mode 100644
index 0000000..e0837e4
--- /dev/null
+++ b/strutil/string.go
@@ -0,0 +1,217 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package strutil implements some functions to manipulate string.
+package strutil
+
+import (
+ "regexp"
+ "strings"
+)
+
+// CamelCase covert string to camelCase string.
+func CamelCase(s string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ res := ""
+ blankSpace := " "
+ regex, _ := regexp.Compile("[-_&]+")
+ ss := regex.ReplaceAllString(s, blankSpace)
+ for i, v := range strings.Split(ss, blankSpace) {
+ vv := []rune(v)
+ if i == 0 {
+ if vv[i] >= 65 && vv[i] <= 96 {
+ vv[0] += 32
+ }
+ res += string(vv)
+ } else {
+ res += Capitalize(v)
+ }
+ }
+
+ return res
+}
+
+// Capitalize converts the first character of a string to upper case and the remaining to lower case.
+func Capitalize(s string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ res := ""
+ for i, v := range []rune(s) {
+ if i == 0 {
+ if v >= 97 && v <= 122 {
+ v -= 32
+ }
+ res += string(v)
+ } else {
+ res += strings.ToLower(string(v))
+ }
+ }
+ return res
+}
+
+// LowerFirst converts the first character of string to lower case.
+func LowerFirst(s string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ res := ""
+ for i, v := range []rune(s) {
+ if i == 0 {
+ if v >= 65 && v <= 96 {
+ v += 32
+ res += string(v)
+ } else {
+ return s
+ }
+ } else {
+ res += string(v)
+ }
+ }
+ return res
+}
+
+// PadEnd pads string on the right side if it's shorter than size.
+// Padding characters are truncated if they exceed size.
+func PadEnd(source string, size int, padStr string) string {
+ len1 := len(source)
+ len2 := len(padStr)
+
+ if len1 >= size {
+ return source
+ }
+
+ fill := ""
+ if len2 >= size-len1 {
+ fill = padStr[0 : size-len1]
+ } else {
+ fill = strings.Repeat(padStr, size-len1)
+ }
+ return source + fill[0:size-len1]
+}
+
+// PadStart pads string on the left side if it's shorter than size.
+// Padding characters are truncated if they exceed size.
+func PadStart(source string, size int, padStr string) string {
+ len1 := len(source)
+ len2 := len(padStr)
+
+ if len1 >= size {
+ return source
+ }
+
+ fill := ""
+ if len2 >= size-len1 {
+ fill = padStr[0 : size-len1]
+ } else {
+ fill = strings.Repeat(padStr, size-len1)
+ }
+ return fill[0:size-len1] + source
+}
+
+// KebabCase covert string to kebab-case
+func KebabCase(s string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ regex := regexp.MustCompile(`[\W|_]+`)
+ blankSpace := " "
+ match := regex.ReplaceAllString(s, blankSpace)
+ rs := strings.Split(match, blankSpace)
+
+ var res []string
+ for _, v := range rs {
+ splitWords := splitWordsToLower(v)
+ if len(splitWords) > 0 {
+ res = append(res, splitWords...)
+ }
+ }
+
+ return strings.Join(res, "-")
+}
+
+// SnakeCase covert string to snake_case
+func SnakeCase(s string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ regex := regexp.MustCompile(`[\W|_]+`)
+ blankSpace := " "
+ match := regex.ReplaceAllString(s, blankSpace)
+ rs := strings.Split(match, blankSpace)
+
+ var res []string
+ for _, v := range rs {
+ splitWords := splitWordsToLower(v)
+ if len(splitWords) > 0 {
+ res = append(res, splitWords...)
+ }
+ }
+
+ return strings.Join(res, "_")
+}
+
+// Before create substring in source string before position when char first appear
+func Before(s, char string) string {
+ if s == "" || char == "" {
+ return s
+ }
+ i := strings.Index(s, char)
+ return s[0:i]
+}
+
+// BeforeLast create substring in source string before position when char last appear
+func BeforeLast(s, char string) string {
+ if s == "" || char == "" {
+ return s
+ }
+ i := strings.LastIndex(s, char)
+ return s[0:i]
+}
+
+// After create substring in source string after position when char first appear
+func After(s, char string) string {
+ if s == "" || char == "" {
+ return s
+ }
+ i := strings.Index(s, char)
+ return s[i+len(char):]
+}
+
+// AfterLast create substring in source string after position when char last appear
+func AfterLast(s, char string) string {
+ if s == "" || char == "" {
+ return s
+ }
+ i := strings.LastIndex(s, char)
+ return s[i+len(char):]
+}
+
+// IsString check if the value data type is string or not.
+func IsString(v interface{}) bool {
+ if v == nil {
+ return false
+ }
+ switch v.(type) {
+ case string:
+ return true
+ default:
+ return false
+ }
+}
+
+// ReverseStr return string whose char order is reversed to the given string
+func ReverseStr(s string) string {
+ r := []rune(s)
+ for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
+ r[i], r[j] = r[j], r[i]
+ }
+ return string(r)
+}
diff --git a/strutil/string_test.go b/strutil/string_test.go
new file mode 100644
index 0000000..7d46d30
--- /dev/null
+++ b/strutil/string_test.go
@@ -0,0 +1,200 @@
+package strutil
+
+import (
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestCamelCase(t *testing.T) {
+ camelCase(t, "foo_bar", "fooBar")
+ camelCase(t, "Foo-Bar", "fooBar")
+ camelCase(t, "Foo&bar", "fooBar")
+ camelCase(t, "foo bar", "fooBar")
+}
+
+func camelCase(t *testing.T, test string, expected string) {
+ res := CamelCase(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "CamelCase", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestCapitalize(t *testing.T) {
+ capitalize(t, "foo", "Foo")
+ capitalize(t, "fOO", "Foo")
+ capitalize(t, "FOo", "Foo")
+}
+
+func capitalize(t *testing.T, test string, expected string) {
+ res := Capitalize(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "Capitalize", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestKebabCase(t *testing.T) {
+ kebabCase(t, "Foo Bar-", "foo-bar")
+ kebabCase(t, "foo_Bar", "foo-bar")
+ kebabCase(t, "fooBar", "foo-bar")
+ kebabCase(t, "__FOO_BAR__", "f-o-o-b-a-r")
+}
+
+func kebabCase(t *testing.T, test string, expected string) {
+ res := KebabCase(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "KebabCase", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestSnakeCase(t *testing.T) {
+ snakeCase(t, "Foo Bar-", "foo_bar")
+ snakeCase(t, "foo_Bar", "foo_bar")
+ snakeCase(t, "fooBar", "foo_bar")
+ snakeCase(t, "__FOO_BAR__", "f_o_o_b_a_r")
+ snakeCase(t, "aBbc-s$@a&%_B.B^C", "a_bbc_s_a_b_b_c")
+}
+
+func snakeCase(t *testing.T, test string, expected string) {
+ res := SnakeCase(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "SnakeCase", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestLowerFirst(t *testing.T) {
+ lowerFirst(t, "foo", "foo")
+ lowerFirst(t, "BAR", "bAR")
+ lowerFirst(t, "FOo", "fOo")
+}
+
+func lowerFirst(t *testing.T, test string, expected string) {
+ res := LowerFirst(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "LowerFirst", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestPadEnd(t *testing.T) {
+ padEnd(t, "a", 1, "b", "a")
+ padEnd(t, "a", 2, "b", "ab")
+ padEnd(t, "abcd", 6, "mno", "abcdmn")
+ padEnd(t, "abcd", 6, "m", "abcdmm")
+ padEnd(t, "abc", 6, "ab", "abcaba")
+}
+
+func padEnd(t *testing.T, source string, size int, fillString string, expected string) {
+ res := PadEnd(source, size, fillString)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "PadEnd", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestPadStart(t *testing.T) {
+ padStart(t, "a", 1, "b", "a")
+ padStart(t, "a", 2, "b", "ba")
+ padStart(t, "abcd", 6, "mno", "mnabcd")
+ padStart(t, "abcd", 6, "m", "mmabcd")
+ padStart(t, "abc", 6, "ab", "abaabc")
+}
+
+func padStart(t *testing.T, source string, size int, fillString string, expected string) {
+ res := PadStart(source, size, fillString)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "PadEnd", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestBefore(t *testing.T) {
+ before(t, "lancet", "", "lancet")
+ before(t, "github.com/test/lancet", "/", "github.com")
+ before(t, "github.com/test/lancet", "test", "github.com/")
+}
+
+func before(t *testing.T, source, char, expected string) {
+ res := Before(source, char)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "Before", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestBeforeLast(t *testing.T) {
+ beforeLast(t, "lancet", "", "lancet")
+ beforeLast(t, "github.com/test/lancet", "/", "github.com/test")
+ beforeLast(t, "github.com/test/test/lancet", "test", "github.com/test/")
+}
+
+func beforeLast(t *testing.T, source, char, expected string) {
+ res := BeforeLast(source, char)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "BeforeLast", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestAfter(t *testing.T) {
+ after(t, "lancet", "", "lancet")
+ after(t, "github.com/test/lancet", "/", "test/lancet")
+ after(t, "github.com/test/lancet", "test", "/lancet")
+}
+
+func after(t *testing.T, source, char, expected string) {
+ res := After(source, char)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "After", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestAfterLast(t *testing.T) {
+ afterLast(t, "lancet", "", "lancet")
+ afterLast(t, "github.com/test/lancet", "/", "lancet")
+ afterLast(t, "github.com/test/test/lancet", "test", "/lancet")
+}
+
+func afterLast(t *testing.T, source, char, expected string) {
+ res := AfterLast(source, char)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "AfterLast", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsString(t *testing.T) {
+ isString(t, "lancet", true)
+ isString(t, 1, false)
+ isString(t, true, false)
+ isString(t, []string{}, false)
+}
+
+func isString(t *testing.T, test interface{}, expected bool) {
+ res := IsString(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsString", test, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestReverseStr(t *testing.T) {
+ reverseStr(t, "abc", "cba")
+ reverseStr(t, "12345", "54321")
+
+ //failed
+ //reverseStr(t, "abc", "abc")
+}
+
+func reverseStr(t *testing.T, test string, expected string) {
+ res := ReverseStr(test)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "ReverseStr", test, expected, res)
+ t.FailNow()
+ }
+}
diff --git a/strutil/string_util.go b/strutil/string_util.go
new file mode 100644
index 0000000..6776d43
--- /dev/null
+++ b/strutil/string_util.go
@@ -0,0 +1,40 @@
+package strutil
+
+import "strings"
+
+// splitWordsToLower split a string into worlds by uppercase char
+func splitWordsToLower(s string) []string {
+ var res []string
+
+ upperIndexes := upperIndex(s)
+ l := len(upperIndexes)
+ if upperIndexes == nil || l == 0 {
+ if s != "" {
+ res = append(res, s)
+ }
+ return res
+ }
+ for i := 0; i < l; i++ {
+ if i < l-1 {
+ res = append(res, strings.ToLower(s[upperIndexes[i]:upperIndexes[i+1]]))
+ } else {
+ res = append(res, strings.ToLower(s[upperIndexes[i]:]))
+ }
+ }
+ return res
+}
+
+// upperIndex get a int slice which elements are all the uppercase char index of a string
+func upperIndex(s string) []int {
+ var res []int
+ for i := 0; i < len(s); i++ {
+ if 64 < s[i] && s[i] < 91 {
+ res = append(res, i)
+ }
+ }
+ if len(s) > 0 && res != nil && res[0] != 0 {
+ res = append([]int{0}, res...)
+ }
+
+ return res
+}
diff --git a/utils/utils.go b/utils/utils.go
new file mode 100644
index 0000000..0da9844
--- /dev/null
+++ b/utils/utils.go
@@ -0,0 +1,15 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package utils implements is for internal use.
+package utils
+
+import (
+ "fmt"
+ "testing"
+)
+
+func LogFailedTestInfo(t *testing.T, testCase, input, expected, result interface{}) {
+ errInfo := fmt.Sprintf("Test case %v: input is %+v, expected %v, but result is %v", testCase, input, expected, result)
+ t.Error(errInfo)
+}
diff --git a/validator/validator.go b/validator/validator.go
new file mode 100644
index 0000000..3c01cfd
--- /dev/null
+++ b/validator/validator.go
@@ -0,0 +1,194 @@
+// Copyright 2021 dudaodong@gmail.com. All rights reserved.
+// Use of this source code is governed by MIT license
+
+// Package validator implements some validate function for string.
+package validator
+
+import (
+ "net"
+ "regexp"
+ "strconv"
+ "unicode"
+)
+
+// IsAlpha checks if the string contains only letters (a-zA-Z)
+func IsAlpha(s string) bool {
+ pattern := `^[a-zA-Z]+$`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(s)
+}
+
+// IsNumberStr check if the string can convert to a number.
+func IsNumberStr(s string) bool {
+ return IsIntStr(s) || IsFloatStr(s)
+}
+
+// IsFloatStr check if the string can convert to a float.
+func IsFloatStr(s string) bool {
+ _, e := strconv.ParseFloat(s, 64)
+
+ if e != nil {
+ return false
+ }
+ return true
+}
+
+// IsIntStr check if the string can convert to a integer.
+func IsIntStr(s string) bool {
+ match, _ := regexp.MatchString(`^[\+-]?\d+$`, s)
+ return match
+}
+
+// IsIp check if the string is a ip address.
+func IsIp(ipstr string) bool {
+ ip := net.ParseIP(ipstr)
+ if ip == nil {
+ return false
+ }
+ return true
+}
+
+// IsIpV4 check if the string is a ipv4 address.
+func IsIpV4(ipstr string) bool {
+ ip := net.ParseIP(ipstr)
+ if ip == nil {
+ return false
+ }
+ for i := 0; i < len(ipstr); i++ {
+ switch ipstr[i] {
+ case '.':
+ return true
+ }
+ }
+ return false
+}
+
+// IsIpV6 check if the string is a ipv6 address.
+func IsIpV6(ipstr string) bool {
+ ip := net.ParseIP(ipstr)
+ if ip == nil {
+ return false
+ }
+ for i := 0; i < len(ipstr); i++ {
+ switch ipstr[i] {
+ case ':':
+ return true
+ }
+ }
+ return false
+}
+
+// IsDns check if the string is dns.
+func IsDns(dns string) bool {
+ pattern := `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(dns)
+}
+
+// IsEmail check if the string is a email address.
+func IsEmail(email string) bool {
+ pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(email)
+}
+
+// IsChineseMobile check if the string is chinese mobile number.
+func IsChineseMobile(mobileNum string) bool {
+ pattern := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(mobileNum)
+}
+
+// IsChineseIdNum check if the string is chinese id number.
+func IsChineseIdNum(id string) bool {
+ pattern := `^[1-9]\d{5}(18|19|20|21|22)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(id)
+}
+
+// ContainChinese check if the string contain mandarin chinese.
+func ContainChinese(s string) bool {
+ pattern := "[\u4e00-\u9fa5]"
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(s)
+}
+
+// IsChinesePhone check if the string is chinese phone number.
+// Valid chinese phone is xxx-xxxxxxxx or xxxx-xxxxxxx
+func IsChinesePhone(phone string) bool {
+ pattern := `\d{3}-\d{8}|\d{4}-\d{7}`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(phone)
+}
+
+// IsCreditCard check if the string is credit card.
+func IsCreditCard(creditCart string) bool {
+ pattern := `^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(creditCart)
+}
+
+// IsBase64 check if the string is base64 string.
+func IsBase64(base64 string) bool {
+ pattern := `^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`
+ reg := regexp.MustCompile(pattern)
+ return reg.MatchString(base64)
+}
+
+// IsEmptyString check if the string is empty.
+func IsEmptyString(s string) bool {
+ return len(s) == 0
+}
+
+// IsRegexMatch check if the string match the regexp
+func IsRegexMatch(s, regex string) bool {
+ reg := regexp.MustCompile(regex)
+ return reg.MatchString(s)
+}
+
+// IsStrongPassword check if the string is strong password, if len(password) is less than the length param, return false
+// Strong password: alpha(lower+upper) + number + special chars(!@#$%^&*()?><)
+func IsStrongPassword(password string, length int) bool {
+ if len(password) < length {
+ return false
+ }
+ var num, lower, upper, special bool
+ for _, r := range password {
+ switch {
+ case unicode.IsDigit(r):
+ num = true
+ case unicode.IsUpper(r):
+ upper = true
+ case unicode.IsLower(r):
+ lower = true
+ case unicode.IsSymbol(r), unicode.IsPunct(r):
+ special = true
+ }
+ }
+
+ return num && lower && upper && special
+
+ // go doesn't support regexp (?=re)
+ //pattern := `^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=])(?=\S+$).$`
+ //reg := regexp.MustCompile(pattern)
+ //return reg.MatchString(password)
+}
+
+// IsWeakPassword check if the string is weak password
+// Weak password: only letter or only number or letter + number
+func IsWeakPassword(password string) bool {
+ var num, letter, special bool
+ for _, r := range password {
+ switch {
+ case unicode.IsDigit(r):
+ num = true
+ case unicode.IsLetter(r):
+ letter = true
+ case unicode.IsSymbol(r), unicode.IsPunct(r):
+ special = true
+ }
+ }
+
+ return (num || letter) && !special
+}
+
diff --git a/validator/validator_test.go b/validator/validator_test.go
new file mode 100644
index 0000000..cfbd896
--- /dev/null
+++ b/validator/validator_test.go
@@ -0,0 +1,283 @@
+package validator
+
+import (
+ "testing"
+
+ "github.com/duke-git/lancet/utils"
+)
+
+func TestIsNumberStr(t *testing.T) {
+ isNumberStr(t, "3.", true)
+ isNumberStr(t, "+3.", true)
+ isNumberStr(t, "-3.", true)
+ isNumberStr(t, "+3e2", true)
+ isNumberStr(t, "abc", false)
+}
+
+func isNumberStr(t *testing.T, source string, expected bool) {
+ res := IsNumberStr(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsNumberStr", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsFloatStr(t *testing.T) {
+ isFloatStr(t, "3.", true)
+ isFloatStr(t, "+3.", true)
+ isFloatStr(t, "-3.", true)
+ isFloatStr(t, "12", true)
+ isFloatStr(t, "abc", false)
+}
+
+func isFloatStr(t *testing.T, source string, expected bool) {
+ res := IsFloatStr(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsFloatStr", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsIntStr(t *testing.T) {
+ isIntStr(t, "+3", true)
+ isIntStr(t, "-3", true)
+ isIntStr(t, "3.", false)
+ isIntStr(t, "abc", false)
+}
+
+func isIntStr(t *testing.T, source string, expected bool) {
+ res := IsIntStr(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsIntStr", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsIp(t *testing.T) {
+ isIp(t, "127.0.0.1", true)
+ isIp(t, "::0:0:0:0:0:0:1", true)
+ isIp(t, "120.0.0", false)
+ isIp(t, "abc", false)
+}
+
+func isIp(t *testing.T, source string, expected bool) {
+ res := IsIp(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsIp", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsIpV4(t *testing.T) {
+ isIpV4(t, "127.0.0.1", true)
+ isIpV4(t, "::0:0:0:0:0:0:1", false)
+}
+
+func isIpV4(t *testing.T, source string, expected bool) {
+ res := IsIpV4(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsIpV4", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsIpV6(t *testing.T) {
+ isIpV6(t, "127.0.0.1", false)
+ isIpV6(t, "::0:0:0:0:0:0:1", true)
+}
+
+func isIpV6(t *testing.T, source string, expected bool) {
+ res := IsIpV6(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsIpV6", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsDns(t *testing.T) {
+ isDns(t, "abc.com", true)
+ isDns(t, "a.b.com", false)
+}
+
+func isDns(t *testing.T, source string, expected bool) {
+ res := IsDns(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsDns", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsEmail(t *testing.T) {
+ isEmail(t, "abc@xyz.com", true)
+ isEmail(t, "a.b@@com", false)
+}
+
+func isEmail(t *testing.T, source string, expected bool) {
+ res := IsEmail(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsEmail", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestContainChinese(t *testing.T) {
+ containChinese(t, "你好", true)
+ containChinese(t, "hello", false)
+ containChinese(t, "hello你好", true)
+}
+
+func containChinese(t *testing.T, source string, expected bool) {
+ res := ContainChinese(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsContainChineseChar", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsChineseMobile(t *testing.T) {
+ isChineseMobile(t, "13263527980", true)
+ isChineseMobile(t, "434324324", false)
+}
+
+func isChineseMobile(t *testing.T, source string, expected bool) {
+ res := IsChineseMobile(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsChineseMobile", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsChinesePhone(t *testing.T) {
+ isChinesePhone(t, "010-32116675", true)
+ isChinesePhone(t, "0464-8756213", true)
+ isChinesePhone(t, "123-87562", false)
+}
+
+func isChinesePhone(t *testing.T, source string, expected bool) {
+ res := IsChinesePhone(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsChinesePhone", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsChineseIdNum(t *testing.T) {
+ isChineseIdNum(t, "210911192105130715", true)
+ isChineseIdNum(t, "21091119210513071X", true)
+ isChineseIdNum(t, "21091119210513071x", true)
+ isChineseIdNum(t, "123456", false)
+}
+
+func isChineseIdNum(t *testing.T, source string, expected bool) {
+ res := IsChineseIdNum(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsChineseIdNum", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsCreditCard(t *testing.T) {
+ isCreditCard(t, "4111111111111111", true)
+ isCreditCard(t, "123456", false)
+}
+
+func isCreditCard(t *testing.T, source string, expected bool) {
+ res := IsCreditCard(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsCreditCard", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsBase64(t *testing.T) {
+ isBase64(t, "aGVsbG8", true)
+ isBase64(t, "123456", false)
+}
+
+func isBase64(t *testing.T, source string, expected bool) {
+ res := IsBase64(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsBase64", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsEmptyString(t *testing.T) {
+ isEmptyString(t, "111", false)
+ isEmptyString(t, " ", false)
+ isEmptyString(t, "\t", false)
+ isEmptyString(t, "", true)
+}
+
+func isEmptyString(t *testing.T, source string, expected bool) {
+ res := IsEmptyString(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsEmptyString", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsAlpha(t *testing.T) {
+ isAlpha(t, "abc", true)
+ isAlpha(t, "111", false)
+ isAlpha(t, " ", false)
+ isAlpha(t, "\t", false)
+ isAlpha(t, "", false)
+}
+
+func isAlpha(t *testing.T, source string, expected bool) {
+ res := IsAlpha(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsAlpha", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsRegexMatch(t *testing.T) {
+ isRegexMatch(t, "abc", `^[a-zA-Z]+$`, true)
+ isRegexMatch(t, "1ab", `^[a-zA-Z]+$`, false)
+ isRegexMatch(t, "111", `^[a-zA-Z]+$`, false)
+ isRegexMatch(t, "", `^[a-zA-Z]+$`, false)
+}
+
+func isRegexMatch(t *testing.T, source, regex string, expected bool) {
+ res := IsRegexMatch(source, regex)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsRegexMatch", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsStrongPassword(t *testing.T) {
+ isStrongPassword(t, "abc", 3, false)
+ isStrongPassword(t, "abc123", 6, false)
+ isStrongPassword(t, "abcABC", 6, false)
+ isStrongPassword(t, "abc123@#$", 9, false)
+ isStrongPassword(t, "abcABC123@#$", 16, false)
+ isStrongPassword(t, "abcABC123@#$", 12, true)
+ isStrongPassword(t, "abcABC123@#$", 10, true)
+}
+
+func isStrongPassword(t *testing.T, source string, length int, expected bool) {
+ res := IsStrongPassword(source, length)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsStrongPassword", source, expected, res)
+ t.FailNow()
+ }
+}
+
+func TestIsWeakPassword(t *testing.T) {
+ isWeakPassword(t, "abc", true)
+ isWeakPassword(t, "123", true)
+ isWeakPassword(t, "abc123", true)
+ isWeakPassword(t, "abcABC123", true)
+ isWeakPassword(t, "abc123@#$", false)
+}
+
+func isWeakPassword(t *testing.T, source string, expected bool) {
+ res := IsWeakPassword(source)
+ if res != expected {
+ utils.LogFailedTestInfo(t, "IsWeakPassword", source, expected, res)
+ t.FailNow()
+ }
+}