mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
publish lancet
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.idea/*
|
||||||
|
.vscode/*
|
||||||
|
.DS_Store
|
||||||
444
README.md
Normal file
444
README.md
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1 style="width: 100%; text-align: center;">Lancet</h1>
|
||||||
|
<p style="font-size: 18px">
|
||||||
|
Lancet is a comprehensive, efficient, and reusable util function library of go. Inspired by the java apache common package and lodash.js.
|
||||||
|
</p>
|
||||||
|
<div align="center" style="text-align: center;">
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
English | [简体中文](./README_zh-CN.md)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
```
|
||||||
445
README_zh-CN.md
Normal file
445
README_zh-CN.md
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1 style="width: 100%; text-align: center;">Lancet</h1>
|
||||||
|
<p style="font-size: 18px">
|
||||||
|
lancet(柳叶刀)是一个全面、高效、可复用的go语言工具函数库。 lancet受到了java apache common包和lodash.js的启发。
|
||||||
|
</p>
|
||||||
|
<div align="center" style="text-align: center;">
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
简体中文 | [English](./README.md)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 特性
|
||||||
|
|
||||||
|
- 👏 全面、高效、可复用
|
||||||
|
- 💪 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 //判断字符串是否是弱密码(只有字母或数字)
|
||||||
|
```
|
||||||
210
convertor/convertor.go
Normal file
210
convertor/convertor.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
210
convertor/convertor_test.go
Normal file
210
convertor/convertor_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
168
cryptor/aes.go
Normal file
168
cryptor/aes.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
72
cryptor/aes_test.go
Normal file
72
cryptor/aes_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
96
cryptor/basic.go
Normal file
96
cryptor/basic.go
Normal file
@@ -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("")))
|
||||||
|
}
|
||||||
133
cryptor/basic_test.go
Normal file
133
cryptor/basic_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
37
cryptor/crypt_util.go
Normal file
37
cryptor/crypt_util.go
Normal file
@@ -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)]
|
||||||
|
}
|
||||||
166
cryptor/des.go
Normal file
166
cryptor/des.go
Normal file
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
72
cryptor/des_test.go
Normal file
72
cryptor/des_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
116
cryptor/rsa.go
Normal file
116
cryptor/rsa.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
19
cryptor/rsa_test.go
Normal file
19
cryptor/rsa_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
117
datetime/datetime.go
Normal file
117
datetime/datetime.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
145
datetime/datetime_test.go
Normal file
145
datetime/datetime_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
fileutil/file.go
Normal file
104
fileutil/file.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
99
fileutil/file_test.go
Normal file
99
fileutil/file_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
formatter/formatter.go
Normal file
17
formatter/formatter.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
25
formatter/formatter_test.go
Normal file
25
formatter/formatter_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
39
formatter/formatter_util.go
Normal file
39
formatter/formatter_util.go
Normal file
@@ -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 ""
|
||||||
|
}
|
||||||
83
netutil/net.go
Normal file
83
netutil/net.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
46
netutil/net_test.go
Normal file
46
netutil/net_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
netutil/request.go
Normal file
67
netutil/request.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
98
netutil/request_test.go
Normal file
98
netutil/request_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
181
netutil/request_util.go
Normal file
181
netutil/request_util.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
50
random/random.go
Normal file
50
random/random.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
46
random/random_test.go
Normal file
46
random/random_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
411
slice/slice.go
Normal file
411
slice/slice.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
438
slice/slice_test.go
Normal file
438
slice/slice_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
54
slice/slice_util.go
Normal file
54
slice/slice_util.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
217
strutil/string.go
Normal file
217
strutil/string.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
200
strutil/string_test.go
Normal file
200
strutil/string_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
40
strutil/string_util.go
Normal file
40
strutil/string_util.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
15
utils/utils.go
Normal file
15
utils/utils.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
194
validator/validator.go
Normal file
194
validator/validator.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
283
validator/validator_test.go
Normal file
283
validator/validator_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user