mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-08 14:42:27 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2e743dcf4 | ||
|
|
f8f58cae10 | ||
|
|
215b79140d | ||
|
|
0bd675340f | ||
|
|
f3d73899b1 | ||
|
|
d4a20b239a | ||
|
|
4c28431451 | ||
|
|
168ed096c7 | ||
|
|
a060769635 | ||
|
|
0a99492cf6 | ||
|
|
fb3de03f37 | ||
|
|
43c2fd2a22 |
@@ -4,7 +4,7 @@
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/duke-git/lancet/releases)
|
[](https://github.com/duke-git/lancet/releases)
|
||||||
[](https://pkg.go.dev/github.com/duke-git/lancet)
|
[](https://pkg.go.dev/github.com/duke-git/lancet)
|
||||||
[](https://goreportcard.com/report/github.com/duke-git/lancet)
|
[](https://goreportcard.com/report/github.com/duke-git/lancet)
|
||||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||||
@@ -83,6 +83,7 @@ import "github.com/duke-git/lancet/convertor"
|
|||||||
- [StructToMap](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#StructToMap)
|
- [StructToMap](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#StructToMap)
|
||||||
- [EncodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#EncodeByte)
|
- [EncodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#EncodeByte)
|
||||||
- [DecodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#DecodeByte)
|
- [DecodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#DecodeByte)
|
||||||
|
- [DeepClone](https://github.com/duke-git/lancet/blob/v1/docs/convertor.md#DeepClone)
|
||||||
|
|
||||||
### 2. Cryptor package is for data encryption and decryption.
|
### 2. Cryptor package is for data encryption and decryption.
|
||||||
|
|
||||||
@@ -350,12 +351,14 @@ import "github.com/duke-git/lancet/strutil"
|
|||||||
- [Capitalize](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Capitalize)
|
- [Capitalize](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Capitalize)
|
||||||
- [IsString](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#IsString)
|
- [IsString](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#IsString)
|
||||||
- [KebabCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#KebabCase)
|
- [KebabCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#KebabCase)
|
||||||
|
- [UpperKebabCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#UpperKebabCase)
|
||||||
- [LowerFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#LowerFirst)
|
- [LowerFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#LowerFirst)
|
||||||
- [UpperFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#UpperFirst)
|
- [UpperFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#UpperFirst)
|
||||||
- [PadEnd](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#PadEnd)
|
- [PadEnd](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#PadEnd)
|
||||||
- [PadStart](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#PadStart)
|
- [PadStart](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#PadStart)
|
||||||
- [Reverse](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Reverse)
|
- [Reverse](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Reverse)
|
||||||
- [SnakeCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#SnakeCase)
|
- [SnakeCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#SnakeCase)
|
||||||
|
- [UpperSnakeCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#UpperSnakeCase)
|
||||||
- [SplitEx](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#SplitEx)
|
- [SplitEx](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#SplitEx)
|
||||||
- [Wrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Wrap)
|
- [Wrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Wrap)
|
||||||
- [Unwrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Unwrap)
|
- [Unwrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Unwrap)
|
||||||
@@ -413,6 +416,7 @@ import "github.com/duke-git/lancet/validator"
|
|||||||
- [IsUrl](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsUrl)
|
- [IsUrl](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsUrl)
|
||||||
- [IsWeakPassword](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsWeakPassword)
|
- [IsWeakPassword](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsWeakPassword)
|
||||||
- [IsZeroValue](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsZeroValue)
|
- [IsZeroValue](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsZeroValue)
|
||||||
|
- [IsGBK](https://github.com/duke-git/lancet/blob/v1/docs/validator.md#IsGBK)
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/duke-git/lancet/releases)
|
[](https://github.com/duke-git/lancet/releases)
|
||||||
[](https://pkg.go.dev/github.com/duke-git/lancet)
|
[](https://pkg.go.dev/github.com/duke-git/lancet)
|
||||||
[](https://goreportcard.com/report/github.com/duke-git/lancet)
|
[](https://goreportcard.com/report/github.com/duke-git/lancet)
|
||||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||||
@@ -82,6 +82,8 @@ import "github.com/duke-git/lancet/convertor"
|
|||||||
- [StructToMap](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#StructToMap)
|
- [StructToMap](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#StructToMap)
|
||||||
- [EncodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#EncodeByte)
|
- [EncodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#EncodeByte)
|
||||||
- [DecodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#DecodeByte)
|
- [DecodeByte](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#DecodeByte)
|
||||||
|
- [DeepClone](https://github.com/duke-git/lancet/blob/v1/docs/convertor_zh-CN.md#DeepClone)
|
||||||
|
|
||||||
|
|
||||||
### 2. cryptor 加密包支持数据加密和解密,获取 md5,hash 值。支持 base64, md5, hmac, aes, des, rsa。
|
### 2. cryptor 加密包支持数据加密和解密,获取 md5,hash 值。支持 base64, md5, hmac, aes, des, rsa。
|
||||||
|
|
||||||
@@ -349,12 +351,14 @@ import "github.com/duke-git/lancet/strutil"
|
|||||||
- [Capitalize](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Capitalize)
|
- [Capitalize](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Capitalize)
|
||||||
- [IsString](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#IsString)
|
- [IsString](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#IsString)
|
||||||
- [KebabCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#KebabCase)
|
- [KebabCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#KebabCase)
|
||||||
|
- [UpperKebabCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#UpperKebabCase)
|
||||||
- [LowerFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#LowerFirst)
|
- [LowerFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#LowerFirst)
|
||||||
- [UpperFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#UpperFirst)
|
- [UpperFirst](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#UpperFirst)
|
||||||
- [PadEnd](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#PadEnd)
|
- [PadEnd](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#PadEnd)
|
||||||
- [PadStart](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#PadStart)
|
- [PadStart](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#PadStart)
|
||||||
- [Reverse](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Reverse)
|
- [Reverse](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Reverse)
|
||||||
- [SnakeCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#SnakeCase)
|
- [SnakeCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#SnakeCase)
|
||||||
|
- [UpperSnakeCase](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#UpperSnakeCase)
|
||||||
- [SplitEx](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#SplitEx)
|
- [SplitEx](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#SplitEx)
|
||||||
- [Wrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Wrap)
|
- [Wrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Wrap)
|
||||||
- [Unwrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Unwrap)
|
- [Unwrap](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Unwrap)
|
||||||
@@ -412,6 +416,7 @@ import "github.com/duke-git/lancet/validator"
|
|||||||
- [IsUrl](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsUrl)
|
- [IsUrl](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsUrl)
|
||||||
- [IsWeakPassword](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsWeakPassword)
|
- [IsWeakPassword](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsWeakPassword)
|
||||||
- [IsZeroValue](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsZeroValue)
|
- [IsZeroValue](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsZeroValue)
|
||||||
|
- [IsGBK](https://github.com/duke-git/lancet/blob/v1/docs/validator_zh-CN.md#IsGBK)
|
||||||
|
|
||||||
## 如何贡献代码
|
## 如何贡献代码
|
||||||
|
|
||||||
|
|||||||
@@ -262,3 +262,17 @@ func DecodeByte(data []byte, target interface{}) error {
|
|||||||
decoder := gob.NewDecoder(buffer)
|
decoder := gob.NewDecoder(buffer)
|
||||||
return decoder.Decode(target)
|
return decoder.Decode(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepClone creates a deep copy of passed item.
|
||||||
|
// can't clone unexported field of struct
|
||||||
|
func DeepClone(src interface{}) interface{} {
|
||||||
|
c := cloner{
|
||||||
|
ptrs: map[reflect.Type]map[uintptr]reflect.Value{},
|
||||||
|
}
|
||||||
|
result := c.clone(reflect.ValueOf(src))
|
||||||
|
if result.Kind() == reflect.Invalid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Interface()
|
||||||
|
}
|
||||||
|
|||||||
216
convertor/convertor_internal.go
Normal file
216
convertor/convertor_internal.go
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// Copyright 2023 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 "reflect"
|
||||||
|
|
||||||
|
type cloner struct {
|
||||||
|
ptrs map[reflect.Type]map[uintptr]reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone return a duplicate of passed item.
|
||||||
|
func (c *cloner) clone(v reflect.Value) reflect.Value {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return reflect.ValueOf(nil)
|
||||||
|
|
||||||
|
// bool
|
||||||
|
case reflect.Bool:
|
||||||
|
return reflect.ValueOf(v.Bool())
|
||||||
|
|
||||||
|
//int
|
||||||
|
case reflect.Int:
|
||||||
|
return reflect.ValueOf(int(v.Int()))
|
||||||
|
case reflect.Int8:
|
||||||
|
return reflect.ValueOf(int8(v.Int()))
|
||||||
|
case reflect.Int16:
|
||||||
|
return reflect.ValueOf(int16(v.Int()))
|
||||||
|
case reflect.Int32:
|
||||||
|
return reflect.ValueOf(int32(v.Int()))
|
||||||
|
case reflect.Int64:
|
||||||
|
return reflect.ValueOf(v.Int())
|
||||||
|
|
||||||
|
// uint
|
||||||
|
case reflect.Uint:
|
||||||
|
return reflect.ValueOf(uint(v.Uint()))
|
||||||
|
case reflect.Uint8:
|
||||||
|
return reflect.ValueOf(uint8(v.Uint()))
|
||||||
|
case reflect.Uint16:
|
||||||
|
return reflect.ValueOf(uint16(v.Uint()))
|
||||||
|
case reflect.Uint32:
|
||||||
|
return reflect.ValueOf(uint32(v.Uint()))
|
||||||
|
case reflect.Uint64:
|
||||||
|
return reflect.ValueOf(v.Uint())
|
||||||
|
|
||||||
|
// float
|
||||||
|
case reflect.Float32:
|
||||||
|
return reflect.ValueOf(float32(v.Float()))
|
||||||
|
case reflect.Float64:
|
||||||
|
return reflect.ValueOf(v.Float())
|
||||||
|
|
||||||
|
// complex
|
||||||
|
case reflect.Complex64:
|
||||||
|
return reflect.ValueOf(complex64(v.Complex()))
|
||||||
|
case reflect.Complex128:
|
||||||
|
return reflect.ValueOf(v.Complex())
|
||||||
|
|
||||||
|
// string
|
||||||
|
case reflect.String:
|
||||||
|
return reflect.ValueOf(v.String())
|
||||||
|
|
||||||
|
// array
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
return c.cloneArray(v)
|
||||||
|
|
||||||
|
// map
|
||||||
|
case reflect.Map:
|
||||||
|
return c.cloneMap(v)
|
||||||
|
|
||||||
|
// Ptr
|
||||||
|
case reflect.Ptr:
|
||||||
|
return c.clonePtr(v)
|
||||||
|
|
||||||
|
// struct
|
||||||
|
case reflect.Struct:
|
||||||
|
return c.cloneStruct(v)
|
||||||
|
|
||||||
|
// func
|
||||||
|
case reflect.Func:
|
||||||
|
return v
|
||||||
|
|
||||||
|
// interface
|
||||||
|
case reflect.Interface:
|
||||||
|
return c.clone(v.Elem())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.Zero(v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cloner) cloneArray(v reflect.Value) reflect.Value {
|
||||||
|
if v.IsNil() {
|
||||||
|
return reflect.Zero(v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||||||
|
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
val := c.clone(v.Index(i))
|
||||||
|
|
||||||
|
if val.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := arr.Index(i)
|
||||||
|
if !item.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Set(val.Convert(item.Type()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cloner) cloneMap(v reflect.Value) reflect.Value {
|
||||||
|
if v.IsNil() {
|
||||||
|
return reflect.Zero(v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedMap := reflect.MakeMap(v.Type())
|
||||||
|
|
||||||
|
for _, key := range v.MapKeys() {
|
||||||
|
value := v.MapIndex(key)
|
||||||
|
clonedKey := c.clone(key)
|
||||||
|
clonedValue := c.clone(value)
|
||||||
|
|
||||||
|
if !isNillable(clonedKey) || !clonedKey.IsNil() {
|
||||||
|
clonedKey = clonedKey.Convert(key.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNillable(clonedValue) || !clonedValue.IsNil()) && clonedValue.IsValid() {
|
||||||
|
clonedValue = clonedValue.Convert(value.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !clonedValue.IsValid() {
|
||||||
|
clonedValue = reflect.Zero(clonedMap.Type().Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedMap.SetMapIndex(clonedKey, clonedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clonedMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNillable(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Interface, reflect.Ptr, reflect.Func:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cloner) clonePtr(v reflect.Value) reflect.Value {
|
||||||
|
if v.IsNil() {
|
||||||
|
return reflect.Zero(v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVal reflect.Value
|
||||||
|
|
||||||
|
if v.Elem().CanAddr() {
|
||||||
|
ptrs, exists := c.ptrs[v.Type()]
|
||||||
|
if exists {
|
||||||
|
if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists {
|
||||||
|
return newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newVal = c.clone(v.Elem())
|
||||||
|
|
||||||
|
if v.Elem().CanAddr() {
|
||||||
|
ptrs, exists := c.ptrs[v.Type()]
|
||||||
|
if exists {
|
||||||
|
if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists {
|
||||||
|
return newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedPtr := reflect.New(newVal.Type())
|
||||||
|
clonedPtr.Elem().Set(newVal)
|
||||||
|
|
||||||
|
return clonedPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cloner) cloneStruct(v reflect.Value) reflect.Value {
|
||||||
|
clonedStructPtr := reflect.New(v.Type())
|
||||||
|
clonedStruct := clonedStructPtr.Elem()
|
||||||
|
|
||||||
|
if v.CanAddr() {
|
||||||
|
ptrs := c.ptrs[clonedStructPtr.Type()]
|
||||||
|
if ptrs == nil {
|
||||||
|
ptrs = make(map[uintptr]reflect.Value)
|
||||||
|
c.ptrs[clonedStructPtr.Type()] = ptrs
|
||||||
|
}
|
||||||
|
ptrs[v.UnsafeAddr()] = clonedStructPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
newStructValue := clonedStruct.Field(i)
|
||||||
|
if !newStructValue.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedVal := c.clone(v.Field(i))
|
||||||
|
if !clonedVal.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newStructValue.Set(clonedVal.Convert(newStructValue.Type()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return clonedStruct
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package convertor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/duke-git/lancet/internal"
|
"github.com/duke-git/lancet/internal"
|
||||||
@@ -213,3 +214,47 @@ func TestDecodeByte(t *testing.T) {
|
|||||||
DecodeByte(byteData, &obj)
|
DecodeByte(byteData, &obj)
|
||||||
assert.Equal("abc", obj)
|
assert.Equal("abc", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeepClone(t *testing.T) {
|
||||||
|
// assert := internal.NewAssert(t, "TestDeepClone")
|
||||||
|
|
||||||
|
type Struct struct {
|
||||||
|
Str string
|
||||||
|
Int int
|
||||||
|
Float float64
|
||||||
|
Bool bool
|
||||||
|
Nil interface{}
|
||||||
|
unexported string
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []interface{}{
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
map[string]int{
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
},
|
||||||
|
&Struct{
|
||||||
|
Str: "test",
|
||||||
|
Int: 1,
|
||||||
|
Float: 0.1,
|
||||||
|
Bool: true,
|
||||||
|
Nil: nil,
|
||||||
|
// unexported: "can't be cloned",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range cases {
|
||||||
|
cloned := DeepClone(item)
|
||||||
|
|
||||||
|
t.Log(cloned)
|
||||||
|
if &cloned == &item {
|
||||||
|
t.Fatalf("[TestDeepClone case #%d failed]: equal pointer", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(item, cloned) {
|
||||||
|
t.Fatalf("[TestDeepClone case #%d failed] unequal objects", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,14 +17,23 @@ import (
|
|||||||
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
||||||
// len(key) should be 16, 24 or 32
|
// len(key) should be 16, 24 or 32
|
||||||
func AesEcbEncrypt(data, key []byte) []byte {
|
func AesEcbEncrypt(data, key []byte) []byte {
|
||||||
cipher, _ := aes.NewCipher(generateAesKey(key))
|
size := len(key)
|
||||||
|
if size != 16 && size != 24 && size != 32 {
|
||||||
|
panic("key length shoud be 16 or 24 or 32")
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher, _ := aes.NewCipher(generateAesKey(key, size))
|
||||||
length := (len(data) + aes.BlockSize) / aes.BlockSize
|
length := (len(data) + aes.BlockSize) / aes.BlockSize
|
||||||
|
|
||||||
plain := make([]byte, length*aes.BlockSize)
|
plain := make([]byte, length*aes.BlockSize)
|
||||||
|
|
||||||
copy(plain, data)
|
copy(plain, data)
|
||||||
|
|
||||||
pad := byte(len(plain) - len(data))
|
pad := byte(len(plain) - len(data))
|
||||||
for i := len(data); i < len(plain); i++ {
|
for i := len(data); i < len(plain); i++ {
|
||||||
plain[i] = pad
|
plain[i] = pad
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted := make([]byte, len(plain))
|
encrypted := make([]byte, len(plain))
|
||||||
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
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])
|
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
||||||
@@ -36,9 +45,14 @@ func AesEcbEncrypt(data, key []byte) []byte {
|
|||||||
// AesEcbDecrypt decrypt data with key use AES ECB algorithm
|
// AesEcbDecrypt decrypt data with key use AES ECB algorithm
|
||||||
// len(key) should be 16, 24 or 32
|
// len(key) should be 16, 24 or 32
|
||||||
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
||||||
cipher, _ := aes.NewCipher(generateAesKey(key))
|
size := len(key)
|
||||||
|
if size != 16 && size != 24 && size != 32 {
|
||||||
|
panic("key length shoud be 16 or 24 or 32")
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher, _ := aes.NewCipher(generateAesKey(key, size))
|
||||||
decrypted := make([]byte, len(encrypted))
|
decrypted := make([]byte, len(encrypted))
|
||||||
//
|
|
||||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
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])
|
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ package cryptor
|
|||||||
|
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
|
||||||
func generateAesKey(key []byte) []byte {
|
func generateAesKey(key []byte, size int) []byte {
|
||||||
genKey := make([]byte, 16)
|
aesKey := make([]byte, size)
|
||||||
copy(genKey, key)
|
copy(aesKey, key)
|
||||||
for i := 16; i < len(key); {
|
for i := size; i < len(key); {
|
||||||
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
|
for j := 0; j < size && i < len(key); j, i = j+1, i+1 {
|
||||||
genKey[j] ^= key[i]
|
aesKey[j] ^= key[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return genKey
|
return aesKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDesKey(key []byte) []byte {
|
func generateDesKey(key []byte) []byte {
|
||||||
|
|||||||
@@ -19,18 +19,21 @@ import (
|
|||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
- [ColorHexToRGB](#ColorHexToRGB)
|
- [ColorHexToRGB](#ColorHexToRGB)
|
||||||
- [ColorRGBToHex](#ColorRGBToHex)
|
- [ColorRGBToHex](#ColorRGBToHex)
|
||||||
- [ToBool](#ToBool)
|
- [ToBool](#ToBool)
|
||||||
- [ToBytes](#ToBytes)
|
- [ToBytes](#ToBytes)
|
||||||
- [ToChar](#ToChar)
|
- [ToChar](#ToChar)
|
||||||
- [ToChannel](#ToChannel)
|
- [ToChannel](#ToChannel)
|
||||||
|
- [ToFloat](#ToFloat)
|
||||||
- [ToInt](#ToInt)
|
- [ToInt](#ToInt)
|
||||||
- [ToJson](#ToJson)
|
- [ToJson](#ToJson)
|
||||||
- [ToString](#ToString)
|
- [ToString](#ToString)
|
||||||
- [StructToMap](#StructToMap)
|
- [StructToMap](#StructToMap)
|
||||||
- [EncodeByte](#EncodeByte)
|
- [EncodeByte](#EncodeByte)
|
||||||
- [DecodeByte](#DecodeByte)
|
- [DecodeByte](#DecodeByte)
|
||||||
|
- [DeepClone](#DeepClone)
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -442,3 +445,67 @@ func main() {
|
|||||||
fmt.Println(result) //"abc"
|
fmt.Println(result) //"abc"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### <span id="DeepClone">DeepClone</span>
|
||||||
|
|
||||||
|
<p>Creates a deep copy of passed item, can't clone unexported field of struct.</p>
|
||||||
|
|
||||||
|
<b>Signature:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func DeepClone[T any](src T) T
|
||||||
|
```
|
||||||
|
|
||||||
|
<b>Example:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/convertor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type Struct struct {
|
||||||
|
Str string
|
||||||
|
Int int
|
||||||
|
Float float64
|
||||||
|
Bool bool
|
||||||
|
Nil interface{}
|
||||||
|
unexported string
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []interface{}{
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
map[string]int{
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
},
|
||||||
|
&Struct{
|
||||||
|
Str: "test",
|
||||||
|
Int: 1,
|
||||||
|
Float: 0.1,
|
||||||
|
Bool: true,
|
||||||
|
Nil: nil,
|
||||||
|
// unexported: "can't be cloned",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range cases {
|
||||||
|
cloned := convertor.DeepClone(item)
|
||||||
|
|
||||||
|
isPointerEqual := &cloned == &item
|
||||||
|
fmt.Println(cloned, isPointerEqual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true false
|
||||||
|
// 1 false
|
||||||
|
// 0.1 false
|
||||||
|
// map[a:1 b:2] false
|
||||||
|
// &{test 1 0.1 true <nil> } false
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -21,25 +21,20 @@ import (
|
|||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
- [Convertor](#convertor)
|
- [ColorHexToRGB](#ColorHexToRGB)
|
||||||
- [源码:](#源码)
|
- [ColorRGBToHex](#ColorRGBToHex)
|
||||||
- [用法:](#用法)
|
- [ToBool](#ToBool)
|
||||||
- [目录](#目录)
|
- [ToBytes](#ToBytes)
|
||||||
- [文档](#文档)
|
- [ToChar](#ToChar)
|
||||||
- [<span id="ColorHexToRGB">ColorHexToRGB</span>](#colorhextorgb)
|
- [ToChannel](#ToChannel)
|
||||||
- [<span id="ColorRGBToHex">ColorRGBToHex</span>](#colorrgbtohex)
|
- [ToFloat](#ToFloat)
|
||||||
- [<span id="ToBool">ToBool</span>](#tobool)
|
- [ToInt](#ToInt)
|
||||||
- [<span id="ToBytes">ToBytes</span>](#tobytes)
|
- [ToJson](#ToJson)
|
||||||
- [<span id="ToChar">ToChar</span>](#tochar)
|
- [ToString](#ToString)
|
||||||
- [<span id="ToChannel">ToChannel</span>](#tochannel)
|
- [StructToMap](#StructToMap)
|
||||||
- [<span id="ToFloat">ToFloat</span>](#tofloat)
|
- [EncodeByte](#EncodeByte)
|
||||||
- [<span id="ToInt">ToInt</span>](#toint)
|
- [DecodeByte](#DecodeByte)
|
||||||
- [<span id="ToJson">ToJson</span>](#tojson)
|
- [DeepClone](#DeepClone)
|
||||||
- [<span id="ToString">ToString</span>](#tostring)
|
|
||||||
- [<span id="StructToMap">StructToMap</span>](#structtomap)
|
|
||||||
- [<span id="EncodeByte">EncodeByte</span>](#encodebyte)
|
|
||||||
- [<span id="DecodeByte">DecodeByte</span>](#decodebyte)
|
|
||||||
|
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -452,3 +447,68 @@ func main() {
|
|||||||
fmt.Println(result) //"abc"
|
fmt.Println(result) //"abc"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="DeepClone">DeepClone</span>
|
||||||
|
|
||||||
|
<p>创建一个传入值的深拷贝, 无法克隆结构体的非导出字段。</p>
|
||||||
|
|
||||||
|
<b>函数签名:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func DeepClone[T any](src T) T
|
||||||
|
```
|
||||||
|
|
||||||
|
<b>示例:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/convertor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type Struct struct {
|
||||||
|
Str string
|
||||||
|
Int int
|
||||||
|
Float float64
|
||||||
|
Bool bool
|
||||||
|
Nil interface{}
|
||||||
|
unexported string
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []interface{}{
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
map[string]int{
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
},
|
||||||
|
&Struct{
|
||||||
|
Str: "test",
|
||||||
|
Int: 1,
|
||||||
|
Float: 0.1,
|
||||||
|
Bool: true,
|
||||||
|
Nil: nil,
|
||||||
|
// unexported: "can't be cloned",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range cases {
|
||||||
|
cloned := convertor.DeepClone(item)
|
||||||
|
|
||||||
|
isPointerEqual := &cloned == &item
|
||||||
|
fmt.Println(cloned, isPointerEqual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true false
|
||||||
|
// 1 false
|
||||||
|
// 0.1 false
|
||||||
|
// map[a:1 b:2] false
|
||||||
|
// &{test 1 0.1 true <nil> } false
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -47,7 +47,6 @@ import (
|
|||||||
- [HmacSha1](#HmacSha1)
|
- [HmacSha1](#HmacSha1)
|
||||||
- [HmacSha256](#HmacSha256)
|
- [HmacSha256](#HmacSha256)
|
||||||
- [HmacSha512](#HmacSha512)
|
- [HmacSha512](#HmacSha512)
|
||||||
|
|
||||||
- [Md5String](#Md5String)
|
- [Md5String](#Md5String)
|
||||||
- [Md5File](#Md5File)
|
- [Md5File](#Md5File)
|
||||||
- [Sha1](#Sha1)
|
- [Sha1](#Sha1)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import (
|
|||||||
- [AesOfbDecrypt](#AesOfbDecrypt)
|
- [AesOfbDecrypt](#AesOfbDecrypt)
|
||||||
- [Base64StdEncode](#Base64StdEncode)
|
- [Base64StdEncode](#Base64StdEncode)
|
||||||
- [Base64StdDecode](#Base64StdDecode)
|
- [Base64StdDecode](#Base64StdDecode)
|
||||||
|
|
||||||
- [DesEcbEncrypt](#DesEcbEncrypt)
|
- [DesEcbEncrypt](#DesEcbEncrypt)
|
||||||
- [DesEcbDecrypt](#DesEcbDecrypt)
|
- [DesEcbDecrypt](#DesEcbDecrypt)
|
||||||
- [DesCbcEncrypt](#DesCbcEncrypt)
|
- [DesCbcEncrypt](#DesCbcEncrypt)
|
||||||
@@ -43,7 +42,6 @@ import (
|
|||||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||||
- [DesOfbEncrypt](#DesOfbEncrypt)
|
- [DesOfbEncrypt](#DesOfbEncrypt)
|
||||||
- [DesOfbDecrypt](#DesOfbDecrypt)
|
- [DesOfbDecrypt](#DesOfbDecrypt)
|
||||||
|
|
||||||
- [HmacMd5](#HmacMd5)
|
- [HmacMd5](#HmacMd5)
|
||||||
- [HmacSha1](#HmacSha1)
|
- [HmacSha1](#HmacSha1)
|
||||||
- [HmacSha256](#HmacSha256)
|
- [HmacSha256](#HmacSha256)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
- [BeginOfWeek](#BeginOfWeek)
|
- [BeginOfWeek](#BeginOfWeek)
|
||||||
- [BeginOfMonth](#BeginOfMonth)
|
- [BeginOfMonth](#BeginOfMonth)
|
||||||
- [BeginOfYear](#BeginOfYear)
|
- [BeginOfYear](#BeginOfYear)
|
||||||
|
|
||||||
- [EndOfMinute](#EndOfMinute)
|
- [EndOfMinute](#EndOfMinute)
|
||||||
- [EndOfHour](#EndOfHour)
|
- [EndOfHour](#EndOfHour)
|
||||||
- [EndOfDay](#EndOfDay)
|
- [EndOfDay](#EndOfDay)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
- [BeginOfWeek](#BeginOfWeek)
|
- [BeginOfWeek](#BeginOfWeek)
|
||||||
- [BeginOfMonth](#BeginOfMonth)
|
- [BeginOfMonth](#BeginOfMonth)
|
||||||
- [BeginOfYear](#BeginOfYear)
|
- [BeginOfYear](#BeginOfYear)
|
||||||
|
|
||||||
- [EndOfMinute](#EndOfMinute)
|
- [EndOfMinute](#EndOfMinute)
|
||||||
- [EndOfHour](#EndOfHour)
|
- [EndOfHour](#EndOfHour)
|
||||||
- [EndOfDay](#EndOfDay)
|
- [EndOfDay](#EndOfDay)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
## Index
|
## Index
|
||||||
- [ClearFile](#ClearFile)
|
- [ClearFile](#ClearFile)
|
||||||
- [CreateFile](#CreateFile)
|
- [CreateFile](#CreateFile)
|
||||||
|
|
||||||
- [CreateDir](#CreateDir)
|
- [CreateDir](#CreateDir)
|
||||||
- [CopyFile](#CopyFile)
|
- [CopyFile](#CopyFile)
|
||||||
- [FileMode](#FileMode)
|
- [FileMode](#FileMode)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
- [IsExist](#IsExist)
|
- [IsExist](#IsExist)
|
||||||
- [IsLink](#IsLink)
|
- [IsLink](#IsLink)
|
||||||
- [IsDir](#IsDir)
|
- [IsDir](#IsDir)
|
||||||
|
|
||||||
- [ListFileNames](#ListFileNames)
|
- [ListFileNames](#ListFileNames)
|
||||||
- [RemoveFile](#RemoveFile)
|
- [RemoveFile](#RemoveFile)
|
||||||
- [ReadFileToString](#ReadFileToString)
|
- [ReadFileToString](#ReadFileToString)
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import (
|
|||||||
- [SendRequest](#SendRequest)
|
- [SendRequest](#SendRequest)
|
||||||
- [DecodeResponse](#DecodeResponse)
|
- [DecodeResponse](#DecodeResponse)
|
||||||
- [StructToUrlValues](#StructToUrlValues)
|
- [StructToUrlValues](#StructToUrlValues)
|
||||||
|
|
||||||
- [HttpGet<sup>Deprecated</sup>](#HttpGet)
|
- [HttpGet<sup>Deprecated</sup>](#HttpGet)
|
||||||
- [HttpDelete<sup>Deprecated</sup>](#HttpDelete)
|
- [HttpDelete<sup>Deprecated</sup>](#HttpDelete)
|
||||||
- [HttpPost<sup>Deprecated</sup>](#HttpPost)
|
- [HttpPost<sup>Deprecated</sup>](#HttpPost)
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import (
|
|||||||
- [SendRequest](#SendRequest)
|
- [SendRequest](#SendRequest)
|
||||||
- [DecodeResponse](#DecodeResponse)
|
- [DecodeResponse](#DecodeResponse)
|
||||||
- [StructToUrlValues](#StructToUrlValues)
|
- [StructToUrlValues](#StructToUrlValues)
|
||||||
|
|
||||||
- [HttpGet<sup>Deprecated</sup>](#HttpGet)
|
- [HttpGet<sup>Deprecated</sup>](#HttpGet)
|
||||||
- [HttpDelete<sup>Deprecated</sup>](#HttpDelete)
|
- [HttpDelete<sup>Deprecated</sup>](#HttpDelete)
|
||||||
- [HttpPost<sup>Deprecated</sup>](#HttpPost)
|
- [HttpPost<sup>Deprecated</sup>](#HttpPost)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import (
|
|||||||
- [RandInt](#RandInt)
|
- [RandInt](#RandInt)
|
||||||
- [RandString](#RandString)
|
- [RandString](#RandString)
|
||||||
- [RandUpper](#RandUpper)
|
- [RandUpper](#RandUpper)
|
||||||
|
|
||||||
- [RandLower](#RandLower)
|
- [RandLower](#RandLower)
|
||||||
- [RandNumeral](#RandNumeral)
|
- [RandNumeral](#RandNumeral)
|
||||||
- [RandNumeralOrLetter](#RandNumeralOrLetter)
|
- [RandNumeralOrLetter](#RandNumeralOrLetter)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import (
|
|||||||
- [RandInt](#RandInt)
|
- [RandInt](#RandInt)
|
||||||
- [RandString](#RandString)
|
- [RandString](#RandString)
|
||||||
- [RandUpper](#RandUpper)
|
- [RandUpper](#RandUpper)
|
||||||
|
|
||||||
- [RandLower](#RandLower)
|
- [RandLower](#RandLower)
|
||||||
- [RandNumeral](#RandNumeral)
|
- [RandNumeral](#RandNumeral)
|
||||||
- [RandNumeralOrLetter](#RandNumeralOrLetter)
|
- [RandNumeralOrLetter](#RandNumeralOrLetter)
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import (
|
|||||||
- [FindLast](#FindLast)
|
- [FindLast](#FindLast)
|
||||||
- [FlattenDeep](#FlattenDeep)
|
- [FlattenDeep](#FlattenDeep)
|
||||||
- [ForEach](#ForEach)
|
- [ForEach](#ForEach)
|
||||||
|
|
||||||
- [GroupBy](#GroupBy)
|
- [GroupBy](#GroupBy)
|
||||||
- [IntSlice](#IntSlice)
|
- [IntSlice](#IntSlice)
|
||||||
- [InterfaceSlice](#InterfaceSlice)
|
- [InterfaceSlice](#InterfaceSlice)
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import (
|
|||||||
- [FindLast](#FindLast)
|
- [FindLast](#FindLast)
|
||||||
- [FlattenDeep](#FlattenDeep)
|
- [FlattenDeep](#FlattenDeep)
|
||||||
- [ForEach](#ForEach)
|
- [ForEach](#ForEach)
|
||||||
|
|
||||||
- [GroupBy](#GroupBy)
|
- [GroupBy](#GroupBy)
|
||||||
- [IntSlice](#IntSlice)
|
- [IntSlice](#IntSlice)
|
||||||
- [InterfaceSlice](#InterfaceSlice)
|
- [InterfaceSlice](#InterfaceSlice)
|
||||||
|
|||||||
@@ -28,14 +28,15 @@ import (
|
|||||||
- [Capitalize](#Capitalize)
|
- [Capitalize](#Capitalize)
|
||||||
- [IsString](#IsString)
|
- [IsString](#IsString)
|
||||||
- [KebabCase](#KebabCase)
|
- [KebabCase](#KebabCase)
|
||||||
|
- [UpperKebabCase](#UpperKebabCase)
|
||||||
- [LowerFirst](#LowerFirst)
|
- [LowerFirst](#LowerFirst)
|
||||||
- [UpperFirst](#UpperFirst)
|
- [UpperFirst](#UpperFirst)
|
||||||
- [PadEnd](#PadEnd)
|
- [PadEnd](#PadEnd)
|
||||||
- [PadStart](#PadStart)
|
- [PadStart](#PadStart)
|
||||||
- [ReverseStr](#ReverseStr)
|
- [Reverse](#Reverse)
|
||||||
- [SnakeCase](#SnakeCase)
|
- [SnakeCase](#SnakeCase)
|
||||||
|
- [UpperSnakeCase](#UpperSnakeCase)
|
||||||
- [Wrap](#Wrap)
|
- [Wrap](#Wrap)
|
||||||
|
|
||||||
- [Unwrap](#Unwrap)
|
- [Unwrap](#Unwrap)
|
||||||
- [SplitEx](#SplitEx)
|
- [SplitEx](#SplitEx)
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@ func main() {
|
|||||||
|
|
||||||
|
|
||||||
### <span id="CamelCase">CamelCase</span>
|
### <span id="CamelCase">CamelCase</span>
|
||||||
<p>Covert string to camelCase string.</p>
|
<p>Coverts string to camelCase string, non letters and numbers will be ignored.</p>
|
||||||
|
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
@@ -196,7 +197,9 @@ func main() {
|
|||||||
|
|
||||||
s4 := strutil.CamelCase("foo bar")
|
s4 := strutil.CamelCase("foo bar")
|
||||||
fmt.Println(s4) //fooBar
|
fmt.Println(s4) //fooBar
|
||||||
}
|
|
||||||
|
s4 := strutil.CamelCase("Foo-#1😄$_%^&*(1bar")
|
||||||
|
fmt.Println(s4) //foo11Bar
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -261,7 +264,7 @@ func main() {
|
|||||||
|
|
||||||
|
|
||||||
### <span id="KebabCase">KebabCase</span>
|
### <span id="KebabCase">KebabCase</span>
|
||||||
<p>Covert string to kebab-case.</p>
|
<p>KebabCase covert string to kebab-case, non letters and numbers will be ignored.</p>
|
||||||
|
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
@@ -287,12 +290,44 @@ func main() {
|
|||||||
fmt.Println(s3) //foo-bar
|
fmt.Println(s3) //foo-bar
|
||||||
|
|
||||||
s4 := strutil.KebabCase("__FOO_BAR__")
|
s4 := strutil.KebabCase("__FOO_BAR__")
|
||||||
fmt.Println(s4) //f-o-o-b-a-r
|
fmt.Println(s4) //foo-bar
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="UpperKebabCase">UpperKebabCase</span>
|
||||||
|
<p>UpperKebabCase covert string to upper KEBAB-CASE, non letters and numbers will be ignored.</p>
|
||||||
|
|
||||||
|
<b>Signature:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func KebabCase(s string) string
|
||||||
|
```
|
||||||
|
<b>Example:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/strutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s1 := strutil.UpperKebabCase("Foo Bar-")
|
||||||
|
fmt.Println(s1) //FOO-BAR
|
||||||
|
|
||||||
|
s2 := strutil.UpperKebabCase("foo_Bar")
|
||||||
|
fmt.Println(s2) //FOO-BAR
|
||||||
|
|
||||||
|
s3 := strutil.UpperKebabCase("fooBar")
|
||||||
|
fmt.Println(s3) //FOO-BAR
|
||||||
|
|
||||||
|
s4 := strutil.UpperKebabCase("__FOO_BAR__")
|
||||||
|
fmt.Println(s4) //FOO-BAR
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### <span id="LowerFirst">LowerFirst</span>
|
### <span id="LowerFirst">LowerFirst</span>
|
||||||
<p>Convert the first character of string to lower case.</p>
|
<p>Convert the first character of string to lower case.</p>
|
||||||
@@ -456,9 +491,8 @@ func main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### <span id="SnakeCase">SnakeCase</span>
|
### <span id="SnakeCase">SnakeCase</span>
|
||||||
<p>Covert string to snake_case.</p>
|
<p>Coverts string to snake_case, non letters and numbers will be ignored.</p>
|
||||||
|
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
@@ -484,14 +518,48 @@ func main() {
|
|||||||
fmt.Println(s3) //foo_bar
|
fmt.Println(s3) //foo_bar
|
||||||
|
|
||||||
s4 := strutil.SnakeCase("__FOO_BAR__")
|
s4 := strutil.SnakeCase("__FOO_BAR__")
|
||||||
fmt.Println(s4) //f_o_o_b_a_r
|
fmt.Println(s4) //foo_bar
|
||||||
|
|
||||||
s5 := strutil.SnakeCase("aBbc-s$@a&%_B.B^C")
|
s5 := strutil.SnakeCase("Foo-#1😄$_%^&*(1bar")
|
||||||
fmt.Println(s5) //a_bbc_s_a_b_b_c
|
fmt.Println(s5) //foo_1_1_bar
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="UpperSnakeCase">UpperSnakeCase</span>
|
||||||
|
<p>Coverts string to upper KEBAB-CASE, non letters and numbers will be ignored.</p>
|
||||||
|
|
||||||
|
<b>Signature:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SnakeCase(s string) string
|
||||||
|
```
|
||||||
|
<b>Example:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/strutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s1 := strutil.UpperSnakeCase("Foo Bar-")
|
||||||
|
fmt.Println(s1) //FOO_BAR
|
||||||
|
|
||||||
|
s2 := strutil.UpperSnakeCase("foo_Bar")
|
||||||
|
fmt.Println(s2) //FOO_BAR
|
||||||
|
|
||||||
|
s3 := strutil.UpperSnakeCase("fooBar")
|
||||||
|
fmt.Println(s3) //FOO_BAR
|
||||||
|
|
||||||
|
s4 := strutil.UpperSnakeCase("__FOO_BAR__")
|
||||||
|
fmt.Println(s4) //FOO_BAR
|
||||||
|
|
||||||
|
s5 := strutil.UpperSnakeCase("Foo-#1😄$_%^&*(1bar")
|
||||||
|
fmt.Println(s5) //FOO_1_1_BAR
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### <span id="Wrap">Wrap</span>
|
### <span id="Wrap">Wrap</span>
|
||||||
|
|||||||
@@ -28,14 +28,15 @@ import (
|
|||||||
- [Capitalize](#Capitalize)
|
- [Capitalize](#Capitalize)
|
||||||
- [IsString](#IsString)
|
- [IsString](#IsString)
|
||||||
- [KebabCase](#KebabCase)
|
- [KebabCase](#KebabCase)
|
||||||
|
- [UpperKebabCase](#UpperKebabCase)
|
||||||
- [LowerFirst](#LowerFirst)
|
- [LowerFirst](#LowerFirst)
|
||||||
- [UpperFirst](#UpperFirst)
|
- [UpperFirst](#UpperFirst)
|
||||||
- [PadEnd](#PadEnd)
|
- [PadEnd](#PadEnd)
|
||||||
- [PadStart](#PadStart)
|
- [PadStart](#PadStart)
|
||||||
- [ReverseStr](#ReverseStr)
|
- [Reverse](#Reverse)
|
||||||
- [SnakeCase](#SnakeCase)
|
- [SnakeCase](#SnakeCase)
|
||||||
|
- [UpperSnakeCase](#UpperSnakeCase)
|
||||||
- [Wrap](#Wrap)
|
- [Wrap](#Wrap)
|
||||||
|
|
||||||
- [Unwrap](#Unwrap)
|
- [Unwrap](#Unwrap)
|
||||||
- [SplitEx](#SplitEx)
|
- [SplitEx](#SplitEx)
|
||||||
|
|
||||||
@@ -170,7 +171,7 @@ func main() {
|
|||||||
|
|
||||||
|
|
||||||
### <span id="CamelCase">CamelCase</span>
|
### <span id="CamelCase">CamelCase</span>
|
||||||
<p>将字符串转换为驼峰式字符串</p>
|
<p>将字符串转换为驼峰式字符串, 非字母和数字会被忽略</p>
|
||||||
|
|
||||||
<b>函数签名:</b>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
@@ -197,6 +198,9 @@ func main() {
|
|||||||
|
|
||||||
s4 := strutil.CamelCase("foo bar")
|
s4 := strutil.CamelCase("foo bar")
|
||||||
fmt.Println(s4) //fooBar
|
fmt.Println(s4) //fooBar
|
||||||
|
|
||||||
|
s4 := strutil.CamelCase("Foo-#1😄$_%^&*(1bar")
|
||||||
|
fmt.Println(s4) //foo11Bar
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -260,9 +264,8 @@ func main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### <span id="KebabCase">KebabCase</span>
|
### <span id="KebabCase">KebabCase</span>
|
||||||
<p>将字符串转换为kebab-case</p>
|
<p>将字符串转换为kebab-case, 非字母和数字会被忽略</p>
|
||||||
|
|
||||||
<b>函数签名:</b>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
@@ -288,12 +291,43 @@ func main() {
|
|||||||
fmt.Println(s3) //foo-bar
|
fmt.Println(s3) //foo-bar
|
||||||
|
|
||||||
s4 := strutil.KebabCase("__FOO_BAR__")
|
s4 := strutil.KebabCase("__FOO_BAR__")
|
||||||
fmt.Println(s4) //f-o-o-b-a-r
|
fmt.Println(s4) //foo-bar
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="UpperKebabCase">UpperKebabCase</span>
|
||||||
|
<p>将字符串转换为大写KEBAB-CASE, 非字母和数字会被忽略</p>
|
||||||
|
|
||||||
|
<b>函数签名:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func KebabCase(s string) string
|
||||||
|
```
|
||||||
|
<b>例子:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/strutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s1 := strutil.UpperKebabCase("Foo Bar-")
|
||||||
|
fmt.Println(s1) //FOO-BAR
|
||||||
|
|
||||||
|
s2 := strutil.UpperKebabCase("foo_Bar")
|
||||||
|
fmt.Println(s2) //FOO-BAR
|
||||||
|
|
||||||
|
s3 := strutil.UpperKebabCase("fooBar")
|
||||||
|
fmt.Println(s3) //FOO-BAR
|
||||||
|
|
||||||
|
s4 := strutil.UpperKebabCase("__FOO_BAR__")
|
||||||
|
fmt.Println(s4) //FOO-BAR
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### <span id="LowerFirst">LowerFirst</span>
|
### <span id="LowerFirst">LowerFirst</span>
|
||||||
<p>将字符串的第一个字符转换为小写</p>
|
<p>将字符串的第一个字符转换为小写</p>
|
||||||
@@ -457,9 +491,8 @@ func main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### <span id="SnakeCase">SnakeCase</span>
|
### <span id="SnakeCase">SnakeCase</span>
|
||||||
<p>将字符串转换为snake_case形式</p>
|
<p>将字符串转换为snake_case形式, 非字母和数字会被忽略</p>
|
||||||
|
|
||||||
<b>函数签名:</b>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
@@ -485,14 +518,48 @@ func main() {
|
|||||||
fmt.Println(s3) //foo_bar
|
fmt.Println(s3) //foo_bar
|
||||||
|
|
||||||
s4 := strutil.SnakeCase("__FOO_BAR__")
|
s4 := strutil.SnakeCase("__FOO_BAR__")
|
||||||
fmt.Println(s4) //f_o_o_b_a_r
|
fmt.Println(s4) //foo_bar
|
||||||
|
|
||||||
s5 := strutil.SnakeCase("aBbc-s$@a&%_B.B^C")
|
s5 := strutil.SnakeCase("Foo-#1😄$_%^&*(1bar")
|
||||||
fmt.Println(s5) //a_bbc_s_a_b_b_c
|
fmt.Println(s5) //foo_1_1_bar
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="UpperSnakeCase">UpperSnakeCase</span>
|
||||||
|
<p>将字符串转换为大写SNAKE_CASE形式, 非字母和数字会被忽略</p>
|
||||||
|
|
||||||
|
<b>函数签名:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SnakeCase(s string) string
|
||||||
|
```
|
||||||
|
<b>例子:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/strutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s1 := strutil.UpperSnakeCase("Foo Bar-")
|
||||||
|
fmt.Println(s1) //FOO_BAR
|
||||||
|
|
||||||
|
s2 := strutil.UpperSnakeCase("foo_Bar")
|
||||||
|
fmt.Println(s2) //FOO_BAR
|
||||||
|
|
||||||
|
s3 := strutil.UpperSnakeCase("fooBar")
|
||||||
|
fmt.Println(s3) //FOO_BAR
|
||||||
|
|
||||||
|
s4 := strutil.UpperSnakeCase("__FOO_BAR__")
|
||||||
|
fmt.Println(s4) //FOO_BAR
|
||||||
|
|
||||||
|
s5 := strutil.UpperSnakeCase("Foo-#1😄$_%^&*(1bar")
|
||||||
|
fmt.Println(s5) //FOO_1_1_BAR
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### <span id="Wrap">Wrap</span>
|
### <span id="Wrap">Wrap</span>
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import (
|
|||||||
- [IsCreditCard](#IsCreditCard)
|
- [IsCreditCard](#IsCreditCard)
|
||||||
- [IsDns](#IsDns)
|
- [IsDns](#IsDns)
|
||||||
- [IsEmail](#IsEmail)
|
- [IsEmail](#IsEmail)
|
||||||
|
|
||||||
- [IsEmptyString](#IsEmptyString)
|
- [IsEmptyString](#IsEmptyString)
|
||||||
- [IsFloatStr](#IsFloatStr)
|
- [IsFloatStr](#IsFloatStr)
|
||||||
- [IsNumberStr](#IsNumberStr)
|
- [IsNumberStr](#IsNumberStr)
|
||||||
@@ -48,7 +47,7 @@ import (
|
|||||||
- [IsUrl](#IsUrl)
|
- [IsUrl](#IsUrl)
|
||||||
- [IsWeakPassword](#IsWeakPassword)
|
- [IsWeakPassword](#IsWeakPassword)
|
||||||
- [IsZeroValue](#IsZeroValue)
|
- [IsZeroValue](#IsZeroValue)
|
||||||
|
- [IsGBK](#IsGBK)
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -820,7 +819,34 @@ func main() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="IsGBK">IsGBK</span>
|
||||||
|
<p>Checks if data encoding is gbk(Chinese character internal code extension specification). this function is implemented by whether double bytes fall within the encoding range of gbk,while each byte of utf-8 encoding format falls within the encoding range of gbk.Therefore, utf8.valid() should be called first to check whether it is not utf-8 encoding and then call IsGBK() to check gbk encoding. like the example.</p>
|
||||||
|
|
||||||
|
<b>Signature:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func IsGBK(data []byte) bool
|
||||||
|
```
|
||||||
|
<b>Example:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := []byte("你好")
|
||||||
|
|
||||||
|
// check utf8 first
|
||||||
|
if utf8.Valid(data) {
|
||||||
|
fmt.Println("data encoding is utf-8")
|
||||||
|
}else if(validator.IsGBK(data)) {
|
||||||
|
fmt.Println("data encoding is GBK")
|
||||||
|
}
|
||||||
|
fmt.Println("data encoding is unknown")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import (
|
|||||||
- [IsCreditCard](#IsCreditCard)
|
- [IsCreditCard](#IsCreditCard)
|
||||||
- [IsDns](#IsDns)
|
- [IsDns](#IsDns)
|
||||||
- [IsEmail](#IsEmail)
|
- [IsEmail](#IsEmail)
|
||||||
|
|
||||||
- [IsEmptyString](#IsEmptyString)
|
- [IsEmptyString](#IsEmptyString)
|
||||||
- [IsFloatStr](#IsFloatStr)
|
- [IsFloatStr](#IsFloatStr)
|
||||||
- [IsNumberStr](#IsNumberStr)
|
- [IsNumberStr](#IsNumberStr)
|
||||||
@@ -48,7 +47,7 @@ import (
|
|||||||
- [IsUrl](#IsUrl)
|
- [IsUrl](#IsUrl)
|
||||||
- [IsWeakPassword](#IsWeakPassword)
|
- [IsWeakPassword](#IsWeakPassword)
|
||||||
- [IsZeroValue](#IsZeroValue)
|
- [IsZeroValue](#IsZeroValue)
|
||||||
|
- [IsGBK](#IsGBK)
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -822,6 +821,33 @@ func main() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### <span id="IsGBK">IsGBK</span>
|
||||||
|
<p>检查数据编码是否为gbk(汉字内部代码扩展规范)。该函数的实现取决于双字节是否在gbk的编码范围内,而utf-8编码格式的每个字节都在gbk编码范围内。因此,应该首先调用utf8.valid检查它是否是utf-8编码,然后调用IsGBK检查gbk编码。如示例所示。</p>
|
||||||
|
|
||||||
|
<b>函数签名:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
func IsGBK(data []byte) bool
|
||||||
|
```
|
||||||
|
<b>例子:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := []byte("你好")
|
||||||
|
|
||||||
|
// 先检查utf8编码
|
||||||
|
if utf8.Valid(data) {
|
||||||
|
fmt.Println("data encoding is utf-8")
|
||||||
|
}else if(validator.IsGBK(data)) {
|
||||||
|
fmt.Println("data encoding is GBK")
|
||||||
|
}
|
||||||
|
fmt.Println("data encoding is unknown")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,3 +1,5 @@
|
|||||||
module github.com/duke-git/lancet
|
module github.com/duke-git/lancet
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
require golang.org/x/text v0.5.0
|
||||||
|
|||||||
25
go.sum
Normal file
25
go.sum
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -5,53 +5,41 @@
|
|||||||
package strutil
|
package strutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CamelCase covert string to camelCase string.
|
// CamelCase covert string to camelCase string.
|
||||||
|
// non letters and numbers will be ignored
|
||||||
|
// eg. "Foo-#1😄$_%^&*(1bar" => "foo11Bar"
|
||||||
func CamelCase(s string) string {
|
func CamelCase(s string) string {
|
||||||
if len(s) == 0 {
|
var builder strings.Builder
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
res := ""
|
strs := splitIntoStrings(s, false)
|
||||||
blankSpace := " "
|
for i, str := range strs {
|
||||||
regex, _ := regexp.Compile("[-_&]+")
|
|
||||||
ss := regex.ReplaceAllString(s, blankSpace)
|
|
||||||
for i, v := range strings.Split(ss, blankSpace) {
|
|
||||||
vv := []rune(v)
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
if vv[i] >= 65 && vv[i] <= 96 {
|
builder.WriteString(strings.ToLower(str))
|
||||||
vv[0] += 32
|
|
||||||
}
|
|
||||||
res += string(vv)
|
|
||||||
} else {
|
} else {
|
||||||
res += Capitalize(v)
|
builder.WriteString(Capitalize(str))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capitalize converts the first character of a string to upper case and the remaining to lower case.
|
// Capitalize converts the first character of a string to upper case and the remaining to lower case.
|
||||||
func Capitalize(s string) string {
|
func Capitalize(s string) string {
|
||||||
if len(s) == 0 {
|
result := make([]rune, len(s))
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]rune, len(s))
|
|
||||||
for i, v := range s {
|
for i, v := range s {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
out[i] = unicode.ToUpper(v)
|
result[i] = unicode.ToUpper(v)
|
||||||
} else {
|
} else {
|
||||||
out[i] = unicode.ToLower(v)
|
result[i] = unicode.ToLower(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(out)
|
return string(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpperFirst converts the first character of string to upper case.
|
// UpperFirst converts the first character of string to upper case.
|
||||||
@@ -117,47 +105,35 @@ func PadStart(source string, size int, padStr string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KebabCase covert string to kebab-case
|
// KebabCase covert string to kebab-case
|
||||||
|
// non letters and numbers will be ignored
|
||||||
|
// eg. "Foo-#1😄$_%^&*(1bar" => "foo-1-1-bar"
|
||||||
func KebabCase(s string) string {
|
func KebabCase(s string) string {
|
||||||
if len(s) == 0 {
|
result := splitIntoStrings(s, false)
|
||||||
return ""
|
return strings.Join(result, "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
regex := regexp.MustCompile(`[\W|_]+`)
|
// UpperKebabCase covert string to upper KEBAB-CASE
|
||||||
blankSpace := " "
|
// non letters and numbers will be ignored
|
||||||
match := regex.ReplaceAllString(s, blankSpace)
|
// eg. "Foo-#1😄$_%^&*(1bar" => "FOO-1-1-BAR"
|
||||||
rs := strings.Split(match, blankSpace)
|
func UpperKebabCase(s string) string {
|
||||||
|
result := splitIntoStrings(s, true)
|
||||||
var res []string
|
return strings.Join(result, "-")
|
||||||
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
|
// SnakeCase covert string to snake_case
|
||||||
|
// non letters and numbers will be ignored
|
||||||
|
// eg. "Foo-#1😄$_%^&*(1bar" => "foo_1_1_bar"
|
||||||
func SnakeCase(s string) string {
|
func SnakeCase(s string) string {
|
||||||
if len(s) == 0 {
|
result := splitIntoStrings(s, false)
|
||||||
return ""
|
return strings.Join(result, "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
regex := regexp.MustCompile(`[\W|_]+`)
|
// UpperSnakeCase covert string to upper SNAKE_CASE
|
||||||
blankSpace := " "
|
// non letters and numbers will be ignored
|
||||||
match := regex.ReplaceAllString(s, blankSpace)
|
// eg. "Foo-#1😄$_%^&*(1bar" => "FOO_1_1_BAR"
|
||||||
rs := strings.Split(match, blankSpace)
|
func UpperSnakeCase(s string) string {
|
||||||
|
result := splitIntoStrings(s, true)
|
||||||
var res []string
|
return strings.Join(result, "_")
|
||||||
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
|
// Before create substring in source string before position when char first appear
|
||||||
|
|||||||
@@ -1,40 +1,98 @@
|
|||||||
package strutil
|
package strutil
|
||||||
|
|
||||||
import "strings"
|
import "unicode"
|
||||||
|
|
||||||
// splitWordsToLower split a string into worlds by uppercase char
|
func splitIntoStrings(s string, upperCase bool) []string {
|
||||||
func splitWordsToLower(s string) []string {
|
var runes [][]rune
|
||||||
var res []string
|
lastCharType := 0
|
||||||
|
charType := 0
|
||||||
|
|
||||||
upperIndexes := upperIndex(s)
|
// split into fields based on type of unicode character
|
||||||
l := len(upperIndexes)
|
for _, r := range s {
|
||||||
if upperIndexes == nil || l == 0 {
|
switch true {
|
||||||
if s != "" {
|
case isLower(r):
|
||||||
res = append(res, s)
|
charType = 1
|
||||||
|
case isUpper(r):
|
||||||
|
charType = 2
|
||||||
|
case isDigit(r):
|
||||||
|
charType = 3
|
||||||
|
default:
|
||||||
|
charType = 4
|
||||||
}
|
}
|
||||||
return res
|
|
||||||
}
|
if charType == lastCharType {
|
||||||
for i := 0; i < l; i++ {
|
runes[len(runes)-1] = append(runes[len(runes)-1], r)
|
||||||
if i < l-1 {
|
|
||||||
res = append(res, strings.ToLower(s[upperIndexes[i]:upperIndexes[i+1]]))
|
|
||||||
} else {
|
} else {
|
||||||
res = append(res, strings.ToLower(s[upperIndexes[i]:]))
|
runes = append(runes, []rune{r})
|
||||||
}
|
}
|
||||||
}
|
lastCharType = charType
|
||||||
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
|
for i := 0; i < len(runes)-1; i++ {
|
||||||
|
if isUpper(runes[i][0]) && isLower(runes[i+1][0]) {
|
||||||
|
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
|
||||||
|
runes[i] = runes[i][:len(runes[i])-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter all none letters and none digit
|
||||||
|
var result []string
|
||||||
|
for _, rs := range runes {
|
||||||
|
if len(rs) > 0 && (unicode.IsLetter(rs[0]) || isDigit(rs[0])) {
|
||||||
|
if upperCase {
|
||||||
|
result = append(result, string(toUpperAll(rs)))
|
||||||
|
} else {
|
||||||
|
result = append(result, string(toLowerAll(rs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDigit checks if a character is digit ('0' to '9')
|
||||||
|
func isDigit(r rune) bool {
|
||||||
|
return r >= '0' && r <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLower checks if a character is lower case ('a' to 'z')
|
||||||
|
func isLower(r rune) bool {
|
||||||
|
return r >= 'a' && r <= 'z'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUpper checks if a character is upper case ('A' to 'Z')
|
||||||
|
func isUpper(r rune) bool {
|
||||||
|
return r >= 'A' && r <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
// toLower converts a character 'A' to 'Z' to its lower case
|
||||||
|
func toLower(r rune) rune {
|
||||||
|
if r >= 'A' && r <= 'Z' {
|
||||||
|
return r + 32
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// toLowerAll converts a character 'A' to 'Z' to its lower case
|
||||||
|
func toLowerAll(rs []rune) []rune {
|
||||||
|
for i := range rs {
|
||||||
|
rs[i] = toLower(rs[i])
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUpper converts a character 'a' to 'z' to its upper case
|
||||||
|
func toUpper(r rune) rune {
|
||||||
|
if r >= 'a' && r <= 'z' {
|
||||||
|
return r - 32
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// toUpperAll converts a character 'a' to 'z' to its upper case
|
||||||
|
func toUpperAll(rs []rune) []rune {
|
||||||
|
for i := range rs {
|
||||||
|
rs[i] = toUpper(rs[i])
|
||||||
|
}
|
||||||
|
return rs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,67 +9,163 @@ import (
|
|||||||
func TestCamelCase(t *testing.T) {
|
func TestCamelCase(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestCamelCase")
|
assert := internal.NewAssert(t, "TestCamelCase")
|
||||||
|
|
||||||
assert.Equal("fooBar", CamelCase("foo_bar"))
|
cases := map[string]string{
|
||||||
assert.Equal("fooBar", CamelCase("Foo-Bar"))
|
"": "",
|
||||||
assert.Equal("fooBar", CamelCase("Foo&bar"))
|
"foobar": "foobar",
|
||||||
assert.Equal("fooBar", CamelCase("foo bar"))
|
"&FOO:BAR$BAZ": "fooBarBaz",
|
||||||
|
"fooBar": "fooBar",
|
||||||
|
"FOObar": "foObar",
|
||||||
|
"$foo%": "foo",
|
||||||
|
" $#$Foo 22 bar ": "foo22Bar",
|
||||||
|
"Foo-#1😄$_%^&*(1bar": "foo11Bar",
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotEqual("FooBar", CamelCase("foo_bar"))
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, CamelCase(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCapitalize(t *testing.T) {
|
func TestCapitalize(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestCapitalize")
|
assert := internal.NewAssert(t, "TestCapitalize")
|
||||||
|
|
||||||
assert.Equal("Foo", Capitalize("foo"))
|
cases := map[string]string{
|
||||||
assert.Equal("Foo", Capitalize("Foo"))
|
"": "",
|
||||||
assert.Equal("Foo", Capitalize("Foo"))
|
"Foo": "Foo",
|
||||||
|
"_foo": "_foo",
|
||||||
|
"foobar": "Foobar",
|
||||||
|
"fooBar": "Foobar",
|
||||||
|
"foo Bar": "Foo bar",
|
||||||
|
"foo-bar": "Foo-bar",
|
||||||
|
"$foo%": "$foo%",
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotEqual("foo", Capitalize("Foo"))
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, Capitalize(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKebabCase(t *testing.T) {
|
func TestKebabCase(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestKebabCase")
|
assert := internal.NewAssert(t, "TestKebabCase")
|
||||||
|
|
||||||
assert.Equal("foo-bar", KebabCase("Foo Bar-"))
|
cases := map[string]string{
|
||||||
assert.Equal("foo-bar", KebabCase("foo_Bar"))
|
"": "",
|
||||||
assert.Equal("foo-bar", KebabCase("fooBar"))
|
"foo-bar": "foo-bar",
|
||||||
assert.Equal("f-o-o-b-a-r", KebabCase("__FOO_BAR__"))
|
"--Foo---Bar-": "foo-bar",
|
||||||
|
"Foo Bar-": "foo-bar",
|
||||||
|
"foo_Bar": "foo-bar",
|
||||||
|
"fooBar": "foo-bar",
|
||||||
|
"FOOBAR": "foobar",
|
||||||
|
"FOO_BAR": "foo-bar",
|
||||||
|
"__FOO_BAR__": "foo-bar",
|
||||||
|
"$foo@Bar": "foo-bar",
|
||||||
|
" $#$Foo 22 bar ": "foo-22-bar",
|
||||||
|
"Foo-#1😄$_%^&*(1bar": "foo-1-1-bar",
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotEqual("foo_bar", KebabCase("fooBar"))
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, KebabCase(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpperKebabCase(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestUpperKebabCase")
|
||||||
|
|
||||||
|
cases := map[string]string{
|
||||||
|
"": "",
|
||||||
|
"foo-bar": "FOO-BAR",
|
||||||
|
"--Foo---Bar-": "FOO-BAR",
|
||||||
|
"Foo Bar-": "FOO-BAR",
|
||||||
|
"foo_Bar": "FOO-BAR",
|
||||||
|
"fooBar": "FOO-BAR",
|
||||||
|
"FOOBAR": "FOOBAR",
|
||||||
|
"FOO_BAR": "FOO-BAR",
|
||||||
|
"__FOO_BAR__": "FOO-BAR",
|
||||||
|
"$foo@Bar": "FOO-BAR",
|
||||||
|
" $#$Foo 22 bar ": "FOO-22-BAR",
|
||||||
|
"Foo-#1😄$_%^&*(1bar": "FOO-1-1-BAR",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, UpperKebabCase(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnakeCase(t *testing.T) {
|
func TestSnakeCase(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestSnakeCase")
|
assert := internal.NewAssert(t, "TestSnakeCase")
|
||||||
|
|
||||||
assert.Equal("foo_bar", SnakeCase("Foo Bar-"))
|
cases := map[string]string{
|
||||||
assert.Equal("foo_bar", SnakeCase("foo_Bar"))
|
"": "",
|
||||||
assert.Equal("foo_bar", SnakeCase("fooBar"))
|
"foo-bar": "foo_bar",
|
||||||
assert.Equal("f_o_o_b_a_r", SnakeCase("__FOO_BAR__"))
|
"--Foo---Bar-": "foo_bar",
|
||||||
assert.Equal("a_bbc_s_a_b_b_c", SnakeCase("aBbc-s$@a&%_B.B^C"))
|
"Foo Bar-": "foo_bar",
|
||||||
|
"foo_Bar": "foo_bar",
|
||||||
|
"fooBar": "foo_bar",
|
||||||
|
"FOOBAR": "foobar",
|
||||||
|
"FOO_BAR": "foo_bar",
|
||||||
|
"__FOO_BAR__": "foo_bar",
|
||||||
|
"$foo@Bar": "foo_bar",
|
||||||
|
" $#$Foo 22 bar ": "foo_22_bar",
|
||||||
|
"Foo-#1😄$_%^&*(1bar": "foo_1_1_bar",
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotEqual("foo-bar", SnakeCase("foo_Bar"))
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, SnakeCase(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpperSnakeCase(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestUpperSnakeCase")
|
||||||
|
|
||||||
|
cases := map[string]string{
|
||||||
|
"": "",
|
||||||
|
"foo-bar": "FOO_BAR",
|
||||||
|
"--Foo---Bar-": "FOO_BAR",
|
||||||
|
"Foo Bar-": "FOO_BAR",
|
||||||
|
"foo_Bar": "FOO_BAR",
|
||||||
|
"fooBar": "FOO_BAR",
|
||||||
|
"FOOBAR": "FOOBAR",
|
||||||
|
"FOO_BAR": "FOO_BAR",
|
||||||
|
"__FOO_BAR__": "FOO_BAR",
|
||||||
|
"$foo@Bar": "FOO_BAR",
|
||||||
|
" $#$Foo 22 bar ": "FOO_22_BAR",
|
||||||
|
"Foo-#1😄$_%^&*(1bar": "FOO_1_1_BAR",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, UpperSnakeCase(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpperFirst(t *testing.T) {
|
func TestUpperFirst(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestLowerFirst")
|
assert := internal.NewAssert(t, "TestLowerFirst")
|
||||||
|
|
||||||
assert.Equal("Foo", UpperFirst("foo"))
|
cases := map[string]string{
|
||||||
assert.Equal("BAR", UpperFirst("bAR"))
|
"": "",
|
||||||
assert.Equal("FOo", UpperFirst("FOo"))
|
"foo": "Foo",
|
||||||
assert.Equal("FOo大", UpperFirst("fOo大"))
|
"bAR": "BAR",
|
||||||
|
"FOo": "FOo",
|
||||||
|
"fOo大": "FOo大",
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotEqual("Bar", UpperFirst("BAR"))
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, UpperFirst(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLowerFirst(t *testing.T) {
|
func TestLowerFirst(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestLowerFirst")
|
assert := internal.NewAssert(t, "TestLowerFirst")
|
||||||
|
|
||||||
assert.Equal("foo", LowerFirst("foo"))
|
cases := map[string]string{
|
||||||
assert.Equal("bAR", LowerFirst("BAR"))
|
"": "",
|
||||||
assert.Equal("fOo", LowerFirst("FOo"))
|
"foo": "foo",
|
||||||
assert.Equal("fOo大", LowerFirst("FOo大"))
|
"bAR": "bAR",
|
||||||
|
"FOo": "fOo",
|
||||||
|
"fOo大": "fOo大",
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotEqual("Bar", LowerFirst("BAR"))
|
for k, v := range cases {
|
||||||
|
assert.Equal(v, LowerFirst(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPadEnd(t *testing.T) {
|
func TestPadEnd(t *testing.T) {
|
||||||
|
|||||||
50
system/os.go
50
system/os.go
@@ -9,6 +9,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/validator"
|
||||||
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsWindows check if current os is windows
|
// IsWindows check if current os is windows
|
||||||
@@ -50,27 +54,61 @@ func CompareOsEnv(key, comparedEnv string) bool {
|
|||||||
return env == comparedEnv
|
return env == comparedEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCommand use shell /bin/bash -c to execute command
|
// ExecCommand execute command, return the stdout and stderr string of command, and error if error occur
|
||||||
|
// param `command` is a complete command string, like, ls -a (linux), dir(windows), ping 127.0.0.1
|
||||||
|
// in linux, use /bin/bash -c to execute command
|
||||||
|
// in windows, use powershell.exe to execute command
|
||||||
func ExecCommand(command string) (stdout, stderr string, err error) {
|
func ExecCommand(command string) (stdout, stderr string, err error) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
var errout bytes.Buffer
|
var errOut bytes.Buffer
|
||||||
|
|
||||||
cmd := exec.Command("/bin/bash", "-c", command)
|
cmd := exec.Command("/bin/bash", "-c", command)
|
||||||
if IsWindows() {
|
if IsWindows() {
|
||||||
cmd = exec.Command("cmd")
|
cmd = exec.Command("powershell.exe", command)
|
||||||
}
|
}
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stderr = &errout
|
cmd.Stderr = &errOut
|
||||||
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stderr = string(errout.Bytes())
|
if utf8.Valid(errOut.Bytes()) {
|
||||||
|
stderr = byteToString(errOut.Bytes(), "UTF8")
|
||||||
|
} else if validator.IsGBK(errOut.Bytes()) {
|
||||||
|
stderr = byteToString(errOut.Bytes(), "GBK")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := out.Bytes()
|
||||||
|
if utf8.Valid(data) {
|
||||||
|
stdout = byteToString(data, "UTF8")
|
||||||
|
} else if validator.IsGBK(data) {
|
||||||
|
stdout = byteToString(data, "GBK")
|
||||||
}
|
}
|
||||||
stdout = string(out.Bytes())
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func byteToString(data []byte, charset string) string {
|
||||||
|
var result string
|
||||||
|
|
||||||
|
switch charset {
|
||||||
|
case "GBK":
|
||||||
|
decodeBytes, _ := simplifiedchinese.GBK.NewDecoder().Bytes(data)
|
||||||
|
result = string(decodeBytes)
|
||||||
|
case "GB18030":
|
||||||
|
decodeBytes, _ := simplifiedchinese.GB18030.NewDecoder().Bytes(data)
|
||||||
|
result = string(decodeBytes)
|
||||||
|
case "UTF8":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
result = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// GetOsBits get this system bits 32bit or 64bit
|
// GetOsBits get this system bits 32bit or 64bit
|
||||||
// return bit int (32/64)
|
// return bit int (32/64)
|
||||||
func GetOsBits() int {
|
func GetOsBits() int {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ func TestOsDetection(t *testing.T) {
|
|||||||
assert := internal.NewAssert(t, "TestOsJudgment")
|
assert := internal.NewAssert(t, "TestOsJudgment")
|
||||||
|
|
||||||
osType, _, _ := ExecCommand("echo $OSTYPE")
|
osType, _, _ := ExecCommand("echo $OSTYPE")
|
||||||
if strings.Index(osType, "linux") != -1 {
|
if strings.Contains(osType, "linux") {
|
||||||
assert.Equal(true, IsLinux())
|
assert.Equal(true, IsLinux())
|
||||||
}
|
}
|
||||||
if strings.Index(osType, "darwin") != -1 {
|
if strings.Contains(osType, "darwin") {
|
||||||
assert.Equal(true, IsMac())
|
assert.Equal(true, IsMac())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,21 +44,26 @@ func TestOsEnvOperation(t *testing.T) {
|
|||||||
func TestExecCommand(t *testing.T) {
|
func TestExecCommand(t *testing.T) {
|
||||||
assert := internal.NewAssert(t, "TestExecCommand")
|
assert := internal.NewAssert(t, "TestExecCommand")
|
||||||
|
|
||||||
out, errout, err := ExecCommand("ls")
|
// linux or mac
|
||||||
t.Log("std out: ", out)
|
stdout, stderr, err := ExecCommand("ls")
|
||||||
t.Log("std err: ", errout)
|
t.Log("std out: ", stdout)
|
||||||
|
t.Log("std err: ", stderr)
|
||||||
|
assert.Equal("", stderr)
|
||||||
assert.IsNil(err)
|
assert.IsNil(err)
|
||||||
|
|
||||||
out, errout, err = ExecCommand("abc")
|
// windows
|
||||||
t.Log("std out: ", out)
|
stdout, stderr, err = ExecCommand("dir")
|
||||||
t.Log("std err: ", errout)
|
t.Log("std out: ", stdout)
|
||||||
if err != nil {
|
t.Log("std err: ", stderr)
|
||||||
t.Logf("error: %v\n", err)
|
if IsWindows() {
|
||||||
|
assert.IsNil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsWindows() {
|
// error command
|
||||||
assert.IsNotNil(err)
|
stdout, stderr, err = ExecCommand("abc")
|
||||||
}
|
t.Log("std out: ", stdout)
|
||||||
|
t.Log("std err: ", stderr)
|
||||||
|
assert.IsNotNil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOsBits(t *testing.T) {
|
func TestGetOsBits(t *testing.T) {
|
||||||
|
|||||||
@@ -15,11 +15,24 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var isAlphaRegexMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
var (
|
||||||
|
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
||||||
|
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
||||||
|
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
||||||
|
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
||||||
|
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$`)
|
||||||
|
emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`)
|
||||||
|
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||||
|
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^[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]$`)
|
||||||
|
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||||
|
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}`)
|
||||||
|
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?: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})$`)
|
||||||
|
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
||||||
|
)
|
||||||
|
|
||||||
// IsAlpha checks if the string contains only letters (a-zA-Z)
|
// IsAlpha checks if the string contains only letters (a-zA-Z)
|
||||||
func IsAlpha(str string) bool {
|
func IsAlpha(str string) bool {
|
||||||
return isAlphaRegexMatcher.MatchString(str)
|
return alphaMatcher.MatchString(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllUpper check if the string is all upper case letters A-Z
|
// IsAllUpper check if the string is all upper case letters A-Z
|
||||||
@@ -62,11 +75,9 @@ func ContainLower(str string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var containLetterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
|
||||||
|
|
||||||
// ContainLetter check if the string contain at least one letter
|
// ContainLetter check if the string contain at least one letter
|
||||||
func ContainLetter(str string) bool {
|
func ContainLetter(str string) bool {
|
||||||
return containLetterRegexMatcher.MatchString(str)
|
return letterRegexMatcher.MatchString(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsJSON checks if the string is valid JSON
|
// IsJSON checks if the string is valid JSON
|
||||||
@@ -86,11 +97,9 @@ func IsFloatStr(str string) bool {
|
|||||||
return e == nil
|
return e == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var isIntStrRegexMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
|
||||||
|
|
||||||
// IsIntStr check if the string can convert to a integer.
|
// IsIntStr check if the string can convert to a integer.
|
||||||
func IsIntStr(str string) bool {
|
func IsIntStr(str string) bool {
|
||||||
return isIntStrRegexMatcher.MatchString(str)
|
return intStrMatcher.MatchString(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsIp check if the string is a ip address.
|
// IsIp check if the string is a ip address.
|
||||||
@@ -125,8 +134,6 @@ func IsPort(str string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var isUrlRegexMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
|
||||||
|
|
||||||
// IsUrl check if the string is url.
|
// IsUrl check if the string is url.
|
||||||
func IsUrl(str string) bool {
|
func IsUrl(str string) bool {
|
||||||
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
|
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
|
||||||
@@ -143,64 +150,48 @@ func IsUrl(str string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return isUrlRegexMatcher.MatchString(str)
|
return urlMatcher.MatchString(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isDnsRegexMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$`)
|
|
||||||
|
|
||||||
// IsDns check if the string is dns.
|
// IsDns check if the string is dns.
|
||||||
func IsDns(dns string) bool {
|
func IsDns(dns string) bool {
|
||||||
return isDnsRegexMatcher.MatchString(dns)
|
return dnsMatcher.MatchString(dns)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isEmailRegexMatcher *regexp.Regexp = regexp.MustCompile(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`)
|
|
||||||
|
|
||||||
// IsEmail check if the string is a email address.
|
// IsEmail check if the string is a email address.
|
||||||
func IsEmail(email string) bool {
|
func IsEmail(email string) bool {
|
||||||
return isEmailRegexMatcher.MatchString(email)
|
return emailMatcher.MatchString(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isChineseMobileRegexMatcher *regexp.Regexp = regexp.MustCompile("^((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}$")
|
|
||||||
|
|
||||||
// IsChineseMobile check if the string is chinese mobile number.
|
// IsChineseMobile check if the string is chinese mobile number.
|
||||||
func IsChineseMobile(mobileNum string) bool {
|
func IsChineseMobile(mobileNum string) bool {
|
||||||
return isChineseMobileRegexMatcher.MatchString(mobileNum)
|
return chineseMobileMatcher.MatchString(mobileNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isChineseIdNumRegexMatcher *regexp.Regexp = regexp.MustCompile(`^[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]$`)
|
|
||||||
|
|
||||||
// IsChineseIdNum check if the string is chinese id number.
|
// IsChineseIdNum check if the string is chinese id number.
|
||||||
func IsChineseIdNum(id string) bool {
|
func IsChineseIdNum(id string) bool {
|
||||||
return isChineseIdNumRegexMatcher.MatchString(id)
|
return chineseIdMatcher.MatchString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var containChineseRegexMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
|
||||||
|
|
||||||
// ContainChinese check if the string contain mandarin chinese.
|
// ContainChinese check if the string contain mandarin chinese.
|
||||||
func ContainChinese(s string) bool {
|
func ContainChinese(s string) bool {
|
||||||
return containChineseRegexMatcher.MatchString(s)
|
return chineseMatcher.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isChinesePhoneRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}`)
|
|
||||||
|
|
||||||
// IsChinesePhone check if the string is chinese phone number.
|
// IsChinesePhone check if the string is chinese phone number.
|
||||||
// Valid chinese phone is xxx-xxxxxxxx or xxxx-xxxxxxx
|
// Valid chinese phone is xxx-xxxxxxxx or xxxx-xxxxxxx
|
||||||
func IsChinesePhone(phone string) bool {
|
func IsChinesePhone(phone string) bool {
|
||||||
return isChinesePhoneRegexMatcher.MatchString(phone)
|
return chinesePhoneMatcher.MatchString(phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isCreditCardRegexMatcher *regexp.Regexp = regexp.MustCompile(`^(?: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})$`)
|
|
||||||
|
|
||||||
// IsCreditCard check if the string is credit card.
|
// IsCreditCard check if the string is credit card.
|
||||||
func IsCreditCard(creditCart string) bool {
|
func IsCreditCard(creditCart string) bool {
|
||||||
return isCreditCardRegexMatcher.MatchString(creditCart)
|
return creditCardMatcher.MatchString(creditCart)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isBase64RegexMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
|
||||||
|
|
||||||
// IsBase64 check if the string is base64 string.
|
// IsBase64 check if the string is base64 string.
|
||||||
func IsBase64(base64 string) bool {
|
func IsBase64(base64 string) bool {
|
||||||
return isBase64RegexMatcher.MatchString(base64)
|
return base64Matcher.MatchString(base64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmptyString check if the string is empty.
|
// IsEmptyString check if the string is empty.
|
||||||
@@ -288,3 +279,40 @@ func IsZeroValue(value interface{}) bool {
|
|||||||
|
|
||||||
return reflect.DeepEqual(rv.Interface(), reflect.Zero(rv.Type()).Interface())
|
return reflect.DeepEqual(rv.Interface(), reflect.Zero(rv.Type()).Interface())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsGBK check if data encoding is gbk
|
||||||
|
// Note: this function is implemented by whether double bytes fall within the encoding range of gbk,
|
||||||
|
// while each byte of utf-8 encoding format falls within the encoding range of gbk.
|
||||||
|
// Therefore, utf8.valid() should be called first to check whether it is not utf-8 encoding,
|
||||||
|
// and then call IsGBK() to check gbk encoding. like below
|
||||||
|
/**
|
||||||
|
data := []byte("你好")
|
||||||
|
if utf8.Valid(data) {
|
||||||
|
fmt.Println("data encoding is utf-8")
|
||||||
|
}else if(IsGBK(data)) {
|
||||||
|
fmt.Println("data encoding is GBK")
|
||||||
|
}
|
||||||
|
fmt.Println("data encoding is unknown")
|
||||||
|
**/
|
||||||
|
func IsGBK(data []byte) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(data) {
|
||||||
|
if data[i] <= 0xff {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if data[i] >= 0x81 &&
|
||||||
|
data[i] <= 0xfe &&
|
||||||
|
data[i+1] >= 0x40 &&
|
||||||
|
data[i+1] <= 0xfe &&
|
||||||
|
data[i+1] != 0xf7 {
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/duke-git/lancet/internal"
|
"github.com/duke-git/lancet/internal"
|
||||||
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsAllUpper(t *testing.T) {
|
func TestIsAllUpper(t *testing.T) {
|
||||||
@@ -388,3 +390,13 @@ func TestIsZeroValue(t *testing.T) {
|
|||||||
assert.Equal(false, IsZeroValue(value))
|
assert.Equal(false, IsZeroValue(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsGBK(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestIsGBK")
|
||||||
|
|
||||||
|
str := "你好"
|
||||||
|
gbkData, _ := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(str))
|
||||||
|
|
||||||
|
assert.Equal(true, IsGBK(gbkData))
|
||||||
|
assert.Equal(false, utf8.Valid(gbkData))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user