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() + } +}