Initial QSfera import
This commit is contained in:
+192
@@ -0,0 +1,192 @@
|
||||
# String Util
|
||||
|
||||
This is a go string operate util package.
|
||||
|
||||
- Github: https://github.com/gookit/goutil/strutil
|
||||
- GoDoc: https://godoc.org/github.com/gookit/goutil/strutil
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/strutil
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
ss := strutil.ToArray("a,b,c", ",")
|
||||
// Output: []string{"a", "b", "c"}
|
||||
|
||||
ints, err := strutil.ToIntSlice("1,2,3")
|
||||
// Output: []int{1, 2, 3}
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
```go
|
||||
func AddSlashes(s string) string
|
||||
func AnyToString(val interface{}, defaultAsErr bool) (str string, err error)
|
||||
func B32Decode(str string) string
|
||||
func B32Encode(str string) string
|
||||
func B64Decode(str string) string
|
||||
func B64Encode(str string) string
|
||||
func Base64(str string) string
|
||||
func Bool(s string) (bool, error)
|
||||
func Byte2str(b []byte) string
|
||||
func Byte2string(b []byte) string
|
||||
func BytePos(s string, bt byte) int
|
||||
func Camel(s string, sep ...string) string
|
||||
func CamelCase(s string, sep ...string) string
|
||||
func Compare(s1, s2, op string) bool
|
||||
func Cut(s, sep string) (before string, after string, found bool)
|
||||
func EscapeHTML(s string) string
|
||||
func EscapeJS(s string) string
|
||||
func FilterEmail(s string) string
|
||||
func GenMd5(src interface{}) string
|
||||
func HasAllSubs(s string, subs []string) bool
|
||||
func HasOnePrefix(s string, prefixes []string) bool
|
||||
func HasOneSub(s string, subs []string) bool
|
||||
func HasPrefix(s string, prefix string) bool
|
||||
func HasSuffix(s string, suffix string) bool
|
||||
func Implode(sep string, ss ...string) string
|
||||
func Indent(s, prefix string) string
|
||||
func IndentBytes(b, prefix []byte) []byte
|
||||
func Int(s string) (int, error)
|
||||
func Int64(s string) int64
|
||||
func Int64OrErr(s string) (int64, error)
|
||||
func Int64OrPanic(s string) int64
|
||||
func IntOrPanic(s string) int
|
||||
func Ints(s string, sep ...string) []int
|
||||
func IsAlphaNum(c uint8) bool
|
||||
func IsAlphabet(char uint8) bool
|
||||
func IsBlank(s string) bool
|
||||
func IsBlankBytes(bs []byte) bool
|
||||
func IsEmpty(s string) bool
|
||||
func IsEndOf(s, suffix string) bool
|
||||
func IsNotBlank(s string) bool
|
||||
func IsNumChar(c byte) bool
|
||||
func IsNumeric(s string) bool
|
||||
func IsSpace(c byte) bool
|
||||
func IsSpaceRune(r rune) bool
|
||||
func IsStartOf(s, prefix string) bool
|
||||
func IsStartsOf(s string, prefixes []string) bool
|
||||
func IsSymbol(r rune) bool
|
||||
func IsValidUtf8(s string) bool
|
||||
func IsVersion(s string) bool
|
||||
func Join(sep string, ss ...string) string
|
||||
func JoinList(sep string, ss []string) string
|
||||
func LTrim(s string, cutSet ...string) string
|
||||
func Lower(s string) string
|
||||
func LowerFirst(s string) string
|
||||
func Lowercase(s string) string
|
||||
func Ltrim(s string, cutSet ...string) string
|
||||
func MD5(src interface{}) string
|
||||
func Md5(src interface{}) string
|
||||
func MicroTimeHexID() string
|
||||
func MicroTimeID() string
|
||||
func MustBool(s string) bool
|
||||
func MustCut(s, sep string) (before string, after string)
|
||||
func MustInt(s string) int
|
||||
func MustInt64(s string) int64
|
||||
func MustString(in interface{}) string
|
||||
func MustToTime(s string, layouts ...string) time.Time
|
||||
func NoCaseEq(s, t string) bool
|
||||
func PadLeft(s, pad string, length int) string
|
||||
func PadRight(s, pad string, length int) string
|
||||
func Padding(s, pad string, length int, pos uint8) string
|
||||
func PrettyJSON(v interface{}) (string, error)
|
||||
func QuietBool(s string) bool
|
||||
func QuietInt(s string) int
|
||||
func QuietInt64(s string) int64
|
||||
func QuietString(in interface{}) string
|
||||
func Quote(s string) string
|
||||
func RTrim(s string, cutSet ...string) string
|
||||
func RandomBytes(length int) ([]byte, error)
|
||||
func RandomChars(ln int) string
|
||||
func RandomCharsV2(ln int) string
|
||||
func RandomCharsV3(ln int) string
|
||||
func RandomString(length int) (string, error)
|
||||
func RenderTemplate(input string, data interface{}, fns template.FuncMap, isFile ...bool) string
|
||||
func RenderText(input string, data interface{}, fns template.FuncMap, isFile ...bool) string
|
||||
func Repeat(s string, times int) string
|
||||
func RepeatBytes(char byte, times int) (chars []byte)
|
||||
func RepeatRune(char rune, times int) (chars []rune)
|
||||
func Replaces(str string, pairs map[string]string) string
|
||||
func Rtrim(s string, cutSet ...string) string
|
||||
func RuneCount(s string) int
|
||||
func RuneIsLower(c rune) bool
|
||||
func RuneIsUpper(c rune) bool
|
||||
func RuneIsWord(c rune) bool
|
||||
func RunePos(s string, ru rune) int
|
||||
func RuneWidth(r rune) int
|
||||
func Similarity(s, t string, rate float32) (float32, bool)
|
||||
func SnakeCase(s string, sep ...string) string
|
||||
func Split(s, sep string) (ss []string)
|
||||
func SplitInlineComment(val string) (string, string)
|
||||
func SplitN(s, sep string, n int) (ss []string)
|
||||
func SplitNTrimmed(s, sep string, n int) (ss []string)
|
||||
func SplitNValid(s, sep string, n int) (ss []string)
|
||||
func SplitTrimmed(s, sep string) (ss []string)
|
||||
func SplitValid(s, sep string) (ss []string)
|
||||
func StrPos(s, sub string) int
|
||||
func String(val interface{}) (string, error)
|
||||
func StringOrErr(val interface{}) (string, error)
|
||||
func Strings(s string, sep ...string) []string
|
||||
func StripSlashes(s string) string
|
||||
func Substr(s string, pos, length int) string
|
||||
func TextSplit(s string, w int) []string
|
||||
func TextTruncate(s string, w int, tail string) string
|
||||
func TextWidth(s string) int
|
||||
func TextWrap(s string, w int) string
|
||||
func Title(s string) string
|
||||
func ToArray(s string, sep ...string) []string
|
||||
func ToBool(s string) (bool, error)
|
||||
func ToBytes(s string) (b []byte)
|
||||
func ToDuration(s string) (time.Duration, error)
|
||||
func ToInt(s string) (int, error)
|
||||
func ToInt64(s string) (int64, error)
|
||||
func ToIntSlice(s string, sep ...string) (ints []int, err error)
|
||||
func ToInts(s string, sep ...string) ([]int, error)
|
||||
func ToSlice(s string, sep ...string) []string
|
||||
func ToString(val interface{}) (string, error)
|
||||
func ToStrings(s string, sep ...string) []string
|
||||
func ToTime(s string, layouts ...string) (t time.Time, err error)
|
||||
func Trim(s string, cutSet ...string) string
|
||||
func TrimCut(s, sep string) (string, string)
|
||||
func TrimLeft(s string, cutSet ...string) string
|
||||
func TrimRight(s string, cutSet ...string) string
|
||||
func URLDecode(s string) string
|
||||
func URLEncode(s string) string
|
||||
func Unquote(s string) string
|
||||
func Upper(s string) string
|
||||
func UpperFirst(s string) string
|
||||
func UpperWord(s string) string
|
||||
func Uppercase(s string) string
|
||||
func Utf8Len(s string) int
|
||||
func Utf8Split(s string, w int) []string
|
||||
func Utf8Truncate(s string, w int, tail string) string
|
||||
func Utf8Width(s string) (size int)
|
||||
func Utf8len(s string) int
|
||||
func VersionCompare(v1, v2, op string) bool
|
||||
func WidthWrap(s string, w int) string
|
||||
func WrapTag(s, tag string) string
|
||||
```
|
||||
|
||||
## Code Check & Testing
|
||||
|
||||
```bash
|
||||
gofmt -w -l ./
|
||||
golint ./...
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
|
||||
```shell
|
||||
go test -v ./strutil/...
|
||||
```
|
||||
|
||||
**Test limit by regexp**:
|
||||
|
||||
```shell
|
||||
go test -v -run ^TestSetByKeys ./strutil/...
|
||||
```
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"github.com/gookit/goutil/byteutil"
|
||||
)
|
||||
|
||||
// Buffer wrap and extends the bytes.Buffer
|
||||
type Buffer = byteutil.Buffer
|
||||
|
||||
// NewBuffer instance, can set init size
|
||||
func NewBuffer(initSize ...int) *Buffer {
|
||||
if len(initSize) > 0 && initSize[0] > 0 {
|
||||
buf := &Buffer{}
|
||||
buf.Grow(initSize[0])
|
||||
return buf
|
||||
}
|
||||
return &Buffer{}
|
||||
}
|
||||
|
||||
// ByteChanPool struct
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// bp := strutil.NewByteChanPool(500, 1024, 1024)
|
||||
// buf:=bp.Get()
|
||||
// defer bp.Put(buf)
|
||||
// // use buf do something ...
|
||||
type ByteChanPool = byteutil.ChanPool
|
||||
|
||||
// NewByteChanPool instance
|
||||
func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool {
|
||||
return byteutil.NewChanPool(maxSize, width, capWidth)
|
||||
}
|
||||
+513
@@ -0,0 +1,513 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
)
|
||||
|
||||
// Equal check, alias of strings.EqualFold
|
||||
var Equal = strings.EqualFold
|
||||
var IsHttpURL = checkfn.IsHttpURL
|
||||
|
||||
// IsNumChar returns true if the given character is a numeric, otherwise false.
|
||||
func IsNumChar(c byte) bool { return c >= '0' && c <= '9' }
|
||||
|
||||
var (
|
||||
uintReg = regexp.MustCompile(`^\d+$`)
|
||||
intReg = regexp.MustCompile(`^[-+]?\d+$`)
|
||||
|
||||
floatReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`)
|
||||
)
|
||||
|
||||
// IsInt check the string is an integer number
|
||||
func IsInt(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
return intReg.MatchString(s)
|
||||
}
|
||||
|
||||
// IsUint check the string is an unsigned integer number
|
||||
func IsUint(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
return uintReg.MatchString(s)
|
||||
}
|
||||
|
||||
// IsFloat check the string is a float number
|
||||
func IsFloat(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
return floatReg.MatchString(s)
|
||||
}
|
||||
|
||||
// IsNumeric returns true if the given string is a numeric(int/float), otherwise false.
|
||||
func IsNumeric(s string) bool { return checkfn.IsNumeric(s) }
|
||||
|
||||
// IsPositiveNum check the string is a positive number
|
||||
func IsPositiveNum(s string) bool { return checkfn.IsPositiveNum(s) }
|
||||
|
||||
// IsAlphabet char
|
||||
func IsAlphabet(char uint8) bool {
|
||||
// A 65 -> Z 90
|
||||
if char >= 'A' && char <= 'Z' {
|
||||
return true
|
||||
}
|
||||
|
||||
// a 97 -> z 122
|
||||
if char >= 'a' && char <= 'z' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAlphaNum reports whether the byte is an ASCII letter, number, or underscore
|
||||
func IsAlphaNum(c uint8) bool {
|
||||
return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
|
||||
}
|
||||
|
||||
// IsUpper returns true if the given string is an uppercase, otherwise false.
|
||||
func IsUpper(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= 'A' && s[i] <= 'Z' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsLower returns true if the given string is a lowercase, otherwise false.
|
||||
func IsLower(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= 'a' && s[i] <= 'z' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StrPos alias of the strings.Index
|
||||
func StrPos(s, sub string) int { return strings.Index(s, sub) }
|
||||
|
||||
// BytePos alias of the strings.IndexByte
|
||||
func BytePos(s string, bt byte) int { return strings.IndexByte(s, bt) }
|
||||
|
||||
// IEqual ignore case check given two strings are equals.
|
||||
func IEqual(s1, s2 string) bool { return strings.EqualFold(s1, s2) }
|
||||
|
||||
// NoCaseEq check two strings is equals and case-insensitivity
|
||||
func NoCaseEq(s, t string) bool { return strings.EqualFold(s, t) }
|
||||
|
||||
// IContains ignore case check substr in the given string.
|
||||
func IContains(s, sub string) bool {
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(sub))
|
||||
}
|
||||
|
||||
// ContainsByte in given string.
|
||||
func ContainsByte(s string, c byte) bool { return strings.IndexByte(s, c) >= 0 }
|
||||
|
||||
// ContainsByteOne in given string.
|
||||
func ContainsByteOne(s string, bs []byte) bool {
|
||||
for _, b := range bs {
|
||||
if strings.IndexByte(s, b) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InArray alias of HasOneSub()
|
||||
var InArray = HasOneSub
|
||||
|
||||
// ContainsOne substr(s) in the given string. alias of HasOneSub()
|
||||
func ContainsOne(s string, subs []string) bool { return HasOneSub(s, subs) }
|
||||
|
||||
// HasOneSub substr(s) in the given string.
|
||||
func HasOneSub(s string, subs []string) bool {
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IContainsOne ignore case check has one substr(s) in the given string.
|
||||
func IContainsOne(s string, subs []string) bool {
|
||||
s = strings.ToLower(s)
|
||||
for _, sub := range subs {
|
||||
if strings.Contains(s, strings.ToLower(sub)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAll given string should contain all substrings. alias of HasAllSubs()
|
||||
func ContainsAll(s string, subs []string) bool { return HasAllSubs(s, subs) }
|
||||
|
||||
// HasAllSubs given string should contain all substrings
|
||||
func HasAllSubs(s string, subs []string) bool {
|
||||
for _, sub := range subs {
|
||||
if !strings.Contains(s, sub) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IContainsAll like ContainsAll(), but ignore case
|
||||
func IContainsAll(s string, subs []string) bool {
|
||||
s = strings.ToLower(s)
|
||||
for _, sub := range subs {
|
||||
if !strings.Contains(s, strings.ToLower(sub)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StartsWithAny alias of the HasOnePrefix
|
||||
var StartsWithAny = HasOneSuffix
|
||||
|
||||
// IsStartsOf alias of the HasOnePrefix
|
||||
func IsStartsOf(s string, prefixes []string) bool {
|
||||
return HasOnePrefix(s, prefixes)
|
||||
}
|
||||
|
||||
// HasOnePrefix the string starts with one of the subs
|
||||
func HasOnePrefix(s string, prefixes []string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if len(s) >= len(prefix) && s[0:len(prefix)] == prefix {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StartsWith alias func for HasPrefix
|
||||
var StartsWith = strings.HasPrefix
|
||||
|
||||
// HasPrefix substr in the given string.
|
||||
func HasPrefix(s string, prefix string) bool { return strings.HasPrefix(s, prefix) }
|
||||
|
||||
// IsStartOf alias of the strings.HasPrefix
|
||||
func IsStartOf(s, prefix string) bool { return strings.HasPrefix(s, prefix) }
|
||||
|
||||
// HasSuffix substr in the given string.
|
||||
func HasSuffix(s string, suffix string) bool { return strings.HasSuffix(s, suffix) }
|
||||
|
||||
// IsEndOf alias of the strings.HasSuffix
|
||||
func IsEndOf(s, suffix string) bool { return strings.HasSuffix(s, suffix) }
|
||||
|
||||
// HasOneSuffix the string end with one of the subs
|
||||
func HasOneSuffix(s string, suffixes []string) bool {
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(s, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsValidUtf8 valid utf8 string check
|
||||
func IsValidUtf8(s string) bool { return utf8.ValidString(s) }
|
||||
|
||||
// ----- refer from github.com/yuin/goldmark/util
|
||||
|
||||
// refer from github.com/yuin/goldmark/util
|
||||
var spaceTable = [256]int8{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
// IsSpace returns true if the given character is a space, otherwise false.
|
||||
func IsSpace(c byte) bool { return spaceTable[c] == 1 }
|
||||
|
||||
// IsEmpty returns true if the given string is empty.
|
||||
func IsEmpty(s string) bool { return len(s) == 0 }
|
||||
|
||||
// IsBlank returns true if the given string is all space characters.
|
||||
func IsBlank(s string) bool { return IsBlankBytes([]byte(s)) }
|
||||
|
||||
// IsNotBlank returns true if the given string is not blank.
|
||||
func IsNotBlank(s string) bool { return !IsBlankBytes([]byte(s)) }
|
||||
|
||||
// IsBlankBytes returns true if the given []byte is all space characters.
|
||||
func IsBlankBytes(bs []byte) bool {
|
||||
for _, b := range bs {
|
||||
if !IsSpace(b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSymbol reports whether the rune is a symbolic character.
|
||||
func IsSymbol(r rune) bool { return unicode.IsSymbol(r) }
|
||||
|
||||
// HasEmpty value for input strings
|
||||
func HasEmpty(ss ...string) bool {
|
||||
for _, s := range ss {
|
||||
if s == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAllEmpty for input strings
|
||||
func IsAllEmpty(ss ...string) bool {
|
||||
for _, s := range ss {
|
||||
if s != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
// regex for check version number
|
||||
verRegex = regexp.MustCompile(`^[0-9][\d.]+(-\w+)?$`)
|
||||
// regex for check variable name
|
||||
varRegex = regexp.MustCompile(`^[a-zA-Z][\w-]*$`)
|
||||
// regex for check env var name
|
||||
envRegex = regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`)
|
||||
// IsVariableName alias for IsVarName
|
||||
IsVariableName = IsVarName
|
||||
// regex for check uuid string. format: 8-4-4-4-12
|
||||
uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
||||
)
|
||||
|
||||
// IsVersion number. eg: 1.2.0
|
||||
func IsVersion(s string) bool { return verRegex.MatchString(s) }
|
||||
|
||||
// IsVarName is valid variable name.
|
||||
func IsVarName(s string) bool { return varRegex.MatchString(s) }
|
||||
|
||||
// IsEnvName is valid ENV var name. eg: APP_NAME
|
||||
func IsEnvName(s string) bool { return envRegex.MatchString(s) }
|
||||
|
||||
// IsUUID check if the string is a valid UUID format.
|
||||
func IsUUID(s string) bool { return uuidPattern.MatchString(s) }
|
||||
|
||||
// Compare for two strings.
|
||||
func Compare(s1, s2, op string) bool {
|
||||
switch op {
|
||||
case ">", "gt":
|
||||
return s1 > s2
|
||||
case "<", "lt":
|
||||
return s1 < s2
|
||||
case ">=", "gte":
|
||||
return s1 >= s2
|
||||
case "<=", "lte":
|
||||
return s1 <= s2
|
||||
case "!=", "ne", "neq":
|
||||
return s1 != s2
|
||||
default: // eq
|
||||
return s1 == s2
|
||||
}
|
||||
}
|
||||
|
||||
// VersionCompare for two version strings. eg: 1.2.0 > 1.1.0
|
||||
func VersionCompare(v1, v2, op string) bool {
|
||||
parts1 := parseVersion(v1)
|
||||
parts2 := parseVersion(v2)
|
||||
|
||||
result := compareVersions(parts1, parts2)
|
||||
switch op {
|
||||
case ">", "gt":
|
||||
return result > 0
|
||||
case "<", "lt":
|
||||
return result < 0
|
||||
case "=", "==", "eq":
|
||||
return result == 0
|
||||
case "!=", "ne", "neq":
|
||||
return result != 0
|
||||
case ">=", "gte":
|
||||
return result >= 0
|
||||
case "<=", "lte":
|
||||
return result <= 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// parseVersion 将版本号字符串解析为整数数组
|
||||
func parseVersion(version string) []int {
|
||||
parts := strings.Split(version, ".")
|
||||
result := make([]int, len(parts))
|
||||
|
||||
for i, part := range parts {
|
||||
num, _ := strconv.Atoi(part)
|
||||
result[i] = num
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// compareVersions 比较两个版本号数组
|
||||
// 返回: -1 表示 v1 < v2, 0 表示 v1 = v2, 1 表示 v1 > v2
|
||||
func compareVersions(v1, v2 []int) int {
|
||||
maxLen := len(v1)
|
||||
if len(v2) > maxLen {
|
||||
maxLen = len(v2)
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
num1 := 0
|
||||
if i < len(v1) {
|
||||
num1 = v1[i]
|
||||
}
|
||||
|
||||
num2 := 0
|
||||
if i < len(v2) {
|
||||
num2 = v2[i]
|
||||
}
|
||||
|
||||
if num1 > num2 {
|
||||
return 1
|
||||
} else if num1 < num2 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// SimpleMatch all substring in the give text string.
|
||||
//
|
||||
// Difference the ContainsAll:
|
||||
//
|
||||
// - start with ^ for exclude contains check.
|
||||
// - end with $ for the check end with keyword.
|
||||
func SimpleMatch(s string, keywords []string) bool {
|
||||
for _, keyword := range keywords {
|
||||
kln := len(keyword)
|
||||
if kln == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// exclude
|
||||
if kln > 1 && keyword[0] == '^' {
|
||||
if strings.Contains(s, keyword[1:]) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// end with
|
||||
if kln > 1 && keyword[kln-1] == '$' {
|
||||
return strings.HasSuffix(s, keyword[:kln-1])
|
||||
}
|
||||
|
||||
// include
|
||||
if !strings.Contains(s, keyword) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// QuickMatch check for a string. pattern can be a substring.
|
||||
func QuickMatch(pattern, s string) bool {
|
||||
if strings.ContainsRune(pattern, '*') {
|
||||
return GlobMatch(pattern, s)
|
||||
}
|
||||
return strings.Contains(s, pattern)
|
||||
}
|
||||
|
||||
// PathMatch check for a string match the pattern. alias of the path.Match()
|
||||
//
|
||||
// TIP: `*` can match any char, not contain `/`.
|
||||
func PathMatch(pattern, s string) bool {
|
||||
ok, err := path.Match(pattern, s)
|
||||
if err != nil {
|
||||
ok = false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// GlobMatch check for a string match the pattern.
|
||||
//
|
||||
// Difference with PathMatch() is: `*` can match any char, contain `/`.
|
||||
func GlobMatch(pattern, s string) bool {
|
||||
// replace `/` to `S` for path.Match
|
||||
pattern = strings.Replace(pattern, "/", "S", -1)
|
||||
s = strings.Replace(s, "/", "S", -1)
|
||||
|
||||
ok, err := path.Match(pattern, s)
|
||||
if err != nil {
|
||||
ok = false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// LikeMatch simple check for a string match the pattern. pattern like the SQL LIKE.
|
||||
func LikeMatch(pattern, s string) bool {
|
||||
ln := len(pattern)
|
||||
if ln < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
// eg `%abc` `%abc%`
|
||||
if pattern[0] == '%' {
|
||||
if ln > 2 && pattern[ln-1] == '%' {
|
||||
return strings.Contains(s, pattern[1:ln-1])
|
||||
}
|
||||
return strings.HasSuffix(s, pattern[1:])
|
||||
}
|
||||
|
||||
// eg `abc%`
|
||||
if pattern[ln-1] == '%' {
|
||||
return strings.HasPrefix(s, pattern[:ln-1])
|
||||
}
|
||||
return pattern == s
|
||||
}
|
||||
|
||||
// MatchNodePath check for a string match the pattern.
|
||||
//
|
||||
// Use on a pattern:
|
||||
// - `*` match any to sep
|
||||
// - `**` match any to end. only allow at start or end on pattern.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// strutil.MatchNodePath()
|
||||
func MatchNodePath(pattern, s string, sep string) bool {
|
||||
if pattern == "**" || pattern == s {
|
||||
return true
|
||||
}
|
||||
if pattern == "" {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
||||
if i := strings.Index(pattern, "**"); i >= 0 {
|
||||
if i == 0 { // at start
|
||||
return strings.HasSuffix(s, pattern[2:])
|
||||
}
|
||||
return strings.HasPrefix(s, pattern[:len(pattern)-2])
|
||||
}
|
||||
|
||||
pattern = strings.Replace(pattern, sep, "/", -1)
|
||||
s = strings.Replace(s, sep, "/", -1)
|
||||
|
||||
ok, err := path.Match(pattern, s)
|
||||
if err != nil {
|
||||
ok = false
|
||||
}
|
||||
return ok
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
//
|
||||
// -------------------- convert base --------------------
|
||||
//
|
||||
|
||||
const (
|
||||
Base10Chars = "0123456789"
|
||||
Base16Chars = "0123456789abcdef"
|
||||
Base32Chars = "0123456789abcdefghjkmnpqrstvwxyz"
|
||||
Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
Base48Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
|
||||
Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
|
||||
)
|
||||
|
||||
// Base10Conv convert base10 string to new base string.
|
||||
func Base10Conv(src string, to int) string { return BaseConv(src, 10, to) }
|
||||
|
||||
// BaseConv convert base string by from and to base.
|
||||
//
|
||||
// Note: from and to base must be in [2, 64]
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// BaseConv("123", 10, 16) // Output: "7b"
|
||||
// BaseConv("7b", 16, 10) // Output: "123"
|
||||
func BaseConv(src string, from, to int) string {
|
||||
if from > 64 || from < 2 {
|
||||
from = 10
|
||||
}
|
||||
if to > 64 || to < 2 {
|
||||
to = 16
|
||||
}
|
||||
return BaseConvByTpl(src, Base64Chars[:from], Base64Chars[:to])
|
||||
}
|
||||
|
||||
// BaseConvInt convert base int to new base string.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// BaseConv(123, 16) // Output: "7b"
|
||||
func BaseConvInt(src uint64, toBase int) string {
|
||||
if toBase > 64 || toBase < 2 {
|
||||
toBase = 16
|
||||
}
|
||||
|
||||
// bigInt 支持 2-62 进制转换处理 TODO
|
||||
if toBase <= 36 {
|
||||
return strconv.FormatUint(src, toBase)
|
||||
}
|
||||
if toBase <= 62 {
|
||||
bigInt := new(big.Int).SetUint64(src)
|
||||
return bigInt.Text(toBase)
|
||||
}
|
||||
|
||||
return BaseConvIntByTpl(src, Base64Chars[:toBase])
|
||||
}
|
||||
|
||||
// BaseConvByTpl convert base string by template.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// BaseConvert("123", Base62Chars, Base16Chars) // Output: "1e"
|
||||
// BaseConvert("1e", Base16Chars, Base62Chars) // Output: "123"
|
||||
func BaseConvByTpl(src string, fromBase, toBase string) string {
|
||||
if fromBase == toBase {
|
||||
return src
|
||||
}
|
||||
|
||||
// convert to base 10
|
||||
var dec uint64
|
||||
if fromBase == Base10Chars {
|
||||
var err error
|
||||
dec, err = strconv.ParseUint(src, 10, 0)
|
||||
if err != nil {
|
||||
basefn.Panicf("input is not a valid decimal number: %s(%v)", src, err)
|
||||
}
|
||||
} else {
|
||||
fLen := uint64(len(fromBase))
|
||||
for _, c := range src {
|
||||
dec = dec*fLen + uint64(strings.IndexRune(fromBase, c))
|
||||
}
|
||||
}
|
||||
|
||||
// convert to new base
|
||||
return BaseConvIntByTpl(dec, toBase)
|
||||
}
|
||||
|
||||
// BaseConvIntByTpl convert base int to new base string.
|
||||
func BaseConvIntByTpl(dec uint64, toBase string) string {
|
||||
// convert to new base
|
||||
var res string
|
||||
toLen := uint64(len(toBase))
|
||||
for dec > 0 {
|
||||
res = string(toBase[dec%toLen]) + res
|
||||
dec /= toLen
|
||||
}
|
||||
return res
|
||||
}
|
||||
+413
@@ -0,0 +1,413 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDateLayout error
|
||||
ErrDateLayout = errors.New("invalid date layout string")
|
||||
// ErrInvalidParam error
|
||||
ErrInvalidParam = errors.New("invalid input for parse time")
|
||||
|
||||
// some regex for convert string.
|
||||
toSnakeReg = regexp.MustCompile("[A-Z][a-z]")
|
||||
toCamelRegs = map[string]*regexp.Regexp{
|
||||
" ": regexp.MustCompile(" +[a-zA-Z]"),
|
||||
"-": regexp.MustCompile("-+[a-zA-Z]"),
|
||||
"_": regexp.MustCompile("_+[a-zA-Z]"),
|
||||
}
|
||||
)
|
||||
|
||||
// Internal func refers:
|
||||
// strconv.QuoteRune()
|
||||
// strconv.QuoteToASCII()
|
||||
// strconv.AppendQuote()
|
||||
// strconv.AppendQuoteRune()
|
||||
|
||||
// Quote alias of strings.Quote
|
||||
func Quote(s string) string { return strconv.Quote(s) }
|
||||
|
||||
// Unquote remove start and end quotes by single-quote or double-quote
|
||||
//
|
||||
// tip: strconv.Unquote cannot unquote single-quote
|
||||
func Unquote(s string) string {
|
||||
ln := len(s)
|
||||
if ln < 2 {
|
||||
return s
|
||||
}
|
||||
|
||||
qs, qe := s[0], s[ln-1]
|
||||
|
||||
var valid bool
|
||||
if qs == '"' && qe == '"' {
|
||||
valid = true
|
||||
} else if qs == '\'' && qe == '\'' {
|
||||
valid = true
|
||||
}
|
||||
|
||||
if valid {
|
||||
s = s[1 : ln-1] // exclude quotes
|
||||
}
|
||||
// strconv.Unquote cannot unquote single-quote
|
||||
// if ns, err := strconv.Unquote(s); err == nil {
|
||||
// return ns
|
||||
// }
|
||||
return s
|
||||
}
|
||||
|
||||
// Join alias of strings.Join
|
||||
func Join(sep string, ss ...string) string { return strings.Join(ss, sep) }
|
||||
|
||||
// JoinList alias of strings.Join
|
||||
func JoinList(sep string, ss []string) string { return strings.Join(ss, sep) }
|
||||
|
||||
// JoinComma quick join strings by comma
|
||||
func JoinComma(ss []string) string { return strings.Join(ss, ",") }
|
||||
|
||||
// JoinAny type to string
|
||||
func JoinAny(sep string, parts ...any) string {
|
||||
ss := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
ss = append(ss, QuietString(part))
|
||||
}
|
||||
|
||||
return strings.Join(ss, sep)
|
||||
}
|
||||
|
||||
// Implode alias of strings.Join
|
||||
func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) }
|
||||
|
||||
/*************************************************************
|
||||
* region value to string
|
||||
*************************************************************/
|
||||
|
||||
// String convert value to string, return error on failed
|
||||
func String(val any) (string, error) { return ToStringWith(val) }
|
||||
|
||||
// ToString convert value to string, return error on failed
|
||||
func ToString(val any) (string, error) { return ToStringWith(val) }
|
||||
|
||||
// StringOrErr convert value to string, return error on failed
|
||||
func StringOrErr(val any) (string, error) { return ToStringWith(val) }
|
||||
|
||||
// QuietString convert value to string, will ignore error. same as SafeString()
|
||||
func QuietString(val any) string { return SafeString(val) }
|
||||
|
||||
// SafeString convert value to string. Will ignore error
|
||||
func SafeString(in any) string {
|
||||
s, _ := AnyToString(in, false)
|
||||
return s
|
||||
}
|
||||
|
||||
// StringOrPanic convert value to string, will panic on error
|
||||
func StringOrPanic(val any) string { return MustString(val) }
|
||||
|
||||
// MustString convert value to string. will panic on error
|
||||
func MustString(val any) string {
|
||||
s, err := ToStringWith(val)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// StringOrDefault convert any value to string, return default value on failed
|
||||
func StringOrDefault(val any, defVal string) string { return StringOr(val, defVal) }
|
||||
|
||||
// StringOr convert any value to string, return default value on failed
|
||||
func StringOr(val any, defVal string) string {
|
||||
s, err := ToStringWith(val)
|
||||
if err != nil {
|
||||
return defVal
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// AnyToString convert any value to string.
|
||||
//
|
||||
// For defaultAsErr:
|
||||
//
|
||||
// - False will use fmt.Sprint convert unsupported type
|
||||
// - True will return error on convert fail.
|
||||
func AnyToString(val any, defaultAsErr bool) (s string, err error) {
|
||||
var optFn comfunc.ConvOptionFn
|
||||
if !defaultAsErr {
|
||||
optFn = comfunc.WithUserConvFn(comfunc.StrBySprintFn)
|
||||
}
|
||||
return comfunc.ToStringWith(val, optFn)
|
||||
}
|
||||
|
||||
// ToStringWith try to convert value to string. can with some option func, more see comfunc.ConvOption.
|
||||
func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error) {
|
||||
return comfunc.ToStringWith(in, optFns...)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* region string value to bool
|
||||
*************************************************************/
|
||||
|
||||
// ToBool convert string to bool
|
||||
func ToBool(s string) (bool, error) {
|
||||
return comfunc.StrToBool(strings.TrimSpace(s))
|
||||
}
|
||||
|
||||
// QuietBool convert to bool, will ignore error
|
||||
func QuietBool(s string) bool { return SafeBool(s) }
|
||||
|
||||
// SafeBool convert to bool and will ignore error
|
||||
func SafeBool(s string) bool {
|
||||
val, _ := comfunc.StrToBool(strings.TrimSpace(s))
|
||||
return val
|
||||
}
|
||||
|
||||
// MustBool convert to bool and will panic on error
|
||||
func MustBool(s string) bool {
|
||||
val, err := ToBool(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Bool parse string to bool. like strconv.ParseBool()
|
||||
func Bool(s string) (bool, error) {
|
||||
return comfunc.StrToBool(strings.TrimSpace(s))
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* region string value to int
|
||||
*************************************************************/
|
||||
|
||||
// Int convert string to int, alias of ToInt()
|
||||
func Int(s string) (int, error) {
|
||||
return strconv.Atoi(strings.TrimSpace(s))
|
||||
}
|
||||
|
||||
// ToInt convert string to int, return error on fail
|
||||
func ToInt(s string) (int, error) {
|
||||
return strconv.Atoi(strings.TrimSpace(s))
|
||||
}
|
||||
|
||||
// IntOrDefault convert string to int, return default value on fail
|
||||
func IntOrDefault(s string, defVal int) int {
|
||||
return IntOr(s, defVal)
|
||||
}
|
||||
|
||||
// IntOr convert string to int, return default value on fail
|
||||
func IntOr(s string, defVal int) int {
|
||||
val, err := ToInt(s)
|
||||
if err != nil {
|
||||
return defVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// SafeInt convert string to int, will ignore error
|
||||
func SafeInt(s string) int {
|
||||
val, _ := ToInt(s)
|
||||
return val
|
||||
}
|
||||
|
||||
// QuietInt convert string to int, will ignore error
|
||||
func QuietInt(s string) int { return SafeInt(s) }
|
||||
|
||||
// MustInt convert string to int, will panic on error
|
||||
func MustInt(s string) int { return IntOrPanic(s) }
|
||||
|
||||
// IntOrPanic convert value to int, will panic on error
|
||||
func IntOrPanic(s string) int {
|
||||
val, err := ToInt(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* region convert string to int64
|
||||
*************************************************************/
|
||||
|
||||
// Int64 convert string to int, will ignore error
|
||||
func Int64(s string) int64 { return SafeInt64(s) }
|
||||
|
||||
// QuietInt64 convert string to int, will ignore error
|
||||
func QuietInt64(s string) int64 { return SafeInt64(s) }
|
||||
|
||||
// SafeInt64 convert string to int, will ignore error
|
||||
func SafeInt64(s string) int64 {
|
||||
val, _ := Int64OrErr(s)
|
||||
return val
|
||||
}
|
||||
|
||||
// ToInt64 convert string to int, return error on fail
|
||||
func ToInt64(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 0)
|
||||
}
|
||||
|
||||
// Int64OrDefault convert string to int, return default value on fail
|
||||
func Int64OrDefault(s string, defVal int64) int64 {
|
||||
return Int64Or(s, defVal)
|
||||
}
|
||||
|
||||
// Int64Or convert string to int, return default value on fail
|
||||
func Int64Or(s string, defVal int64) int64 {
|
||||
val, err := ToInt64(s)
|
||||
if err != nil {
|
||||
return defVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Int64OrErr convert string to int, return error on fail
|
||||
func Int64OrErr(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 0)
|
||||
}
|
||||
|
||||
// MustInt64 convert value to int, will panic on error
|
||||
func MustInt64(s string) int64 { return Int64OrPanic(s) }
|
||||
|
||||
// Int64OrPanic convert value to int, will panic on error
|
||||
func Int64OrPanic(s string) int64 {
|
||||
val, err := strconv.ParseInt(s, 10, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* region string value to uint
|
||||
*************************************************************/
|
||||
|
||||
// Uint convert string to uint, will ignore error
|
||||
func Uint(s string) uint64 { return SafeUint(s) }
|
||||
|
||||
// SafeUint convert string to uint, will ignore error
|
||||
func SafeUint(s string) uint64 {
|
||||
val, _ := UintOrErr(s)
|
||||
return val
|
||||
}
|
||||
|
||||
// ToUint convert string to uint, return error on fail. alias of UintOrErr()
|
||||
func ToUint(s string) (uint64, error) {
|
||||
return strconv.ParseUint(s, 10, 0)
|
||||
}
|
||||
|
||||
// UintOrErr convert string to uint, return error on fail
|
||||
func UintOrErr(s string) (uint64, error) {
|
||||
return strconv.ParseUint(s, 10, 0)
|
||||
}
|
||||
|
||||
// MustUint convert value to uint, will panic on error. alias of UintOrPanic()
|
||||
func MustUint(s string) uint64 { return UintOrPanic(s) }
|
||||
|
||||
// UintOrPanic convert value to uint, will panic on error
|
||||
func UintOrPanic(s string) uint64 {
|
||||
val, err := UintOrErr(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// UintOrDefault convert string to uint, return default value on fail
|
||||
func UintOrDefault(s string, defVal uint64) uint64 {
|
||||
return UintOr(s, defVal)
|
||||
}
|
||||
|
||||
// UintOr convert string to uint, return default value on fail
|
||||
func UintOr(s string, defVal uint64) uint64 {
|
||||
val, err := UintOrErr(s)
|
||||
if err != nil {
|
||||
return defVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* region string value to byte
|
||||
* refer from https://github.com/valyala/fastjson/blob/master/util.go
|
||||
*************************************************************/
|
||||
|
||||
// Byte2str convert bytes to string
|
||||
func Byte2str(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// Byte2string convert bytes to string
|
||||
func Byte2string(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// ToBytes convert string to bytes
|
||||
func ToBytes(s string) (b []byte) {
|
||||
strh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
sh.Data = strh.Data
|
||||
sh.Len = strh.Len
|
||||
sh.Cap = strh.Len
|
||||
return b
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* region string to int/string slice, time.Time
|
||||
*************************************************************/
|
||||
|
||||
// Ints alias of the ToIntSlice(). default sep is comma(,)
|
||||
func Ints(s string, sep ...string) []int {
|
||||
ints, _ := ToIntSlice(s, sep...)
|
||||
return ints
|
||||
}
|
||||
|
||||
// ToInts alias of the ToIntSlice(). default sep is comma(,)
|
||||
func ToInts(s string, sep ...string) ([]int, error) { return ToIntSlice(s, sep...) }
|
||||
|
||||
// ToIntSlice split string to slice and convert item to int.
|
||||
//
|
||||
// Default sep is comma
|
||||
func ToIntSlice(s string, sep ...string) (ints []int, err error) {
|
||||
ss := ToSlice(s, sep...)
|
||||
for _, item := range ss {
|
||||
iVal, err := mathutil.ToInt(item)
|
||||
if err != nil {
|
||||
return []int{}, err
|
||||
}
|
||||
|
||||
ints = append(ints, iVal)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ToArray alias of the ToSlice()
|
||||
func ToArray(s string, sep ...string) []string { return ToSlice(s, sep...) }
|
||||
|
||||
// Strings alias of the ToSlice()
|
||||
func Strings(s string, sep ...string) []string { return ToSlice(s, sep...) }
|
||||
|
||||
// ToStrings alias of the ToSlice()
|
||||
func ToStrings(s string, sep ...string) []string { return ToSlice(s, sep...) }
|
||||
|
||||
// ToSlice split string to array.
|
||||
func ToSlice(s string, sep ...string) []string {
|
||||
if len(sep) > 0 {
|
||||
return Split(s, sep[0])
|
||||
}
|
||||
return Split(s, ",")
|
||||
}
|
||||
|
||||
// ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m".
|
||||
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
func ToDuration(s string) (time.Duration, error) {
|
||||
return comfunc.ToDuration(s)
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
//
|
||||
// -------------------- escape --------------------
|
||||
//
|
||||
|
||||
// EscapeJS escape javascript string
|
||||
func EscapeJS(s string) string {
|
||||
return template.JSEscapeString(s)
|
||||
}
|
||||
|
||||
// EscapeHTML escape html string
|
||||
func EscapeHTML(s string) string {
|
||||
return template.HTMLEscapeString(s)
|
||||
}
|
||||
|
||||
// AddSlashes add slashes for the string.
|
||||
func AddSlashes(s string) string {
|
||||
if ln := len(s); ln == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, char := range s {
|
||||
switch char {
|
||||
case '\'', '"', '\\':
|
||||
buf.WriteRune('\\')
|
||||
}
|
||||
buf.WriteRune(char)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// StripSlashes strip slashes for the string.
|
||||
func StripSlashes(s string) string {
|
||||
ln := len(s)
|
||||
if ln == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var skip bool
|
||||
var buf bytes.Buffer
|
||||
|
||||
for i, char := range s {
|
||||
if skip {
|
||||
skip = false
|
||||
} else if char == '\\' {
|
||||
if i+1 < ln && s[i+1] == '\\' {
|
||||
skip = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
buf.WriteRune(char)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
//
|
||||
// -------------------- encode --------------------
|
||||
//
|
||||
|
||||
// URLEncode encode url string.
|
||||
func URLEncode(s string) string {
|
||||
if pos := strings.IndexRune(s, '?'); pos > -1 { // escape query data
|
||||
return s[0:pos+1] + url.QueryEscape(s[pos+1:])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// URLDecode decode url string.
|
||||
func URLDecode(s string) string {
|
||||
if pos := strings.IndexRune(s, '?'); pos > -1 { // un-escape query data
|
||||
qy, err := url.QueryUnescape(s[pos+1:])
|
||||
if err == nil {
|
||||
return s[0:pos+1] + qy
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
//
|
||||
// -------------------- base encode --------------------
|
||||
//
|
||||
|
||||
// base32 encoding with no padding
|
||||
var (
|
||||
B32Std = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
B32Hex = base32.HexEncoding.WithPadding(base32.NoPadding)
|
||||
)
|
||||
|
||||
// B32Encode base32 encode
|
||||
func B32Encode(str string) string {
|
||||
return B32Std.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
// B32Decode base32 decode
|
||||
func B32Decode(str string) string {
|
||||
dec, _ := B32Std.DecodeString(str)
|
||||
return string(dec)
|
||||
}
|
||||
|
||||
// B64Std base64 encoding with no padding
|
||||
var B64Std = base64.StdEncoding.WithPadding(base64.NoPadding)
|
||||
|
||||
// B64Encode base64 encode
|
||||
func B64Encode(str string) string {
|
||||
return B64Std.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
// B64EncodeBytes base64 encode
|
||||
func B64EncodeBytes(src []byte) []byte {
|
||||
buf := make([]byte, B64Std.EncodedLen(len(src)))
|
||||
B64Std.Encode(buf, src)
|
||||
return buf
|
||||
}
|
||||
|
||||
// B64Decode base64 decode
|
||||
func B64Decode(str string) string {
|
||||
dec, _ := B64Std.DecodeString(str)
|
||||
return string(dec)
|
||||
}
|
||||
|
||||
// B64DecodeBytes base64 decode
|
||||
func B64DecodeBytes(str []byte) []byte {
|
||||
dbuf := make([]byte, B64Std.DecodedLen(len(str)))
|
||||
n, _ := B64Std.Decode(dbuf, str)
|
||||
return dbuf[:n]
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
)
|
||||
|
||||
// SimilarComparator definition
|
||||
//
|
||||
// links:
|
||||
//
|
||||
// https://github.com/mkideal/cli/blob/master/fuzzy.go
|
||||
type SimilarComparator struct {
|
||||
src, dst string
|
||||
}
|
||||
|
||||
// NewComparator create
|
||||
func NewComparator(src, dst string) *SimilarComparator {
|
||||
return &SimilarComparator{src, dst}
|
||||
}
|
||||
|
||||
// Similarity calc for two string.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rate, ok := Similarity("hello", "he")
|
||||
func Similarity(s, t string, rate float32) (float32, bool) {
|
||||
return NewComparator(s, t).Similar(rate)
|
||||
}
|
||||
|
||||
// Similar by minDifferRate
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// c := NewComparator("hello", "he")
|
||||
// rate, ok :c.Similar(0.3)
|
||||
func (c *SimilarComparator) Similar(minDifferRate float32) (float32, bool) {
|
||||
dist := c.editDistance([]byte(c.src), []byte(c.dst))
|
||||
differRate := dist / float32(mathutil.Max(len(c.src), len(c.dst))+4)
|
||||
|
||||
return differRate, differRate >= minDifferRate
|
||||
}
|
||||
|
||||
func (c *SimilarComparator) editDistance(s, t []byte) float32 {
|
||||
var (
|
||||
m = len(s)
|
||||
n = len(t)
|
||||
d = make([][]float32, m+1)
|
||||
)
|
||||
for i := 0; i < m+1; i++ {
|
||||
d[i] = make([]float32, n+1)
|
||||
d[i][0] = float32(i)
|
||||
}
|
||||
for j := 0; j < n+1; j++ {
|
||||
d[0][j] = float32(j)
|
||||
}
|
||||
|
||||
for j := 1; j < n+1; j++ {
|
||||
for i := 1; i < m+1; i++ {
|
||||
if s[i-1] == t[j-1] {
|
||||
d[i][j] = d[i-1][j-1]
|
||||
} else {
|
||||
d[i][j] = mathutil.Min(d[i-1][j]+1, mathutil.Min(d[i][j-1]+1, d[i-1][j-1]+1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return d[m][n]
|
||||
}
|
||||
|
||||
// Builder struct
|
||||
type Builder struct {
|
||||
strings.Builder
|
||||
}
|
||||
|
||||
// Write bytes and no error report
|
||||
func (b *Builder) Write(p []byte) {
|
||||
_, _ = b.Builder.Write(p)
|
||||
}
|
||||
|
||||
// WriteRune and no error report
|
||||
func (b *Builder) WriteRune(r rune) {
|
||||
_, _ = b.Builder.WriteRune(r)
|
||||
}
|
||||
|
||||
// WriteByteNE write byte and no error report
|
||||
func (b *Builder) WriteByteNE(c byte) {
|
||||
_ = b.WriteByte(c)
|
||||
}
|
||||
|
||||
// WriteString to builder
|
||||
func (b *Builder) WriteString(s string) {
|
||||
_, _ = b.Builder.WriteString(s)
|
||||
}
|
||||
|
||||
// Writef write string by fmt.Sprintf formatted
|
||||
func (b *Builder) Writef(tpl string, vs ...any) {
|
||||
_, _ = b.Builder.WriteString(fmt.Sprintf(tpl, vs...))
|
||||
}
|
||||
|
||||
// Writeln write string with newline.
|
||||
func (b *Builder) Writeln(s string) {
|
||||
_, _ = b.Builder.WriteString(s)
|
||||
_ = b.WriteByte('\n')
|
||||
}
|
||||
|
||||
// WriteAny write any type value.
|
||||
func (b *Builder) WriteAny(v any) {
|
||||
_, _ = b.Builder.WriteString(QuietString(v))
|
||||
}
|
||||
|
||||
// WriteAnys write any type values.
|
||||
func (b *Builder) WriteAnys(vs ...any) {
|
||||
for _, v := range vs {
|
||||
_, _ = b.Builder.WriteString(QuietString(v))
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMulti write multi byte at once.
|
||||
func (b *Builder) WriteMulti(bs ...byte) {
|
||||
for _, b2 := range bs {
|
||||
_ = b.WriteByte(b2)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteStrings write multi string at once.
|
||||
func (b *Builder) WriteStrings(ss ...string) {
|
||||
for _, s := range ss {
|
||||
_, _ = b.Builder.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
// ResetGet return current string and reset builder
|
||||
func (b *Builder) ResetGet() string {
|
||||
s := b.String()
|
||||
b.Reset()
|
||||
return s
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package strutil
|
||||
|
||||
import "strings"
|
||||
|
||||
/*************************************************************
|
||||
* String filtering
|
||||
*************************************************************/
|
||||
|
||||
// Trim string. if cutSet is empty, will trim SPACE.
|
||||
func Trim(s string, cutSet ...string) string {
|
||||
if ln := len(cutSet); ln > 0 && cutSet[0] != "" {
|
||||
if ln == 1 {
|
||||
return strings.Trim(s, cutSet[0])
|
||||
}
|
||||
|
||||
return strings.Trim(s, strings.Join(cutSet, ""))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
// Ltrim alias of TrimLeft
|
||||
func Ltrim(s string, cutSet ...string) string { return TrimLeft(s, cutSet...) }
|
||||
|
||||
// LTrim alias of TrimLeft
|
||||
func LTrim(s string, cutSet ...string) string { return TrimLeft(s, cutSet...) }
|
||||
|
||||
// TrimLeft char in the string. if cutSet is empty, will trim SPACE.
|
||||
func TrimLeft(s string, cutSet ...string) string {
|
||||
if ln := len(cutSet); ln > 0 && cutSet[0] != "" {
|
||||
if ln == 1 {
|
||||
return strings.TrimLeft(s, cutSet[0])
|
||||
}
|
||||
|
||||
return strings.TrimLeft(s, strings.Join(cutSet, ""))
|
||||
}
|
||||
|
||||
return strings.TrimLeft(s, " ")
|
||||
}
|
||||
|
||||
// Rtrim alias of TrimRight
|
||||
func Rtrim(s string, cutSet ...string) string { return TrimRight(s, cutSet...) }
|
||||
|
||||
// RTrim alias of TrimRight
|
||||
func RTrim(s string, cutSet ...string) string { return TrimRight(s, cutSet...) }
|
||||
|
||||
// TrimRight char in the string. if cutSet is empty, will trim SPACE.
|
||||
func TrimRight(s string, cutSet ...string) string {
|
||||
if ln := len(cutSet); ln > 0 && cutSet[0] != "" {
|
||||
if ln == 1 {
|
||||
return strings.TrimRight(s, cutSet[0])
|
||||
}
|
||||
return strings.TrimRight(s, strings.Join(cutSet, ""))
|
||||
}
|
||||
|
||||
return strings.TrimRight(s, " ")
|
||||
}
|
||||
|
||||
// FilterEmail filter email, clear invalid chars.
|
||||
func FilterEmail(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
i := strings.LastIndex(s, "@")
|
||||
if i == -1 {
|
||||
return s
|
||||
}
|
||||
|
||||
// According to rfc5321, "The local-part of a mailbox MUST BE treated as case-sensitive"
|
||||
return s[0:i] + "@" + strings.ToLower(s[i+1:])
|
||||
}
|
||||
|
||||
// func Filter(ss []string, fls ...comdef.StringMatchFunc) []string {
|
||||
// }
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* change string case
|
||||
*************************************************************/
|
||||
|
||||
// methods aliases
|
||||
var (
|
||||
UpWords = UpperWord
|
||||
LoFirst = LowerFirst
|
||||
UpFirst = UpperFirst
|
||||
|
||||
Snake = SnakeCase
|
||||
)
|
||||
|
||||
// Title alias of the strings.ToTitle()
|
||||
func Title(s string) string { return strings.ToTitle(s) }
|
||||
|
||||
// Lower alias of the strings.ToLower()
|
||||
func Lower(s string) string { return strings.ToLower(s) }
|
||||
|
||||
// Lowercase alias of the strings.ToLower()
|
||||
func Lowercase(s string) string { return strings.ToLower(s) }
|
||||
|
||||
// Upper alias of the strings.ToUpper()
|
||||
func Upper(s string) string { return strings.ToUpper(s) }
|
||||
|
||||
// Uppercase alias of the strings.ToUpper()
|
||||
func Uppercase(s string) string { return strings.ToUpper(s) }
|
||||
|
||||
// UpperWord Change the first character of each word to uppercase
|
||||
func UpperWord(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
if len(s) == 1 {
|
||||
return strings.ToUpper(s)
|
||||
}
|
||||
|
||||
inWord := true
|
||||
buf := make([]byte, 0, len(s))
|
||||
|
||||
i := 0
|
||||
rs := []rune(s)
|
||||
if RuneIsLower(rs[i]) {
|
||||
buf = append(buf, []byte(string(unicode.ToUpper(rs[i])))...)
|
||||
} else {
|
||||
buf = append(buf, []byte(string(rs[i]))...)
|
||||
}
|
||||
|
||||
for j := i + 1; j < len(rs); j++ {
|
||||
if !RuneIsWord(rs[i]) && RuneIsWord(rs[j]) {
|
||||
inWord = false
|
||||
}
|
||||
|
||||
if RuneIsLower(rs[j]) && !inWord {
|
||||
buf = append(buf, []byte(string(unicode.ToUpper(rs[j])))...)
|
||||
inWord = true
|
||||
} else {
|
||||
buf = append(buf, []byte(string(rs[j]))...)
|
||||
}
|
||||
|
||||
if RuneIsWord(rs[j]) {
|
||||
inWord = true
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// LowerFirst lower first char
|
||||
func LowerFirst(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
rs := []rune(s)
|
||||
f := rs[0]
|
||||
|
||||
if 'A' <= f && f <= 'Z' {
|
||||
return string(unicode.ToLower(f)) + string(rs[1:])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// UpperFirst upper first char
|
||||
func UpperFirst(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
rs := []rune(s)
|
||||
f := rs[0]
|
||||
|
||||
if 'a' <= f && f <= 'z' {
|
||||
return string(unicode.ToUpper(f)) + string(rs[1:])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SnakeCase convert. eg "RangePrice" -> "range_price"
|
||||
func SnakeCase(s string, sep ...string) string {
|
||||
sepChar := "_"
|
||||
if len(sep) > 0 {
|
||||
sepChar = sep[0]
|
||||
}
|
||||
|
||||
str := toSnakeReg.ReplaceAllStringFunc(s, func(s string) string {
|
||||
return sepChar + LowerFirst(s)
|
||||
})
|
||||
|
||||
return strings.TrimLeft(str, sepChar)
|
||||
}
|
||||
|
||||
// Camel alias of the CamelCase
|
||||
func Camel(s string, sep ...string) string { return CamelCase(s, sep...) }
|
||||
|
||||
// CamelCase convert string to camel case.
|
||||
//
|
||||
// Support:
|
||||
//
|
||||
// "range_price" -> "rangePrice"
|
||||
// "range price" -> "rangePrice"
|
||||
// "range-price" -> "rangePrice"
|
||||
func CamelCase(s string, sep ...string) string {
|
||||
sepChar := "_"
|
||||
if len(sep) > 0 {
|
||||
sepChar = sep[0]
|
||||
}
|
||||
|
||||
// Not contains sep char
|
||||
if !strings.Contains(s, sepChar) {
|
||||
return s
|
||||
}
|
||||
|
||||
// Get regexp instance
|
||||
rgx, ok := toCamelRegs[sepChar]
|
||||
if !ok {
|
||||
rgx = regexp.MustCompile(regexp.QuoteMeta(sepChar) + "+[a-zA-Z]")
|
||||
}
|
||||
|
||||
return rgx.ReplaceAllStringFunc(s, func(s string) string {
|
||||
s = strings.TrimLeft(s, sepChar)
|
||||
return UpperFirst(s)
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Indent format multi line text
|
||||
// from package: github.com/kr/text
|
||||
//
|
||||
|
||||
// Indent inserts prefix at the beginning of each non-empty line of s. The
|
||||
// end-of-line marker is NL.
|
||||
func Indent(s, prefix string) string {
|
||||
return string(IndentBytes([]byte(s), []byte(prefix)))
|
||||
}
|
||||
|
||||
// IndentBytes inserts prefix at the beginning of each non-empty line of b.
|
||||
// The end-of-line marker is NL.
|
||||
func IndentBytes(b, prefix []byte) []byte {
|
||||
if len(b) == 0 {
|
||||
return b
|
||||
}
|
||||
|
||||
bol := true
|
||||
res := make([]byte, 0, len(b)+len(prefix)*4)
|
||||
|
||||
for _, c := range b {
|
||||
if bol && c != '\n' {
|
||||
res = append(res, prefix...)
|
||||
}
|
||||
res = append(res, c)
|
||||
bol = c == '\n'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Replaces replace multi strings
|
||||
//
|
||||
// pairs: {old1: new1, old2: new2, ...}
|
||||
//
|
||||
// Can also use:
|
||||
//
|
||||
// strings.NewReplacer("old1", "new1", "old2", "new2").Replace(str)
|
||||
func Replaces(str string, pairs map[string]string) string {
|
||||
return NewReplacer(pairs).Replace(str)
|
||||
}
|
||||
|
||||
// ReplaceVars replaces simple variables in a string. format: {varName}
|
||||
//
|
||||
// Usage:
|
||||
// strutil.ReplaceVars("{name}, age is {age}", map[string]string{
|
||||
// "name": "Joe",
|
||||
// "age": "18"
|
||||
// })
|
||||
func ReplaceVars(s string, vars map[string]string) string {
|
||||
if !ContainsByte(s, '{') {
|
||||
return s
|
||||
}
|
||||
|
||||
// format var name to {name}
|
||||
pairs := make(map[string]string)
|
||||
for k, v := range vars {
|
||||
vName := "{" + k + "}"
|
||||
pairs[vName] = v
|
||||
}
|
||||
return NewReplacer(pairs).Replace(s)
|
||||
}
|
||||
|
||||
// NewReplacer instance
|
||||
func NewReplacer(pairs map[string]string) *strings.Replacer {
|
||||
ss := make([]string, len(pairs)*2)
|
||||
for old, newVal := range pairs {
|
||||
ss = append(ss, old, newVal)
|
||||
}
|
||||
return strings.NewReplacer(ss...)
|
||||
}
|
||||
|
||||
// WrapTag for given string.
|
||||
func WrapTag(s, tag string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("<%s>%s</%s>", tag, s, tag)
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"math/rand" // TODO use v2 on 1.22+
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// global id:
|
||||
//
|
||||
// https://github.com/rs/xid
|
||||
// https://github.com/satori/go.uuid
|
||||
var (
|
||||
DefMinInt = 1000
|
||||
DefMaxInt = 9999
|
||||
)
|
||||
|
||||
// MicroTimeID generate.
|
||||
// - return like: 16074145697981929446(len: 20)
|
||||
//
|
||||
// Conv Base:
|
||||
//
|
||||
// mtId := MicroTimeID() // eg: 16935349145643425047 len: 20
|
||||
// b16id := Base10Conv(mtId, 16) // eg: eb067252154a9d17 len: 16
|
||||
// b32id := Base10Conv(mtId, 32) // eg: em1jia8akl78n len: 13
|
||||
// b36id := Base10Conv(mtId, 36) // eg: 3ko088phiuoev len: 13
|
||||
// b62id := Base10Conv(mtId, 62) // eg: kb24SKgsQ9V len: 11
|
||||
func MicroTimeID() string { return MTimeBaseID(10) }
|
||||
|
||||
// MicroTimeHexID micro time HEX ID generate.
|
||||
//
|
||||
// return like: 643d4cec7db9e(len: 13)
|
||||
func MicroTimeHexID() string { return MTimeHexID() }
|
||||
|
||||
// MTimeHexID micro time HEX ID generate.
|
||||
//
|
||||
// return like: 643d4cec7db9e(len: 13)
|
||||
func MTimeHexID() string { return MTimeBaseID(16) }
|
||||
|
||||
// MTimeBase36 micro time BASE36 id generate.
|
||||
func MTimeBase36() string { return MTimeBaseID(36) }
|
||||
|
||||
// MTimeBaseID micro time BASE id generate. toBase: 2-36
|
||||
//
|
||||
// Examples:
|
||||
// - toBase=16: 643d4cec7db9e(len: 13)
|
||||
// - toBase=36: hd312z9ka2(len: 10)
|
||||
func MTimeBaseID(toBase int) string {
|
||||
// eg: 1763431181849557
|
||||
ms := time.Now().UnixMicro()
|
||||
// rand 1000 - 9999
|
||||
// ri := mathutil.RandomInt(DefMinInt, DefMaxInt)
|
||||
ri := 1000 + rand.Int63n(8999)
|
||||
|
||||
if toBase > 36 {
|
||||
return BaseConvInt(uint64(ms)+uint64(ri), toBase)
|
||||
}
|
||||
return strconv.FormatInt(ms+ri, toBase)
|
||||
}
|
||||
|
||||
// DatetimeNo generate. can use for order-no.
|
||||
//
|
||||
// - No prefix, return like: 2023041410484904074285478388(len: 28)
|
||||
// - With prefix, return like: prefix2023041410484904074285478388(len: 28 + len(prefix))
|
||||
func DatetimeNo(prefix string) string { return DateSN(prefix) }
|
||||
|
||||
// DateSN generate date serial number. PREFIX + yyyyMMddHHmmss + ext(微秒+随机数)
|
||||
func DateSN(prefix string) string {
|
||||
nt := time.Now()
|
||||
pl := len(prefix)
|
||||
bs := make([]byte, 0, 28+pl)
|
||||
if pl > 0 {
|
||||
bs = append(bs, prefix...)
|
||||
}
|
||||
|
||||
// micro datetime
|
||||
bs = nt.AppendFormat(bs, "20060102150405.000000")
|
||||
bs[14+pl] = '0'
|
||||
|
||||
// host
|
||||
name, err := os.Hostname()
|
||||
if err != nil {
|
||||
name = "default"
|
||||
}
|
||||
c32 := crc32.ChecksumIEEE([]byte(name)) // eg: 4006367001
|
||||
bs = strconv.AppendUint(bs, uint64(c32%99), 10)
|
||||
|
||||
// rand 1000 - 9999
|
||||
// rs := rand.New(rand.NewSource(nt.UnixNano()))
|
||||
bs = strconv.AppendInt(bs, 1000+rand.Int63n(8999), 10)
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// DateSNOpt 基于时间生成唯一编号
|
||||
type DateSNOpt struct {
|
||||
Layout string // time layout
|
||||
// RandMax int // rand max
|
||||
DateLen int // 时间格式长度,后面部分将会进行进制转换 默认 8(yyyyMMdd)
|
||||
ConvBase int // DateLen 之后的转换 base 2-64. default 36
|
||||
// EnableSeq bool // 需要高并发生成时可以启用自增序号。默认不启用
|
||||
SeqMaxVal int // 自增序号最大值,之后后自动重置
|
||||
globalSeq int64 // 自增,确保同一时刻生成的编号不重复. EnableSeq=true 时启用
|
||||
}
|
||||
|
||||
// default setting: {时间年到秒14位}
|
||||
var defOpt = NewDateSNOpt()
|
||||
|
||||
// ConfigSNOpt config default date sn option
|
||||
func ConfigSNOpt(fn func(opt *DateSNOpt)) {
|
||||
fn(defOpt)
|
||||
}
|
||||
|
||||
// NewDateSNOpt create a new DateSNOpt instance.
|
||||
func NewDateSNOpt() *DateSNOpt {
|
||||
return &DateSNOpt{
|
||||
Layout: "20060102150405.000000",
|
||||
// RandMax: 8999,
|
||||
DateLen: 8,
|
||||
ConvBase: 36,
|
||||
// EnableSeq: true,
|
||||
SeqMaxVal: 8999,
|
||||
}
|
||||
}
|
||||
|
||||
// prepare for generate
|
||||
func (do *DateSNOpt) prepare() {
|
||||
if do.DateLen <= 0 {
|
||||
do.DateLen = 8 // default 8 for yyyyMMdd
|
||||
}
|
||||
if do.ConvBase <= 0 {
|
||||
do.ConvBase = 36
|
||||
}
|
||||
if do.Layout == "" {
|
||||
do.Layout = "20060102150405.000000"
|
||||
}
|
||||
// get sequence max value (default 9999)
|
||||
if do.SeqMaxVal <= 0 {
|
||||
do.SeqMaxVal = 8999
|
||||
}
|
||||
}
|
||||
|
||||
func (do *DateSNOpt) getSeqValue() int64 {
|
||||
// use atomic sequence for guaranteed uniqueness (even without EnableSeq)
|
||||
// this ensures no collisions in tight loops
|
||||
seq := atomic.AddInt64(&do.globalSeq, 1)
|
||||
|
||||
// auto reset when seq exceeds SeqMaxVal (thread-safe using CAS)
|
||||
if seq > int64(do.SeqMaxVal) {
|
||||
// try to reset to 1, other goroutines may have already done it
|
||||
atomic.CompareAndSwapInt64(&do.globalSeq, seq, 1)
|
||||
seq = seq % int64(do.SeqMaxVal)
|
||||
if seq == 0 {
|
||||
seq = 1
|
||||
}
|
||||
}
|
||||
return 1000 + seq
|
||||
}
|
||||
|
||||
// GenSN generate date serial number.
|
||||
func (do *DateSNOpt) GenSN(prefix string) string {
|
||||
pl := len(prefix)
|
||||
bs := make([]byte, 0, 22+pl)
|
||||
if pl > 0 {
|
||||
bs = append(bs, prefix...)
|
||||
}
|
||||
do.prepare()
|
||||
|
||||
// get time and format
|
||||
nt := time.Now()
|
||||
bs = nt.AppendFormat(bs, do.Layout)
|
||||
|
||||
// remove the dot separator if exists
|
||||
dotIdx := -1
|
||||
for i := pl; i < len(bs); i++ {
|
||||
if bs[i] == '.' {
|
||||
dotIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if dotIdx > 0 {
|
||||
bs = append(bs[:dotIdx], bs[dotIdx+1:]...)
|
||||
}
|
||||
|
||||
// determine date length (default 8 for yyyyMMdd)
|
||||
idx := do.DateLen + pl
|
||||
// high concurrency mode: sequence is the main differentiator
|
||||
extBs := strconv.AppendInt(bs[idx:], do.getSeqValue(), 10)
|
||||
if extBs[0] == '0' {
|
||||
extBs[0] = '1'
|
||||
}
|
||||
extInt := SafeUint(string(extBs))
|
||||
|
||||
// convert extension to target base
|
||||
bs = append(bs[:idx], BaseConvInt(extInt, do.ConvBase)...)
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// 确保同一时刻生成的编号不重复 max: 89999
|
||||
var globalSeqSnV2 int64 = 0
|
||||
|
||||
func getSeqValue() int64 {
|
||||
// use atomic sequence for guaranteed uniqueness (even without EnableSeq)
|
||||
// this ensures no collisions in tight loops
|
||||
seq := atomic.AddInt64(&globalSeqSnV2, 1)
|
||||
|
||||
// auto reset when seq exceeds SeqMaxVal (thread-safe using CAS)
|
||||
if seq > 89999 {
|
||||
// try to reset to 1, other goroutines may have already done it
|
||||
atomic.CompareAndSwapInt64(&globalSeqSnV2, seq, 1)
|
||||
seq = seq % 89999
|
||||
if seq == 0 {
|
||||
seq = 1
|
||||
}
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
// DateSNv2 generate date serial number.
|
||||
// - 2 < extBase <= 36
|
||||
// - return: PREFIX + yyyyMMddHHmmss + extBase(6bit micro + 5bit random number)
|
||||
//
|
||||
// Example:
|
||||
// - prefix=P, extBase=16, return: P2023091414361354b4490(len=22)
|
||||
// - prefix=P, extBase=36, return: P202309141436131gw3jg(len=21)
|
||||
func DateSNv2(prefix string, extBase ...int) string {
|
||||
pl := len(prefix)
|
||||
bs := make([]byte, 0, 22+pl)
|
||||
if pl > 0 {
|
||||
bs = append(bs, prefix...)
|
||||
}
|
||||
|
||||
// micro datetime
|
||||
nt := time.Now()
|
||||
bs = nt.AppendFormat(bs, "20060102150405.000000")
|
||||
|
||||
// 6bit micro + 5bit rand 10000 - 99999
|
||||
// ext := strconv.AppendInt(bs[16+pl:], 10000+rand.Int63n(89999), 10)
|
||||
ext := strconv.AppendInt(bs[16+pl:], 10000+getSeqValue(), 10)
|
||||
// prefix + yyyyMMddHHmmss + ext(convert to base)
|
||||
base := basefn.FirstOr(extBase, 36)
|
||||
bs = append(bs[:14+pl], strconv.FormatInt(SafeInt64(string(ext)), base)...)
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
var (
|
||||
// key is dateLen + extBase
|
||||
dsoMap = make(map[string]*DateSNOpt)
|
||||
dsoMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// DateSNv3 generate date serial number.
|
||||
// - 2 < extBase <= 64
|
||||
// - return: PREFIX + DATETIME(yyyyMMddHHmmss).dateLen + extBase(DATETIME.after+6bit micro + 5bit random number)
|
||||
// - dateLen: 为 DATETIME(yyyyMMddHHmmss) 保留的长度,默认为 8(yyyyMMdd) 后面的给 extBase 使用
|
||||
//
|
||||
// Example:
|
||||
// - prefix=P, dateLen=8, extBase=16, return: P202511139vs99gbifnj len: 20
|
||||
// - prefix=P, dateLen=6, extBase=36, return: P2025119yn52qhefati len: 19
|
||||
// - prefix=P, dateLen=6, extBase=48, return: P202511k9ksgD1fe6x len: 18
|
||||
// - prefix=P, dateLen=4, extBase=62, return: P2025aZl8N0y58M7 len: 16
|
||||
func DateSNv3(prefix string, dateLen int, extBase ...int) string {
|
||||
baseVal := basefn.FirstOr(extBase, 36)
|
||||
cacheKey := fmt.Sprintf("%d%d", dateLen, baseVal)
|
||||
|
||||
dsoMutex.RLock()
|
||||
dso, ok := dsoMap[cacheKey]
|
||||
dsoMutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
dsoMutex.Lock()
|
||||
// double check,防止在加锁期间其他 goroutine 已经创建
|
||||
if dso, ok = dsoMap[cacheKey]; !ok {
|
||||
dso = NewDateSNOpt()
|
||||
// dso.EnableSeq = true
|
||||
dso.DateLen = dateLen
|
||||
dso.ConvBase = baseVal
|
||||
dsoMap[cacheKey] = dso
|
||||
}
|
||||
dsoMutex.Unlock()
|
||||
}
|
||||
return dso.GenSN(prefix)
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/byteutil"
|
||||
)
|
||||
|
||||
// Md5 Generate a 32-bit md5 string
|
||||
func Md5(src any) string { return string(Md5Bytes(src)) }
|
||||
|
||||
// MD5 Generate a 32-bit md5 string
|
||||
func MD5(src any) string { return Md5(src) }
|
||||
|
||||
// GenMd5 Generate a 32-bit md5 string
|
||||
func GenMd5(src any) string { return Md5(src) }
|
||||
|
||||
// Md5Simple md5 加密原始二进制的每个byte转为 base62,缩短长度(16)
|
||||
func Md5Simple(src any) string {
|
||||
md5Bytes := byteutil.Md5Sum(src)
|
||||
b62str := new(strings.Builder)
|
||||
b62str.Grow(16)
|
||||
|
||||
// 直接将每个 byte 转为 base62 数字然后拼接
|
||||
for _, b := range md5Bytes {
|
||||
b62str.WriteByte(Base62Chars[b%0x3E])
|
||||
}
|
||||
return b62str.String()
|
||||
}
|
||||
|
||||
// Md5Base62 md5 加密原始二进制的转为 base62 字符串,缩短长度(21~22)
|
||||
func Md5Base62(src any) string {
|
||||
md5Bytes := byteutil.Md5Sum(src)
|
||||
|
||||
// Step 2: 将字节数组转为 big.Int
|
||||
number := new(big.Int)
|
||||
number.SetBytes(md5Bytes)
|
||||
|
||||
// Step 3: 转换为 Base62 字符串
|
||||
base62 := new(strings.Builder)
|
||||
// base62.Grow(22)
|
||||
for number.Sign() > 0 {
|
||||
remainder := new(big.Int)
|
||||
number.DivMod(number, big.NewInt(62), remainder)
|
||||
base62.WriteByte(Base62Chars[remainder.Int64()])
|
||||
}
|
||||
|
||||
// 反转字符串以得到正确的顺序
|
||||
runes := []rune(base62.String())
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// Md5Bytes Generate a 32-bit md5 bytes
|
||||
func Md5Bytes(src any) []byte { return byteutil.Md5(src) }
|
||||
|
||||
// ShortMd5 Generate a 16-bit md5 string. remove the first 8 and last 8 bytes from 32-bit md5 string.
|
||||
func ShortMd5(src any) string { return string(byteutil.ShortMd5(src)) }
|
||||
|
||||
//
|
||||
// ----------------------- Simple UUID -----------------------------
|
||||
//
|
||||
|
||||
// UUIDv4 Generate a simple UUIDv4 string
|
||||
func UUIDv4() (string, error) {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("generate UUID fail: %w", err)
|
||||
}
|
||||
|
||||
// 设置版本(4)和变体
|
||||
b[6] = (b[6] & 0x0f) | 0x40 // Version 4
|
||||
b[8] = (b[8] & 0x3f) | 0x80 // Variant 10
|
||||
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
|
||||
}
|
||||
|
||||
// ShortUUID Generate a short UUID (8 characters)
|
||||
func ShortUUID() string {
|
||||
b := make([]byte, 4)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return fmt.Sprintf("%04x", b)
|
||||
}
|
||||
return fmt.Sprintf("%08x", b)
|
||||
}
|
||||
|
||||
//
|
||||
// ----------------------- hash password -----------------------------
|
||||
//
|
||||
|
||||
// HashPasswd for quick hash an input password string
|
||||
func HashPasswd(pwd, key string) string {
|
||||
hm := hmac.New(sha256.New, []byte(key))
|
||||
hm.Write([]byte(pwd))
|
||||
|
||||
return hex.EncodeToString(hm.Sum(nil))
|
||||
}
|
||||
|
||||
// VerifyPasswd for quick verify input password is valid
|
||||
//
|
||||
// - pwdMAC from db or config, generated by HashPasswd()
|
||||
func VerifyPasswd(pwdMAC, pwd, key string) bool {
|
||||
decBts, err := hex.DecodeString(pwdMAC)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hm := hmac.New(sha256.New, []byte(key))
|
||||
hm.Write([]byte(pwd))
|
||||
|
||||
return hmac.Equal(decBts, hm.Sum(nil))
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
)
|
||||
|
||||
// PosFlag type
|
||||
type PosFlag = comdef.Position
|
||||
|
||||
// Position for padding/resize string
|
||||
const (
|
||||
PosLeft PosFlag = iota
|
||||
PosMiddle
|
||||
PosRight
|
||||
PosAuto
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* String padding operation
|
||||
*************************************************************/
|
||||
|
||||
// Padding Fill the string to the specified length.
|
||||
//
|
||||
// params:
|
||||
// - s: 原始字符串
|
||||
// - pad: 用于填充的字符或字符串
|
||||
// - length: 目标长度
|
||||
// - padPos: 填充位置标志(左填充或右填充)
|
||||
func Padding(s, pad string, length int, padPos PosFlag) string {
|
||||
return padding(s, pad, len(s), length, padPos)
|
||||
}
|
||||
|
||||
// Utf8Padding Fill the string to the specified length. use utf8 width.
|
||||
func Utf8Padding(s, pad string, wantLen int, padPos PosFlag) string {
|
||||
return padding(s, pad, Utf8Width(s), wantLen, padPos)
|
||||
}
|
||||
|
||||
func padding(s, pad string, sLen, wantLen int, padPos PosFlag) string {
|
||||
diff := sLen - wantLen
|
||||
if diff >= 0 { // do not need padding.
|
||||
return s
|
||||
}
|
||||
|
||||
// pad space.
|
||||
if len(s) == sLen && (pad == "" || pad == " ") {
|
||||
// Sprintf: 是按字数来填充的,不管中英文都是一个字符 - 有问题
|
||||
if padPos == PosRight { // to right
|
||||
return s + strings.Repeat(" ", -diff)
|
||||
}
|
||||
return strings.Repeat(" ", -diff) + s
|
||||
}
|
||||
|
||||
// other character.
|
||||
if padPos == PosRight { // to right
|
||||
return s + Repeat(pad, -diff)
|
||||
}
|
||||
return Repeat(pad, -diff) + s
|
||||
}
|
||||
|
||||
// PadLeft a string.
|
||||
func PadLeft(s, pad string, length int) string {
|
||||
return Padding(s, pad, length, PosLeft)
|
||||
}
|
||||
|
||||
// PadRight a string.
|
||||
func PadRight(s, pad string, length int) string {
|
||||
return Padding(s, pad, length, PosRight)
|
||||
}
|
||||
|
||||
// PadChars padding a rune/byte to want length and with position flag
|
||||
func PadChars[T byte | rune](cs []T, pad T, length int, pos PosFlag) []T {
|
||||
ln := len(cs)
|
||||
if ln >= length {
|
||||
ns := make([]T, length)
|
||||
copy(ns, cs[:length])
|
||||
return ns
|
||||
}
|
||||
|
||||
idx := length - ln
|
||||
ns := make([]T, length)
|
||||
if pos == PosRight {
|
||||
copy(ns, cs)
|
||||
for i := ln; i < length; i++ {
|
||||
ns[i] = pad
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
// to left
|
||||
for i := 0; i < idx; i++ {
|
||||
ns[i] = pad
|
||||
}
|
||||
copy(ns[idx:], cs)
|
||||
return ns
|
||||
}
|
||||
|
||||
// PadBytes padding a byte to want length and with position flag
|
||||
func PadBytes(bs []byte, pad byte, length int, pos PosFlag) []byte {
|
||||
return PadChars(bs, pad, length, pos)
|
||||
}
|
||||
|
||||
// PadBytesLeft a byte to want length
|
||||
func PadBytesLeft(bs []byte, pad byte, length int) []byte {
|
||||
return PadChars(bs, pad, length, PosLeft)
|
||||
}
|
||||
|
||||
// PadBytesRight a byte to want length
|
||||
func PadBytesRight(bs []byte, pad byte, length int) []byte {
|
||||
return PadChars(bs, pad, length, PosRight)
|
||||
}
|
||||
|
||||
// PadRunes padding a rune to want length and with position flag
|
||||
func PadRunes(rs []rune, pad rune, length int, pos PosFlag) []rune {
|
||||
return PadChars(rs, pad, length, pos)
|
||||
}
|
||||
|
||||
// PadRunesLeft a rune to want length
|
||||
func PadRunesLeft(rs []rune, pad rune, length int) []rune {
|
||||
return PadChars(rs, pad, length, PosLeft)
|
||||
}
|
||||
|
||||
// PadRunesRight a rune to want length
|
||||
func PadRunesRight(rs []rune, pad rune, length int) []rune {
|
||||
return PadChars(rs, pad, length, PosRight)
|
||||
}
|
||||
|
||||
// Align a string by given length and align settings. alias of Resize
|
||||
func Align(s string, length int, align comdef.Align) string {
|
||||
return resize(s, len(s), length, align, false)
|
||||
}
|
||||
|
||||
// Utf8Align a string by given length and align settings. alias of Utf8Resize
|
||||
func Utf8Align(s string, length int, align comdef.Align) string {
|
||||
return resize(s, Utf8Width(s), length, align, false)
|
||||
}
|
||||
|
||||
// Resize a string by given length and align settings. use padding space.
|
||||
// If len(s) > wantLen, will truncate it.
|
||||
func Resize(s string, length int, align comdef.Align) string {
|
||||
return resize(s, len(s), length, align, true)
|
||||
}
|
||||
|
||||
// Utf8Resize a string by given length and align settings. use padding space.
|
||||
// If width(s) > wantLen, will truncate it.
|
||||
func Utf8Resize(s string, length int, align comdef.Align) string {
|
||||
return resize(s, Utf8Width(s), length, align, true)
|
||||
}
|
||||
|
||||
// resize a string by given length and align settings. use padding space.
|
||||
func resize(s string, sLen, wantLen int, align comdef.Align, cutOverflow bool) string {
|
||||
diff := sLen - wantLen
|
||||
if diff >= 0 { // do not need padding.
|
||||
// cutOverflow: truncate on sLen > wantLen
|
||||
if cutOverflow && sLen > wantLen {
|
||||
if len(s) == sLen {
|
||||
return s[:wantLen]
|
||||
}
|
||||
return utf8Truncate(s, sLen, wantLen, "")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if align == comdef.Center {
|
||||
padLn := (wantLen - sLen) / 2
|
||||
if diff = wantLen - padLn*2; diff > 0 {
|
||||
s += " "
|
||||
}
|
||||
padStr := string(RepeatBytes(' ', padLn))
|
||||
return padStr + s + padStr
|
||||
}
|
||||
|
||||
padStr := string(RepeatBytes(' ', wantLen-sLen))
|
||||
// tip: 左对齐 - 使用空白填充右边
|
||||
if align == comdef.Left || align == comdef.PosAuto {
|
||||
return s + padStr
|
||||
}
|
||||
return padStr + s
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* String repeat operation
|
||||
*************************************************************/
|
||||
|
||||
// Repeat a string by given times.
|
||||
func Repeat(s string, times int) string {
|
||||
if times < 1 {
|
||||
return ""
|
||||
}
|
||||
if times == 1 {
|
||||
return s
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(s) * times)
|
||||
|
||||
for i := 0; i < times; i++ {
|
||||
sb.WriteString(s)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// RepeatRune repeat a rune char.
|
||||
func RepeatRune(char rune, times int) []rune { return RepeatChars(char, times) }
|
||||
|
||||
// RepeatBytes repeat a byte char.
|
||||
func RepeatBytes(char byte, times int) []byte { return RepeatChars(char, times) }
|
||||
|
||||
// RepeatChars repeat a byte char.
|
||||
func RepeatChars[T byte | rune](char T, times int) []T {
|
||||
if times <= 0 {
|
||||
return make([]T, 0)
|
||||
}
|
||||
|
||||
chars := make([]T, times)
|
||||
for i := 0; i < times; i++ {
|
||||
chars[i] = char
|
||||
}
|
||||
return chars
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/gookit/goutil/byteutil"
|
||||
)
|
||||
|
||||
var regNumVersion = regexp.MustCompile(`[0-9][\d.]+([_-]\d+)?`)
|
||||
|
||||
// NumVersion parse input string, get valid number version. eg: go-1.22.3 -> 1.22.3
|
||||
func NumVersion(s string) string {
|
||||
return regNumVersion.FindString(s)
|
||||
}
|
||||
|
||||
// MustToTime convert date string to time.Time
|
||||
func MustToTime(s string, layouts ...string) time.Time {
|
||||
t, err := ToTime(s, layouts...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// auto match uses some common layouts.
|
||||
// key is layout length.
|
||||
var layoutMap = map[int][]string{
|
||||
6: {"200601", "060102", time.Kitchen},
|
||||
8: {"20060102", "06-01-02"},
|
||||
10: {"2006-01-02"},
|
||||
13: {"2006-01-02 15"},
|
||||
15: {time.Stamp},
|
||||
16: {"2006-01-02 15:04"},
|
||||
19: {"2006-01-02 15:04:05", time.RFC822, time.StampMilli},
|
||||
20: {"2006-01-02 15:04:05Z"},
|
||||
21: {time.RFC822Z},
|
||||
22: {time.StampMicro},
|
||||
23: {"2006-01-02 15:04:05.000", "2006-01-02 15:04:05.999"},
|
||||
24: {time.ANSIC},
|
||||
25: {time.RFC3339, time.StampNano},
|
||||
// time.Layout}, // must go >= 1.19
|
||||
26: {"2006-01-02 15:04:05.000000"},
|
||||
28: {time.UnixDate},
|
||||
29: {time.RFC1123, "2006-01-02 15:04:05.000000000"},
|
||||
30: {time.RFC850},
|
||||
31: {time.RFC1123Z},
|
||||
35: {time.RFC3339Nano},
|
||||
}
|
||||
|
||||
// ToTime convert date string to time.Time
|
||||
//
|
||||
// NOTE: always use local timezone.
|
||||
func ToTime(s string, layouts ...string) (t time.Time, err error) {
|
||||
// custom layout
|
||||
if len(layouts) > 0 {
|
||||
if len(layouts[0]) > 0 {
|
||||
return time.ParseInLocation(layouts[0], s, time.Local)
|
||||
}
|
||||
|
||||
err = ErrDateLayout
|
||||
return
|
||||
}
|
||||
|
||||
// auto match use some commonly layouts.
|
||||
strLn := len(s)
|
||||
maybeLayouts, ok := layoutMap[strLn]
|
||||
if !ok {
|
||||
err = ErrInvalidParam
|
||||
return
|
||||
}
|
||||
|
||||
var hasAlphaT bool
|
||||
if pos := strings.IndexByte(s, 'T'); pos > 0 && pos < 12 {
|
||||
hasAlphaT = true
|
||||
}
|
||||
|
||||
hasSlashR := strings.IndexByte(s, '/') > 0
|
||||
for _, layout := range maybeLayouts {
|
||||
// date string has "T". eg: "2006-01-02T15:04:05"
|
||||
if hasAlphaT {
|
||||
layout = strings.Replace(layout, " ", "T", 1)
|
||||
}
|
||||
|
||||
// date string has "/". eg: "2006/01/02 15:04:05"
|
||||
if hasSlashR {
|
||||
layout = strings.Replace(layout, "-", "/", -1)
|
||||
}
|
||||
|
||||
t, err = time.ParseInLocation(layout, s, time.Local)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// t, err = time.ParseInLocation(layout, s, time.Local)
|
||||
return
|
||||
}
|
||||
|
||||
// ParseSizeOpt parse size expression options
|
||||
type ParseSizeOpt struct {
|
||||
// OneAsMax if only one size value, use it as max size. default is false
|
||||
OneAsMax bool
|
||||
// SepChar is the separator char for time range string. default is '~'
|
||||
SepChar byte
|
||||
// KeywordFn is the function for parse keyword time string.
|
||||
KeywordFn func(string) (min, max uint64, err error)
|
||||
}
|
||||
|
||||
func ensureOpt(opt *ParseSizeOpt) *ParseSizeOpt {
|
||||
if opt == nil {
|
||||
opt = &ParseSizeOpt{SepChar: '~'}
|
||||
} else {
|
||||
if opt.SepChar == 0 {
|
||||
opt.SepChar = '~'
|
||||
}
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// ErrInvalidSizeExpr invalid size expression error
|
||||
var ErrInvalidSizeExpr = errors.New("invalid size expr")
|
||||
|
||||
// ParseSizeRange parse range size expression to min and max size.
|
||||
//
|
||||
// Expression format:
|
||||
//
|
||||
// "1KB~2MB" => 1KB to 2MB
|
||||
// "-1KB" => <1KB
|
||||
// "~1MB" => <1MB
|
||||
// "< 1KB" => <1KB
|
||||
// "1KB" => >1KB
|
||||
// "1KB~" => >1KB
|
||||
// ">1KB" => >1KB
|
||||
// "+1KB" => >1KB
|
||||
func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) {
|
||||
opt = ensureOpt(opt)
|
||||
expr = strings.TrimSpace(expr)
|
||||
if expr == "" {
|
||||
err = ErrInvalidSizeExpr
|
||||
return
|
||||
}
|
||||
|
||||
// parse size range. eg: "1KB~2MB"
|
||||
if strings.IndexByte(expr, '~') > -1 {
|
||||
s1, s2 := TrimCut(expr, "~")
|
||||
if s1 != "" {
|
||||
min, err = ToByteSize(s1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s2 != "" {
|
||||
max, err = ToByteSize(s2)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parse single size. eg: "1KB"
|
||||
if byteutil.IsNumChar(expr[0]) {
|
||||
min, err = ToByteSize(expr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if opt.OneAsMax {
|
||||
max = min
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parse with prefix. eg: "<1KB", ">= 1KB", "-1KB", "+1KB"
|
||||
switch expr[0] {
|
||||
case '<', '-':
|
||||
max, err = ToByteSize(strings.Trim(expr[1:], " ="))
|
||||
case '>', '+':
|
||||
min, err = ToByteSize(strings.Trim(expr[1:], " ="))
|
||||
default:
|
||||
// parse keyword. eg: "small", "large"
|
||||
if opt.KeywordFn != nil {
|
||||
min, max, err = opt.KeywordFn(expr)
|
||||
} else {
|
||||
err = ErrInvalidSizeExpr
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SafeByteSize converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes
|
||||
func SafeByteSize(sizeStr string) uint64 {
|
||||
size, _ := ToByteSize(sizeStr)
|
||||
return size
|
||||
}
|
||||
|
||||
// ToByteSize converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes
|
||||
func ToByteSize(sizeStr string) (uint64, error) {
|
||||
sizeStr = strings.TrimSpace(sizeStr)
|
||||
lastPos := len(sizeStr) - 1
|
||||
if lastPos < 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if sizeStr[lastPos] == 'b' || sizeStr[lastPos] == 'B' {
|
||||
// last second char is k,m,g,t
|
||||
lastSec := sizeStr[lastPos-1]
|
||||
if lastSec > 'A' {
|
||||
lastPos--
|
||||
}
|
||||
} else if IsNumChar(sizeStr[lastPos]) { // not unit suffix. eg: 346
|
||||
return strconv.ParseUint(sizeStr, 10, 32)
|
||||
}
|
||||
|
||||
multiplier := float64(1)
|
||||
switch unicode.ToLower(rune(sizeStr[lastPos])) {
|
||||
case 'k':
|
||||
multiplier = 1 << 10
|
||||
case 'm':
|
||||
multiplier = 1 << 20
|
||||
case 'g':
|
||||
multiplier = 1 << 30
|
||||
case 't':
|
||||
multiplier = 1 << 40
|
||||
case 'p':
|
||||
multiplier = 1 << 50
|
||||
default: // b
|
||||
multiplier = 1
|
||||
}
|
||||
|
||||
sizeNum := strings.TrimSpace(sizeStr[:lastPos])
|
||||
size, err := strconv.ParseFloat(sizeNum, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(size * multiplier), nil
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"github.com/gookit/goutil/byteutil"
|
||||
"github.com/gookit/goutil/x/encodes"
|
||||
)
|
||||
|
||||
// some constant string chars
|
||||
const (
|
||||
Numbers = "0123456789"
|
||||
HexChars = "0123456789abcdef" // base16
|
||||
|
||||
AlphaBet = "abcdefghijklmnopqrstuvwxyz"
|
||||
AlphaBet1 = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
||||
|
||||
// AlphaNum chars, can use for base36 encode
|
||||
AlphaNum = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
// AlphaNum2 chars, can use for base62 encode
|
||||
AlphaNum2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
AlphaNum3 = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
||||
)
|
||||
|
||||
// RandomChars generate give length random chars at `a-z`
|
||||
func RandomChars(ln int) string {
|
||||
return buildRandomString(AlphaBet, ln)
|
||||
}
|
||||
|
||||
// RandomCharsV2 generate give length random chars in `0-9a-z`
|
||||
func RandomCharsV2(ln int) string {
|
||||
return buildRandomString(AlphaNum, ln)
|
||||
}
|
||||
|
||||
// RandomCharsV3 generate give length random chars in `0-9a-zA-Z`
|
||||
func RandomCharsV3(ln int) string {
|
||||
return buildRandomString(AlphaNum2, ln)
|
||||
}
|
||||
|
||||
// RandWithTpl generate random string with give template
|
||||
func RandWithTpl(n int, letters string) string {
|
||||
if len(letters) == 0 {
|
||||
letters = AlphaNum2
|
||||
}
|
||||
return buildRandomString(letters, n)
|
||||
}
|
||||
|
||||
// RandomString generate.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // this will give us a 44 byte, base64 encoded output
|
||||
// token, err := RandomString(16) // eg: "I7S4yFZddRMxQoudLZZ-eg"
|
||||
func RandomString(length int) (string, error) {
|
||||
b, err := RandomBytes(length)
|
||||
return encodes.B64URL.EncodeToString(b), err
|
||||
}
|
||||
|
||||
// RandomBytes generate
|
||||
func RandomBytes(length int) ([]byte, error) {
|
||||
return byteutil.Random(length)
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build !windows
|
||||
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var rn = newRand()
|
||||
|
||||
func newRand() *rand.Rand {
|
||||
return rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
// buildRandomString 生成随机字符串
|
||||
func buildRandomString(letters string, length int) string {
|
||||
// rn := newRand()
|
||||
cs := make([]byte, length)
|
||||
|
||||
lettersN := len(letters)
|
||||
for i := 0; i < length; i++ {
|
||||
cs[i] = letters[rn.Intn(lettersN)]
|
||||
}
|
||||
|
||||
return Byte2str(cs)
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//go:build windows
|
||||
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const MaximumCapacity = math.MaxInt>>1 + 1
|
||||
|
||||
var rn = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
// nearestPowerOfTwo 返回一个大于等于cap的最近的2的整数次幂,参考java8的hashmap的tableSizeFor函数
|
||||
// - cap 输入参数
|
||||
//
|
||||
// 返回一个大于等于cap的最近的2的整数次幂
|
||||
func nearestPowerOfTwo(cap int) int {
|
||||
n := cap - 1
|
||||
n |= n >> 1
|
||||
n |= n >> 2
|
||||
n |= n >> 4
|
||||
n |= n >> 8
|
||||
n |= n >> 16
|
||||
if n < 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if n >= MaximumCapacity {
|
||||
return MaximumCapacity
|
||||
}
|
||||
return n + 1
|
||||
}
|
||||
|
||||
// buildRandomString 生成随机字符串
|
||||
// - letters 字符串模板
|
||||
// - length 生成长度
|
||||
//
|
||||
// 返回一个指定长度的随机字符串
|
||||
func buildRandomString(letters string, length int) string {
|
||||
// 仿照strings.Builder
|
||||
// 创建一个长度为 length 的字节切片
|
||||
bytes := make([]byte, length)
|
||||
strLength := len(letters)
|
||||
if strLength <= 0 {
|
||||
return ""
|
||||
}
|
||||
if strLength == 1 {
|
||||
for i := 0; i < length; i++ {
|
||||
bytes[i] = letters[0]
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&bytes))
|
||||
}
|
||||
|
||||
// letters的字符需要使用多少个比特位数才能表示完
|
||||
// letterIdBits := int(math.Ceil(math.Log2(strLength))),下面比上面的代码快
|
||||
letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(strLength))))
|
||||
// 最大的字母id掩码
|
||||
var letterIdMask int64 = 1<<letterIdBits - 1
|
||||
// 可用次数的最大值
|
||||
letterIdMax := 63 / letterIdBits
|
||||
|
||||
// UnixNano: 1607400451937462000
|
||||
// 循环生成随机字符串
|
||||
for i, cache, remain := length-1, rn.Int63(), letterIdMax; i >= 0; {
|
||||
// 检查随机数生成器是否用尽所有随机数
|
||||
if remain == 0 {
|
||||
cache, remain = rn.Int63(), letterIdMax
|
||||
}
|
||||
// 从可用字符的字符串中随机选择一个字符
|
||||
if idx := int(cache & letterIdMask); idx < strLength {
|
||||
bytes[i] = letters[idx]
|
||||
i--
|
||||
}
|
||||
// 右移比特位数,为下次选择字符做准备
|
||||
cache >>= letterIdBits
|
||||
remain--
|
||||
}
|
||||
|
||||
// 仿照strings.Builder用unsafe包返回一个字符串,避免拷贝
|
||||
// 将字节切片转换为字符串并返回
|
||||
return *(*string)(unsafe.Pointer(&bytes))
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
// RuneIsWord char: a-zA-Z
|
||||
func RuneIsWord(c rune) bool {
|
||||
return RuneIsLower(c) || RuneIsUpper(c)
|
||||
}
|
||||
|
||||
// RuneIsLower char
|
||||
func RuneIsLower(c rune) bool {
|
||||
return 'a' <= c && c <= 'z'
|
||||
}
|
||||
|
||||
// RuneIsUpper char
|
||||
func RuneIsUpper(c rune) bool {
|
||||
return 'A' <= c && c <= 'Z'
|
||||
}
|
||||
|
||||
// RunePos alias of the strings.IndexRune
|
||||
func RunePos(s string, ru rune) int { return strings.IndexRune(s, ru) }
|
||||
|
||||
// IsSpaceRune returns true if the given rune is a space, otherwise false.
|
||||
func IsSpaceRune(r rune) bool {
|
||||
return r <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
|
||||
}
|
||||
|
||||
// Utf8Len count rune of the string.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// str := "hi,你好"
|
||||
//
|
||||
// len(str) // 9
|
||||
// strutil.RunesWidth(str) // 7 一个中文字占两个字符
|
||||
// RuneCount(str) = Utf8Len(s) // 5 按字算
|
||||
func Utf8Len(s string) int { return utf8.RuneCountInString(s) }
|
||||
|
||||
// Utf8len count rune of the string.
|
||||
func Utf8len(s string) int { return utf8.RuneCountInString(s) }
|
||||
|
||||
// RuneCount of the string.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// str := "hi,你好"
|
||||
//
|
||||
// len(str) // 9
|
||||
// strutil.RunesWidth(str) // 7 一个中文字占两个字符
|
||||
// RuneCount(str) = utf8.RuneCountInString(s) // 5 按字算
|
||||
func RuneCount(s string) int { return len([]rune(s)) }
|
||||
|
||||
// RuneWidth of the rune.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// RuneWidth('你') // 2
|
||||
// RuneWidth('a') // 1
|
||||
// RuneWidth('\n') // 0
|
||||
func RuneWidth(r rune) int {
|
||||
p := width.LookupRune(r)
|
||||
k := p.Kind()
|
||||
|
||||
// eg: "\n"
|
||||
if k == width.Neutral {
|
||||
return 0
|
||||
}
|
||||
|
||||
if k == width.EastAsianFullwidth || k == width.EastAsianWide || k == width.EastAsianAmbiguous {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// TextWidth utf8 string width. alias of RunesWidth()
|
||||
func TextWidth(s string) int { return Utf8Width(s) }
|
||||
|
||||
// Utf8Width utf8 string width. alias of RunesWidth
|
||||
func Utf8Width(s string) int { return RunesWidth([]rune(s)) }
|
||||
|
||||
// RunesWidth utf8 runes string width.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// str := "hi,你好"
|
||||
//
|
||||
// len(str) // 9
|
||||
// strutil.RunesWidth(str) // 7 一个中文字占两个字符
|
||||
// len([]rune(str)) = utf8.RuneCountInString(s) // 5 按字算
|
||||
func RunesWidth(rs []rune) (w int) {
|
||||
if len(rs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, runeVal := range rs {
|
||||
w += RuneWidth(runeVal)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Truncate alias of the Utf8Truncate()
|
||||
func Truncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) }
|
||||
|
||||
// TextTruncate alias of the Utf8Truncate()
|
||||
func TextTruncate(s string, w int, tail string) string { return Utf8Truncate(s, w, tail) }
|
||||
|
||||
// Utf8Truncate a string with given width.
|
||||
func Utf8Truncate(s string, w int, tail string) string { return utf8Truncate(s, Utf8Width(s), w, tail) }
|
||||
|
||||
// utf8Truncate a string with given width.
|
||||
func utf8Truncate(s string, sw, w int, tail string) string {
|
||||
if sw <= w {
|
||||
return s
|
||||
}
|
||||
|
||||
i := 0
|
||||
r := []rune(s)
|
||||
w -= TextWidth(tail)
|
||||
|
||||
tmpW := 0
|
||||
for ; i < len(r); i++ {
|
||||
cw := RuneWidth(r[i])
|
||||
if tmpW+cw > w {
|
||||
break
|
||||
}
|
||||
tmpW += cw
|
||||
}
|
||||
return string(r[0:i]) + tail
|
||||
}
|
||||
|
||||
// Chunk split string to chunks by size.
|
||||
// func Chunk[T ~string](s T, size int) []T {
|
||||
// }
|
||||
|
||||
// TextSplit alias of the Utf8Split()
|
||||
func TextSplit(s string, w int) []string { return Utf8Split(s, w) }
|
||||
|
||||
// Utf8Split split a string by width.
|
||||
func Utf8Split(s string, w int) []string {
|
||||
sw := Utf8Width(s)
|
||||
if sw <= w {
|
||||
return []string{s}
|
||||
}
|
||||
|
||||
tmpW := 0
|
||||
tmpS := ""
|
||||
|
||||
ss := make([]string, 0, sw/w+1)
|
||||
for _, r := range s {
|
||||
rw := RuneWidth(r)
|
||||
if tmpW+rw == w {
|
||||
tmpS += string(r)
|
||||
ss = append(ss, tmpS)
|
||||
|
||||
tmpW, tmpS = 0, "" // reset
|
||||
continue
|
||||
}
|
||||
|
||||
if tmpW+rw > w {
|
||||
ss = append(ss, tmpS)
|
||||
|
||||
// append to next line.
|
||||
tmpW, tmpS = rw, string(r)
|
||||
continue
|
||||
}
|
||||
|
||||
tmpW += rw
|
||||
tmpS += string(r)
|
||||
}
|
||||
|
||||
if tmpW > 0 {
|
||||
ss = append(ss, tmpS)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
// TextWrap a string by "\n". alias of the WidthWrap()
|
||||
func TextWrap(s string, w int) string { return WidthWrap(s, w) }
|
||||
|
||||
// WidthWrap a string by "\n"
|
||||
//
|
||||
// Example:
|
||||
// s := "hello 你好, world 世界"
|
||||
// s1 := strutil.TextWrap(s, 6) // "hello \n你好, \nworld \n世界"
|
||||
func WidthWrap(s string, w int) string {
|
||||
tmpW := 0
|
||||
out := ""
|
||||
|
||||
for _, r := range s {
|
||||
cw := RuneWidth(r)
|
||||
if r == '\n' {
|
||||
out += string(r)
|
||||
tmpW = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if tmpW+cw > w {
|
||||
out += "\n"
|
||||
tmpW = 0
|
||||
out += string(r)
|
||||
tmpW += cw
|
||||
continue
|
||||
}
|
||||
|
||||
out += string(r)
|
||||
tmpW += cw
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// WordWrap text string and limit width.
|
||||
func WordWrap(s string, w int) string {
|
||||
tmpW := 0
|
||||
out := ""
|
||||
|
||||
for _, sub := range strings.Split(s, " ") {
|
||||
cw := TextWidth(sub)
|
||||
if tmpW+cw > w {
|
||||
if tmpW != 0 {
|
||||
out += "\n"
|
||||
}
|
||||
|
||||
tmpW = 0
|
||||
out += sub
|
||||
tmpW += cw
|
||||
continue
|
||||
}
|
||||
|
||||
out += sub
|
||||
tmpW += cw
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Runes data slice
|
||||
type Runes []rune
|
||||
|
||||
// Padding a rune to want length and with position
|
||||
func (rs Runes) Padding(pad rune, length int, pos PosFlag) []rune {
|
||||
return PadChars(rs, pad, length, pos)
|
||||
}
|
||||
|
||||
// PadLeft a rune to want length
|
||||
func (rs Runes) PadLeft(pad rune, length int) []rune {
|
||||
return rs.Padding(pad, length, PosLeft)
|
||||
}
|
||||
|
||||
// PadRight a rune to want length
|
||||
func (rs Runes) PadRight(pad rune, length int) []rune {
|
||||
return rs.Padding(pad, length, PosRight)
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BeforeFirst get substring before first sep.
|
||||
func BeforeFirst(s, sep string) string {
|
||||
if i := strings.Index(s, sep); i >= 0 {
|
||||
return s[:i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// AfterFirst get substring after first sep.
|
||||
func AfterFirst(s, sep string) string {
|
||||
if i := strings.Index(s, sep); i >= 0 {
|
||||
return s[i+len(sep):]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BeforeLast get substring before last sep.
|
||||
func BeforeLast(s, sep string) string {
|
||||
if i := strings.LastIndex(s, sep); i >= 0 {
|
||||
return s[:i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// AfterLast get substring after last sep.
|
||||
func AfterLast(s, sep string) string {
|
||||
if i := strings.LastIndex(s, sep); i >= 0 {
|
||||
return s[i+len(sep):]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* String split operation
|
||||
*************************************************************/
|
||||
|
||||
// Cut alias of the strings.Cut
|
||||
func Cut(s, sep string) (before string, after string, found bool) {
|
||||
if i := strings.Index(s, sep); i >= 0 {
|
||||
return s[:i], s[i+len(sep):], true
|
||||
}
|
||||
return s, "", false
|
||||
}
|
||||
|
||||
// QuietCut always returns two substring.
|
||||
func QuietCut(s, sep string) (before string, after string) {
|
||||
before, after, _ = Cut(s, sep)
|
||||
return
|
||||
}
|
||||
|
||||
// MustCut always returns two substring.
|
||||
func MustCut(s, sep string) (before string, after string) {
|
||||
var ok bool
|
||||
before, after, ok = Cut(s, sep)
|
||||
if !ok {
|
||||
panic("cannot split input string to two nodes")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TrimCut always returns two substring and trim space for items.
|
||||
func TrimCut(s, sep string) (string, string) {
|
||||
before, after, _ := Cut(s, sep)
|
||||
return strings.TrimSpace(before), strings.TrimSpace(after)
|
||||
}
|
||||
|
||||
// SplitKV split string to key and value.
|
||||
func SplitKV(s, sep string) (string, string) { return TrimCut(s, sep) }
|
||||
|
||||
// SplitValid string to slice. will trim each item and filter empty string node.
|
||||
func SplitValid(s, sep string) (ss []string) { return Split(s, sep) }
|
||||
|
||||
// Split string to slice. will trim each item and filter empty string node.
|
||||
func Split(s, sep string) (ss []string) {
|
||||
if s = strings.TrimSpace(s); s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, val := range strings.Split(s, sep) {
|
||||
if val = strings.TrimSpace(val); val != "" {
|
||||
ss = append(ss, val)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SplitNValid string to slice. will filter empty string node.
|
||||
func SplitNValid(s, sep string, n int) (ss []string) { return SplitN(s, sep, n) }
|
||||
|
||||
// SplitN string to slice. will filter empty string node.
|
||||
func SplitN(s, sep string, n int) (ss []string) {
|
||||
if s = strings.TrimSpace(s); s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
rawList := strings.Split(s, sep)
|
||||
for i, val := range rawList {
|
||||
if val = strings.TrimSpace(val); val != "" {
|
||||
if len(ss) == n-1 {
|
||||
ss = append(ss, strings.TrimSpace(strings.Join(rawList[i:], sep)))
|
||||
break
|
||||
}
|
||||
|
||||
ss = append(ss, val)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SplitTrimmed split string to slice.
|
||||
// will trim space for each node, but not filter empty
|
||||
func SplitTrimmed(s, sep string) (ss []string) {
|
||||
if s = strings.TrimSpace(s); s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, val := range strings.Split(s, sep) {
|
||||
ss = append(ss, strings.TrimSpace(val))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SplitNTrimmed split string to slice.
|
||||
// will trim space for each node, but not filter empty
|
||||
func SplitNTrimmed(s, sep string, n int) (ss []string) {
|
||||
if s = strings.TrimSpace(s); s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, val := range strings.SplitN(s, sep, n) {
|
||||
ss = append(ss, strings.TrimSpace(val))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 根据空白字符(空格,TAB,换行等)分隔字符串
|
||||
var whitespaceRegexp = regexp.MustCompile("\\s+")
|
||||
|
||||
// SplitByWhitespace Separate strings by whitespace characters (space, TAB, newline, etc.)
|
||||
func SplitByWhitespace(s string) []string {
|
||||
return whitespaceRegexp.Split(s, -1)
|
||||
}
|
||||
|
||||
// Substr for a string. NOTE: strLn := len(runes)
|
||||
// if length <= 0, return pos to end.
|
||||
func Substr(s string, pos, length int) string {
|
||||
runes := []rune(s)
|
||||
strLn := len(runes)
|
||||
|
||||
// pos is too large
|
||||
if pos >= strLn {
|
||||
return ""
|
||||
}
|
||||
|
||||
stopIdx := pos + length
|
||||
if length == 0 || stopIdx > strLn {
|
||||
stopIdx = strLn
|
||||
} else if length < 0 {
|
||||
stopIdx = strLn + length
|
||||
}
|
||||
|
||||
return string(runes[pos:stopIdx])
|
||||
}
|
||||
|
||||
// SplitInlineComment for an inline text string. default is strict mode.
|
||||
func SplitInlineComment(val string, strict ...bool) (string, string) {
|
||||
// strict check: must with a space
|
||||
if len(strict) == 0 || strict[0] {
|
||||
if pos := strings.Index(val, " #"); pos > -1 {
|
||||
return strings.TrimRight(val[0:pos], " "), val[pos+1:]
|
||||
}
|
||||
if pos := strings.Index(val, " //"); pos > -1 {
|
||||
return strings.TrimRight(val[0:pos], " "), val[pos+1:]
|
||||
}
|
||||
return val, ""
|
||||
}
|
||||
|
||||
if pos := strings.IndexByte(val, '#'); pos > -1 {
|
||||
return strings.TrimRight(val[0:pos], " "), val[pos:]
|
||||
}
|
||||
if pos := strings.Index(val, "//"); pos > -1 {
|
||||
return strings.TrimRight(val[0:pos], " "), val[pos:]
|
||||
}
|
||||
return val, ""
|
||||
}
|
||||
|
||||
// FirstLine from command output
|
||||
func FirstLine(output string) string {
|
||||
if i := strings.IndexByte(output, '\n'); i >= 0 {
|
||||
return output[0:i]
|
||||
}
|
||||
return output
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Package strutil provide some string,char,byte util functions
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
)
|
||||
|
||||
// OrCond return s1 on cond is True, OR return s2.
|
||||
// Like: cond ? s1 : s2
|
||||
func OrCond(cond bool, s1, s2 string) string {
|
||||
if cond {
|
||||
return s1
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
// BlankOr return default value on val is blank, otherwise return val
|
||||
func BlankOr(val, defVal string) string {
|
||||
val = strings.TrimSpace(val)
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
// ZeroOr return default value on val is zero, otherwise return val. same of OrElse()
|
||||
func ZeroOr[T ~string](val, defVal T) T {
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
// ErrorOr return default value on err is not nil, otherwise return s
|
||||
// func ErrorOr(s string, err error, defVal string) string {
|
||||
// if err != nil {
|
||||
// return defVal
|
||||
// }
|
||||
// return s
|
||||
// }
|
||||
|
||||
// OrElse return default value on s is empty, otherwise return s
|
||||
func OrElse(s, orVal string) string {
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
return orVal
|
||||
}
|
||||
|
||||
// OrElseNilSafe return default value on s is nil, otherwise return s
|
||||
func OrElseNilSafe(s *string, orVal string) string {
|
||||
if s == nil || *s == "" {
|
||||
return orVal
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
// OrHandle return fn(s) on s is not empty.
|
||||
func OrHandle(s string, fn comdef.StringHandleFunc) string {
|
||||
if s != "" {
|
||||
return fn(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Valid return first not empty element.
|
||||
func Valid(ss ...string) string {
|
||||
for _, s := range ss {
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SubstrCount returns the number of times the substr substring occurs in the s string.
|
||||
// Actually, it comes from strings.Count().
|
||||
//
|
||||
// - s The string to search in
|
||||
// - substr The substring to search for
|
||||
// - params[0] The offset where to start counting.
|
||||
// - params[1] The maximum length after the specified offset to search for the substring.
|
||||
func SubstrCount(s, substr string, params ...uint64) (int, error) {
|
||||
larg := len(params)
|
||||
hasArgs := larg != 0
|
||||
if hasArgs && larg > 2 {
|
||||
return 0, errors.New("too many parameters")
|
||||
}
|
||||
if !hasArgs {
|
||||
return strings.Count(s, substr), nil
|
||||
}
|
||||
|
||||
strlen := len(s)
|
||||
offset := 0
|
||||
end := strlen
|
||||
|
||||
if hasArgs {
|
||||
offset = int(params[0])
|
||||
if larg == 2 {
|
||||
length := int(params[1])
|
||||
end = offset + length
|
||||
}
|
||||
if end > strlen {
|
||||
end = strlen
|
||||
}
|
||||
}
|
||||
|
||||
s = string([]rune(s)[offset:end])
|
||||
return strings.Count(s, substr), nil
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
package textutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/fsutil"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
"github.com/gookit/goutil/structs"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// LTemplateOptFn lite template option func
|
||||
type LTemplateOptFn func(opt *LiteTemplateOpt)
|
||||
|
||||
// LiteTemplateOpt template options for LiteTemplate
|
||||
type LiteTemplateOpt struct {
|
||||
// func name alias map. eg: {"up_first": "upFirst"}
|
||||
nameMp structs.Aliases
|
||||
Funcs template.FuncMap
|
||||
|
||||
Left, Right string
|
||||
|
||||
ParseDef bool
|
||||
ParseEnv bool
|
||||
}
|
||||
|
||||
// SetVarFmt custom sets the variable format in template
|
||||
func (o *LiteTemplateOpt) SetVarFmt(varFmt string) {
|
||||
o.Left, o.Right = strutil.TrimCut(varFmt, ",")
|
||||
}
|
||||
|
||||
// LiteTemplate implement a simple text template engine.
|
||||
//
|
||||
// - support parse template vars
|
||||
// - support access multi-level map field. eg: {{ user.name }}
|
||||
// - support parse default value
|
||||
// - support parse env vars
|
||||
// - support custom pipeline func handle. eg: {{ name | upper }} {{ name | def:guest }}
|
||||
//
|
||||
// NOTE: not support control flow, eg: if/else/for/with
|
||||
type LiteTemplate struct {
|
||||
LiteTemplateOpt
|
||||
vr VarReplacer
|
||||
// template func map. refer the text/template
|
||||
//
|
||||
// Func allow return 1 or 2 values, if return 2 values, the second value is error.
|
||||
fxs map[string]*reflects.FuncX
|
||||
}
|
||||
|
||||
// NewLiteTemplate instance
|
||||
func NewLiteTemplate(opFns ...LTemplateOptFn) *LiteTemplate {
|
||||
st := &LiteTemplate{
|
||||
fxs: make(map[string]*reflects.FuncX),
|
||||
// with default options
|
||||
LiteTemplateOpt: LiteTemplateOpt{
|
||||
Left: "{{",
|
||||
Right: "}}",
|
||||
ParseDef: true,
|
||||
ParseEnv: true,
|
||||
},
|
||||
}
|
||||
|
||||
st.vr.RenderFn = st.renderVars
|
||||
for _, fn := range opFns {
|
||||
fn(&st.LiteTemplateOpt)
|
||||
}
|
||||
|
||||
st.Init()
|
||||
return st
|
||||
}
|
||||
|
||||
// Init LiteTemplate
|
||||
func (t *LiteTemplate) Init() {
|
||||
if t.vr.init {
|
||||
return
|
||||
}
|
||||
|
||||
// init var replacer
|
||||
t.vr.init = true
|
||||
t.initReplacer(&t.vr)
|
||||
|
||||
// add built-in funcs
|
||||
t.AddFuncs(builtInFuncs)
|
||||
t.nameMp.AddAliasMap(map[string]string{
|
||||
"up_first": "upFirst",
|
||||
"lc_first": "lcFirst",
|
||||
"def": "default",
|
||||
})
|
||||
|
||||
// add custom funcs
|
||||
if len(t.Funcs) > 0 {
|
||||
t.AddFuncs(t.Funcs)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *LiteTemplate) initReplacer(vr *VarReplacer) {
|
||||
vr.flatSubs = true
|
||||
vr.parseDef = t.ParseDef
|
||||
vr.parseEnv = t.ParseEnv
|
||||
vr.Left, vr.Right = t.Left, t.Right
|
||||
basefn.PanicIf(vr.Right == "", "var format right chars is required")
|
||||
|
||||
vr.lLen, vr.rLen = len(vr.Left), len(vr.Right)
|
||||
rightLast := string(vr.Right[vr.rLen-1]) // 排除匹配,防止匹配到类似 "{} adb ddf {var}"
|
||||
|
||||
// eg: \{(?s:([^\}]+?))\}
|
||||
// (?s:...) - 让 "." 匹配换行
|
||||
// (?s:(.+?)) - 第二个 "?" 非贪婪匹配
|
||||
pattern := regexp.QuoteMeta(vr.Left) + `(?s:([^` + regexp.QuoteMeta(rightLast) + `]+?))` + regexp.QuoteMeta(vr.Right)
|
||||
vr.varReg = regexp.MustCompile(pattern)
|
||||
}
|
||||
|
||||
// AddFuncs add custom template functions
|
||||
func (t *LiteTemplate) AddFuncs(fns map[string]any) {
|
||||
for name, fn := range fns {
|
||||
t.fxs[name] = reflects.NewFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// RenderString render template string with vars
|
||||
func (t *LiteTemplate) RenderString(s string, vars map[string]any) string {
|
||||
return t.vr.Replace(s, vars)
|
||||
}
|
||||
|
||||
// RenderFile render template file with vars
|
||||
func (t *LiteTemplate) RenderFile(filePath string, vars map[string]any) (string, error) {
|
||||
// read file contents
|
||||
s, err := fsutil.ReadStringOrErr(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return t.vr.Replace(s, vars), nil
|
||||
}
|
||||
|
||||
// RenderWrite render template string with vars, and write result to writer
|
||||
func (t *LiteTemplate) RenderWrite(wr io.Writer, s string, vars map[string]any) error {
|
||||
s = t.vr.Replace(s, vars)
|
||||
_, err := io.WriteString(wr, s)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *LiteTemplate) renderVars(s string, varMap map[string]string) string {
|
||||
return t.vr.varReg.ReplaceAllStringFunc(s, func(sub string) string {
|
||||
// var name or pipe expression.
|
||||
name := strings.TrimSpace(sub[t.vr.lLen : len(sub)-t.vr.rLen])
|
||||
name = strings.TrimLeft(name, "$.")
|
||||
|
||||
var defVal string
|
||||
var pipes []string
|
||||
if strings.ContainsRune(name, '|') {
|
||||
pipes = strutil.Split(name, "|")
|
||||
// compatible default value. eg: {{ name | inhere }}
|
||||
if len(pipes) == 2 && !strings.ContainsRune(pipes[1], ':') && !t.isFunc(pipes[1]) {
|
||||
name, defVal = pipes[0], pipes[1]
|
||||
pipes = nil // clear pipes
|
||||
} else { // collect pipe functions
|
||||
name, pipes = pipes[0], pipes[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := varMap[name]; ok {
|
||||
if len(pipes) > 0 {
|
||||
var err error
|
||||
val, err = t.applyPipes(val, pipes)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Render var %q error: %v", name, err)
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// var not found
|
||||
if len(defVal) > 0 {
|
||||
return defVal
|
||||
}
|
||||
|
||||
if t.vr.NotFound != nil {
|
||||
if val, ok := t.vr.NotFound(name); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// check is default func. eg: {{ name | def:guest }}
|
||||
if len(pipes) == 1 && strings.ContainsRune(pipes[0], ':') {
|
||||
fName, argVal := strutil.TrimCut(pipes[0], ":")
|
||||
if t.isDefaultFunc(fName) {
|
||||
return argVal
|
||||
}
|
||||
}
|
||||
|
||||
t.vr.missVars = append(t.vr.missVars, name)
|
||||
return sub
|
||||
})
|
||||
}
|
||||
|
||||
func (t *LiteTemplate) applyPipes(val any, pipes []string) (string, error) {
|
||||
var err error
|
||||
|
||||
// pipe expr: "trim|upper|substr:1,2"
|
||||
// =>
|
||||
// pipes: ["trim", "upper", "substr:1,2"]
|
||||
for _, name := range pipes {
|
||||
args := []any{val}
|
||||
|
||||
// has custom args. eg: "substr:1,2"
|
||||
if strings.ContainsRune(name, ':') {
|
||||
var argStr string
|
||||
name, argStr = strutil.TrimCut(name, ":")
|
||||
|
||||
if otherArgs := parseArgStr(argStr); len(otherArgs) > 0 {
|
||||
args = append(args, otherArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
name = t.nameMp.ResolveAlias(name)
|
||||
|
||||
// call pipe func
|
||||
if fx, ok := t.fxs[name]; ok {
|
||||
val, err = fx.Call2(args...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("template func %q not found", name)
|
||||
}
|
||||
}
|
||||
|
||||
return strutil.ToString(val)
|
||||
}
|
||||
|
||||
func (t *LiteTemplate) isFunc(name string) bool {
|
||||
_, ok := t.fxs[name]
|
||||
if !ok {
|
||||
// check name alias
|
||||
return t.nameMp.HasAlias(name)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (t *LiteTemplate) isDefaultFunc(name string) bool {
|
||||
return name == "default" || name == "def"
|
||||
}
|
||||
|
||||
var stdTpl = NewLiteTemplate()
|
||||
|
||||
// RenderFile render template file with vars
|
||||
func RenderFile(filePath string, vars map[string]any) (string, error) {
|
||||
return stdTpl.RenderFile(filePath, vars)
|
||||
}
|
||||
|
||||
// RenderString render str template string or file.
|
||||
func RenderString(input string, data map[string]any) string {
|
||||
return stdTpl.RenderString(input, data)
|
||||
}
|
||||
|
||||
// RenderWrite render template string with vars, and write result to writer
|
||||
func RenderWrite(wr io.Writer, s string, vars map[string]any) error {
|
||||
return stdTpl.RenderWrite(wr, s, vars)
|
||||
}
|
||||
|
||||
func parseArgStr(argStr string) (ss []any) {
|
||||
if argStr == "" { // no arg
|
||||
return
|
||||
}
|
||||
|
||||
if len(argStr) == 1 { // one char
|
||||
return []any{argStr}
|
||||
}
|
||||
return arrutil.StringsToAnys(strutil.Split(argStr, ","))
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package textutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// type SimpleAnyFunc func(args ...any) any
|
||||
|
||||
// StrVarRenderer implements like shell vars renderer
|
||||
// 简单的实现类似 php, kotlin, shell 插值变量渲染,表达式解析处理。
|
||||
//
|
||||
// - var format: $var_name, ${some_var}, ${top.sub_var}
|
||||
// - func call: ${func($var_name, 'const string')}
|
||||
type StrVarRenderer struct {
|
||||
// global variables
|
||||
vars map[string]any
|
||||
// fallback func for var not exists
|
||||
getter FallbackFn
|
||||
// funcMap map[string]any TODO use any, add reflect value to rfs
|
||||
funcMap map[string]func(...any) any
|
||||
// var func map. refer the text/template TODO
|
||||
//
|
||||
// Func allow return 1 or 2 values, if return 2 values, the second value is error.
|
||||
rfs map[string]*reflects.FuncX
|
||||
}
|
||||
|
||||
// NewStrVarRenderer create a new StrVarRenderer
|
||||
func NewStrVarRenderer() *StrVarRenderer {
|
||||
return &StrVarRenderer{
|
||||
vars: make(map[string]any),
|
||||
// funcMap: make(map[string]any),
|
||||
funcMap: make(map[string]func(...any) any),
|
||||
}
|
||||
}
|
||||
|
||||
// SetVars set variables
|
||||
func (r *StrVarRenderer) SetVars(vars map[string]any) *StrVarRenderer {
|
||||
for k, v := range vars {
|
||||
r.vars[k] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// SetVar set a variable
|
||||
func (r *StrVarRenderer) SetVar(name string, value any) *StrVarRenderer {
|
||||
r.vars[name] = value
|
||||
return r
|
||||
}
|
||||
|
||||
// SetFuncMap set function map
|
||||
func (r *StrVarRenderer) SetFuncMap(funcMap map[string]func(...any) any) *StrVarRenderer {
|
||||
for k, v := range funcMap {
|
||||
r.funcMap[k] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// SetFunc set a function
|
||||
func (r *StrVarRenderer) SetFunc(name string, fn func(...any) any) *StrVarRenderer {
|
||||
r.funcMap[name] = fn
|
||||
return r
|
||||
}
|
||||
|
||||
// SetGetter set variable getter
|
||||
func (r *StrVarRenderer) SetGetter(getter FallbackFn) *StrVarRenderer {
|
||||
r.getter = getter
|
||||
return r
|
||||
}
|
||||
|
||||
var (
|
||||
// 处理 $var_name 格式
|
||||
// - 允许:$1..$N 这样的变量
|
||||
// - 也支持 $@, $* 变量
|
||||
reS = regexp.MustCompile(`\$(\w[a-zA-Z0-9_]*|[@|*])`)
|
||||
// 处理 ${var_name} ${top.sub} 格式
|
||||
reQ = regexp.MustCompile(`\$\{([a-zA-Z][a-zA-Z0-9_.]*)\}`)
|
||||
// 处理 ${func(...)} 格式
|
||||
reFn = regexp.MustCompile(`\$\{([a-zA-Z][a-zA-Z0-9_]*)\(([^}]*)\)\}`)
|
||||
)
|
||||
|
||||
// Render rendering input string with variables
|
||||
func (r *StrVarRenderer) Render(input string, vars map[string]any) string {
|
||||
vars = maputil.Merge1level(r.vars, vars)
|
||||
data := maputil.Map(vars)
|
||||
|
||||
// 处理 $var_name 格式
|
||||
input = r.replaceVars(input, reS, data)
|
||||
|
||||
if strings.Contains(input, "${") {
|
||||
// 处理 ${var.name} 格式
|
||||
input = r.replaceVars(input, reQ, data)
|
||||
// 处理 ${func(...)} 格式
|
||||
input = r.handleFuncCalls(input, reFn, data)
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
func (r *StrVarRenderer) handleFuncCalls(input string, re *regexp.Regexp, vars maputil.Map) string {
|
||||
return re.ReplaceAllStringFunc(input, func(matched string) string {
|
||||
submatch := re.FindStringSubmatch(matched)
|
||||
funcName := submatch[1]
|
||||
|
||||
// 解析参数 并 调用函数
|
||||
if fn, ok := r.funcMap[funcName]; ok {
|
||||
args := r.parseArgs(submatch[2], vars)
|
||||
result := fn(args...)
|
||||
return fmt.Sprint(result)
|
||||
}
|
||||
|
||||
return matched
|
||||
})
|
||||
}
|
||||
|
||||
func (r *StrVarRenderer) parseArgs(argsStr string, data maputil.Map) []any {
|
||||
if argsStr == "" {
|
||||
return []any{}
|
||||
}
|
||||
|
||||
// 简单参数解析,按逗号分割
|
||||
args := strings.Split(argsStr, ",")
|
||||
results := make([]any, len(args))
|
||||
|
||||
for i, arg := range args {
|
||||
arg = strings.TrimSpace(arg)
|
||||
// is var name
|
||||
if arg[0] == '$' {
|
||||
results[i] = data.Get(arg[1:])
|
||||
continue
|
||||
}
|
||||
|
||||
// 常量值:去掉引号
|
||||
last := len(arg) - 1
|
||||
// strconv.Unquote( arg)
|
||||
if (arg[0] == '"' && arg[last] == '"') || (arg[0] == '\'' && arg[last] == '\'') {
|
||||
results[i] = arg[1 : len(arg)-1]
|
||||
} else if arg == "true" || arg == "false" {
|
||||
results[i] = strutil.SafeBool(arg)
|
||||
} else if strutil.IsInt(arg) {
|
||||
results[i] = strutil.SafeInt(arg)
|
||||
} else {
|
||||
results[i] = arg
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *StrVarRenderer) replaceVars(input string, re *regexp.Regexp, data maputil.Map) string {
|
||||
return re.ReplaceAllStringFunc(input, func(matched string) string {
|
||||
var varName string
|
||||
|
||||
// format: ${var_name} 提取变量名
|
||||
if strings.HasPrefix(matched, "${") {
|
||||
varName = matched[2 : len(matched)-1]
|
||||
} else {
|
||||
// format: $var_name
|
||||
varName = matched[1:]
|
||||
}
|
||||
|
||||
// 从 vars map 获取值,支持嵌套变量名
|
||||
if val, ok := data.GetByPath(varName); ok {
|
||||
return fmt.Sprint(val)
|
||||
}
|
||||
|
||||
// fallback: 使用 getter fn 获取值
|
||||
if r.getter != nil {
|
||||
if val, ok := r.getter(varName); ok {
|
||||
return fmt.Sprint(val)
|
||||
}
|
||||
}
|
||||
|
||||
return matched
|
||||
})
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package textutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gookit/goutil"
|
||||
"github.com/gookit/goutil/fsutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
var builtInFuncs = template.FuncMap{
|
||||
// don't escape content
|
||||
"raw": func(s string) string {
|
||||
return s
|
||||
},
|
||||
"trim": strings.TrimSpace,
|
||||
// lower first char
|
||||
"lcFirst": strutil.LowerFirst,
|
||||
// upper first char
|
||||
"upFirst": strutil.UpperFirst,
|
||||
// upper case
|
||||
"upper": strings.ToUpper,
|
||||
// lower case
|
||||
"lower": strings.ToLower,
|
||||
// cut sub-string
|
||||
"substr": strutil.Substr,
|
||||
// default value
|
||||
"default": func(v, defVal any) string {
|
||||
if goutil.IsEmpty(v) {
|
||||
return strutil.SafeString(defVal)
|
||||
}
|
||||
return strutil.SafeString(v)
|
||||
},
|
||||
// join strings
|
||||
"join": func(ss []string, sep string) string {
|
||||
return strings.Join(ss, sep)
|
||||
},
|
||||
}
|
||||
|
||||
// TextRenderOpt render text template options
|
||||
type TextRenderOpt struct {
|
||||
// Output use custom output writer
|
||||
Output io.Writer
|
||||
// Funcs add custom template functions
|
||||
Funcs template.FuncMap
|
||||
}
|
||||
|
||||
// RenderOptFn render option func
|
||||
type RenderOptFn func(opt *TextRenderOpt)
|
||||
|
||||
// NewRenderOpt create a new render options
|
||||
func NewRenderOpt(optFns []RenderOptFn) *TextRenderOpt {
|
||||
opt := &TextRenderOpt{}
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// RenderGoTpl render input text or template file.
|
||||
func RenderGoTpl(input string, data any, optFns ...RenderOptFn) string {
|
||||
opt := NewRenderOpt(optFns)
|
||||
|
||||
t := template.New("text-renderer")
|
||||
t.Funcs(builtInFuncs)
|
||||
if len(opt.Funcs) > 0 {
|
||||
t.Funcs(opt.Funcs)
|
||||
}
|
||||
|
||||
if !strings.Contains(input, "{{") && fsutil.IsFile(input) {
|
||||
template.Must(t.ParseFiles(input))
|
||||
} else {
|
||||
template.Must(t.Parse(input))
|
||||
}
|
||||
|
||||
// use custom output writer
|
||||
if opt.Output != nil {
|
||||
basefn.MustOK(t.Execute(opt.Output, data))
|
||||
return "" // return empty string
|
||||
}
|
||||
|
||||
// use buffer receive rendered content
|
||||
buf := new(bytes.Buffer)
|
||||
basefn.MustOK(t.Execute(buf, data))
|
||||
return buf.String()
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Package textutil provide some extensions text handle util functions.
|
||||
package textutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// ReplaceVars by regex replace given tpl vars.
|
||||
//
|
||||
// If a format is empty, will use {const DefaultVarFormat}
|
||||
func ReplaceVars(text string, vars map[string]any, format string) string {
|
||||
return NewVarReplacer(format).Replace(text, vars)
|
||||
}
|
||||
|
||||
// RenderSMap by regex replacement given tpl vars.
|
||||
//
|
||||
// If a format is empty, will use {const DefaultVarFormat}
|
||||
func RenderSMap(text string, vars map[string]string, format string) string {
|
||||
return NewVarReplacer(format).RenderSimple(text, vars)
|
||||
}
|
||||
|
||||
// IsMatchAll keywords in the give text string.
|
||||
//
|
||||
// TIP: can use ^ for exclude match.
|
||||
func IsMatchAll(s string, keywords []string) bool {
|
||||
return strutil.SimpleMatch(s, keywords)
|
||||
}
|
||||
|
||||
// ParseInlineINI parse config string to string-map. it's like INI format contents.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// eg: "name=val0;shorts=i;required=true;desc=a message"
|
||||
// =>
|
||||
// {name: val0, shorts: i, required: true, desc: a message}
|
||||
func ParseInlineINI(tagVal string, keys ...string) (mp maputil.SMap, err error) {
|
||||
ss := strutil.Split(tagVal, ";")
|
||||
ln := len(ss)
|
||||
if ln == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
mp = make(maputil.SMap, ln)
|
||||
for _, s := range ss {
|
||||
if !strings.ContainsRune(s, '=') {
|
||||
err = fmt.Errorf("parse inline config error: must match `KEY=VAL`")
|
||||
return
|
||||
}
|
||||
|
||||
key, val := strutil.TrimCut(s, "=")
|
||||
if len(keys) > 0 && !arrutil.StringsHas(keys, key) {
|
||||
err = fmt.Errorf("parse inline config error: invalid key name %q", key)
|
||||
return
|
||||
}
|
||||
|
||||
mp[key] = val
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseSimpleINI parse simple multiline config string to a string-map.
|
||||
// Can use to parse simple INI or dotenv file contents.
|
||||
//
|
||||
// NOTE:
|
||||
//
|
||||
// - it's like INI format contents.
|
||||
// - support comments line with: "#", ";", "//"
|
||||
// - support inline comments with: " #" eg: name=tom # a comments
|
||||
// - DON'T support submap parse.
|
||||
func ParseSimpleINI(text string) (mp maputil.SMap, err error) {
|
||||
return comfunc.ParseEnvLines(text, comfunc.ParseEnvLineOption{})
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
package textutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/internal/varexpr"
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// DefaultVarFormat var template
|
||||
const DefaultVarFormat = "{{,}}"
|
||||
|
||||
// FallbackFn type
|
||||
type FallbackFn = func(name string) (val string, ok bool)
|
||||
|
||||
// VarReplacer struct
|
||||
type VarReplacer struct {
|
||||
init bool
|
||||
|
||||
Left, Right string
|
||||
lLen, rLen int
|
||||
|
||||
varReg *regexp.Regexp
|
||||
// flatten sub map in vars. default: true
|
||||
//
|
||||
// eg: {name: {a: 1, b: 2}} => {name.a: 1, name.b: 2}
|
||||
flatSubs bool
|
||||
// do parse env value in var-value and tpl var-name. default: false
|
||||
parseEnv bool
|
||||
// do parse default value. default: false
|
||||
//
|
||||
// eg: {{ name | inhere }}
|
||||
parseDef bool
|
||||
// keepMissVars list.
|
||||
//
|
||||
// default: False - will clear on each replacement
|
||||
keepMissVars bool
|
||||
// missing vars list
|
||||
missVars []string
|
||||
// NotFound hook func. on var-name not found
|
||||
NotFound FallbackFn
|
||||
// RenderFn custom render func
|
||||
RenderFn func(s string, vs map[string]string) string
|
||||
}
|
||||
|
||||
// NewVarReplacer instance. default format is: DefaultVarFormat
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rpl := NewVarReplacer("{{,}}") // access var: {{ var }}, {{ top.sub }}
|
||||
// // or
|
||||
// rpl := NewVarReplacer("$") // access var: $var, $top.sub
|
||||
func NewVarReplacer(format string, opFns ...func(vp *VarReplacer)) *VarReplacer {
|
||||
vp := &VarReplacer{flatSubs: true}
|
||||
for _, fn := range opFns {
|
||||
fn(vp)
|
||||
}
|
||||
return vp.WithFormat(format)
|
||||
}
|
||||
|
||||
// NewFullReplacer instance. will enable parse env and parse default.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rpl := NewFullReplacer("{{,}}")
|
||||
func NewFullReplacer(format string) *VarReplacer {
|
||||
return NewVarReplacer(format, func(vp *VarReplacer) {
|
||||
vp.WithParseEnv().WithParseDefault().KeepMissingVars()
|
||||
})
|
||||
}
|
||||
|
||||
// DisableFlatten on the input vars map
|
||||
func (r *VarReplacer) DisableFlatten() *VarReplacer {
|
||||
r.flatSubs = false
|
||||
return r
|
||||
}
|
||||
|
||||
// KeepMissingVars on the replacement handle
|
||||
func (r *VarReplacer) KeepMissingVars() *VarReplacer {
|
||||
r.keepMissVars = true
|
||||
return r
|
||||
}
|
||||
|
||||
// WithParseDefault value on the input template contents. eg: {{ name | inhere }}
|
||||
func (r *VarReplacer) WithParseDefault() *VarReplacer {
|
||||
r.parseDef = true
|
||||
return r
|
||||
}
|
||||
|
||||
// WithParseEnv on the input vars value
|
||||
func (r *VarReplacer) WithParseEnv() *VarReplacer {
|
||||
r.parseEnv = true
|
||||
return r
|
||||
}
|
||||
|
||||
// OnNotFound var handle func
|
||||
func (r *VarReplacer) OnNotFound(fn FallbackFn) *VarReplacer {
|
||||
r.NotFound = fn
|
||||
return r
|
||||
}
|
||||
|
||||
// WithFormat custom var template
|
||||
func (r *VarReplacer) WithFormat(format string) *VarReplacer {
|
||||
r.Left, r.Right = strutil.QuietCut(strutil.OrElse(format, DefaultVarFormat), ",")
|
||||
r.Init()
|
||||
return r
|
||||
}
|
||||
|
||||
// Init var replacer
|
||||
func (r *VarReplacer) Init() {
|
||||
if !r.init {
|
||||
r.lLen, r.rLen = len(r.Left), len(r.Right)
|
||||
if r.Right != "" {
|
||||
r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `([\w\s\|.-]+)` + regexp.QuoteMeta(r.Right))
|
||||
} else {
|
||||
// no right tag. eg: $name, $user.age
|
||||
r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `(\w[\w-]*(?:\.[\w-]+)*)`)
|
||||
}
|
||||
|
||||
r.init = true
|
||||
}
|
||||
}
|
||||
|
||||
// ParseVars parse the text contents and collect vars
|
||||
func (r *VarReplacer) ParseVars(s string) []string {
|
||||
ss := arrutil.StringsMap(r.varReg.FindAllString(s, -1), func(val string) string {
|
||||
return strings.TrimSpace(val[r.lLen : len(val)-r.rLen])
|
||||
})
|
||||
|
||||
return arrutil.Unique(ss)
|
||||
}
|
||||
|
||||
// Replace any-map vars in the text contents
|
||||
func (r *VarReplacer) Replace(s string, tplVars map[string]any) string {
|
||||
return r.Render(s, tplVars)
|
||||
}
|
||||
|
||||
// Render any-map vars in the text contents
|
||||
func (r *VarReplacer) Render(s string, tplVars map[string]any) string {
|
||||
if !strings.Contains(s, r.Left) {
|
||||
return s
|
||||
}
|
||||
if !r.parseDef && len(tplVars) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
r.Init()
|
||||
|
||||
var varMap map[string]string
|
||||
if r.flatSubs {
|
||||
varMap = make(map[string]string, len(tplVars)*2)
|
||||
maputil.FlatWithFunc(tplVars, func(path string, val reflect.Value) {
|
||||
if val.Kind() == reflect.String {
|
||||
if r.parseEnv {
|
||||
varMap[path] = varexpr.SafeParse(val.String())
|
||||
} else {
|
||||
varMap[path] = val.String()
|
||||
}
|
||||
} else {
|
||||
varMap[path] = strutil.QuietString(val.Interface())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
varMap = maputil.ToStringMap(tplVars)
|
||||
}
|
||||
|
||||
return r.doReplace(s, varMap)
|
||||
}
|
||||
|
||||
// ReplaceSMap string-map vars in the text contents
|
||||
func (r *VarReplacer) ReplaceSMap(s string, varMap map[string]string) string {
|
||||
return r.RenderSimple(s, varMap)
|
||||
}
|
||||
|
||||
// RenderSimple string-map vars in the text contents. alias of ReplaceSMap()
|
||||
func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string {
|
||||
if len(varMap) == 0 || !strings.Contains(s, r.Left) {
|
||||
return s
|
||||
}
|
||||
|
||||
if r.parseEnv {
|
||||
for name, val := range varMap {
|
||||
if strings.Contains(val, "${") {
|
||||
varMap[name] = varexpr.SafeParse(val)
|
||||
} else {
|
||||
varMap[name] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.Init()
|
||||
return r.doReplace(s, varMap)
|
||||
}
|
||||
|
||||
// MissVars list
|
||||
func (r *VarReplacer) MissVars() []string {
|
||||
return r.missVars
|
||||
}
|
||||
|
||||
// ResetMissVars list
|
||||
func (r *VarReplacer) ResetMissVars() {
|
||||
r.missVars = make([]string, 0)
|
||||
}
|
||||
|
||||
// Replace string-map vars in the text contents
|
||||
func (r *VarReplacer) doReplace(s string, varMap map[string]string) string {
|
||||
if !r.keepMissVars {
|
||||
r.missVars = make([]string, 0) // clear on each replacement
|
||||
}
|
||||
|
||||
// use custom render func
|
||||
if r.RenderFn != nil {
|
||||
return r.RenderFn(s, varMap)
|
||||
}
|
||||
|
||||
return r.varReg.ReplaceAllStringFunc(s, func(sub string) string {
|
||||
name := strings.TrimSpace(sub[r.lLen : len(sub)-r.rLen])
|
||||
|
||||
var defVal string
|
||||
if r.parseDef && strings.ContainsRune(name, '|') {
|
||||
name, defVal = strutil.TrimCut(name, "|")
|
||||
}
|
||||
|
||||
if val, ok := varMap[name]; ok {
|
||||
return val
|
||||
}
|
||||
if r.parseEnv && strutil.IsEnvName(name) {
|
||||
if val := os.Getenv(name); val != "" {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// has custom not found handle func
|
||||
if r.NotFound != nil {
|
||||
if val, ok := r.NotFound(name); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
if len(defVal) > 0 {
|
||||
return defVal
|
||||
}
|
||||
r.missVars = append(r.missVars, name)
|
||||
return sub
|
||||
})
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package strutil
|
||||
|
||||
import "strings"
|
||||
|
||||
// Value string
|
||||
type Value string
|
||||
|
||||
// StrVal string. alias of Value
|
||||
type StrVal = Value
|
||||
|
||||
// Set value
|
||||
func (s *Value) Set(val string) error {
|
||||
*s = Value(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEmpty check
|
||||
func (s Value) IsEmpty() bool {
|
||||
return string(s) == ""
|
||||
}
|
||||
|
||||
// IsBlank check
|
||||
func (s Value) IsBlank() bool {
|
||||
return strings.TrimSpace(string(s)) == ""
|
||||
}
|
||||
|
||||
// IsStartWith prefix
|
||||
func (s Value) IsStartWith(sub string) bool {
|
||||
return strings.HasPrefix(string(s), sub)
|
||||
}
|
||||
|
||||
// HasPrefix prefix
|
||||
func (s Value) HasPrefix(sub string) bool {
|
||||
return strings.HasPrefix(string(s), sub)
|
||||
}
|
||||
|
||||
// IsEndWith suffix
|
||||
func (s Value) IsEndWith(sub string) bool {
|
||||
return strings.HasSuffix(string(s), sub)
|
||||
}
|
||||
|
||||
// HasSuffix suffix
|
||||
func (s Value) HasSuffix(sub string) bool {
|
||||
return strings.HasSuffix(string(s), sub)
|
||||
}
|
||||
|
||||
// Bytes string to bytes
|
||||
func (s Value) Bytes() []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
// Val string
|
||||
func (s Value) Val() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Int convert
|
||||
func (s Value) Int() int {
|
||||
return QuietInt(string(s))
|
||||
}
|
||||
|
||||
// Int64 convert
|
||||
func (s Value) Int64() int64 {
|
||||
return QuietInt64(string(s))
|
||||
}
|
||||
|
||||
// Bool convert
|
||||
func (s Value) Bool() bool {
|
||||
return QuietBool(string(s))
|
||||
}
|
||||
|
||||
// Value string
|
||||
func (s Value) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// OrElse string
|
||||
func (s Value) OrElse(or string) string {
|
||||
if s != "" {
|
||||
return string(s)
|
||||
}
|
||||
return or
|
||||
}
|
||||
|
||||
// Split string
|
||||
func (s Value) Split(sep string) []string {
|
||||
return strings.Split(string(s), sep)
|
||||
}
|
||||
|
||||
// SplitN string
|
||||
func (s Value) SplitN(sep string, n int) []string {
|
||||
return strings.SplitN(string(s), sep, n)
|
||||
}
|
||||
|
||||
// WithTrimSpace string and return new
|
||||
func (s Value) WithTrimSpace() Value {
|
||||
return Value(strings.TrimSpace(string(s)))
|
||||
}
|
||||
Reference in New Issue
Block a user