diff --git a/go.mod b/go.mod
index 0a236aa1b3..5560a15f48 100644
--- a/go.mod
+++ b/go.mod
@@ -241,7 +241,7 @@ require (
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
github.com/google/renameio/v2 v2.0.2 // indirect
- github.com/gookit/goutil v0.7.1 // indirect
+ github.com/gookit/goutil v0.7.4 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
diff --git a/go.sum b/go.sum
index 2f1e645d34..bec340ed1d 100644
--- a/go.sum
+++ b/go.sum
@@ -589,8 +589,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/config/v2 v2.2.7 h1:P58/uENzkDp7r7Hp8YSZxOhZ/F5a5Y/AzyhDUkQYa9A=
github.com/gookit/config/v2 v2.2.7/go.mod h1:QST99HmkZXXD/HkZmOm1OXpgdAnc6Rl9syGl+u62Pi8=
-github.com/gookit/goutil v0.7.1 h1:AaFJPN9mrdeYBv8HOybri26EHGCC34WJVT7jUStGJsI=
-github.com/gookit/goutil v0.7.1/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
+github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
+github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/ini/v2 v2.3.2 h1:W6tzOGE6zOLQelH2xhcH8BIBZPtnEpJgQ+J6SsAKBSw=
github.com/gookit/ini/v2 v2.3.2/go.mod h1:StKSqY5niArRwYBS8Z71+iWUt5ow47qt359sS9YQLYY=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
diff --git a/vendor/github.com/gookit/goutil/.gitignore b/vendor/github.com/gookit/goutil/.gitignore
index 6eec81adfe..ebbbfe6c44 100644
--- a/vendor/github.com/gookit/goutil/.gitignore
+++ b/vendor/github.com/gookit/goutil/.gitignore
@@ -18,7 +18,9 @@
*.out
*.cov
.DS_Store
+.xenv.toml
*~
+.claude/
testdata/
vendor/
\ No newline at end of file
diff --git a/vendor/github.com/gookit/goutil/README.md b/vendor/github.com/gookit/goutil/README.md
index 60ae33df5a..0d03f6e20c 100644
--- a/vendor/github.com/gookit/goutil/README.md
+++ b/vendor/github.com/gookit/goutil/README.md
@@ -1,4 +1,4 @@
-# Go Util
+# GoUtil

[](https://github.com/gookit/goutil)
@@ -7,7 +7,7 @@
[](https://coveralls.io/github/gookit/goutil?branch=master)
[](https://pkg.go.dev/github.com/gookit/goutil)
-💪 Useful utils(**800+**) package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more.
+💪 Useful utils(**900+**) package for the Go: int, string, array/slice, map, struct, reflect, error, time, format, CLI, ENV, filesystem, system, testing and more.
> **[中文说明](README.zh-CN.md)**
@@ -52,8 +52,8 @@
- [`encodes`](encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc.
- [`finder`](x/finder) Provides a simple and convenient file/dir lookup function, supports filtering, excluding, matching, ignoring, etc.
- [`netutil`](netutil) Network util functions. eg: Ip, IpV4, IpV6, Mac, Port, Hostname, etc.
-- [textutil](strutil/textutil) Provide some extensions text handle util functions. eg: text replace, etc.
-- [textscan](strutil/textscan) Implemented a parser that quickly scans and analyzes text content. It can be used to parse INI, Properties and other formats
+- [`textutil`](strutil/textutil) Provide some extensions text handle util functions. eg: text replace, etc.
+- [`textscan`](strutil/textscan) Implemented a parser that quickly scans and analyzes text content. It can be used to parse INI, Properties and other formats
- [`cmdr`](sysutil/cmdr) Provide for quick build and run a cmd, batch run multi cmd tasks
- [`clipboard`](x/clipboard) Provide a simple clipboard read and write operations.
- [`process`](sysutil/process) Provide some process handle util functions.
@@ -63,7 +63,7 @@
## Go Doc
Please see [Go doc](https://pkg.go.dev/github.com/gookit/goutil).
-Wiki docs on [DeepWiki - gookit/goutil](https://deepwiki.com/gookit/goutil)
+Wiki docs on [ZRead.ai - gookit/goutil](https://zread.ai/gookit/goutil)
## Install
@@ -109,6 +109,8 @@ dump.Print(somevar, somevar2, ...)
> Package `github.com/gookit/goutil/arrutil`
+Click to see functions 👈
+
```go
// source at arrutil/arrutil.go
func GetRandomOne[T any](arr []T) T
@@ -152,6 +154,7 @@ func IntsToString[T comdef.Integer](ints []T) string
func ToInt64s(arr any) (ret []int64, err error)
func MustToInt64s(arr any) []int64
func SliceToInt64s(arr []any) []int64
+func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V
func AnyToSlice(sl any) (ls []any, err error)
func AnyToStrings(arr any) []string
func MustToStrings(arr any) []string
@@ -171,7 +174,8 @@ func FormatIndent(arr any, indent string) string
func Reverse[T any](ls []T)
func Remove[T comdef.Compared](ls []T, val T) []T
func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T
-func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V
+func Map[T, V any](list []T, mapFn MapFn[T, V]) []V
+func Map1[T, R any](list []T, fn func(t T) R) []R
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V
func Unique[T comdef.NumberOrString](list []T) []T
func IndexOf[T comdef.NumberOrString](val T, list []T) int
@@ -189,6 +193,8 @@ func StringsFilter(ss []string, filter ...comdef.StringMatchFunc) []string
func StringsMap(ss []string, mapFn func(s string) string) []string
func TrimStrings(ss []string, cutSet ...string) []string
```
+
+
#### ArrUtil Usage
**check value**:
@@ -211,10 +217,13 @@ ss, err := arrutil.ToStrings([]int{1, 2}) // ss: []string{"1", "2"}
```
+
### Bytes Utils
> Package `github.com/gookit/goutil/byteutil`
+Click to see functions 👈
+
```go
// source at byteutil/buffer.go
func NewBuffer() *Buffer
@@ -244,18 +253,22 @@ func NewStdEncoder(encFn BytesEncodeFunc, decFn BytesDecodeFunc) *StdEncoder
// source at byteutil/pool.go
func NewChanPool(chSize int, width int, capWidth int) *ChanPool
```
+
+
### Cflag
> Package `github.com/gookit/goutil/cflag`
+`cflag` - Wraps and extends go `flag.FlagSet` to build simple command line applications
+
+
+Click to see functions 👈
+
```go
-// source at cflag/app.go
-func NewApp(fns ...func(app *App)) *App
-func NewCmd(name, desc string, runFunc ...func(c *Cmd) error) *Cmd
// source at cflag/cflag.go
-func SetDebug(open bool)
func New(fns ...func(c *CFlags)) *CFlags
+func NewWith(name, version, desc string, fns ...func(c *CFlags)) *CFlags
func NewEmpty(fns ...func(c *CFlags)) *CFlags
func WithDesc(desc string) func(c *CFlags)
func WithVersion(version string) func(c *CFlags)
@@ -269,6 +282,8 @@ func Value
// source at cflag/optarg.go
func NewArg(name, desc string, required bool) *FlagArg
// source at cflag/util.go
+func SetDebug(open bool)
+func DebugMsg(format string, args ...any)
func IsGoodName(name string) bool
func IsZeroValue(opt *flag.Flag, value string) (bool, bool)
func AddPrefix(name string) string
@@ -280,15 +295,20 @@ func IsFlagHelpErr(err error) bool
func WrapColorForCode(s string) string
func ReplaceShorts(args []string, shortsMap map[string]string) []string
```
+
+
#### `cflag` Usage
`cflag` usage please see [cflag/README.md](cflag/README.md)
+
### CLI Utils
> Package `github.com/gookit/goutil/cliutil`
+Click to see functions 👈
+
```go
// source at cliutil/cliutil.go
func SplitMulti(ss []string, sep string) []string
@@ -325,6 +345,8 @@ func InputIsYes(ans string) bool
func ByteIsYes(ans byte) bool
func ReadPassword(question ...string) string
```
+
+
#### CLI Util Usage
@@ -381,10 +403,13 @@ Build line: ./myapp -a val0 -m "this is message" arg0
> More, please see [./cliutil/README](cliutil/README.md)
+
### Var Dumper
> Package `github.com/gookit/goutil/dump`
+Click to see functions 👈
+
```go
// source at dump/dump.go
func Std() *Dumper
@@ -411,7 +436,10 @@ func WithoutPosition() OptionFunc
func WithoutOutput(out io.Writer) OptionFunc
func WithoutColor() OptionFunc
func WithoutType() OptionFunc
+func WithoutLen() OptionFunc
```
+
+
#### Examples
example code:
@@ -456,10 +484,13 @@ Preview:

+
### ENV/Environment
> Package `github.com/gookit/goutil/envutil`
+Click to see functions 👈
+
```go
// source at envutil/envutil.go
func VarReplace(s string) string
@@ -503,6 +534,8 @@ func UnsetEnvs(keys ...string)
func LoadText(text string)
func LoadString(line string) bool
```
+
+
#### ENV Util Usage
**helper functions:**
@@ -522,12 +555,17 @@ envutil.ParseValue("${ENV_NAME | defValue}")
```
+
### Errorx
> Package `github.com/gookit/goutil/errorx`
-Package errorx provide a enhanced error implements, allow with call stack and wrap another error.
+`errorx` provides an enhanced error reporting implementation that contains call stack information and can wrap the previous level of error.
+> Additional call stack information is included when printing errors, making it easy to log and find problems.
+
+
+Click to see functions 👈
```go
// source at errorx/assert.go
@@ -578,6 +616,8 @@ func Is(err, target error) bool
func To(err error, target any) bool
func As(err error, target any) bool
```
+
+
#### Errorx Usage
@@ -655,16 +695,23 @@ runtime.goexit()
```
+
### File System
> Package `github.com/gookit/goutil/fsutil`
+Package `fsutil` Filesystem util functions: quick check, create, read and write file. eg: file and dir check, operate
+
+
+Click to see functions 👈
+
```go
// source at fsutil/check.go
func PathExists(path string) bool
func IsDir(path string) bool
func FileExists(path string) bool
func IsFile(path string) bool
+func IsSymlink(path string) bool
func IsAbsPath(aPath string) bool
func IsEmptyDir(dirPath string) bool
func IsImageFile(path string) bool
@@ -680,6 +727,10 @@ func FirstExistsDir(paths ...string) string
func FirstExistsFile(paths ...string) string
func MatchPaths(paths []string, matcher PathMatchFunc) []string
func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) string
+func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string
+func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string
+func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn)
+func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int)
func SearchNameUp(dirPath, name string) string
func SearchNameUpx(dirPath, name string) (string, bool)
func WalkDir(dir string, fn fs.WalkDirFunc) error
@@ -713,9 +764,12 @@ func FileExt(fPath string) string
func Extname(fPath string) string
func Suffix(fPath string) string
func Expand(pathStr string) string
+func ExpandHome(pathStr string) string
func ExpandPath(pathStr string) string
func ResolvePath(pathStr string) string
func SplitPath(pathStr string) (dir, name string)
+func UserHomeDir() string
+func HomeDir() string
// source at fsutil/info_nonwin.go
func Realpath(pathStr string) string
// source at fsutil/mime.go
@@ -723,9 +777,11 @@ func DetectMime(path string) string
func MimeType(path string) (mime string)
func ReaderMimeType(r io.Reader) (mime string)
// source at fsutil/operate.go
-func Mkdir(dirPath string, perm os.FileMode) error
-func MkDirs(perm os.FileMode, dirPaths ...string) error
-func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error
+func Mkdir(dirPath string, perm fs.FileMode) error
+func MkdirQuick(dirPath string) error
+func EnsureDir(path string) error
+func MkDirs(perm fs.FileMode, dirPaths ...string) error
+func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error
func MkParentDir(fpath string) error
func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption
func OpenOptOrNew(opt *OpenOption) *OpenOption
@@ -779,7 +835,10 @@ func WriteOSFile(f *os.File, data any) (n int, err error)
func CopyFile(srcPath, dstPath string) error
func MustCopyFile(srcPath, dstPath string)
func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error
+func CreateSymlink(target, linkPath string) error
```
+
+
#### FsUtil Usage
@@ -811,6 +870,7 @@ func main() {
```
+
### JSON Utils
> Package `github.com/gookit/goutil/jsonutil`
@@ -841,10 +901,13 @@ func IsObject(s string) bool
func StripComments(src string) string
```
-### Map
+
+### Maputil
> Package `github.com/gookit/goutil/maputil`
+Click to see functions 👈
+
```go
// source at maputil/check.go
func HasKey(mp, key any) (ok bool)
@@ -890,6 +953,7 @@ func Merge1level(mps ...map[string]any) map[string]any
func DeepMerge(src, dst map[string]any, deep int) map[string]any
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeStrMap(src, dst map[string]string) map[string]string
+func AppendSMap(dst, src map[string]string) map[string]string
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeMultiSMap(mps ...map[string]string) map[string]string
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string
@@ -900,16 +964,23 @@ func MakeByKeys(keys []string, val any) (mp map[string]any)
func SetByPath(mp *map[string]any, path string, val any) error
func SetByKeys(mp *map[string]any, keys []string, val any) (err error)
```
+
+
### Math/Number
> Package `github.com/gookit/goutil/mathutil`
+Package `mathutil` provide math(int, number) util functions. eg: convert, math calc, random
+
+Click to see functions 👈
+
```go
// source at mathutil/calc.go
func Abs[T comdef.Int](val T) T
// source at mathutil/check.go
func IsNumeric(c byte) bool
+func IsInteger(val any) bool
func Compare(first, second any, op string) bool
func CompInt[T comdef.Xint](first, second T, op string) (ok bool)
func CompInt64(first, second int64, op string) bool
@@ -928,11 +999,7 @@ func SwapMaxInt(x, y int) (int, int)
func MaxI64(x, y int64) int64
func SwapMaxI64(x, y int64) (int64, int64)
func MaxFloat(x, y float64) float64
-// source at mathutil/convert.go
-func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T]
-func WithNilAsFail[T any](opt *ConvOption[T])
-func WithHandlePtr[T any](opt *ConvOption[T])
-func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T]
+// source at mathutil/conv2int.go
func Int(in any) (int, error)
func SafeInt(in any) int
func QuietInt(in any) int
@@ -943,8 +1010,6 @@ func IntOr(in any, defVal int) int
func IntOrErr(in any) (int, error)
func ToInt(in any) (int, error)
func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error)
-func StrInt(s string) int
-func StrIntOr(s string, defVal int) int
func Int64(in any) (int64, error)
func SafeInt64(in any) int64
func QuietInt64(in any) int64
@@ -972,6 +1037,19 @@ func Uint64Or(in any, defVal uint64) uint64
func Uint64OrErr(in any) (uint64, error)
func ToUint64(in any) (uint64, error)
func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error)
+func StrInt(s string) int
+func StrIntOr(s string, defVal int) int
+func TryStrInt(s string) (int, error)
+func TryStrInt64(s string) (int64, error)
+func TryStrUint64(s string) (uint64, error)
+// source at mathutil/convert.go
+func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T]
+func WithNilAsFail[T any](opt *ConvOption[T])
+func WithHandlePtr[T any](opt *ConvOption[T])
+func WithStrictMode[T any](opt *ConvOption[T])
+func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T]
+func StrictInt(val any) (int64, bool)
+func StrictUint(val any) (uint64, bool)
func QuietFloat(in any) float64
func SafeFloat(in any) float64
func FloatOrPanic(in any) float64
@@ -995,6 +1073,7 @@ func TryToString(val any, defaultAsErr bool) (string, error)
func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error)
// source at mathutil/format.go
func DataSize(size uint64) string
+func FormatBytes(bytes int) string
func HowLongAgo(sec int64) string
// source at mathutil/mathutil.go
func OrElse[T comdef.Number](val, defVal T) T
@@ -1015,11 +1094,17 @@ func RandInt(min, max int) int
func RandIntWithSeed(min, max int, seed int64) int
func RandomIntWithSeed(min, max int, seed int64) int
```
+
+
### Reflects
> Package `github.com/gookit/goutil/reflects`
+Package `reflects` Provide extends reflection util functions. eg: check, convert, value set, etc.
+
+Click to see functions 👈
+
```go
// source at reflects/check.go
func IsTimeType(t reflect.Type) bool
@@ -1085,11 +1170,17 @@ func SetRValue(rv, val reflect.Value)
func Wrap(rv reflect.Value) Value
func ValueOf(v any) Value
```
+
-### Structs
+
+### Struct Utils
> Package `github.com/gookit/goutil/structs`
+Package `structs` Provide some extends util functions for struct. eg: tag parse, struct init, value set/get
+
+Click to see functions 👈
+
```go
// source at structs/alias.go
func NewAliases(checker func(alias string)) *Aliases
@@ -1140,11 +1231,15 @@ func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc
func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error
func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
```
+
-### Strings
+
+### String Utils
> Package `github.com/gookit/goutil/strutil`
+Click to see functions 👈
+
```go
// source at strutil/bytes.go
func NewBuffer(initSize ...int) *Buffer
@@ -1152,16 +1247,21 @@ func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool
// source at strutil/check.go
func IsNumChar(c byte) bool
func IsInt(s string) bool
+func IsUint(s string) bool
func IsFloat(s string) bool
func IsNumeric(s string) bool
+func IsPositiveNum(s string) bool
func IsAlphabet(char uint8) bool
func IsAlphaNum(c uint8) bool
+func IsUpper(s string) bool
+func IsLower(s string) bool
func StrPos(s, sub string) int
func BytePos(s string, bt byte) int
func IEqual(s1, s2 string) bool
func NoCaseEq(s, t string) bool
func IContains(s, sub string) bool
func ContainsByte(s string, c byte) bool
+func ContainsByteOne(s string, bs []byte) bool
func ContainsOne(s string, subs []string) bool
func HasOneSub(s string, subs []string) bool
func IContainsOne(s string, subs []string) bool
@@ -1186,6 +1286,7 @@ func HasEmpty(ss ...string) bool
func IsAllEmpty(ss ...string) bool
func IsVersion(s string) bool
func IsVarName(s string) bool
+func IsEnvName(s string) bool
func Compare(s1, s2, op string) bool
func VersionCompare(v1, v2, op string) bool
func SimpleMatch(s string, keywords []string) bool
@@ -1197,7 +1298,9 @@ func MatchNodePath(pattern, s string, sep string) bool
// source at strutil/convbase.go
func Base10Conv(src string, to int) string
func BaseConv(src string, from, to int) string
+func BaseConvInt(src uint64, to int) string
func BaseConvByTpl(src string, fromBase, toBase string) string
+func BaseConvIntByTpl(dec uint64, toBase string) string
// source at strutil/convert.go
func Quote(s string) string
func Unquote(s string) string
@@ -1257,7 +1360,6 @@ func ToArray(s string, sep ...string) []string
func Strings(s string, sep ...string) []string
func ToStrings(s string, sep ...string) []string
func ToSlice(s string, sep ...string) []string
-func ToOSArgs(s string) []string
func ToDuration(s string) (time.Duration, error)
// source at strutil/encode.go
func EscapeJS(s string) string
@@ -1299,6 +1401,10 @@ func Camel(s string, sep ...string) string
func CamelCase(s string, sep ...string) string
func Indent(s, prefix string) string
func IndentBytes(b, prefix []byte) []byte
+func Replaces(str string, pairs map[string]string) string
+func ReplaceVars(s string, vars map[string]string) string
+func NewReplacer(pairs map[string]string) *strings.Replacer
+func WrapTag(s, tag string) string
// source at strutil/gensn.go
func MicroTimeID() string
func MicroTimeHexID() string
@@ -1307,7 +1413,8 @@ func MTimeBase36() string
func MTimeBaseID(toBase int) string
func DatetimeNo(prefix string) string
func DateSN(prefix string) string
-func DateSNV2(prefix string, extBase ...int) string
+func DateSNv2(prefix string, extBase ...int) string
+func DateSNv3(prefix string, dateLen int, extBase ...int) string
// source at strutil/hash.go
func Md5(src any) string
func MD5(src any) string
@@ -1335,6 +1442,7 @@ func RepeatRune(char rune, times int) []rune
func RepeatBytes(char byte, times int) []byte
func RepeatChars[T byte | rune](char T, times int) []T
// source at strutil/parse.go
+func NumVersion(s string) string
func MustToTime(s string, layouts ...string) time.Time
func ToTime(s string, layouts ...string) (t time.Time, err error)
func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error)
@@ -1398,31 +1506,17 @@ func OrElse(s, orVal string) string
func OrElseNilSafe(s *string, orVal string) string
func OrHandle(s string, fn comdef.StringHandleFunc) string
func Valid(ss ...string) string
-func Replaces(str string, pairs map[string]string) string
-func NewReplacer(pairs map[string]string) *strings.Replacer
-func WrapTag(s, tag string) string
func SubstrCount(s, substr string, params ...uint64) (int, error)
```
+
-### Syncs
-
-> Package `github.com/gookit/goutil/syncs`
-
-```go
-// source at syncs/chan.go
-func Go(f func() error) error
-// source at syncs/group.go
-func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context)
-func NewErrGroup(limit ...int) *ErrGroup
-// source at syncs/signal.go
-func WaitCloseSignals(onClose func(sig os.Signal), sigCh ...chan os.Signal)
-func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error))
-```
### System Utils
> Package `github.com/gookit/goutil/sysutil`
+Click to see functions 👈
+
```go
// source at sysutil/exec.go
func NewCmd(bin string, args ...string) *cmdr.Cmd
@@ -1473,6 +1567,13 @@ func OpenURL(URL string) error
// source at sysutil/sysutil_nonwin.go
func Kill(pid int, signal syscall.Signal) error
func ProcessExists(pid int) bool
+// source at sysutil/sysutil_unix.go
+func IsWin() bool
+func IsWindows() bool
+func IsMac() bool
+func IsDarwin() bool
+func IsLinux() bool
+func OpenURL(URL string) error
// source at sysutil/user.go
func MustFindUser(uname string) *user.User
func LoginUser() *user.User
@@ -1491,24 +1592,29 @@ func ChangeUserByName(newUname string) error
func ChangeUserUidGid(newUID int, newGid int) error
func ChangeUserUIDGid(newUID int, newGid int) (err error)
```
+
+
### Testing Utils
> Package `github.com/gookit/goutil/testutil`
+Click to see functions 👈
+
```go
// source at testutil/buffer.go
func NewBuffer() *byteutil.Buffer
+func NewSafeBuffer() *SafeBuffer
// source at testutil/envmock.go
func MockEnvValue(key, val string, fn func(nv string))
func MockEnvValues(kvMap map[string]string, fn func())
func MockOsEnvByText(envText string, fn func())
-func MockOsEnv(mp map[string]string, fn func())
func SetOsEnvs(mp map[string]string) string
func RemoveTmpEnvs(tmpKey string)
func ClearOSEnv()
func RevertOSEnv()
func RunOnCleanEnv(runFn func())
+func MockOsEnv(mp map[string]string, fn func())
func MockCleanOsEnv(mp map[string]string, fn func())
// source at testutil/httpmock.go
func NewHTTPRequest(method, path string, data *MD) *http.Request
@@ -1533,12 +1639,17 @@ func RestoreTimeLocal()
func NewTestWriter() *TestWriter
func NewDirEnt(fPath string, isDir ...bool) *fakeobj.DirEntry
```
+
+
### Timex
> Package `github.com/gookit/goutil/timex`
-Provides an enhanced time.Time implementation, and add more commonly used functional methods.
+Provides an enhanced `time.Time` implementation, and add more commonly used functional methods.
+
+Click to see functions 👈
+
```go
// source at timex/check.go
func IsDuration(s string) bool
@@ -1546,12 +1657,14 @@ func InRange(dst, start, end time.Time) bool
// source at timex/conv.go
func Elapsed(start, end time.Time) string
func ElapsedNow(start time.Time) string
+func FormatDuration(d time.Duration) string
func FromNow(t time.Time) string
func FromNowWith(u time.Time, tms []TimeMessage) string
func HowLongAgo(diffSec int64) string
func HowLongAgo2(diffSec int64, tms []TimeMessage) string
func ToTime(s string, layouts ...string) (time.Time, error)
func ToDur(s string) (time.Duration, error)
+func ParseDuration(s string) (time.Duration, error)
func ToDuration(s string) (time.Duration, error)
func TryToTime(s string, bt time.Time) (time.Time, error)
func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error)
@@ -1600,6 +1713,8 @@ func FormatUnix(sec int64, layout ...string) string
func FormatUnixBy(sec int64, layout string) string
func FormatUnixByTpl(sec int64, template ...string) string
```
+
+
#### Timex Usage
**Create timex instance**
@@ -1719,6 +1834,7 @@ date := FormatUnixByTpl(ts, "Y-m-d H:I:S") // Get: 2022-04-20 19:40:34
```
+
## Code Check & Testing
```bash
diff --git a/vendor/github.com/gookit/goutil/README.zh-CN.md b/vendor/github.com/gookit/goutil/README.zh-CN.md
index ee6d10db0e..17d654cd61 100644
--- a/vendor/github.com/gookit/goutil/README.zh-CN.md
+++ b/vendor/github.com/gookit/goutil/README.zh-CN.md
@@ -1,4 +1,4 @@
-# Go Util
+# GoUtil

[](https://github.com/gookit/goutil)
@@ -7,7 +7,7 @@
[](https://coveralls.io/github/gookit/goutil?branch=master)
[](https://pkg.go.dev/github.com/gookit/goutil)
-`goutil` Go 常用功能的扩展工具库(**800+**)。包含:数字,byte, 字符串,slice/数组,Map,结构体,反射,文本,文件,错误,时间日期,测试,特殊处理,格式化,常用信息获取等等。
+`goutil` Go 常用功能的扩展工具库(**900+**)。包含:数字,byte, 字符串,slice/数组,Map,结构体,反射,文本,文件,错误,时间日期,测试,特殊处理,格式化,常用信息获取等等。
> **[EN README](README.md)**
@@ -53,17 +53,17 @@
- [`cmdline`](cliutil/cmdline) 提供 cmdline 解析,args 构建到 cmdline
- [`encodes`](x/encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc.
- [`finder`](x/finder) 提供简单方便的file/dir查找功能,支持过滤、排除、匹配、忽略等。
-- [textscan](strutil/textscan) 实现了一个快速扫描和分析文本内容的解析器. 可用于解析 INI, Properties 等格式内容
-- [textutil](strutil/textutil) 提供一些常用的扩展文本处理功能函数。
-- [cmdr](sysutil/cmdr) 提供快速构建和运行一个cmd,批量运行多个cmd任务
-- [process](sysutil/process) 提供一些进程操作相关的实用功能。
+- [`textscan`](strutil/textscan) 实现了一个快速扫描和分析文本内容的解析器. 可用于解析 INI, Properties 等格式内容
+- [`textutil`](strutil/textutil) 提供一些常用的扩展文本处理功能函数。
+- [`cmdr`](sysutil/cmdr) 提供快速构建和运行一个cmd,批量运行多个cmd任务
+- [`process`](sysutil/process) 提供一些进程操作相关的实用功能。
- [`fmtutil`](x/fmtutil) 格式化数据工具函数 eg:数据size
- [`goinfo`](x/goinfo) 提供一些与Go info, runtime 相关的工具函数。
## GoDoc
- [Godoc for github](https://pkg.go.dev/github.com/gookit/goutil)
-- Wiki docs on [DeepWiki - gookit/goutil](https://deepwiki.com/gookit/goutil)
+- Wiki docs on [ZRead.ai - gookit/goutil](https://zread.ai/gookit/goutil)
## 获取
@@ -109,6 +109,8 @@ dump.Print(somevar, somevar2, ...)
> Package `github.com/gookit/goutil/arrutil`
+Click to see functions 👈
+
```go
// source at arrutil/arrutil.go
func GetRandomOne[T any](arr []T) T
@@ -152,6 +154,7 @@ func IntsToString[T comdef.Integer](ints []T) string
func ToInt64s(arr any) (ret []int64, err error)
func MustToInt64s(arr any) []int64
func SliceToInt64s(arr []any) []int64
+func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V
func AnyToSlice(sl any) (ls []any, err error)
func AnyToStrings(arr any) []string
func MustToStrings(arr any) []string
@@ -171,7 +174,8 @@ func FormatIndent(arr any, indent string) string
func Reverse[T any](ls []T)
func Remove[T comdef.Compared](ls []T, val T) []T
func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T
-func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V
+func Map[T, V any](list []T, mapFn MapFn[T, V]) []V
+func Map1[T, R any](list []T, fn func(t T) R) []R
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V
func Unique[T comdef.NumberOrString](list []T) []T
func IndexOf[T comdef.NumberOrString](val T, list []T) int
@@ -189,6 +193,8 @@ func StringsFilter(ss []string, filter ...comdef.StringMatchFunc) []string
func StringsMap(ss []string, mapFn func(s string) string) []string
func TrimStrings(ss []string, cutSet ...string) []string
```
+
+
#### ArrUtil Usage
**check value**:
@@ -211,10 +217,13 @@ ss, err := arrutil.ToStrings([]int{1, 2}) // ss: []string{"1", "2"}
```
+
### Bytes Utils
> Package `github.com/gookit/goutil/byteutil`
+Click to see functions 👈
+
```go
// source at byteutil/buffer.go
func NewBuffer() *Buffer
@@ -244,18 +253,22 @@ func NewStdEncoder(encFn BytesEncodeFunc, decFn BytesDecodeFunc) *StdEncoder
// source at byteutil/pool.go
func NewChanPool(chSize int, width int, capWidth int) *ChanPool
```
+
+
### Cflag
> Package `github.com/gookit/goutil/cflag`
+`cflag` - Wraps and extends go `flag.FlagSet` to build simple command line applications
+
+
+Click to see functions 👈
+
```go
-// source at cflag/app.go
-func NewApp(fns ...func(app *App)) *App
-func NewCmd(name, desc string, runFunc ...func(c *Cmd) error) *Cmd
// source at cflag/cflag.go
-func SetDebug(open bool)
func New(fns ...func(c *CFlags)) *CFlags
+func NewWith(name, version, desc string, fns ...func(c *CFlags)) *CFlags
func NewEmpty(fns ...func(c *CFlags)) *CFlags
func WithDesc(desc string) func(c *CFlags)
func WithVersion(version string) func(c *CFlags)
@@ -269,6 +282,8 @@ func Value
// source at cflag/optarg.go
func NewArg(name, desc string, required bool) *FlagArg
// source at cflag/util.go
+func SetDebug(open bool)
+func DebugMsg(format string, args ...any)
func IsGoodName(name string) bool
func IsZeroValue(opt *flag.Flag, value string) (bool, bool)
func AddPrefix(name string) string
@@ -280,15 +295,20 @@ func IsFlagHelpErr(err error) bool
func WrapColorForCode(s string) string
func ReplaceShorts(args []string, shortsMap map[string]string) []string
```
+
+
#### `cflag` Usage
-`cflag` 使用说明请看 [cflag/README.zh-CN.md](cflag/README.zh-CN.md)
+`cflag` usage please see [cflag/README.md](cflag/README.md)
+
### CLI Utils
> Package `github.com/gookit/goutil/cliutil`
+Click to see functions 👈
+
```go
// source at cliutil/cliutil.go
func SplitMulti(ss []string, sep string) []string
@@ -325,6 +345,8 @@ func InputIsYes(ans string) bool
func ByteIsYes(ans byte) bool
func ReadPassword(question ...string) string
```
+
+
#### CLI Util Usage
@@ -381,10 +403,13 @@ Build line: ./myapp -a val0 -m "this is message" arg0
> More, please see [./cliutil/README](cliutil/README.md)
+
### Var Dumper
> Package `github.com/gookit/goutil/dump`
+Click to see functions 👈
+
```go
// source at dump/dump.go
func Std() *Dumper
@@ -411,7 +436,10 @@ func WithoutPosition() OptionFunc
func WithoutOutput(out io.Writer) OptionFunc
func WithoutColor() OptionFunc
func WithoutType() OptionFunc
+func WithoutLen() OptionFunc
```
+
+
#### Examples
example code:
@@ -456,10 +484,13 @@ Preview:

+
### ENV/Environment
> Package `github.com/gookit/goutil/envutil`
+Click to see functions 👈
+
```go
// source at envutil/envutil.go
func VarReplace(s string) string
@@ -503,6 +534,8 @@ func UnsetEnvs(keys ...string)
func LoadText(text string)
func LoadString(line string) bool
```
+
+
#### ENV Util Usage
**helper functions:**
@@ -522,15 +555,18 @@ envutil.ParseValue("${ENV_NAME | defValue}")
```
+
### Errorx
> Package `github.com/gookit/goutil/errorx`
-`errorx` 提供了增强的错误报告实现,包含调用堆栈信息并且可以包装上一级错误。
+`errorx` provides an enhanced error reporting implementation that contains call stack information and can wrap the previous level of error.
-> 在打印 error 时会额外附带调用栈信息, 方便记录日志和查找问题。
+> Additional call stack information is included when printing errors, making it easy to log and find problems.
+Click to see functions 👈
+
```go
// source at errorx/assert.go
func IsTrue(result bool, fmtAndArgs ...any) error
@@ -580,12 +616,14 @@ func Is(err, target error) bool
func To(err error, target any) bool
func As(err error, target any) bool
```
+
-#### Errorx 使用示例
-**创建错误带有调用栈信息**
+#### Errorx Usage
-- 使用 `errorx.New` 替代 `errors.New`
+**Create error with call stack info**
+
+- use the `errorx.New` instead `errors.New`
```go
func doSomething() error {
@@ -596,7 +634,7 @@ func doSomething() error {
}
```
-- 使用 `errorx.Newf` 或者 `errorx.Errorf` 替代 `fmt.Errorf`
+- use the `errorx.Newf` or `errorx.Errorf` instead `fmt.Errorf`
```go
func doSomething() error {
@@ -607,9 +645,9 @@ func doSomething() error {
}
```
-**包装上一级错误**
+**Wrap the previous error**
-之前这样使用:
+used like this before:
```go
if err := SomeFunc(); err != nil {
@@ -617,7 +655,7 @@ func doSomething() error {
}
```
-可以替换成:
+can be replaced with:
```go
if err := SomeFunc(); err != nil {
@@ -625,9 +663,9 @@ func doSomething() error {
}
```
-**使用效果示例**
+**Print the errorx.New() error**
-更多关于 `errorx` 的使用请看 [./errorx/README](errorx/README.md)
+Examples for use `errorx` package, more please see [./errorx/README](errorx/README.md)
```go
err := errorx.New("the error message")
@@ -657,16 +695,23 @@ runtime.goexit()
```
+
### File System
> Package `github.com/gookit/goutil/fsutil`
+Package `fsutil` Filesystem util functions: quick check, create, read and write file. eg: file and dir check, operate
+
+
+Click to see functions 👈
+
```go
// source at fsutil/check.go
func PathExists(path string) bool
func IsDir(path string) bool
func FileExists(path string) bool
func IsFile(path string) bool
+func IsSymlink(path string) bool
func IsAbsPath(aPath string) bool
func IsEmptyDir(dirPath string) bool
func IsImageFile(path string) bool
@@ -682,6 +727,10 @@ func FirstExistsDir(paths ...string) string
func FirstExistsFile(paths ...string) string
func MatchPaths(paths []string, matcher PathMatchFunc) []string
func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) string
+func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string
+func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string
+func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn)
+func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int)
func SearchNameUp(dirPath, name string) string
func SearchNameUpx(dirPath, name string) (string, bool)
func WalkDir(dir string, fn fs.WalkDirFunc) error
@@ -715,9 +764,12 @@ func FileExt(fPath string) string
func Extname(fPath string) string
func Suffix(fPath string) string
func Expand(pathStr string) string
+func ExpandHome(pathStr string) string
func ExpandPath(pathStr string) string
func ResolvePath(pathStr string) string
func SplitPath(pathStr string) (dir, name string)
+func UserHomeDir() string
+func HomeDir() string
// source at fsutil/info_nonwin.go
func Realpath(pathStr string) string
// source at fsutil/mime.go
@@ -725,9 +777,11 @@ func DetectMime(path string) string
func MimeType(path string) (mime string)
func ReaderMimeType(r io.Reader) (mime string)
// source at fsutil/operate.go
-func Mkdir(dirPath string, perm os.FileMode) error
-func MkDirs(perm os.FileMode, dirPaths ...string) error
-func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error
+func Mkdir(dirPath string, perm fs.FileMode) error
+func MkdirQuick(dirPath string) error
+func EnsureDir(path string) error
+func MkDirs(perm fs.FileMode, dirPaths ...string) error
+func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error
func MkParentDir(fpath string) error
func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption
func OpenOptOrNew(opt *OpenOption) *OpenOption
@@ -781,7 +835,10 @@ func WriteOSFile(f *os.File, data any) (n int, err error)
func CopyFile(srcPath, dstPath string) error
func MustCopyFile(srcPath, dstPath string)
func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error
+func CreateSymlink(target, linkPath string) error
```
+
+
#### FsUtil Usage
@@ -813,6 +870,7 @@ func main() {
```
+
### JSON Utils
> Package `github.com/gookit/goutil/jsonutil`
@@ -843,10 +901,13 @@ func IsObject(s string) bool
func StripComments(src string) string
```
-### Map
+
+### Maputil
> Package `github.com/gookit/goutil/maputil`
+Click to see functions 👈
+
```go
// source at maputil/check.go
func HasKey(mp, key any) (ok bool)
@@ -892,6 +953,7 @@ func Merge1level(mps ...map[string]any) map[string]any
func DeepMerge(src, dst map[string]any, deep int) map[string]any
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeStrMap(src, dst map[string]string) map[string]string
+func AppendSMap(dst, src map[string]string) map[string]string
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeMultiSMap(mps ...map[string]string) map[string]string
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string
@@ -902,16 +964,23 @@ func MakeByKeys(keys []string, val any) (mp map[string]any)
func SetByPath(mp *map[string]any, path string, val any) error
func SetByKeys(mp *map[string]any, keys []string, val any) (err error)
```
+
+
### Math/Number
> Package `github.com/gookit/goutil/mathutil`
+Package `mathutil` provide math(int, number) util functions. eg: convert, math calc, random
+
+Click to see functions 👈
+
```go
// source at mathutil/calc.go
func Abs[T comdef.Int](val T) T
// source at mathutil/check.go
func IsNumeric(c byte) bool
+func IsInteger(val any) bool
func Compare(first, second any, op string) bool
func CompInt[T comdef.Xint](first, second T, op string) (ok bool)
func CompInt64(first, second int64, op string) bool
@@ -930,11 +999,7 @@ func SwapMaxInt(x, y int) (int, int)
func MaxI64(x, y int64) int64
func SwapMaxI64(x, y int64) (int64, int64)
func MaxFloat(x, y float64) float64
-// source at mathutil/convert.go
-func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T]
-func WithNilAsFail[T any](opt *ConvOption[T])
-func WithHandlePtr[T any](opt *ConvOption[T])
-func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T]
+// source at mathutil/conv2int.go
func Int(in any) (int, error)
func SafeInt(in any) int
func QuietInt(in any) int
@@ -945,8 +1010,6 @@ func IntOr(in any, defVal int) int
func IntOrErr(in any) (int, error)
func ToInt(in any) (int, error)
func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error)
-func StrInt(s string) int
-func StrIntOr(s string, defVal int) int
func Int64(in any) (int64, error)
func SafeInt64(in any) int64
func QuietInt64(in any) int64
@@ -974,6 +1037,19 @@ func Uint64Or(in any, defVal uint64) uint64
func Uint64OrErr(in any) (uint64, error)
func ToUint64(in any) (uint64, error)
func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error)
+func StrInt(s string) int
+func StrIntOr(s string, defVal int) int
+func TryStrInt(s string) (int, error)
+func TryStrInt64(s string) (int64, error)
+func TryStrUint64(s string) (uint64, error)
+// source at mathutil/convert.go
+func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T]
+func WithNilAsFail[T any](opt *ConvOption[T])
+func WithHandlePtr[T any](opt *ConvOption[T])
+func WithStrictMode[T any](opt *ConvOption[T])
+func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T]
+func StrictInt(val any) (int64, bool)
+func StrictUint(val any) (uint64, bool)
func QuietFloat(in any) float64
func SafeFloat(in any) float64
func FloatOrPanic(in any) float64
@@ -997,6 +1073,7 @@ func TryToString(val any, defaultAsErr bool) (string, error)
func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error)
// source at mathutil/format.go
func DataSize(size uint64) string
+func FormatBytes(bytes int) string
func HowLongAgo(sec int64) string
// source at mathutil/mathutil.go
func OrElse[T comdef.Number](val, defVal T) T
@@ -1017,11 +1094,17 @@ func RandInt(min, max int) int
func RandIntWithSeed(min, max int, seed int64) int
func RandomIntWithSeed(min, max int, seed int64) int
```
+
+
### Reflects
> Package `github.com/gookit/goutil/reflects`
+Package `reflects` Provide extends reflection util functions. eg: check, convert, value set, etc.
+
+Click to see functions 👈
+
```go
// source at reflects/check.go
func IsTimeType(t reflect.Type) bool
@@ -1087,11 +1170,17 @@ func SetRValue(rv, val reflect.Value)
func Wrap(rv reflect.Value) Value
func ValueOf(v any) Value
```
+
-### Structs
+
+### Struct Utils
> Package `github.com/gookit/goutil/structs`
+Package `structs` Provide some extends util functions for struct. eg: tag parse, struct init, value set/get
+
+Click to see functions 👈
+
```go
// source at structs/alias.go
func NewAliases(checker func(alias string)) *Aliases
@@ -1142,11 +1231,15 @@ func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc
func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error
func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
```
+
-### Strings
+
+### String Utils
> Package `github.com/gookit/goutil/strutil`
+Click to see functions 👈
+
```go
// source at strutil/bytes.go
func NewBuffer(initSize ...int) *Buffer
@@ -1154,16 +1247,21 @@ func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool
// source at strutil/check.go
func IsNumChar(c byte) bool
func IsInt(s string) bool
+func IsUint(s string) bool
func IsFloat(s string) bool
func IsNumeric(s string) bool
+func IsPositiveNum(s string) bool
func IsAlphabet(char uint8) bool
func IsAlphaNum(c uint8) bool
+func IsUpper(s string) bool
+func IsLower(s string) bool
func StrPos(s, sub string) int
func BytePos(s string, bt byte) int
func IEqual(s1, s2 string) bool
func NoCaseEq(s, t string) bool
func IContains(s, sub string) bool
func ContainsByte(s string, c byte) bool
+func ContainsByteOne(s string, bs []byte) bool
func ContainsOne(s string, subs []string) bool
func HasOneSub(s string, subs []string) bool
func IContainsOne(s string, subs []string) bool
@@ -1188,6 +1286,7 @@ func HasEmpty(ss ...string) bool
func IsAllEmpty(ss ...string) bool
func IsVersion(s string) bool
func IsVarName(s string) bool
+func IsEnvName(s string) bool
func Compare(s1, s2, op string) bool
func VersionCompare(v1, v2, op string) bool
func SimpleMatch(s string, keywords []string) bool
@@ -1199,7 +1298,9 @@ func MatchNodePath(pattern, s string, sep string) bool
// source at strutil/convbase.go
func Base10Conv(src string, to int) string
func BaseConv(src string, from, to int) string
+func BaseConvInt(src uint64, to int) string
func BaseConvByTpl(src string, fromBase, toBase string) string
+func BaseConvIntByTpl(dec uint64, toBase string) string
// source at strutil/convert.go
func Quote(s string) string
func Unquote(s string) string
@@ -1259,7 +1360,6 @@ func ToArray(s string, sep ...string) []string
func Strings(s string, sep ...string) []string
func ToStrings(s string, sep ...string) []string
func ToSlice(s string, sep ...string) []string
-func ToOSArgs(s string) []string
func ToDuration(s string) (time.Duration, error)
// source at strutil/encode.go
func EscapeJS(s string) string
@@ -1301,6 +1401,10 @@ func Camel(s string, sep ...string) string
func CamelCase(s string, sep ...string) string
func Indent(s, prefix string) string
func IndentBytes(b, prefix []byte) []byte
+func Replaces(str string, pairs map[string]string) string
+func ReplaceVars(s string, vars map[string]string) string
+func NewReplacer(pairs map[string]string) *strings.Replacer
+func WrapTag(s, tag string) string
// source at strutil/gensn.go
func MicroTimeID() string
func MicroTimeHexID() string
@@ -1309,7 +1413,8 @@ func MTimeBase36() string
func MTimeBaseID(toBase int) string
func DatetimeNo(prefix string) string
func DateSN(prefix string) string
-func DateSNV2(prefix string, extBase ...int) string
+func DateSNv2(prefix string, extBase ...int) string
+func DateSNv3(prefix string, dateLen int, extBase ...int) string
// source at strutil/hash.go
func Md5(src any) string
func MD5(src any) string
@@ -1337,6 +1442,7 @@ func RepeatRune(char rune, times int) []rune
func RepeatBytes(char byte, times int) []byte
func RepeatChars[T byte | rune](char T, times int) []T
// source at strutil/parse.go
+func NumVersion(s string) string
func MustToTime(s string, layouts ...string) time.Time
func ToTime(s string, layouts ...string) (t time.Time, err error)
func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error)
@@ -1400,31 +1506,17 @@ func OrElse(s, orVal string) string
func OrElseNilSafe(s *string, orVal string) string
func OrHandle(s string, fn comdef.StringHandleFunc) string
func Valid(ss ...string) string
-func Replaces(str string, pairs map[string]string) string
-func NewReplacer(pairs map[string]string) *strings.Replacer
-func WrapTag(s, tag string) string
func SubstrCount(s, substr string, params ...uint64) (int, error)
```
+
-### Syncs
-
-> Package `github.com/gookit/goutil/syncs`
-
-```go
-// source at syncs/chan.go
-func Go(f func() error) error
-// source at syncs/group.go
-func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context)
-func NewErrGroup(limit ...int) *ErrGroup
-// source at syncs/signal.go
-func WaitCloseSignals(onClose func(sig os.Signal), sigCh ...chan os.Signal)
-func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error))
-```
### System Utils
> Package `github.com/gookit/goutil/sysutil`
+Click to see functions 👈
+
```go
// source at sysutil/exec.go
func NewCmd(bin string, args ...string) *cmdr.Cmd
@@ -1475,6 +1567,13 @@ func OpenURL(URL string) error
// source at sysutil/sysutil_nonwin.go
func Kill(pid int, signal syscall.Signal) error
func ProcessExists(pid int) bool
+// source at sysutil/sysutil_unix.go
+func IsWin() bool
+func IsWindows() bool
+func IsMac() bool
+func IsDarwin() bool
+func IsLinux() bool
+func OpenURL(URL string) error
// source at sysutil/user.go
func MustFindUser(uname string) *user.User
func LoginUser() *user.User
@@ -1493,24 +1592,29 @@ func ChangeUserByName(newUname string) error
func ChangeUserUidGid(newUID int, newGid int) error
func ChangeUserUIDGid(newUID int, newGid int) (err error)
```
+
+
### Testing Utils
> Package `github.com/gookit/goutil/testutil`
+Click to see functions 👈
+
```go
// source at testutil/buffer.go
func NewBuffer() *byteutil.Buffer
+func NewSafeBuffer() *SafeBuffer
// source at testutil/envmock.go
func MockEnvValue(key, val string, fn func(nv string))
func MockEnvValues(kvMap map[string]string, fn func())
func MockOsEnvByText(envText string, fn func())
-func MockOsEnv(mp map[string]string, fn func())
func SetOsEnvs(mp map[string]string) string
func RemoveTmpEnvs(tmpKey string)
func ClearOSEnv()
func RevertOSEnv()
func RunOnCleanEnv(runFn func())
+func MockOsEnv(mp map[string]string, fn func())
func MockCleanOsEnv(mp map[string]string, fn func())
// source at testutil/httpmock.go
func NewHTTPRequest(method, path string, data *MD) *http.Request
@@ -1535,12 +1639,17 @@ func RestoreTimeLocal()
func NewTestWriter() *TestWriter
func NewDirEnt(fPath string, isDir ...bool) *fakeobj.DirEntry
```
+
+
### Timex
> Package `github.com/gookit/goutil/timex`
-Provides an enhanced time.Time implementation, and add more commonly used functional methods.
+Provides an enhanced `time.Time` implementation, and add more commonly used functional methods.
+
+Click to see functions 👈
+
```go
// source at timex/check.go
func IsDuration(s string) bool
@@ -1548,12 +1657,14 @@ func InRange(dst, start, end time.Time) bool
// source at timex/conv.go
func Elapsed(start, end time.Time) string
func ElapsedNow(start time.Time) string
+func FormatDuration(d time.Duration) string
func FromNow(t time.Time) string
func FromNowWith(u time.Time, tms []TimeMessage) string
func HowLongAgo(diffSec int64) string
func HowLongAgo2(diffSec int64, tms []TimeMessage) string
func ToTime(s string, layouts ...string) (time.Time, error)
func ToDur(s string) (time.Duration, error)
+func ParseDuration(s string) (time.Duration, error)
func ToDuration(s string) (time.Duration, error)
func TryToTime(s string, bt time.Time) (time.Time, error)
func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error)
@@ -1602,6 +1713,8 @@ func FormatUnix(sec int64, layout ...string) string
func FormatUnixBy(sec int64, layout string) string
func FormatUnixByTpl(sec int64, template ...string) string
```
+
+
#### Timex Usage
**Create timex instance**
@@ -1721,6 +1834,7 @@ date := FormatUnixByTpl(ts, "Y-m-d H:I:S") // Get: 2022-04-20 19:40:34
```
+
## Code Check & Testing
```bash
diff --git a/vendor/github.com/gookit/goutil/arrutil/convert.go b/vendor/github.com/gookit/goutil/arrutil/convert.go
index 9b7fe30f09..a505e14fe1 100644
--- a/vendor/github.com/gookit/goutil/arrutil/convert.go
+++ b/vendor/github.com/gookit/goutil/arrutil/convert.go
@@ -125,6 +125,27 @@ func SliceToInt64s(arr []any) []int64 {
return i64s
}
+// ToMap convert a list to new map.
+//
+// Example:
+// type User struct {
+// Name string
+// Age int
+// }
+// users := []User{{"Tom", 18}, {"Jack", 20}}
+// mp := arrutil.ToMap(users, func(u User) (string, int) {
+// return u.Name, u.Age
+// })
+// // mp = map[string]int{"Tom":18, "Jack":20}
+func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V {
+ mp := make(map[K]V, len(list))
+ for _, item := range list {
+ k, v := mapFn(item)
+ mp[k] = v
+ }
+ return mp
+}
+
/*************************************************************
* convert func for any-slice
*************************************************************/
diff --git a/vendor/github.com/gookit/goutil/arrutil/process.go b/vendor/github.com/gookit/goutil/arrutil/process.go
index dc72c9df65..a4b9d631e3 100644
--- a/vendor/github.com/gookit/goutil/arrutil/process.go
+++ b/vendor/github.com/gookit/goutil/arrutil/process.go
@@ -38,6 +38,9 @@ func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T {
fn = filter[0]
} else {
fn = func(el T) bool {
+ // if el == nil { // Filter nil value
+ // return false
+ // }
return !reflect.ValueOf(el).IsZero()
}
}
@@ -51,24 +54,49 @@ func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T {
return newLs
}
-// MapFn map handle function type.
-type MapFn[T any, V any] func(input T) (target V, find bool)
-
-// Map a list to new list
+// Map a list to new list with map filter function.
//
// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
-func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V {
- flatArr := make([]V, 0, len(list))
+func Map[T, V any](list []T, mapFilter func(input T) (target V, ok bool)) []V {
+ if len(list) == 0 {
+ return nil
+ }
+ flatArr := make([]V, 0, len(list))
for _, obj := range list {
- if target, ok := mapFn(obj); ok {
+ if target, ok := mapFilter(obj); ok {
flatArr = append(flatArr, target)
}
}
return flatArr
}
-// Column alias of Map func
+// Map1 a list to new list with map function.
+//
+// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
+func Map1[T, R any](list []T, mapFn func(t T) R) []R {
+ if len(list) == 0 {
+ return nil
+ }
+
+ ret := make([]R, len(list))
+ for i := range list {
+ ret[i] = mapFn(list[i])
+ }
+ return ret
+}
+
+// Column collect sub elements from list. alias of Map func
+//
+// Example:
+// list := []map[string]any{
+// {"id": 1, "name": "one", "age": 23},
+// {"id": 2, "name": "two", "age": 23},
+// {"id": 3, "name": "three", "age": 23},
+// }
+// names := arrutil.Column(list, func(el map[string]any) string {
+// return el["name"].(string)
+// })
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
return Map(list, mapFn)
}
@@ -113,3 +141,82 @@ func FirstOr[T any](list []T, defVal ...T) T {
var zero T
return zero
}
+
+// Chunk split slice to chunks by size.
+//
+// eg: [1,2,3,4,5,6,7,8,9,10] -> [[1,2,3,4], [5,6,7,8], [9,10]]
+func Chunk[T any](list []T, size int) [][]T {
+ if size <= 0 {
+ return nil
+ }
+
+ ln := len(list)
+ if ln == 0 {
+ return nil
+ }
+
+ chunks := make([][]T, 0, ln/size+1)
+
+ for i := 0; i < ln; i += size {
+ end := i + size
+ if end > ln {
+ end = ln
+ }
+ chunks = append(chunks, list[i:end])
+ }
+
+ return chunks
+}
+
+// ChunkBy split slice to chunks by size, and with custom chunk function.
+//
+// Example:
+// list := []map[string]any{
+// {"id": 1, "name": "one", "age": 23},
+// {"id": 2, "name": "two", "age": 23},
+// {"id": 3, "name": "three", "age": 23},
+// }
+// chunks := arrutil.ChunkBy(list, 2, func(el map[string]any) map[string]any {
+// return map[string]any{
+// "id": el["id"],
+// "name": el["name"],
+// }
+// })
+// Output: [
+// [{"id": 1, "name": "one"}, {"id": 2, "name": "two"}],
+// [{"id": 3, "name": "three"}]
+// ]
+func ChunkBy[T, R any](list []T, size int, mapFn func(el T) R) [][]R {
+ if size <= 0 {
+ return nil
+ }
+
+ ln := len(list)
+ if ln == 0 {
+ return nil
+ }
+
+ // 计算需要的块数量
+ numChunks := ln/size + 1
+ if ln%size == 0 {
+ numChunks = ln / size
+ }
+
+ chunks := make([][]R, 0, numChunks)
+
+ for i := 0; i < ln; i += size {
+ end := i + size
+ if end > ln {
+ end = ln
+ }
+
+ // 创建当前块的切片
+ currentChunk := make([]R, 0, size)
+ for j := i; j < end; j++ {
+ currentChunk = append(currentChunk, mapFn(list[j]))
+ }
+ chunks = append(chunks, currentChunk)
+ }
+
+ return chunks
+}
diff --git a/vendor/github.com/gookit/goutil/byteutil/buffer.go b/vendor/github.com/gookit/goutil/byteutil/buffer.go
index 6a833627cd..f8690cede5 100644
--- a/vendor/github.com/gookit/goutil/byteutil/buffer.go
+++ b/vendor/github.com/gookit/goutil/byteutil/buffer.go
@@ -16,9 +16,7 @@ type Buffer struct {
}
// NewBuffer instance
-func NewBuffer() *Buffer {
- return &Buffer{}
-}
+func NewBuffer() *Buffer { return &Buffer{} }
// PrintByte to buffer, ignore error. alias of WriteByte()
func (b *Buffer) PrintByte(c byte) {
@@ -26,14 +24,10 @@ func (b *Buffer) PrintByte(c byte) {
}
// WriteStr1 quiet write one string to buffer
-func (b *Buffer) WriteStr1(s string) {
- b.writeStringNl(s, false)
-}
+func (b *Buffer) WriteStr1(s string) { b.writeStringNl(s, false) }
// WriteStr1Nl quiet write one string and end with newline
-func (b *Buffer) WriteStr1Nl(s string) {
- b.writeStringNl(s, true)
-}
+func (b *Buffer) WriteStr1Nl(s string) { b.writeStringNl(s, true) }
// writeStringNl quiet write one string and end with newline
func (b *Buffer) writeStringNl(s string, nl bool) {
@@ -93,6 +87,9 @@ func (b *Buffer) writeAnysWithNl(vs []any, nl bool) {
}
}
+// Writef write message to buffer, ignore error. alias of Printf()
+func (b *Buffer) Writef(tpl string, vs ...any) { _, _ = fmt.Fprintf(b, tpl, vs...) }
+
// Printf quick write message to buffer, ignore error.
func (b *Buffer) Printf(tpl string, vs ...any) { _, _ = fmt.Fprintf(b, tpl, vs...) }
@@ -112,16 +109,10 @@ func (b *Buffer) ResetAndGet() string {
}
// Close buffer
-func (b *Buffer) Close() error {
- return b.CloseErr
-}
+func (b *Buffer) Close() error { return b.CloseErr }
// Flush buffer
-func (b *Buffer) Flush() error {
- return b.FlushErr
-}
+func (b *Buffer) Flush() error { return b.FlushErr }
// Sync anf flush buffer
-func (b *Buffer) Sync() error {
- return b.SyncErr
-}
+func (b *Buffer) Sync() error { return b.SyncErr }
diff --git a/vendor/github.com/gookit/goutil/byteutil/byteutil.go b/vendor/github.com/gookit/goutil/byteutil/byteutil.go
index 32ffacf9e9..dfb6899d8c 100644
--- a/vendor/github.com/gookit/goutil/byteutil/byteutil.go
+++ b/vendor/github.com/gookit/goutil/byteutil/byteutil.go
@@ -4,9 +4,9 @@ package byteutil
import (
"bytes"
"crypto/md5"
+ "crypto/rand"
"encoding/hex"
"fmt"
- "math/rand"
"strconv"
"time"
)
diff --git a/vendor/github.com/gookit/goutil/byteutil/check.go b/vendor/github.com/gookit/goutil/byteutil/check.go
index f4d2e5ba60..cab9c7a869 100644
--- a/vendor/github.com/gookit/goutil/byteutil/check.go
+++ b/vendor/github.com/gookit/goutil/byteutil/check.go
@@ -7,4 +7,3 @@ func IsNumChar(c byte) bool { return c >= '0' && c <= '9' }
func IsAlphaChar(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
-
diff --git a/vendor/github.com/gookit/goutil/byteutil/conv.go b/vendor/github.com/gookit/goutil/byteutil/conv.go
index b080bb173a..677e9e80a2 100644
--- a/vendor/github.com/gookit/goutil/byteutil/conv.go
+++ b/vendor/github.com/gookit/goutil/byteutil/conv.go
@@ -107,3 +107,10 @@ func ToBytesWithFunc(v any, usrFn ToBytesFunc) ([]byte, error) {
return usrFn(val)
}
}
+
+// Reverse 反转字节数组 eg: ABCD -> DCBA
+func Reverse(arr []byte) {
+ for i := 0; i < len(arr)/2; i++ {
+ arr[i], arr[len(arr)-1-i] = arr[len(arr)-1-i], arr[i]
+ }
+}
diff --git a/vendor/github.com/gookit/goutil/comdef/consts.go b/vendor/github.com/gookit/goutil/comdef/consts.go
index 5f1cf2b1fd..ddd1addce9 100644
--- a/vendor/github.com/gookit/goutil/comdef/consts.go
+++ b/vendor/github.com/gookit/goutil/comdef/consts.go
@@ -1,6 +1,6 @@
package comdef
-// consts for compare operation
+// constants for compare operation
const (
OpEq = "="
OpNeq = "!="
@@ -10,7 +10,7 @@ const (
OpGte = ">="
)
-// consts quote chars
+// constants quote chars
const (
SingleQuote = '\''
DoubleQuote = '"'
@@ -25,3 +25,15 @@ const (
const NoIdx = -1
// const VarPathReg = `(\w[\w-]*(?:\.[\w-]+)*)`
+
+// Align define align, position: L, C, R, Auto
+type Align uint8
+type Position = Align // Position alias of Align
+
+// constants for align, position: L, C, R, Auto
+const (
+ Left Align = iota
+ Center
+ Right
+ PosAuto
+)
diff --git a/vendor/github.com/gookit/goutil/conv.go b/vendor/github.com/gookit/goutil/conv.go
index f9e8047a0b..20eff91cba 100644
--- a/vendor/github.com/gookit/goutil/conv.go
+++ b/vendor/github.com/gookit/goutil/conv.go
@@ -122,7 +122,7 @@ func ConvOrDefault(val any, kind reflect.Kind, defVal any) any {
//
// Examples:
//
-// val, err := ToKind("123", reflect.Int) // 123
+// val, err := ToKind("123", reflect.Int) // 123
func ToKind(val any, kind reflect.Kind, fbFunc func(val any) (any, error)) (newVal any, err error) {
switch kind {
case reflect.Int:
diff --git a/vendor/github.com/gookit/goutil/envutil/dotenv.go b/vendor/github.com/gookit/goutil/envutil/dotenv.go
new file mode 100644
index 0000000000..c9936240b4
--- /dev/null
+++ b/vendor/github.com/gookit/goutil/envutil/dotenv.go
@@ -0,0 +1,198 @@
+package envutil
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/gookit/goutil/internal/comfunc"
+)
+
+// DefaultEnvFile default file name
+const DefaultEnvFile = ".env"
+
+// Dotenv load and parse dotenv files
+type Dotenv struct {
+ // Files dot env file paths, allow multi files.
+ // - filename support simple glob pattern. eg: ".env.*"
+ //
+ // default: [".env"]
+ Files []string
+ // BaseDir base dir for join files path
+ //
+ // default is workdir
+ BaseDir string
+ // UpperKey change key to upper on set ENV. default: true
+ UpperKey bool
+ // IgnoreNotExist only load exists.
+ //
+ // - default: false - will return error if not exists
+ IgnoreNotExist bool
+ // LoadFirstExist only load first exists env file on Files
+ LoadFirstExist bool
+
+ loadFiles []string
+ loadData map[string]string
+}
+
+// NewDotenv create a new dotenv config
+func NewDotenv() *Dotenv {
+ return &Dotenv{
+ UpperKey: true,
+ Files: []string{DefaultEnvFile},
+ // init fields
+ loadData: make(map[string]string),
+ }
+}
+
+// LoadAndInit load dotenv files and parse to os.Environ
+func (c *Dotenv) LoadAndInit() error {
+ return c.doLoadFiles(c.Files)
+}
+
+// LoadFiles append load dotenv files
+// - filename support simple glob pattern. eg: ".env.*"
+func (c *Dotenv) LoadFiles(files ...string) error {
+ return c.doLoadFiles(files)
+}
+
+// LoadText load dotenv contents and parse to os Env
+func (c *Dotenv) LoadText(contents string) error {
+ return c.parseAndSetEnv(contents)
+}
+
+// do load dotenv files
+func (c *Dotenv) doLoadFiles(files []string) error {
+ var filePath string
+ for _, file := range files {
+ filePath = strings.TrimSpace(file)
+ if c.BaseDir != "" && !filepath.IsAbs(file) {
+ filePath = filepath.Join(c.BaseDir, file)
+ }
+
+ // load and parse to ENV
+ if err := c.loadFile(filePath); err != nil {
+ return err
+ }
+ if c.LoadFirstExist && len(c.loadFiles) > 0 {
+ break
+ }
+ }
+
+ return nil
+}
+
+func (c *Dotenv) loadFile(filePath string) error {
+ // filename support simple glob pattern.
+ if strings.ContainsRune(filePath, '*') {
+ matches, err := filepath.Glob(filePath)
+ if err != nil {
+ return err
+ }
+
+ for _, matchFile := range matches {
+ if err = c.parseFile(matchFile); err != nil {
+ return err
+ }
+ }
+ return err
+ }
+
+ // Load single file
+ return c.parseFile(filePath)
+}
+
+// parseFile load single file and parse to ENV
+func (c *Dotenv) parseFile(filePath string) error {
+ contents, err := os.ReadFile(filePath)
+ if err != nil {
+ // IgnoreNotExist: skip non-existent files
+ if c.IgnoreNotExist && os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+
+ err = c.parseAndSetEnv(string(contents))
+ if err == nil {
+ c.loadFiles = append(c.loadFiles, filePath)
+ }
+ return err
+}
+
+func (c *Dotenv) parseAndSetEnv(contents string) error {
+ // Parse ENV lines
+ envMp, err := comfunc.ParseEnvLines(contents, comfunc.ParseEnvLineOption{
+ SkipOnErrorLine: true,
+ })
+
+ // Set to ENV
+ for key, val := range envMp {
+ key = strings.ToUpper(key)
+ c.loadData[key] = val
+ _ = os.Setenv(key, val)
+ }
+ return err
+}
+
+// UnloadEnv remove loaded dotenv data from os.Environ
+func (c *Dotenv) UnloadEnv() bool {
+ if len(c.loadData) == 0 {
+ return false
+ }
+
+ for key := range c.loadData {
+ _ = os.Unsetenv(key)
+ }
+ return true
+}
+
+// LoadedData get loaded dotenv data map
+func (c *Dotenv) LoadedData() map[string]string {
+ return c.loadData
+}
+
+// LoadedFiles get loaded dotenv files
+func (c *Dotenv) LoadedFiles() []string {
+ return c.loadFiles
+}
+
+// Reset unload all loaded ENV and reset data
+func (c *Dotenv) Reset() {
+ c.UnloadEnv()
+ c.loadFiles = nil
+ c.loadData = make(map[string]string)
+}
+
+//
+// region standard dotenv instance
+//
+
+var stdEnv = NewDotenv()
+
+// StdDotenv get standard dotenv instance
+func StdDotenv() *Dotenv { return stdEnv }
+
+// DotenvLoad load dotenv file and parse to ENV
+func DotenvLoad(fns ...func(cfg *Dotenv)) error {
+ for _, fn := range fns {
+ fn(stdEnv)
+ }
+ return stdEnv.LoadAndInit()
+}
+
+// LoadEnvFiles load dotenv files and parse to ENV
+func LoadEnvFiles(baseDir string, files ...string) error {
+ return DotenvLoad(func(cfg *Dotenv) {
+ cfg.BaseDir = baseDir
+ if len(files) > 0 {
+ cfg.Files = files
+ }
+ })
+}
+
+// LoadedEnvFiles get loaded dotenv files
+func LoadedEnvFiles() []string {
+ return stdEnv.LoadedFiles()
+}
+
diff --git a/vendor/github.com/gookit/goutil/envutil/get.go b/vendor/github.com/gookit/goutil/envutil/get.go
index d3be8a855a..1d8d211c94 100644
--- a/vendor/github.com/gookit/goutil/envutil/get.go
+++ b/vendor/github.com/gookit/goutil/envutil/get.go
@@ -9,6 +9,12 @@ import (
"github.com/gookit/goutil/x/basefn"
)
+// HasEnv check ENV key exists
+func HasEnv(name string) bool {
+ _, ok := os.LookupEnv(name)
+ return ok
+}
+
// Getenv get ENV value by key name, can with default value
func Getenv(name string, def ...string) string {
val := os.Getenv(name)
@@ -68,7 +74,7 @@ func GetMulti(names ...string) map[string]string {
return valMap
}
-// OnExist check ENV value by key name, if exists call fn
+// OnExist check ENV value by key name, will call fn on value exists(not-empty)
func OnExist(name string, fn func(val string)) bool {
if val := os.Getenv(name); val != "" {
fn(val)
diff --git a/vendor/github.com/gookit/goutil/envutil/info.go b/vendor/github.com/gookit/goutil/envutil/info.go
index b18cb44855..9ff250b648 100644
--- a/vendor/github.com/gookit/goutil/envutil/info.go
+++ b/vendor/github.com/gookit/goutil/envutil/info.go
@@ -36,7 +36,6 @@ func IsMSys() bool {
return sysutil.IsMSys()
}
-
// IsTerminal isatty check
//
// Usage:
diff --git a/vendor/github.com/gookit/goutil/envutil/set.go b/vendor/github.com/gookit/goutil/envutil/set.go
index b317155f54..bd7b03147d 100644
--- a/vendor/github.com/gookit/goutil/envutil/set.go
+++ b/vendor/github.com/gookit/goutil/envutil/set.go
@@ -34,7 +34,8 @@ func UnsetEnvs(keys ...string) {
// LoadText parse multiline text to ENV. Can use to load .env file contents.
//
// Usage:
-// envutil.LoadText(fsutil.ReadFile(".env"))
+//
+// envutil.LoadText(fsutil.ReadFile(".env"))
func LoadText(text string) {
envMp := SplitText2map(text)
for key, value := range envMp {
@@ -42,7 +43,7 @@ func LoadText(text string) {
}
}
-// LoadString set line to ENV. e.g.: KEY=VALUE
+// LoadString set line to ENV. e.g.: "KEY=VALUE"
func LoadString(line string) bool {
k, v := comfunc.SplitLineToKv(line, "=")
if len(k) > 0 {
diff --git a/vendor/github.com/gookit/goutil/errorx/errorx.go b/vendor/github.com/gookit/goutil/errorx/errorx.go
index 3595e529c3..b3d00e2c20 100644
--- a/vendor/github.com/gookit/goutil/errorx/errorx.go
+++ b/vendor/github.com/gookit/goutil/errorx/errorx.go
@@ -265,7 +265,6 @@ func WithStack(err error) error {
if err == nil {
return nil
}
-
return &ErrorX{
msg: err.Error(),
// prev: err,
@@ -278,7 +277,6 @@ func Traced(err error) error {
if err == nil {
return nil
}
-
return &ErrorX{
msg: err.Error(),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
@@ -290,7 +288,6 @@ func Stacked(err error) error {
if err == nil {
return nil
}
-
return &ErrorX{
msg: err.Error(),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
diff --git a/vendor/github.com/gookit/goutil/fsutil/check.go b/vendor/github.com/gookit/goutil/fsutil/check.go
index a015d40b02..337a10e808 100644
--- a/vendor/github.com/gookit/goutil/fsutil/check.go
+++ b/vendor/github.com/gookit/goutil/fsutil/check.go
@@ -61,6 +61,8 @@ func FileExists(path string) bool {
}
// IsFile reports whether the named file or directory exists.
+//
+// - NOTE: not support symlink file
func IsFile(path string) bool {
if path == "" || len(path) > 468 {
return false
@@ -72,6 +74,15 @@ func IsFile(path string) bool {
return false
}
+// IsSymlink reports whether the named file is a symlink.
+func IsSymlink(path string) bool {
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return false
+ }
+ return fi.Mode()&os.ModeSymlink != 0
+}
+
// IsAbsPath is abs path.
func IsAbsPath(aPath string) bool {
if len(aPath) > 0 {
diff --git a/vendor/github.com/gookit/goutil/fsutil/find.go b/vendor/github.com/gookit/goutil/fsutil/find.go
index 4dadc43072..5eea9f7808 100644
--- a/vendor/github.com/gookit/goutil/fsutil/find.go
+++ b/vendor/github.com/gookit/goutil/fsutil/find.go
@@ -23,7 +23,7 @@ func FilePathInDirs(fPath string, dirs ...string) string {
}
for _, dirPath := range dirs {
- fPath := JoinSubPaths(dirPath, fPath)
+ fPath = JoinSubPaths(dirPath, fPath)
if FileExists(fPath) {
return fPath
}
@@ -67,6 +67,103 @@ func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) strin
return defaultPath
}
+// FindParentOption options
+type FindParentOption struct {
+ MaxLevel int // default: 10
+ // NeedDir true: find dirs; false(default): find files
+ NeedDir bool
+ OnlyOne bool // only find one, default: true
+ // Collector func
+ Collector func(fullPath string)
+ // MatchFunc custom matcher func. return false to stop find.
+ MatchFunc func(currentDir string) bool
+}
+
+// FindParentOptFn find parent option func
+type FindParentOptFn func(opt *FindParentOption)
+
+// FindAllInParentDirs looks for all match file(default)/dir in the current directory and parent directories
+func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string {
+ var foundPaths []string
+ optFns = append(optFns, func(opt *FindParentOption) {
+ opt.OnlyOne = false
+ })
+
+ FindNameInParentDirs(dirPath, name, func(fullPath string) {
+ foundPaths = append(foundPaths, fullPath)
+ }, optFns...)
+ return foundPaths
+}
+
+// FindOneInParentDirs looks for a file(default)/dir in the current directory and parent directories
+func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string {
+ var foundPath string
+ FindNameInParentDirs(dirPath, name, func(fullPath string) {
+ foundPath = fullPath
+ }, optFns...)
+ return foundPath
+}
+
+// FindNameInParentDirs looks for file(default)/dir in the current directory and parent directories
+func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn) {
+ opts := &FindParentOption{
+ MaxLevel: 10,
+ OnlyOne: true,
+ Collector: collectFn,
+ }
+ for _, fn := range optFns {
+ fn(opts)
+ }
+
+ FindInParentDirs(dirPath, func(currentDir string) bool {
+ filePath := filepath.Join(currentDir, name)
+ if fi, err := os.Stat(filePath); err == nil {
+ found := false
+ if fi.IsDir() {
+ found = opts.NeedDir
+ } else {
+ found = !opts.NeedDir
+ }
+
+ if found {
+ opts.Collector(filePath)
+ return !opts.OnlyOne
+ }
+ }
+ return true
+ }, opts.MaxLevel)
+}
+
+// FindInParentDirs looks for file/dir in the current directory and parent directories
+// - MatchFunc custom matcher func. return false to stop find.
+func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int) {
+ currentLv := 1
+ currentDir := ToAbsPath(dirPath)
+
+ for {
+ // Check if the file exists in the current directory
+ if !matchFunc(currentDir) {
+ return
+ }
+
+ // check find level
+ if maxLevel > 0 && currentLv > maxLevel {
+ break
+ }
+
+ // Get parent directory
+ parentDir := filepath.Dir(currentDir)
+ if parentDir == currentDir {
+ // Reached the root, file not found
+ return
+ }
+
+ // Move to parent directory
+ currentLv++
+ currentDir = parentDir
+ }
+}
+
// SearchNameUp find file/dir name in dirPath or parent dirs,
// return the name of directory path
//
@@ -158,14 +255,10 @@ type (
)
// OnlyFindDir on find
-func OnlyFindDir(_ string, ent fs.DirEntry) bool {
- return ent.IsDir()
-}
+func OnlyFindDir(_ string, ent fs.DirEntry) bool { return ent.IsDir() }
// OnlyFindFile on find
-func OnlyFindFile(_ string, ent fs.DirEntry) bool {
- return !ent.IsDir()
-}
+func OnlyFindFile(_ string, ent fs.DirEntry) bool { return !ent.IsDir() }
// ExcludeNames on find
func ExcludeNames(names ...string) FilterFunc {
@@ -182,9 +275,7 @@ func IncludeSuffix(ss ...string) FilterFunc {
}
// ExcludeDotFile on find
-func ExcludeDotFile(_ string, ent fs.DirEntry) bool {
- return ent.Name()[0] != '.'
-}
+func ExcludeDotFile(_ string, ent fs.DirEntry) bool { return ent.Name()[0] != '.' }
// ExcludeSuffix on find
func ExcludeSuffix(ss ...string) FilterFunc {
@@ -205,7 +296,7 @@ func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool {
// FindInDir code refer the go pkg: path/filepath.glob()
//
-// - TIP: will be not found in sub-dir.
+// - TIP: default will be not found in sub-dir.
//
// filters: return false will skip the file.
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) {
diff --git a/vendor/github.com/gookit/goutil/fsutil/info.go b/vendor/github.com/gookit/goutil/fsutil/info.go
index c843bc792a..94b45cc798 100644
--- a/vendor/github.com/gookit/goutil/fsutil/info.go
+++ b/vendor/github.com/gookit/goutil/fsutil/info.go
@@ -9,12 +9,15 @@ import (
)
// DirPath get dir path from filepath, without a last name.
+// eg: "/foo/bar/baz.js" => "/foo/bar"
func DirPath(fPath string) string { return filepath.Dir(fPath) }
// Dir get dir path from filepath, without a last name.
+// eg: "/foo/bar/baz.js" => "/foo/bar"
func Dir(fPath string) string { return filepath.Dir(fPath) }
-// PathName get file/dir name from a full path
+// PathName get file/dir name from a full path.
+// eg: "/foo/bar/baz.js" => "baz.js"
func PathName(fPath string) string { return filepath.Base(fPath) }
// PathNoExt get path from full path, without ext.
@@ -30,7 +33,9 @@ func PathNoExt(fPath string) string {
// Name get file/dir name from full path.
//
-// eg: path/to/main.go => "main.go"
+// eg:
+// "path/to/main.go" => "main.go"
+// "/foo/bar/baz" => "baz"
func Name(fPath string) string {
if fPath == "" {
return ""
@@ -74,16 +79,15 @@ func Extname(fPath string) string {
func Suffix(fPath string) string { return filepath.Ext(fPath) }
// Expand will parse first `~` to user home dir path.
-func Expand(pathStr string) string {
- return comfunc.ExpandHome(pathStr)
-}
+func Expand(pathStr string) string { return comfunc.ExpandHome(pathStr) }
+
+// ExpandHome will parse first `~` to user home dir path.
+func ExpandHome(pathStr string) string { return comfunc.ExpandHome(pathStr) }
// ExpandPath will parse `~` to user home dir path.
-func ExpandPath(pathStr string) string {
- return comfunc.ExpandHome(pathStr)
-}
+func ExpandPath(pathStr string) string { return comfunc.ExpandHome(pathStr) }
-// ResolvePath will parse `~` and env var in path
+// ResolvePath will parse `~` and ENV var in path
func ResolvePath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
// return comfunc.ParseEnvVar()
@@ -91,6 +95,18 @@ func ResolvePath(pathStr string) string {
}
// SplitPath splits path immediately following the final Separator, separating it into a directory and file name component
-func SplitPath(pathStr string) (dir, name string) {
- return filepath.Split(pathStr)
+func SplitPath(pathStr string) (dir, name string) { return filepath.Split(pathStr) }
+
+// homeDir cache
+var _homeDir string
+
+// UserHomeDir is alias of os.UserHomeDir, but ignore error.(by os.UserHomeDir)
+func UserHomeDir() string {
+ if _homeDir == "" {
+ _homeDir, _ = os.UserHomeDir()
+ }
+ return _homeDir
}
+
+// HomeDir get user home dir path.
+func HomeDir() string { return UserHomeDir() }
diff --git a/vendor/github.com/gookit/goutil/fsutil/operate.go b/vendor/github.com/gookit/goutil/fsutil/operate.go
index 1d68e4c537..59bd4d9858 100644
--- a/vendor/github.com/gookit/goutil/fsutil/operate.go
+++ b/vendor/github.com/gookit/goutil/fsutil/operate.go
@@ -13,12 +13,21 @@ import (
)
// Mkdir alias of os.MkdirAll()
-func Mkdir(dirPath string, perm os.FileMode) error {
- return os.MkdirAll(dirPath, perm)
+func Mkdir(dirPath string, perm fs.FileMode) error { return os.MkdirAll(dirPath, perm) }
+
+// MkdirQuick with default permission 0755.
+func MkdirQuick(dirPath string) error { return EnsureDir(dirPath) }
+
+// EnsureDir creates a directory if it doesn't exist
+func EnsureDir(path string) error {
+ if !DirExist(path) {
+ return os.MkdirAll(path, 0755)
+ }
+ return nil
}
// MkDirs batch makes multi dirs at once
-func MkDirs(perm os.FileMode, dirPaths ...string) error {
+func MkDirs(perm fs.FileMode, dirPaths ...string) error {
for _, dirPath := range dirPaths {
if err := os.MkdirAll(dirPath, perm); err != nil {
return err
@@ -28,7 +37,7 @@ func MkDirs(perm os.FileMode, dirPaths ...string) error {
}
// MkSubDirs batch makes multi sub-dirs at once
-func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error {
+func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error {
for _, dirName := range subDirs {
dirPath := parentDir + "/" + dirName
if err := os.MkdirAll(dirPath, perm); err != nil {
diff --git a/vendor/github.com/gookit/goutil/fsutil/opwrite.go b/vendor/github.com/gookit/goutil/fsutil/opwrite.go
index 305d8ce8b1..0431ca9718 100644
--- a/vendor/github.com/gookit/goutil/fsutil/opwrite.go
+++ b/vendor/github.com/gookit/goutil/fsutil/opwrite.go
@@ -1,6 +1,7 @@
package fsutil
import (
+ "fmt"
"io"
"os"
@@ -185,3 +186,16 @@ func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error {
}
return err
}
+
+// CreateSymlink creates a symbolic link
+func CreateSymlink(target, linkPath string) error {
+ // Check if the link already exists
+ if IsFile(linkPath) {
+ // Remove existing link/file
+ if err := os.Remove(linkPath); err != nil {
+ return fmt.Errorf("failed to remove existing symlink: %w", err)
+ }
+ }
+
+ return os.Symlink(target, linkPath)
+}
diff --git a/vendor/github.com/gookit/goutil/goutil.go b/vendor/github.com/gookit/goutil/goutil.go
index c0e92e6a57..33713b85b6 100644
--- a/vendor/github.com/gookit/goutil/goutil.go
+++ b/vendor/github.com/gookit/goutil/goutil.go
@@ -206,4 +206,3 @@ func FuncName(f any) string {
func PkgName(funcName string) string {
return goinfo.PkgName(funcName)
}
-
diff --git a/vendor/github.com/gookit/goutil/group.go b/vendor/github.com/gookit/goutil/group.go
index c2b2feecf9..78067142ac 100644
--- a/vendor/github.com/gookit/goutil/group.go
+++ b/vendor/github.com/gookit/goutil/group.go
@@ -12,11 +12,15 @@ import (
type ErrGroup = syncs.ErrGroup
// NewCtxErrGroup instance. use for batch run tasks, can with context.
+//
+// Deprecated: use syncs.NewCtxErrGroup instead
func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context) {
return syncs.NewCtxErrGroup(ctx, limit...)
}
// NewErrGroup instance. use for batch run tasks
+//
+// Deprecated: use syncs.NewErrGroup instead
func NewErrGroup(limit ...int) *ErrGroup {
return syncs.NewErrGroup(limit...)
}
@@ -47,7 +51,7 @@ func (p *QuickRun) Add(fns ...RunFn) *QuickRun {
// Run all func
func (p *QuickRun) Run() error {
for i, fn := range p.fns {
- p.ctx.Set("index", i)
+ p.ctx.Set("_index", i)
if err := fn(p.ctx); err != nil {
return err
}
diff --git a/vendor/github.com/gookit/goutil/internal/checkfn/check.go b/vendor/github.com/gookit/goutil/internal/checkfn/check.go
index e85f231543..06b4f3595c 100644
--- a/vendor/github.com/gookit/goutil/internal/checkfn/check.go
+++ b/vendor/github.com/gookit/goutil/internal/checkfn/check.go
@@ -108,23 +108,57 @@ func Contains(data, elem any) (valid, found bool) {
return true, false
}
-// StringsContains check string slice contains string
-func StringsContains(ss []string, s string) bool {
+// StringsContains check string slice contains sub-string
+func StringsContains(ss []string, sub string) bool {
for _, v := range ss {
- if v == s {
+ if v == sub {
return true
}
}
return false
}
-// check is number: int or float
-var numReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`)
+var (
+ // check is number: int or float
+ numReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`)
+ // is positive number: int or float
+ pNumReg = regexp.MustCompile(`^\d*\.?\d+$`)
+)
// IsNumeric returns true if the given string is a numeric, otherwise false.
-func IsNumeric(s string) bool { return numReg.MatchString(s) }
+func IsNumeric(s string) bool {
+ if s == "" {
+ return false
+ }
+ return numReg.MatchString(s)
+}
+
+// IsPositiveNum check input string is positive number
+func IsPositiveNum(s string) bool {
+ if s == "" {
+ return false
+ }
+ if s[0] == '-' {
+ return false
+ }
+ return pNumReg.MatchString(s)
+}
// IsHttpURL check input is http/https url
func IsHttpURL(s string) bool {
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
}
+
+// IndexByteAfter find index of byte after startIndex. return -1 if not found
+//
+// eg:
+//
+// IndexByteAfter("abcabc", 'b', 0) = 1
+// IndexByteAfter("abcabc", 'b', 2) = 4
+func IndexByteAfter(s string, b byte, startIndex int) int {
+ idx := strings.IndexByte(s[startIndex:], b)
+ if idx < 0 {
+ return -1
+ }
+ return idx + startIndex
+}
diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/convert.go b/vendor/github.com/gookit/goutil/internal/comfunc/convert.go
index 3d4f3b278e..9e12be8377 100644
--- a/vendor/github.com/gookit/goutil/internal/comfunc/convert.go
+++ b/vendor/github.com/gookit/goutil/internal/comfunc/convert.go
@@ -43,6 +43,10 @@ func StrToBool(s string) (bool, error) {
}
// FormatWithArgs format message with args
+//
+// - only one element, format to string
+// - first is format: fmt.Sprintf(firstElem, fmtAndArgs[1:]...)
+// - all is args: return fmt.Sprint(fmtAndArgs...)
func FormatWithArgs(fmtAndArgs []any) string {
ln := len(fmtAndArgs)
if ln == 0 {
@@ -50,16 +54,15 @@ func FormatWithArgs(fmtAndArgs []any) string {
}
first := fmtAndArgs[0]
-
if ln == 1 {
- if msgAsStr, ok := first.(string); ok {
- return msgAsStr
+ if str, ok := first.(string); ok {
+ return str
}
return fmt.Sprintf("%+v", first)
}
// is template string.
- if tplStr, ok := first.(string); ok {
+ if tplStr, ok := first.(string); ok && strings.IndexByte(tplStr, '%') >= 0 {
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
}
return fmt.Sprint(fmtAndArgs...)
@@ -86,11 +89,6 @@ var StrBySprintFn = func(v any) (string, error) {
return fmt.Sprint(v), nil
}
-// WithHandlePtr set ConvOption.HandlePtr option
-func WithHandlePtr(opt *ConvOption) {
- opt.HandlePtr = true
-}
-
// WithUserConvFn set ConvOption.UserConvFn option
func WithUserConvFn(fn comdef.ToStringFunc) ConvOptionFn {
return func(opt *ConvOption) {
@@ -116,11 +114,6 @@ func (opt *ConvOption) WithOption(optFns ...ConvOptionFn) {
// ToStringWith try to convert value to string. can with some option func, more see ConvOption.
func ToStringWith(in any, optFns ...ConvOptionFn) (str string, err error) {
- opt := NewConvOption(optFns...)
- if !opt.NilAsFail && in == nil {
- return "", nil
- }
-
switch value := in.(type) {
case int:
str = strconv.Itoa(value)
@@ -161,6 +154,18 @@ func ToStringWith(in any, optFns ...ConvOptionFn) (str string, err error) {
case error:
str = value.Error()
default:
+ if len(optFns) == 0 && in == nil {
+ return "", nil
+ }
+
+ opt := NewConvOption(optFns...)
+ if in == nil {
+ if opt.NilAsFail {
+ err = comdef.ErrConvType
+ }
+ return
+ }
+
if opt.HandlePtr {
if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
rv = rv.Elem()
diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/str_func.go b/vendor/github.com/gookit/goutil/internal/comfunc/str_func.go
index 2403280802..a40db29030 100644
--- a/vendor/github.com/gookit/goutil/internal/comfunc/str_func.go
+++ b/vendor/github.com/gookit/goutil/internal/comfunc/str_func.go
@@ -24,7 +24,7 @@ type ParseEnvLineOption struct {
//
// - It's like INI/ENV format contents.
// - Support comments line starts with: "#", ";", "//"
-// - Support inline comments split with: " #" eg: name=tom # a comments
+// - Support inline comments split with: " #" eg: "name=tom # a comments"
// - DON'T support submap parse.
func ParseEnvLines(text string, opt ParseEnvLineOption) (mp map[string]string, err error) {
lines := strings.Split(text, "\n")
@@ -45,51 +45,91 @@ func ParseEnvLines(text string, opt ParseEnvLineOption) (mp map[string]string, e
continue
}
+ key, val := splitLineByChar(line, '=', !opt.NotInlineComments)
// invalid line
- if strings.IndexByte(line, '=') < 1 {
+ if key == "" {
if opt.SkipOnErrorLine {
continue
}
-
strMap = nil
err = fmt.Errorf("invalid line contents: must match `KEY=VAL`(line: %s)", line)
return
}
- key, value := SplitLineToKv(line, "=")
-
- // check and remove inline comments
- if !opt.NotInlineComments {
- if pos := strings.Index(value, " #"); pos > 0 {
- value = strings.TrimRight(value[0:pos], " \t")
- }
- }
-
- strMap[key] = value
+ strMap[key] = val
}
return strMap, nil
}
-// SplitLineToKv parse string line to k-v. eg:
+// SplitLineToKv parse string line to k-v, not support comments.
+//
+// Example:
//
// 'DEBUG=true' => ['DEBUG', 'true']
//
// NOTE: line must contain '=', allow: 'ENV_KEY='
func SplitLineToKv(line, sep string) (string, string) {
- nodes := strings.SplitN(line, sep, 2)
- envKey := strings.TrimSpace(nodes[0])
-
- // key cannot be empty
- if envKey == "" {
- return "", ""
- }
-
- if len(nodes) < 2 {
- if strings.Contains(line, sep) {
- return envKey, ""
- }
- return "", ""
- }
- return envKey, strings.TrimSpace(nodes[1])
+ return SplitKvBySep(line, sep, false)
+}
+
+// SplitKvBySep parse string line to k-v, support parse comments.
+// - rmInlineComments: check and remove inline comments by ' #'
+func SplitKvBySep(line, sep string, rmInlineComments bool) (key, val string) {
+ sepPos := strings.Index(line, sep)
+ if sepPos < 0 {
+ return
+ }
+
+ return splitKvBySepPos(line, sepPos, len(sep), rmInlineComments)
+}
+
+func splitLineByChar(line string, sep byte, rmInlineComments bool) (key, val string) {
+ sepPos := strings.IndexByte(line, sep)
+ if sepPos < 0 {
+ return
+ }
+
+ return splitKvBySepPos(line, sepPos, 1, rmInlineComments)
+}
+
+func splitKvBySepPos(line string, sepPos, sepLen int, rmInlineComments bool) (key, val string) {
+ // key cannot be empty
+ key = strings.TrimSpace(line[0:sepPos])
+ if key == "" {
+ return "", ""
+ }
+ val = strings.TrimSpace(line[sepPos+sepLen:])
+
+ // check quotes if present
+ if vln := len(val); vln >= 2 {
+ // remove quotes
+ if (val[0] == '"' && val[vln-1] == '"') || (val[0] == '\'' && val[vln-1] == '\'') {
+ val = val[1 : vln-1]
+ return
+ }
+
+ if !rmInlineComments {
+ return
+ }
+
+ // value is empty, only inline comments
+ if val[0] == '#' {
+ val = ""
+ return
+ }
+
+ // remove inline comments
+ if pos := strings.Index(val, " #"); pos > 0 {
+ val = strings.TrimRight(val[0:pos], " \t")
+ vln = len(val)
+ // remove quotes
+ if (val[0] == '"' && val[vln-1] == '"') || (val[0] == '\'' && val[vln-1] == '\'') {
+ val = val[1 : vln-1]
+ return
+ }
+ }
+ }
+
+ return
}
diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go b/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go
index 4f43d3ad13..d3f6224be2 100644
--- a/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go
+++ b/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go
@@ -87,19 +87,24 @@ func CurrentShell(onlyName bool, fallbackShell ...string) (binPath string) {
// 检查父进程名称
parentProcess := os.Getenv("GOPROCESS")
if parentProcess != "" {
- return parentProcess
- }
-
- binPath = os.Getenv("SHELL") // 适用于 Unix-like 系统
- if len(binPath) == 0 {
- // TODO check on Windows
- binPath, err = ShellExec("echo $SHELL")
- if err != nil {
- return fbShell
+ binPath = parentProcess
+ } else {
+ binPath = os.Getenv("SHELL") // 适用于 Unix-like 系统
+ if len(binPath) == 0 {
+ // TODO check on Windows git bash
+ binPath, err = ShellExec("echo $SHELL")
+ if err != nil {
+ binPath = fbShell
+ }
}
+ binPath = strings.TrimSpace(binPath)
+ }
+
+ // fix: 去除 .exe 后缀
+ if pos := strings.IndexByte(binPath, '.'); pos > 0 {
+ binPath = binPath[:pos]
}
- binPath = strings.TrimSpace(binPath)
// cache result
curShellCache = binPath
} else {
diff --git a/vendor/github.com/gookit/goutil/jsonutil/encoding.go b/vendor/github.com/gookit/goutil/jsonutil/encoding.go
index b921a42152..791033f753 100644
--- a/vendor/github.com/gookit/goutil/jsonutil/encoding.go
+++ b/vendor/github.com/gookit/goutil/jsonutil/encoding.go
@@ -72,4 +72,4 @@ func DecodeFile(file string, ptr any) error {
}
return json.Unmarshal(bs, ptr)
-}
\ No newline at end of file
+}
diff --git a/vendor/github.com/gookit/goutil/maputil/alias.go b/vendor/github.com/gookit/goutil/maputil/alias.go
index b810959117..9f739d22a7 100644
--- a/vendor/github.com/gookit/goutil/maputil/alias.go
+++ b/vendor/github.com/gookit/goutil/maputil/alias.go
@@ -1,11 +1,15 @@
package maputil
-import "fmt"
+import (
+ "fmt"
+ "sort"
+)
// Aliases implemented a simple string alias map.
+// - key: alias, value: real name
type Aliases map[string]string
-// AddAlias to the Aliases
+// AddAlias to the Aliases map
func (as Aliases) AddAlias(alias, real string) {
if rn, ok := as[alias]; ok {
panic(fmt.Sprintf("The alias '%s' is already used by '%s'", alias, rn))
@@ -13,21 +17,21 @@ func (as Aliases) AddAlias(alias, real string) {
as[alias] = real
}
-// AddAliases to the Aliases
+// AddAliases to the Aliases map
func (as Aliases) AddAliases(real string, aliases []string) {
for _, a := range aliases {
as.AddAlias(a, real)
}
}
-// AddAliasMap to the Aliases
+// AddAliasMap to the Aliases map
func (as Aliases) AddAliasMap(alias2real map[string]string) {
for a, r := range alias2real {
as.AddAlias(a, r)
}
}
-// HasAlias in the Aliases
+// HasAlias in the Aliases map
func (as Aliases) HasAlias(alias string) bool {
if _, ok := as[alias]; ok {
return true
@@ -42,3 +46,24 @@ func (as Aliases) ResolveAlias(alias string) string {
}
return alias
}
+
+// AliasesNames returns all sorted alias names.
+func (as Aliases) AliasesNames() []string {
+ ns := make([]string, 0, len(as))
+ for alias := range as {
+ ns = append(ns, alias)
+ }
+ sort.Strings(ns)
+ return ns
+}
+
+// GroupAliases groups aliases by real name.
+//
+// returns: {real name -> []aliases, ...}
+func (as Aliases) GroupAliases() map[string][]string {
+ gaMap := make(map[string][]string)
+ for alias, name := range as {
+ gaMap[name] = append(gaMap[name], alias)
+ }
+ return gaMap
+}
diff --git a/vendor/github.com/gookit/goutil/maputil/convert.go b/vendor/github.com/gookit/goutil/maputil/convert.go
index fd0a17d4ac..ebd71a6a71 100644
--- a/vendor/github.com/gookit/goutil/maputil/convert.go
+++ b/vendor/github.com/gookit/goutil/maputil/convert.go
@@ -86,7 +86,7 @@ func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
}
// SliceToSMap convert string k-v pairs slice to map[string]string
-// - eg: []string{k1,v1,k2,v2} -> map[string]string{k1:v1, k2:v2}
+// - eg: []string{k1,v1,k2,v2} -> map[string]string{k1:v1, k2:v2}
func SliceToSMap(kvPairs ...string) map[string]string {
ln := len(kvPairs)
// check kvPairs length must be even
@@ -233,6 +233,19 @@ func FormatIndent(mp any, indent string) string {
return NewFormatter(mp).WithIndent(indent).Format()
}
+// StrMapToText 将 map[string]string 转换为多行 key=value 格式文本
+func StrMapToText(m map[string]string) string {
+ if len(m) == 0 {
+ return ""
+ }
+
+ var lines []string
+ for key, value := range m {
+ lines = append(lines, key+"="+value)
+ }
+ return strings.Join(lines, "\n")
+}
+
/*************************************************************
* Flat convert tree map to flatten key-value map.
*************************************************************/
diff --git a/vendor/github.com/gookit/goutil/maputil/data.go b/vendor/github.com/gookit/goutil/maputil/data.go
index 71e227ad99..0254b10d86 100644
--- a/vendor/github.com/gookit/goutil/maputil/data.go
+++ b/vendor/github.com/gookit/goutil/maputil/data.go
@@ -9,12 +9,12 @@ import (
"github.com/gookit/goutil/strutil"
)
-// Data an map data type
-type Data map[string]any
-
// Map alias of Data
type Map = Data
+// Data alias of map[string]any
+type Data map[string]any
+
// Has value on the data map
func (d Data) Has(key string) bool {
_, ok := d.GetByPath(key)
@@ -139,43 +139,70 @@ func (d Data) Default(key string, def any) any {
return def
}
-// Int value get
-func (d Data) Int(key string) int {
+// Int value get, or default value
+func (d Data) Int(key string, defVal ...int) int {
if val, ok := d.GetByPath(key); ok {
- return mathutil.QuietInt(val)
+ return mathutil.SafeInt(val)
+ }
+ if len(defVal) > 0 {
+ return defVal[0]
}
return 0
}
-// Int64 value get
-func (d Data) Int64(key string) int64 {
+// Int64 value get, or default value
+func (d Data) Int64(key string, defVal ...int64) int64 {
if val, ok := d.GetByPath(key); ok {
- return mathutil.QuietInt64(val)
+ return mathutil.SafeInt64(val)
+ }
+ if len(defVal) > 0 {
+ return defVal[0]
}
return 0
}
-// Uint value get
-func (d Data) Uint(key string) uint {
+// Uint value get, or default value
+func (d Data) Uint(key string, defVal ...uint) uint {
if val, ok := d.GetByPath(key); ok {
return mathutil.QuietUint(val)
}
- return 0
-}
-
-// Uint64 value get
-func (d Data) Uint64(key string) uint64 {
- if val, ok := d.GetByPath(key); ok {
- return mathutil.QuietUint64(val)
+ if len(defVal) > 0 {
+ return defVal[0]
}
return 0
}
-// Str value gets by key
-func (d Data) Str(key string) string {
+// Uint16 value get, or default value
+func (d Data) Uint16(key string, defVal ...uint16) uint16 {
+ if val, ok := d.GetByPath(key); ok {
+ return uint16(mathutil.SafeUint(val))
+ }
+
+ if len(defVal) > 0 {
+ return defVal[0]
+ }
+ return 0
+}
+
+// Uint64 value get, or default value
+func (d Data) Uint64(key string, defVal ...uint64) uint64 {
+ if val, ok := d.GetByPath(key); ok {
+ return mathutil.QuietUint64(val)
+ }
+ if len(defVal) > 0 {
+ return defVal[0]
+ }
+ return 0
+}
+
+// Str value gets by key, or default value
+func (d Data) Str(key string, defVal ...string) string {
if val, ok := d.GetByPath(key); ok {
return strutil.SafeString(val)
}
+ if len(defVal) > 0 {
+ return defVal[0]
+ }
return ""
}
diff --git a/vendor/github.com/gookit/goutil/maputil/maputil.go b/vendor/github.com/gookit/goutil/maputil/maputil.go
index 8acd64d409..47e9d01899 100644
--- a/vendor/github.com/gookit/goutil/maputil/maputil.go
+++ b/vendor/github.com/gookit/goutil/maputil/maputil.go
@@ -16,6 +16,25 @@ const (
KeySepChar = '.'
)
+// Copy copies all key/value pairs in src adding them to dst.
+// When a key in src is already present in dst,
+// the value in dst will be overwritten by the value associated
+// with the key in src.
+func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
+ for k, v := range src {
+ dst[k] = v
+ }
+}
+
+// DeleteFunc deletes any key/value pairs from m for which del returns true.
+func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
+ for k, v := range m {
+ if del(k, v) {
+ delete(m, k)
+ }
+ }
+}
+
// SimpleMerge simple merge two data map by string key. will merge the src to dst map
func SimpleMerge(src, dst map[string]any) map[string]any {
if len(src) == 0 {
@@ -63,6 +82,11 @@ func MergeStrMap(src, dst map[string]string) map[string]string {
return MergeStringMap(src, dst, false)
}
+// AppendSMap append string map data to dst map.
+func AppendSMap(dst, src map[string]string) map[string]string {
+ return MergeStringMap(src, dst, false)
+}
+
// MergeStringMap simple merge two string map. merge src to dst map
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string {
if len(src) == 0 {
diff --git a/vendor/github.com/gookit/goutil/maputil/smap.go b/vendor/github.com/gookit/goutil/maputil/smap.go
index 6fc767a64c..63dc2d0fea 100644
--- a/vendor/github.com/gookit/goutil/maputil/smap.go
+++ b/vendor/github.com/gookit/goutil/maputil/smap.go
@@ -5,6 +5,8 @@ import (
"github.com/gookit/goutil/strutil"
)
+// SM is alias of map[string]string
+type SM = StrMap
// SMap and StrMap is alias of map[string]string
type SMap = StrMap
type StrMap map[string]string
diff --git a/vendor/github.com/gookit/goutil/mathutil/check.go b/vendor/github.com/gookit/goutil/mathutil/check.go
index ed1d11957c..4384cc3dba 100644
--- a/vendor/github.com/gookit/goutil/mathutil/check.go
+++ b/vendor/github.com/gookit/goutil/mathutil/check.go
@@ -3,8 +3,26 @@ package mathutil
import "github.com/gookit/goutil/comdef"
// IsNumeric returns true if the given character is a numeric, otherwise false.
-func IsNumeric(c byte) bool {
- return c >= '0' && c <= '9'
+func IsNumeric(c byte) bool { return c >= '0' && c <= '9' }
+
+// IsInteger strict check the given value is an integer(intX,uintX), otherwise false.
+func IsInteger(val any) bool {
+ switch val.(type) {
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr:
+ return true
+ default:
+ return false
+ }
+}
+
+// IsFloat returns true if the given character is a float(32/64), otherwise false.
+func IsFloat(val any) bool {
+ switch val.(type) {
+ case float32, float64:
+ return true
+ default:
+ return false
+ }
}
// Compare any intX,floatX value by given op. returns `first op(=,!=,<,<=,>,>=) second`
@@ -91,3 +109,30 @@ func InUintRange[T comdef.Uint](val, min, max T) bool {
}
return val >= min && val <= max
}
+
+// InDelta Check whether two floating-point numbers are equal within a specified margin of error
+//
+// Params:
+// want - 期望的浮点数值
+// give - 实际给定的浮点数值
+// delta - 允许的误差范围
+func InDelta[T comdef.Float](want, give T, delta float64) bool {
+ diff := float64(want) - float64(give)
+ if diff < 0 {
+ diff = -diff
+ }
+ return diff <= delta
+}
+
+// InDeltaAny Check whether two floating-point numbers are equal within a specified margin of error
+func InDeltaAny(want, give any, delta float64) bool {
+ wantVal, err := ToFloat(want)
+ if err != nil {
+ return false
+ }
+ giveVal, err := ToFloat(give)
+ if err != nil {
+ return false
+ }
+ return InDelta(wantVal, giveVal, delta)
+}
\ No newline at end of file
diff --git a/vendor/github.com/gookit/goutil/mathutil/compare.go b/vendor/github.com/gookit/goutil/mathutil/compare.go
index c805a83256..f534648f89 100644
--- a/vendor/github.com/gookit/goutil/mathutil/compare.go
+++ b/vendor/github.com/gookit/goutil/mathutil/compare.go
@@ -74,3 +74,70 @@ func SwapMaxI64(x, y int64) (int64, int64) {
func MaxFloat(x, y float64) float64 {
return math.Max(x, y)
}
+
+// OrElse return default value on val is zero, else return val
+func OrElse[T comdef.Number](val, defVal T) T {
+ return ZeroOr(val, defVal)
+}
+
+// ZeroOr return default value on val is zero, else return val
+func ZeroOr[T comdef.Number](val, defVal T) T {
+ if val != 0 {
+ return val
+ }
+ return defVal
+}
+
+// LessOr return val on val < max, else return default value.
+//
+// Example:
+//
+// LessOr(11, 10, 1) // 1
+// LessOr(2, 10, 1) // 2
+// LessOr(10, 10, 1) // 1
+func LessOr[T comdef.Number](val, max, devVal T) T {
+ if val < max {
+ return val
+ }
+ return devVal
+}
+
+// LteOr return val on val <= max, else return default value.
+//
+// Example:
+//
+// LteOr(11, 10, 1) // 11
+// LteOr(2, 10, 1) // 2
+// LteOr(10, 10, 1) // 10
+func LteOr[T comdef.Number](val, max, devVal T) T {
+ if val <= max {
+ return val
+ }
+ return devVal
+}
+
+// GreaterOr return val on val > max, else return default value.
+//
+// Example:
+//
+// GreaterOr(23, 0, 2) // 23
+// GreaterOr(0, 0, 2) // 2
+func GreaterOr[T comdef.Number](val, min, defVal T) T {
+ if val > min {
+ return val
+ }
+ return defVal
+}
+
+// GteOr return val on val >= max, else return default value.
+//
+// Example:
+//
+// GteOr(23, 0, 2) // 23
+// GteOr(0, 0, 2) // 0
+func GteOr[T comdef.Number](val, min, defVal T) T {
+ if val >= min {
+ return val
+ }
+ return defVal
+}
diff --git a/vendor/github.com/gookit/goutil/mathutil/conv2int.go b/vendor/github.com/gookit/goutil/mathutil/conv2int.go
new file mode 100644
index 0000000000..969e576827
--- /dev/null
+++ b/vendor/github.com/gookit/goutil/mathutil/conv2int.go
@@ -0,0 +1,648 @@
+package mathutil
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gookit/goutil/comdef"
+ "github.com/gookit/goutil/internal/checkfn"
+)
+
+/*************************************************************
+ * region convert to int
+ *************************************************************/
+
+// Int convert value to int
+func Int(in any) (int, error) { return ToInt(in) }
+
+// SafeInt convert value to int, will ignore error
+func SafeInt(in any) int {
+ val, _ := ToInt(in)
+ return val
+}
+
+// QuietInt convert value to int, will ignore error
+func QuietInt(in any) int { return SafeInt(in) }
+
+// IntOrPanic convert value to int, will panic on error
+func IntOrPanic(in any) int {
+ val, err := ToInt(in)
+ if err != nil {
+ panic(err)
+ }
+ return val
+}
+
+// MustInt convert value to int, will panic on error
+func MustInt(in any) int { return IntOrPanic(in) }
+
+// IntOrDefault convert value to int, return defaultVal on failed
+func IntOrDefault(in any, defVal int) int { return IntOr(in, defVal) }
+
+// IntOr convert value to int, return defaultVal on failed
+func IntOr(in any, defVal int) int {
+ val, err := ToIntWith(in)
+ if err != nil {
+ return defVal
+ }
+ return val
+}
+
+// IntOrErr convert value to int, return error on failed
+func IntOrErr(in any) (int, error) { return ToIntWith(in) }
+
+// ToInt convert value to int, return error on failed
+func ToInt(in any) (int, error) { return ToIntWith(in) }
+
+// ToIntWith convert value to int, can with some option func.
+//
+// Example:
+//
+// ToIntWithFunc(val, mathutil.WithNilAsFail, mathutil.WithUserConvFn(func(in any) (int, error) {
+// })
+func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error) {
+ if len(optFns) == 0 && in == nil {
+ return 0, nil
+ }
+
+ var opt *ConvOption[int]
+ if len(optFns) > 0 {
+ opt = NewConvOption(optFns...)
+ if in == nil && opt.NilAsFail {
+ err = comdef.ErrConvType
+ return
+ }
+ }
+
+ if tVal, ok := in.(string); ok {
+ // in strict mode, cannot convert string to int
+ if opt != nil && opt.StrictMode {
+ err = comdef.ErrConvType
+ return
+ }
+
+ // try convert to int
+ iVal, err = TryStrInt(tVal)
+ return
+ }
+
+ switch tVal := in.(type) {
+ case int:
+ iVal = tVal
+ case *int: // default support int ptr type
+ iVal = *tVal
+ case int8:
+ iVal = int(tVal)
+ case int16:
+ iVal = int(tVal)
+ case int32:
+ iVal = int(tVal)
+ case int64:
+ if tVal > math.MaxInt32 {
+ err = fmt.Errorf("value overflow int32. input: %v", tVal)
+ } else {
+ iVal = int(tVal)
+ }
+ case uint:
+ if tVal > math.MaxInt32 {
+ err = fmt.Errorf("value overflow int32. input: %v", tVal)
+ } else {
+ iVal = int(tVal)
+ }
+ case uint8:
+ iVal = int(tVal)
+ case uint16:
+ iVal = int(tVal)
+ case uint32:
+ if tVal > math.MaxInt32 {
+ err = fmt.Errorf("value overflow int32. input: %v", tVal)
+ } else {
+ iVal = int(tVal)
+ }
+ case uint64:
+ if tVal > math.MaxInt32 {
+ err = fmt.Errorf("value overflow int32. input: %v", tVal)
+ } else {
+ iVal = int(tVal)
+ }
+ case float32:
+ iVal = int(tVal)
+ case float64:
+ iVal = int(tVal)
+ case time.Duration:
+ if tVal > math.MaxInt32 {
+ err = fmt.Errorf("value overflow int32. input: %v", tVal)
+ } else {
+ iVal = int(tVal)
+ }
+ case comdef.Int64able: // eg: json.Number
+ var i64 int64
+ if i64, err = tVal.Int64(); err == nil {
+ if i64 > math.MaxInt32 {
+ err = fmt.Errorf("value overflow int32. input: %v", tVal)
+ } else {
+ iVal = int(i64)
+ }
+ }
+ default:
+ if opt == nil {
+ err = comdef.ErrConvType
+ return
+ }
+
+ if opt.UserConvFn != nil {
+ iVal, err = opt.UserConvFn(in)
+ } else if opt.HandlePtr {
+ if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
+ rv = rv.Elem()
+ if checkfn.IsSimpleKind(rv.Kind()) {
+ return ToIntWith(rv.Interface(), optFns...)
+ }
+ }
+ } else {
+ err = comdef.ErrConvType
+ }
+ }
+ return
+}
+
+/*************************************************************
+ * region convert to int64
+ *************************************************************/
+
+// Int64 convert value to int64, return error on failed
+func Int64(in any) (int64, error) { return ToInt64(in) }
+
+// SafeInt64 convert value to int64, will ignore error
+func SafeInt64(in any) int64 {
+ i64, _ := ToInt64With(in)
+ return i64
+}
+
+// QuietInt64 convert value to int64, will ignore error
+func QuietInt64(in any) int64 { return SafeInt64(in) }
+
+// MustInt64 convert value to int64, will panic on error
+func MustInt64(in any) int64 {
+ i64, err := ToInt64With(in)
+ if err != nil {
+ panic(err)
+ }
+ return i64
+}
+
+// Int64OrDefault convert value to int64, return default val on failed
+func Int64OrDefault(in any, defVal int64) int64 { return Int64Or(in, defVal) }
+
+// Int64Or convert value to int64, return default val on failed
+func Int64Or(in any, defVal int64) int64 {
+ i64, err := ToInt64With(in)
+ if err != nil {
+ return defVal
+ }
+ return i64
+}
+
+// ToInt64 convert value to int64, return error on failed
+func ToInt64(in any) (int64, error) { return ToInt64With(in) }
+
+// Int64OrErr convert value to int64, return error on failed
+func Int64OrErr(in any) (int64, error) { return ToInt64With(in) }
+
+// ToInt64With try to convert value to int64. can with some option func, more see ConvOption.
+func ToInt64With(in any, optFns ...ConvOptionFn[int64]) (i64 int64, err error) {
+ if len(optFns) == 0 && in == nil {
+ return 0, nil
+ }
+
+ var opt *ConvOption[int64]
+ if len(optFns) > 0 {
+ opt = NewConvOption(optFns...)
+ if in == nil && opt.NilAsFail {
+ err = comdef.ErrConvType
+ return
+ }
+ }
+
+ if tVal, ok := in.(string); ok {
+ // in strict mode, cannot convert string to int
+ if opt != nil && opt.StrictMode {
+ err = comdef.ErrConvType
+ return
+ }
+ // try convert to int64
+ i64, err = TryStrInt64(tVal)
+ return
+ }
+
+ switch tVal := in.(type) {
+ case int:
+ i64 = int64(tVal)
+ case int8:
+ i64 = int64(tVal)
+ case int16:
+ i64 = int64(tVal)
+ case int32:
+ i64 = int64(tVal)
+ case int64:
+ i64 = tVal
+ case *int64: // default support int64 ptr type
+ i64 = *tVal
+ case uint:
+ i64 = int64(tVal)
+ case uint8:
+ i64 = int64(tVal)
+ case uint16:
+ i64 = int64(tVal)
+ case uint32:
+ i64 = int64(tVal)
+ case uint64:
+ i64 = int64(tVal)
+ case float32:
+ // in strict mode, cannot convert float to int
+ if opt != nil && opt.StrictMode {
+ err = comdef.ErrConvType
+ return
+ }
+ i64 = int64(tVal)
+ case float64:
+ // in strict mode, cannot convert float to int
+ if opt != nil && opt.StrictMode {
+ err = comdef.ErrConvType
+ return
+ }
+ if tVal > math.MaxInt64 {
+ err = comdef.ErrConvType
+ return
+ }
+ i64 = int64(tVal)
+ case time.Duration:
+ i64 = int64(tVal)
+ case comdef.Int64able: // eg: json.Number
+ i64, err = tVal.Int64()
+ default:
+ if opt == nil {
+ err = comdef.ErrConvType
+ return
+ }
+
+ if opt.UserConvFn != nil {
+ i64, err = opt.UserConvFn(in)
+ } else if opt.HandlePtr {
+ if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
+ rv = rv.Elem()
+ if checkfn.IsSimpleKind(rv.Kind()) {
+ return ToInt64With(rv.Interface(), optFns...)
+ }
+ }
+ } else {
+ err = comdef.ErrConvType
+ }
+ }
+ return
+}
+
+/*************************************************************
+ * region convert to uint
+ *************************************************************/
+
+// Uint convert any to uint, return error on failed
+func Uint(in any) (uint, error) { return ToUint(in) }
+
+// SafeUint convert any to uint, will ignore error
+func SafeUint(in any) uint {
+ val, _ := ToUint(in)
+ return val
+}
+
+// QuietUint convert any to uint, will ignore error
+func QuietUint(in any) uint { return SafeUint(in) }
+
+// MustUint convert any to uint, will panic on error
+func MustUint(in any) uint {
+ val, err := ToUintWith(in)
+ if err != nil {
+ panic(err)
+ }
+ return val
+}
+
+// UintOrDefault convert any to uint, return default val on failed
+func UintOrDefault(in any, defVal uint) uint { return UintOr(in, defVal) }
+
+// UintOr convert any to uint, return default val on failed
+func UintOr(in any, defVal uint) uint {
+ val, err := ToUintWith(in)
+ if err != nil {
+ return defVal
+ }
+ return val
+}
+
+// UintOrErr convert value to uint, return error on failed
+func UintOrErr(in any) (uint, error) { return ToUintWith(in) }
+
+// ToUint convert value to uint, return error on failed
+func ToUint(in any) (u64 uint, err error) { return ToUintWith(in) }
+
+// ToUintWith try to convert value to uint. can with some option func, more see ConvOption.
+func ToUintWith(in any, optFns ...ConvOptionFn[uint]) (uVal uint, err error) {
+ if len(optFns) == 0 && in == nil {
+ return 0, nil
+ }
+
+ var opt *ConvOption[uint]
+ if len(optFns) > 0 {
+ opt = NewConvOption(optFns...)
+ if in == nil && opt.NilAsFail {
+ err = comdef.ErrConvType
+ return
+ }
+ }
+
+ if tVal, ok := in.(string); ok {
+ // in strict mode, cannot convert string to int
+ if opt != nil && opt.StrictMode {
+ err = comdef.ErrConvType
+ return
+ }
+
+ // try convert to uint64
+ var u64 uint64
+ if u64, err = TryStrUint64(tVal); err == nil {
+ uVal = uint(u64)
+ }
+ return
+ }
+
+ switch tVal := in.(type) {
+ case int:
+ uVal = uint(tVal)
+ case int8:
+ uVal = uint(tVal)
+ case int16:
+ uVal = uint(tVal)
+ case int32:
+ uVal = uint(tVal)
+ case int64:
+ uVal = uint(tVal)
+ case uint:
+ uVal = tVal
+ case *uint: // default support uint ptr type
+ uVal = *tVal
+ case uint8:
+ uVal = uint(tVal)
+ case uint16:
+ uVal = uint(tVal)
+ case uint32:
+ uVal = uint(tVal)
+ case uint64:
+ uVal = uint(tVal)
+ case float32:
+ uVal = uint(tVal)
+ case float64:
+ uVal = uint(tVal)
+ case time.Duration:
+ uVal = uint(tVal)
+ case comdef.Int64able: // eg: json.Number
+ var i64 int64
+ i64, err = tVal.Int64()
+ uVal = uint(i64)
+ default:
+ if opt == nil {
+ err = comdef.ErrConvType
+ return
+ }
+
+ if opt.UserConvFn != nil {
+ uVal, err = opt.UserConvFn(in)
+ } else if opt.HandlePtr {
+ if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
+ rv = rv.Elem()
+ if checkfn.IsSimpleKind(rv.Kind()) {
+ return ToUintWith(rv.Interface(), optFns...)
+ }
+ }
+ } else {
+ err = comdef.ErrConvType
+ }
+ }
+ return
+}
+
+/*************************************************************
+ * region convert to uint64
+ *************************************************************/
+
+// Uint64 convert any to uint64, return error on failed
+func Uint64(in any) (uint64, error) { return ToUint64(in) }
+
+// QuietUint64 convert any to uint64, will ignore error
+func QuietUint64(in any) uint64 { return SafeUint64(in) }
+
+// SafeUint64 convert any to uint64, will ignore error
+func SafeUint64(in any) uint64 {
+ val, _ := ToUint64(in)
+ return val
+}
+
+// MustUint64 convert any to uint64, will panic on error
+func MustUint64(in any) uint64 {
+ val, err := ToUint64With(in)
+ if err != nil {
+ panic(err)
+ }
+ return val
+}
+
+// Uint64OrDefault convert any to uint64, return default val on failed
+func Uint64OrDefault(in any, defVal uint64) uint64 { return Uint64Or(in, defVal) }
+
+// Uint64Or convert any to uint64, return default val on failed
+func Uint64Or(in any, defVal uint64) uint64 {
+ val, err := ToUint64With(in)
+ if err != nil {
+ return defVal
+ }
+ return val
+}
+
+// Uint64OrErr convert value to uint64, return error on failed
+func Uint64OrErr(in any) (uint64, error) { return ToUint64With(in) }
+
+// ToUint64 convert value to uint64, return error on failed
+func ToUint64(in any) (uint64, error) { return ToUint64With(in) }
+
+// ToUint64With try to convert value to uint64. can with some option func, more see ConvOption.
+func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error) {
+ if len(optFns) == 0 && in == nil {
+ return 0, nil
+ }
+
+ var opt *ConvOption[uint64]
+ if len(optFns) > 0 {
+ opt = NewConvOption(optFns...)
+ if in == nil && opt.NilAsFail {
+ err = comdef.ErrConvType
+ return
+ }
+ }
+
+ if tVal, ok := in.(string); ok {
+ // in strict mode, cannot convert string to int
+ if opt != nil && opt.StrictMode {
+ err = comdef.ErrConvType
+ return
+ }
+ // try convert to uint64
+ u64, err = TryStrUint64(tVal)
+ return
+ }
+
+ switch tVal := in.(type) {
+ case int:
+ u64 = uint64(tVal)
+ case int8:
+ u64 = uint64(tVal)
+ case int16:
+ u64 = uint64(tVal)
+ case int32:
+ u64 = uint64(tVal)
+ case int64:
+ u64 = uint64(tVal)
+ case uint:
+ u64 = uint64(tVal)
+ case uint8:
+ u64 = uint64(tVal)
+ case uint16:
+ u64 = uint64(tVal)
+ case uint32:
+ u64 = uint64(tVal)
+ case uint64:
+ u64 = tVal
+ case *uint64: // default support uint64 ptr type
+ u64 = *tVal
+ case float32:
+ u64 = uint64(tVal)
+ case float64:
+ u64 = uint64(tVal)
+ case time.Duration:
+ u64 = uint64(tVal)
+ case comdef.Int64able: // eg: json.Number
+ var i64 int64
+ i64, err = tVal.Int64()
+ u64 = uint64(i64)
+ default:
+ if opt == nil {
+ err = comdef.ErrConvType
+ return
+ }
+
+ if opt.UserConvFn != nil {
+ u64, err = opt.UserConvFn(in)
+ } else if opt.HandlePtr {
+ if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
+ rv = rv.Elem()
+ if checkfn.IsSimpleKind(rv.Kind()) {
+ return ToUint64With(rv.Interface(), optFns...)
+ }
+ }
+ } else {
+ err = comdef.ErrConvType
+ }
+ }
+ return
+}
+
+/*************************************************************
+ * region string to intX/uintX
+ *************************************************************/
+
+// StrInt convert string to int, ignore error
+func StrInt(s string) int {
+ iVal, _ := strconv.Atoi(strings.TrimSpace(s))
+ return iVal
+}
+
+// StrIntOr convert string to int, return default val on failed
+func StrIntOr(s string, defVal int) int {
+ iVal, err := strconv.Atoi(strings.TrimSpace(s))
+ if err != nil {
+ return defVal
+ }
+ return iVal
+}
+
+// TryStrInt convert string to int, return error on failed.
+//
+// - empty string will return 0.
+// - allow float string.
+func TryStrInt(s string) (int, error) {
+ s = strings.TrimSpace(s)
+ if s == "" {
+ return 0, nil
+ }
+
+ // try convert to int
+ iVal, err := strconv.Atoi(s)
+
+ // handle the case where the string might be a float
+ if err != nil && checkfn.IsNumeric(s) {
+ var floatVal float64
+ if floatVal, err = strconv.ParseFloat(s, 64); err == nil {
+ iVal = int(math.Round(floatVal))
+ err = nil
+ }
+ }
+ return iVal, err
+}
+
+// TryStrInt64 convert string to int64, return error on failed.
+//
+// - empty string will return 0.
+// - allow float string.
+func TryStrInt64(s string) (int64, error) {
+ s = strings.TrimSpace(s)
+ if s == "" {
+ return 0, nil
+ }
+ i64, err := strconv.ParseInt(s, 10, 0)
+
+ // handle the case where the string might be a float
+ if err != nil && checkfn.IsNumeric(s) {
+ var floatVal float64
+ if floatVal, err = strconv.ParseFloat(s, 64); err == nil {
+ i64 = int64(math.Round(floatVal))
+ err = nil
+ }
+ }
+ return i64, err
+}
+
+// TryStrUint64 try to convert string to uint64, return error on failed
+//
+// - empty string will return 0.
+// - allow float string.
+func TryStrUint64(s string) (uint64, error) {
+ s = strings.TrimSpace(s)
+ if s == "" {
+ return 0, nil
+ }
+
+ // try convert to int64
+ u64, err := strconv.ParseUint(s, 10, 0)
+
+ // handle the case where the string might be a float
+ if err != nil && checkfn.IsPositiveNum(s) {
+ var floatVal float64
+ if floatVal, err = strconv.ParseFloat(s, 64); err == nil {
+ u64 = uint64(math.Round(floatVal))
+ err = nil
+ }
+ }
+ return u64, err
+}
diff --git a/vendor/github.com/gookit/goutil/mathutil/convert.go b/vendor/github.com/gookit/goutil/mathutil/convert.go
index cbedf90f6b..edcb49fcc2 100644
--- a/vendor/github.com/gookit/goutil/mathutil/convert.go
+++ b/vendor/github.com/gookit/goutil/mathutil/convert.go
@@ -1,8 +1,6 @@
package mathutil
import (
- "fmt"
- "math"
"reflect"
"strconv"
"strings"
@@ -13,24 +11,6 @@ import (
"github.com/gookit/goutil/internal/comfunc"
)
-// ToIntFunc convert value to int
-type ToIntFunc func(any) (int, error)
-
-// ToInt64Func convert value to int64
-type ToInt64Func func(any) (int64, error)
-
-// ToUintFunc convert value to uint
-type ToUintFunc func(any) (uint, error)
-
-// ToUint64Func convert value to uint
-type ToUint64Func func(any) (uint64, error)
-
-// ToFloatFunc convert value to float
-type ToFloatFunc func(any) (float64, error)
-
-// ToTypeFunc convert value to defined type
-type ToTypeFunc[T any] func(any) (T, error)
-
// ConvOption convert options
type ConvOption[T any] struct {
// if ture: value is nil, will return convert error;
@@ -40,6 +20,11 @@ type ConvOption[T any] struct {
// - if true: will use real type try convert. default is false
// - NOTE: current T type's ptr is default support.
HandlePtr bool
+ // StrictMode for convert value. default is false
+ //
+ // TRUE:
+ // - to int: string, float will return error
+ StrictMode bool
// set custom fallback convert func for not supported type.
UserConvFn ToTypeFunc[T]
}
@@ -68,14 +53,13 @@ type ConvOptionFn[T any] func(opt *ConvOption[T])
// Example:
//
// ToIntWithFunc(val, mathutil.WithNilAsFail[int])
-func WithNilAsFail[T any](opt *ConvOption[T]) {
- opt.NilAsFail = true
-}
+func WithNilAsFail[T any](opt *ConvOption[T]) { opt.NilAsFail = true }
// WithHandlePtr set ConvOption.HandlePtr option
-func WithHandlePtr[T any](opt *ConvOption[T]) {
- opt.HandlePtr = true
-}
+func WithHandlePtr[T any](opt *ConvOption[T]) { opt.HandlePtr = true }
+
+// WithStrictMode set ConvOption.StrictMode option
+func WithStrictMode[T any](opt *ConvOption[T]) { opt.StrictMode = true }
// WithUserConvFn set ConvOption.UserConvFn option
func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] {
@@ -85,488 +69,71 @@ func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] {
}
/*************************************************************
- * convert value to int
+ * region Strict to int/uint
*************************************************************/
-// Int convert value to int
-func Int(in any) (int, error) { return ToInt(in) }
-
-// SafeInt convert value to int, will ignore error
-func SafeInt(in any) int {
- val, _ := ToInt(in)
- return val
-}
-
-// QuietInt convert value to int, will ignore error
-func QuietInt(in any) int { return SafeInt(in) }
-
-// IntOrPanic convert value to int, will panic on error
-func IntOrPanic(in any) int {
- val, err := ToInt(in)
- if err != nil {
- panic(err)
- }
- return val
-}
-
-// MustInt convert value to int, will panic on error
-func MustInt(in any) int { return IntOrPanic(in) }
-
-// IntOrDefault convert value to int, return defaultVal on failed
-func IntOrDefault(in any, defVal int) int { return IntOr(in, defVal) }
-
-// IntOr convert value to int, return defaultVal on failed
-func IntOr(in any, defVal int) int {
- val, err := ToIntWith(in)
- if err != nil {
- return defVal
- }
- return val
-}
-
-// IntOrErr convert value to int, return error on failed
-func IntOrErr(in any) (int, error) { return ToIntWith(in) }
-
-// ToInt convert value to int, return error on failed
-func ToInt(in any) (int, error) { return ToIntWith(in) }
-
-// ToIntWith convert value to int, can with some option func.
-//
-// Example:
-//
-// ToIntWithFunc(val, mathutil.WithNilAsFail, mathutil.WithUserConvFn(func(in any) (int, error) {
-// })
-func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error) {
- opt := NewConvOption[int](optFns...)
- if !opt.NilAsFail && in == nil {
- return 0, nil
- }
-
- switch tVal := in.(type) {
+// StrictInt check the given value is an integer(intX,uintX), return the int64 value and true if success
+func StrictInt(val any) (int64, bool) {
+ switch tVal := val.(type) {
case int:
- iVal = tVal
- case *int: // default support int ptr type
- iVal = *tVal
+ return int64(tVal), true
case int8:
- iVal = int(tVal)
+ return int64(tVal), true
case int16:
- iVal = int(tVal)
+ return int64(tVal), true
case int32:
- iVal = int(tVal)
+ return int64(tVal), true
case int64:
- if tVal > math.MaxInt32 {
- err = fmt.Errorf("value overflow int32. input: %v", tVal)
- } else {
- iVal = int(tVal)
- }
+ return tVal, true
case uint:
- if tVal > math.MaxInt32 {
- err = fmt.Errorf("value overflow int32. input: %v", tVal)
- } else {
- iVal = int(tVal)
- }
+ return int64(tVal), true
case uint8:
- iVal = int(tVal)
+ return int64(tVal), true
case uint16:
- iVal = int(tVal)
+ return int64(tVal), true
case uint32:
- if tVal > math.MaxInt32 {
- err = fmt.Errorf("value overflow int32. input: %v", tVal)
- } else {
- iVal = int(tVal)
- }
+ return int64(tVal), true
case uint64:
- if tVal > math.MaxInt32 {
- err = fmt.Errorf("value overflow int32. input: %v", tVal)
- } else {
- iVal = int(tVal)
- }
- case float32:
- iVal = int(tVal)
- case float64:
- iVal = int(tVal)
- case time.Duration:
- if tVal > math.MaxInt32 {
- err = fmt.Errorf("value overflow int32. input: %v", tVal)
- } else {
- iVal = int(tVal)
- }
- case string:
- sVal := strings.TrimSpace(tVal)
- iVal, err = strconv.Atoi(sVal)
- // handle the case where the string might be a float
- if err != nil && checkfn.IsNumeric(sVal) {
- var floatVal float64
- if floatVal, err = strconv.ParseFloat(sVal, 64); err == nil {
- iVal = int(math.Round(floatVal))
- err = nil
- }
- }
- case comdef.Int64able: // eg: json.Number
- var i64 int64
- if i64, err = tVal.Int64(); err == nil {
- if i64 > math.MaxInt32 {
- err = fmt.Errorf("value overflow int32. input: %v", tVal)
- } else {
- iVal = int(i64)
- }
- }
+ return int64(tVal), true
+ case uintptr:
+ return int64(tVal), true
default:
- if opt.HandlePtr {
- if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
- rv = rv.Elem()
- if checkfn.IsSimpleKind(rv.Kind()) {
- return ToIntWith(rv.Interface(), optFns...)
- }
- }
- }
-
- if opt.UserConvFn != nil {
- return opt.UserConvFn(in)
- }
- err = comdef.ErrConvType
+ return 0, false
}
- return
}
-// StrInt convert.
-func StrInt(s string) int {
- iVal, _ := strconv.Atoi(strings.TrimSpace(s))
- return iVal
-}
-
-// StrIntOr convert string to int, return default val on failed
-func StrIntOr(s string, defVal int) int {
- iVal, err := strconv.Atoi(strings.TrimSpace(s))
- if err != nil {
- return defVal
+// StrictUint strict check value is integer(intX,uintX) and convert to uint64.
+func StrictUint(val any) (uint64, bool) {
+ switch tVal := val.(type) {
+ case int:
+ return uint64(tVal), true
+ case int8:
+ return uint64(tVal), true
+ case int16:
+ return uint64(tVal), true
+ case int32:
+ return uint64(tVal), true
+ case int64:
+ return uint64(tVal), true
+ case uint:
+ return uint64(tVal), true
+ case uint8:
+ return uint64(tVal), true
+ case uint16:
+ return uint64(tVal), true
+ case uint32:
+ return uint64(tVal), true
+ case uint64:
+ return tVal, true
+ case uintptr:
+ return uint64(tVal), true
+ default:
+ return 0, false
}
- return iVal
}
/*************************************************************
- * convert value to int64
- *************************************************************/
-
-// Int64 convert value to int64, return error on failed
-func Int64(in any) (int64, error) { return ToInt64(in) }
-
-// SafeInt64 convert value to int64, will ignore error
-func SafeInt64(in any) int64 {
- i64, _ := ToInt64With(in)
- return i64
-}
-
-// QuietInt64 convert value to int64, will ignore error
-func QuietInt64(in any) int64 { return SafeInt64(in) }
-
-// MustInt64 convert value to int64, will panic on error
-func MustInt64(in any) int64 {
- i64, err := ToInt64With(in)
- if err != nil {
- panic(err)
- }
- return i64
-}
-
-// Int64OrDefault convert value to int64, return default val on failed
-func Int64OrDefault(in any, defVal int64) int64 { return Int64Or(in, defVal) }
-
-// Int64Or convert value to int64, return default val on failed
-func Int64Or(in any, defVal int64) int64 {
- i64, err := ToInt64With(in)
- if err != nil {
- return defVal
- }
- return i64
-}
-
-// ToInt64 convert value to int64, return error on failed
-func ToInt64(in any) (int64, error) { return ToInt64With(in) }
-
-// Int64OrErr convert value to int64, return error on failed
-func Int64OrErr(in any) (int64, error) { return ToInt64With(in) }
-
-// ToInt64With try to convert value to int64. can with some option func, more see ConvOption.
-func ToInt64With(in any, optFns ...ConvOptionFn[int64]) (i64 int64, err error) {
- opt := NewConvOption(optFns...)
- if !opt.NilAsFail && in == nil {
- return 0, nil
- }
-
- switch tVal := in.(type) {
- case string:
- sVal := strings.TrimSpace(tVal)
- i64, err = strconv.ParseInt(sVal, 10, 0)
- // handle the case where the string might be a float
- if err != nil && checkfn.IsNumeric(sVal) {
- var floatVal float64
- if floatVal, err = strconv.ParseFloat(sVal, 64); err == nil {
- i64 = int64(math.Round(floatVal))
- err = nil
- }
- }
- case int:
- i64 = int64(tVal)
- case int8:
- i64 = int64(tVal)
- case int16:
- i64 = int64(tVal)
- case int32:
- i64 = int64(tVal)
- case int64:
- i64 = tVal
- case *int64: // default support int64 ptr type
- i64 = *tVal
- case uint:
- i64 = int64(tVal)
- case uint8:
- i64 = int64(tVal)
- case uint16:
- i64 = int64(tVal)
- case uint32:
- i64 = int64(tVal)
- case uint64:
- i64 = int64(tVal)
- case float32:
- i64 = int64(tVal)
- case float64:
- i64 = int64(tVal)
- case time.Duration:
- i64 = int64(tVal)
- case comdef.Int64able: // eg: json.Number
- i64, err = tVal.Int64()
- default:
- if opt.HandlePtr {
- if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
- rv = rv.Elem()
- if checkfn.IsSimpleKind(rv.Kind()) {
- return ToInt64With(rv.Interface(), optFns...)
- }
- }
- }
-
- if opt.UserConvFn != nil {
- i64, err = opt.UserConvFn(in)
- } else {
- err = comdef.ErrConvType
- }
- }
- return
-}
-
-/*************************************************************
- * convert value to uint
- *************************************************************/
-
-// Uint convert any to uint, return error on failed
-func Uint(in any) (uint, error) { return ToUint(in) }
-
-// SafeUint convert any to uint, will ignore error
-func SafeUint(in any) uint {
- val, _ := ToUint(in)
- return val
-}
-
-// QuietUint convert any to uint, will ignore error
-func QuietUint(in any) uint { return SafeUint(in) }
-
-// MustUint convert any to uint, will panic on error
-func MustUint(in any) uint {
- val, err := ToUintWith(in)
- if err != nil {
- panic(err)
- }
- return val
-}
-
-// UintOrDefault convert any to uint, return default val on failed
-func UintOrDefault(in any, defVal uint) uint { return UintOr(in, defVal) }
-
-// UintOr convert any to uint, return default val on failed
-func UintOr(in any, defVal uint) uint {
- val, err := ToUintWith(in)
- if err != nil {
- return defVal
- }
- return val
-}
-
-// UintOrErr convert value to uint, return error on failed
-func UintOrErr(in any) (uint, error) { return ToUintWith(in) }
-
-// ToUint convert value to uint, return error on failed
-func ToUint(in any) (u64 uint, err error) { return ToUintWith(in) }
-
-// ToUintWith try to convert value to uint. can with some option func, more see ConvOption.
-func ToUintWith(in any, optFns ...ConvOptionFn[uint]) (uVal uint, err error) {
- opt := NewConvOption(optFns...)
- if !opt.NilAsFail && in == nil {
- return 0, nil
- }
-
- switch tVal := in.(type) {
- case int:
- uVal = uint(tVal)
- case int8:
- uVal = uint(tVal)
- case int16:
- uVal = uint(tVal)
- case int32:
- uVal = uint(tVal)
- case int64:
- uVal = uint(tVal)
- case uint:
- uVal = tVal
- case *uint: // default support uint ptr type
- uVal = *tVal
- case uint8:
- uVal = uint(tVal)
- case uint16:
- uVal = uint(tVal)
- case uint32:
- uVal = uint(tVal)
- case uint64:
- uVal = uint(tVal)
- case float32:
- uVal = uint(tVal)
- case float64:
- uVal = uint(tVal)
- case time.Duration:
- uVal = uint(tVal)
- case comdef.Int64able: // eg: json.Number
- var i64 int64
- i64, err = tVal.Int64()
- uVal = uint(i64)
- case string:
- var u64 uint64
- u64, err = strconv.ParseUint(strings.TrimSpace(tVal), 10, 0)
- uVal = uint(u64)
- default:
- if opt.HandlePtr {
- if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
- rv = rv.Elem()
- if checkfn.IsSimpleKind(rv.Kind()) {
- return ToUintWith(rv.Interface(), optFns...)
- }
- }
- }
-
- if opt.UserConvFn != nil {
- uVal, err = opt.UserConvFn(in)
- } else {
- err = comdef.ErrConvType
- }
- }
- return
-}
-
-/*************************************************************
- * convert value to uint64
- *************************************************************/
-
-// Uint64 convert any to uint64, return error on failed
-func Uint64(in any) (uint64, error) { return ToUint64(in) }
-
-// QuietUint64 convert any to uint64, will ignore error
-func QuietUint64(in any) uint64 { return SafeUint64(in) }
-
-// SafeUint64 convert any to uint64, will ignore error
-func SafeUint64(in any) uint64 {
- val, _ := ToUint64(in)
- return val
-}
-
-// MustUint64 convert any to uint64, will panic on error
-func MustUint64(in any) uint64 {
- val, err := ToUint64With(in)
- if err != nil {
- panic(err)
- }
- return val
-}
-
-// Uint64OrDefault convert any to uint64, return default val on failed
-func Uint64OrDefault(in any, defVal uint64) uint64 { return Uint64Or(in, defVal) }
-
-// Uint64Or convert any to uint64, return default val on failed
-func Uint64Or(in any, defVal uint64) uint64 {
- val, err := ToUint64With(in)
- if err != nil {
- return defVal
- }
- return val
-}
-
-// Uint64OrErr convert value to uint64, return error on failed
-func Uint64OrErr(in any) (uint64, error) { return ToUint64With(in) }
-
-// ToUint64 convert value to uint64, return error on failed
-func ToUint64(in any) (uint64, error) { return ToUint64With(in) }
-
-// ToUint64With try to convert value to uint64. can with some option func, more see ConvOption.
-func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error) {
- opt := NewConvOption(optFns...)
- if !opt.NilAsFail && in == nil {
- return 0, nil
- }
-
- switch tVal := in.(type) {
- case int:
- u64 = uint64(tVal)
- case int8:
- u64 = uint64(tVal)
- case int16:
- u64 = uint64(tVal)
- case int32:
- u64 = uint64(tVal)
- case int64:
- u64 = uint64(tVal)
- case uint:
- u64 = uint64(tVal)
- case uint8:
- u64 = uint64(tVal)
- case uint16:
- u64 = uint64(tVal)
- case uint32:
- u64 = uint64(tVal)
- case uint64:
- u64 = tVal
- case *uint64: // default support uint64 ptr type
- u64 = *tVal
- case float32:
- u64 = uint64(tVal)
- case float64:
- u64 = uint64(tVal)
- case time.Duration:
- u64 = uint64(tVal)
- case comdef.Int64able: // eg: json.Number
- var i64 int64
- i64, err = tVal.Int64()
- u64 = uint64(i64)
- case string:
- u64, err = strconv.ParseUint(strings.TrimSpace(tVal), 10, 0)
- default:
- if opt.HandlePtr {
- if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
- rv = rv.Elem()
- if checkfn.IsSimpleKind(rv.Kind()) {
- return ToUint64With(rv.Interface(), optFns...)
- }
- }
- }
-
- if opt.UserConvFn != nil {
- u64, err = opt.UserConvFn(in)
- } else {
- err = comdef.ErrConvType
- }
- }
- return
-}
-
-/*************************************************************
- * convert value to float64
+ * region convert to float64
*************************************************************/
// QuietFloat convert value to float64, will ignore error. alias of SafeFloat
@@ -671,7 +238,7 @@ func ToFloatWith(in any, optFns ...ConvOptionFn[float64]) (f64 float64, err erro
}
/*************************************************************
- * convert intX/floatX to string
+ * region intX/floatX to string
*************************************************************/
// MustString convert intX/floatX value to string, will panic on error
diff --git a/vendor/github.com/gookit/goutil/mathutil/format.go b/vendor/github.com/gookit/goutil/mathutil/format.go
index deb29fd2a5..e310eb0911 100644
--- a/vendor/github.com/gookit/goutil/mathutil/format.go
+++ b/vendor/github.com/gookit/goutil/mathutil/format.go
@@ -22,6 +22,21 @@ func DataSize(size uint64) string {
}
}
+// FormatBytes Format the byte size to be a readable string. eg: 1024 => 1 KB
+func FormatBytes(bytes int) string {
+ const unit = 1024
+ if bytes < unit {
+ return fmt.Sprintf("%d B", bytes)
+ }
+
+ div, exp := int64(unit), 0
+ for n := bytes / unit; n >= unit; n /= unit {
+ div *= unit
+ exp++
+ }
+ return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
+}
+
var timeFormats = [][]int{
{0},
{1},
diff --git a/vendor/github.com/gookit/goutil/mathutil/mathutil.go b/vendor/github.com/gookit/goutil/mathutil/mathutil.go
index 1a32e50230..e4180bd3d4 100644
--- a/vendor/github.com/gookit/goutil/mathutil/mathutil.go
+++ b/vendor/github.com/gookit/goutil/mathutil/mathutil.go
@@ -2,78 +2,15 @@
package mathutil
import (
+ "fmt"
"math"
+ "strconv"
+ "strings"
"github.com/gookit/goutil/comdef"
+ "github.com/gookit/goutil/internal/checkfn"
)
-// OrElse return default value on val is zero, else return val
-func OrElse[T comdef.Number](val, defVal T) T {
- return ZeroOr(val, defVal)
-}
-
-// ZeroOr return default value on val is zero, else return val
-func ZeroOr[T comdef.Number](val, defVal T) T {
- if val != 0 {
- return val
- }
- return defVal
-}
-
-// LessOr return val on val < max, else return default value.
-//
-// Example:
-//
-// LessOr(11, 10, 1) // 1
-// LessOr(2, 10, 1) // 2
-// LessOr(10, 10, 1) // 1
-func LessOr[T comdef.Number](val, max, devVal T) T {
- if val < max {
- return val
- }
- return devVal
-}
-
-// LteOr return val on val <= max, else return default value.
-//
-// Example:
-//
-// LteOr(11, 10, 1) // 11
-// LteOr(2, 10, 1) // 2
-// LteOr(10, 10, 1) // 10
-func LteOr[T comdef.Number](val, max, devVal T) T {
- if val <= max {
- return val
- }
- return devVal
-}
-
-// GreaterOr return val on val > max, else return default value.
-//
-// Example:
-//
-// GreaterOr(23, 0, 2) // 23
-// GreaterOr(0, 0, 2) // 2
-func GreaterOr[T comdef.Number](val, min, defVal T) T {
- if val > min {
- return val
- }
- return defVal
-}
-
-// GteOr return val on val >= max, else return default value.
-//
-// Example:
-//
-// GteOr(23, 0, 2) // 23
-// GteOr(0, 0, 2) // 0
-func GteOr[T comdef.Number](val, min, defVal T) T {
- if val >= min {
- return val
- }
- return defVal
-}
-
// Mul computes the `a*b` value, rounding the result.
func Mul[T1, T2 comdef.Number](a T1, b T2) float64 {
return math.Round(SafeFloat(a) * SafeFloat(b))
@@ -100,10 +37,104 @@ func DivF2i(a, b float64) int {
return int(math.Round(a / b))
}
-// Percent returns a value percentage of the total
+// Percent returns a value percentage of the total. eg: 1/100 = 1.0%
func Percent(val, total int) float64 {
if total == 0 {
return float64(0)
}
return (float64(val) / float64(total)) * 100
}
+
+// Range a number range expression, and handle each value. eg: "1-100,123,124"
+func Range(expr string, handle func(val int)) error {
+ for _, item := range strings.Split(expr, ",") {
+ item = strings.TrimSpace(item)
+ if item == "" {
+ continue
+ }
+
+ // is range, eg: "1-100", "-20-2"
+ if idx := checkfn.IndexByteAfter(item, '-', 1); idx > 0 {
+ start, end, err := parseIntRange(item, idx)
+ if err != nil {
+ return err
+ }
+
+ // range number
+ for i := start; i <= end; i++ {
+ handle(i)
+ }
+ } else {
+ iVal, err := strconv.Atoi(item)
+ if err != nil {
+ return fmt.Errorf("invalid integer value: %q", item)
+ }
+ handle(iVal)
+ }
+ }
+ return nil
+}
+
+// Expand a number range expression to int[]. eg: "1-100,123,124"
+func Expand(expr string) ([]int, error) {
+ var nums []int
+ for _, item := range strings.Split(expr, ",") {
+ if item == "" {
+ continue
+ }
+
+ // is range, eg: "1-100", "-20-2"
+ if idx := checkfn.IndexByteAfter(item, '-', 1); idx > 0 {
+ ints, err := expandIntRange(item, idx)
+ if err != nil {
+ return nil, err
+ }
+ nums = append(nums, ints...)
+ } else {
+ iVal, err := strconv.Atoi(item)
+ if err != nil {
+ return nil, fmt.Errorf("invalid integer value: %q", item)
+ }
+ nums = append(nums, iVal)
+ }
+ }
+ return nums, nil
+}
+
+// 处理范围格式
+// eg: "1-30" -> [1, 30], "-20-2" -> [-20, 2]
+func parseIntRange(value string, sepIdx int) (min int, max int, err error) {
+ start, end := value[:sepIdx], value[sepIdx+1:]
+
+ min, err = strconv.Atoi(start)
+ if err != nil {
+ err = fmt.Errorf("invalid range start value: %q", start)
+ return
+ }
+
+ max, err = strconv.Atoi(end)
+ if err != nil {
+ err = fmt.Errorf("invalid range end value: %q", end)
+ return
+ }
+
+ // swap min and max
+ if min > max {
+ min, max = max, min
+ }
+ return
+}
+
+// 将 "1-30" 转换为 int 列表
+func expandIntRange(value string, sepIdx int) ([]int, error) {
+ var ints []int
+ start, end, err := parseIntRange(value, sepIdx)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := start; i <= end; i++ {
+ ints = append(ints, i)
+ }
+ return ints, nil
+}
diff --git a/vendor/github.com/gookit/goutil/mathutil/types.go b/vendor/github.com/gookit/goutil/mathutil/types.go
new file mode 100644
index 0000000000..923cc149fb
--- /dev/null
+++ b/vendor/github.com/gookit/goutil/mathutil/types.go
@@ -0,0 +1,19 @@
+package mathutil
+
+// ToIntFunc convert value to int
+type ToIntFunc func(any) (int, error)
+
+// ToInt64Func convert value to int64
+type ToInt64Func func(any) (int64, error)
+
+// ToUintFunc convert value to uint
+type ToUintFunc func(any) (uint, error)
+
+// ToUint64Func convert value to uint
+type ToUint64Func func(any) (uint64, error)
+
+// ToFloatFunc convert value to float
+type ToFloatFunc func(any) (float64, error)
+
+// ToTypeFunc convert value to defined type
+type ToTypeFunc[T any] func(any) (T, error)
diff --git a/vendor/github.com/gookit/goutil/structs/convert.go b/vendor/github.com/gookit/goutil/structs/convert.go
index f8029865da..8c55e94316 100644
--- a/vendor/github.com/gookit/goutil/structs/convert.go
+++ b/vendor/github.com/gookit/goutil/structs/convert.go
@@ -66,11 +66,11 @@ func ToString(st any, optFns ...MapOptFunc) string {
const defaultFieldTag = "json"
// CustomUserFunc for map convert
-// - fName: raw field name in struct
+// - fName: raw field name in struct
//
// Returns:
-// - ok: return true to collect field, otherwise excluded.
-// - newVal: `newVal != nil` return new value to collect, otherwise collect original value.
+// - ok: return true to collect field, otherwise excluded.
+// - newVal: `newVal != nil` return new value to collect, otherwise collect original value.
type CustomUserFunc func(fName string, fv reflect.Value) (ok bool, newVal any)
// MapOptions for convert struct to map
diff --git a/vendor/github.com/gookit/goutil/strutil/check.go b/vendor/github.com/gookit/goutil/strutil/check.go
index 04660c307d..7e8c95cb35 100644
--- a/vendor/github.com/gookit/goutil/strutil/check.go
+++ b/vendor/github.com/gookit/goutil/strutil/check.go
@@ -3,6 +3,7 @@ package strutil
import (
"path"
"regexp"
+ "strconv"
"strings"
"unicode"
"unicode/utf8"
@@ -17,18 +18,43 @@ 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 intReg = regexp.MustCompile(`^\d+$`)
-var floatReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`)
+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 { return intReg.MatchString(s) }
+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 { return floatReg.MatchString(s) }
+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
@@ -48,6 +74,28 @@ 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) }
@@ -68,6 +116,16 @@ func IContains(s, sub string) bool {
// 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
@@ -95,10 +153,10 @@ func IContainsOne(s string, subs []string) bool {
return false
}
-// ContainsAll substr(s) in the given string. alias of HasAllSubs()
+// ContainsAll given string should contain all substrings. alias of HasAllSubs()
func ContainsAll(s string, subs []string) bool { return HasAllSubs(s, subs) }
-// HasAllSubs all substr in the given string.
+// HasAllSubs given string should contain all substrings
func HasAllSubs(s string, subs []string) bool {
for _, sub := range subs {
if !strings.Contains(s, sub) {
@@ -227,8 +285,12 @@ var (
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
@@ -237,27 +299,95 @@ func IsVersion(s string) bool { return verRegex.MatchString(s) }
// IsVarName is valid variable name.
func IsVarName(s string) bool { return varRegex.MatchString(s) }
-// Compare for two strings.
-func Compare(s1, s2, op string) bool { return VersionCompare(s1, s2, op) }
+// IsEnvName is valid ENV var name. eg: APP_NAME
+func IsEnvName(s string) bool { return envRegex.MatchString(s) }
-// VersionCompare for two version strings.
-func VersionCompare(v1, v2, op string) bool {
+// 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 v1 > v2
+ return s1 > s2
case "<", "lt":
- return v1 < v2
+ return s1 < s2
case ">=", "gte":
- return v1 >= v2
+ return s1 >= s2
case "<=", "lte":
- return v1 <= v2
+ return s1 <= s2
case "!=", "ne", "neq":
- return v1 != v2
+ return s1 != s2
default: // eq
- return v1 == v2
+ 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:
diff --git a/vendor/github.com/gookit/goutil/strutil/convbase.go b/vendor/github.com/gookit/goutil/strutil/convbase.go
index a405778a64..d15fc53e95 100644
--- a/vendor/github.com/gookit/goutil/strutil/convbase.go
+++ b/vendor/github.com/gookit/goutil/strutil/convbase.go
@@ -1,6 +1,7 @@
package strutil
import (
+ "math/big"
"strconv"
"strings"
@@ -16,6 +17,7 @@ const (
Base16Chars = "0123456789abcdef"
Base32Chars = "0123456789abcdefghjkmnpqrstvwxyz"
Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"
+ Base48Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
)
@@ -33,14 +35,36 @@ func Base10Conv(src string, to int) string { return BaseConv(src, 10, to) }
// BaseConv("7b", 16, 10) // Output: "123"
func BaseConv(src string, from, to int) string {
if from > 64 || from < 2 {
- return ""
+ from = 10
}
if to > 64 || to < 2 {
- return ""
+ 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:
@@ -58,7 +82,7 @@ func BaseConvByTpl(src string, fromBase, toBase string) string {
var err error
dec, err = strconv.ParseUint(src, 10, 0)
if err != nil {
- basefn.Panicf("input is not a valid decimal number: %s", src)
+ basefn.Panicf("input is not a valid decimal number: %s(%v)", src, err)
}
} else {
fLen := uint64(len(fromBase))
@@ -67,6 +91,12 @@ func BaseConvByTpl(src string, fromBase, toBase string) string {
}
}
+ // 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))
diff --git a/vendor/github.com/gookit/goutil/strutil/convert.go b/vendor/github.com/gookit/goutil/strutil/convert.go
index d258621f8d..f7bbac4b0d 100644
--- a/vendor/github.com/gookit/goutil/strutil/convert.go
+++ b/vendor/github.com/gookit/goutil/strutil/convert.go
@@ -88,7 +88,7 @@ func JoinAny(sep string, parts ...any) string {
func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) }
/*************************************************************
- * convert value to string
+ * region value to string
*************************************************************/
// String convert value to string, return error on failed
@@ -144,7 +144,7 @@ func AnyToString(val any, defaultAsErr bool) (s string, err error) {
if !defaultAsErr {
optFn = comfunc.WithUserConvFn(comfunc.StrBySprintFn)
}
- return ToStringWith(val, optFn)
+ return comfunc.ToStringWith(val, optFn)
}
// ToStringWith try to convert value to string. can with some option func, more see comfunc.ConvOption.
@@ -153,7 +153,7 @@ func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error) {
}
/*************************************************************
- * convert string value to bool
+ * region string value to bool
*************************************************************/
// ToBool convert string to bool
@@ -185,7 +185,7 @@ func Bool(s string) (bool, error) {
}
/*************************************************************
- * convert string value to int
+ * region string value to int
*************************************************************/
// Int convert string to int, alias of ToInt()
@@ -234,7 +234,7 @@ func IntOrPanic(s string) int {
}
/*************************************************************
- * convert string value to int64
+ * region convert string to int64
*************************************************************/
// Int64 convert string to int, will ignore error
@@ -286,7 +286,7 @@ func Int64OrPanic(s string) int64 {
}
/*************************************************************
- * convert string value to uint
+ * region string value to uint
*************************************************************/
// Uint convert string to uint, will ignore error
@@ -335,7 +335,7 @@ func UintOr(s string, defVal uint64) uint64 {
}
/*************************************************************
- * convert string value to byte
+ * region string value to byte
* refer from https://github.com/valyala/fastjson/blob/master/util.go
*************************************************************/
@@ -361,7 +361,7 @@ func ToBytes(s string) (b []byte) {
}
/*************************************************************
- * convert string value to int/string slice, time.Time
+ * region string to int/string slice, time.Time
*************************************************************/
// Ints alias of the ToIntSlice(). default sep is comma(,)
@@ -406,11 +406,6 @@ func ToSlice(s string, sep ...string) []string {
return Split(s, ",")
}
-// ToOSArgs split string to string[](such as os.Args)
-// func ToOSArgs(s string) []string {
-// return cliutil.StringToOSArgs(s) // error: import cycle not allowed
-// }
-
// 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) {
diff --git a/vendor/github.com/gookit/goutil/strutil/format.go b/vendor/github.com/gookit/goutil/strutil/format.go
index 05150f5a4a..e0fb0790d9 100644
--- a/vendor/github.com/gookit/goutil/strutil/format.go
+++ b/vendor/github.com/gookit/goutil/strutil/format.go
@@ -1,6 +1,7 @@
package strutil
import (
+ "fmt"
"regexp"
"strings"
"unicode"
@@ -184,3 +185,52 @@ func IndentBytes(b, prefix []byte) []byte {
}
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)
+}
diff --git a/vendor/github.com/gookit/goutil/strutil/gensn.go b/vendor/github.com/gookit/goutil/strutil/gensn.go
index 31cf6cbf74..6ac1c3e0d3 100644
--- a/vendor/github.com/gookit/goutil/strutil/gensn.go
+++ b/vendor/github.com/gookit/goutil/strutil/gensn.go
@@ -1,13 +1,15 @@
package strutil
import (
+ "fmt"
"hash/crc32"
- "math/rand"
+ "math/rand" // TODO use v2 on 1.22+
"os"
"strconv"
+ "sync"
+ "sync/atomic"
"time"
- "github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/x/basefn"
)
@@ -34,12 +36,12 @@ func MicroTimeID() string { return MTimeBaseID(10) }
// MicroTimeHexID micro time HEX ID generate.
//
-// return like: 5b5f0588af1761ad3(len: 16-17)
+// return like: 643d4cec7db9e(len: 13)
func MicroTimeHexID() string { return MTimeHexID() }
// MTimeHexID micro time HEX ID generate.
//
-// return like: 5b5f0588af1761ad3(len: 16-17)
+// return like: 643d4cec7db9e(len: 13)
func MTimeHexID() string { return MTimeBaseID(16) }
// MTimeBase36 micro time BASE36 id generate.
@@ -48,12 +50,19 @@ func MTimeBase36() string { return MTimeBaseID(36) }
// MTimeBaseID micro time BASE id generate. toBase: 2-36
//
// Examples:
-// - MTimeBaseID(16): 5b5f0588af1761ad3(len: 16-17)
-// - MTimeBaseID(36): gorntzvsa73mo(len: 13)
+// - toBase=16: 643d4cec7db9e(len: 13)
+// - toBase=36: hd312z9ka2(len: 10)
func MTimeBaseID(toBase int) string {
+ // eg: 1763431181849557
ms := time.Now().UnixMicro()
- ri := mathutil.RandomInt(DefMinInt, DefMaxInt)
- return strconv.FormatInt(ms, toBase) + strconv.FormatInt(int64(ri), toBase)
+ // 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.
@@ -84,20 +93,144 @@ func DateSN(prefix string) string {
bs = strconv.AppendUint(bs, uint64(c32%99), 10)
// rand 1000 - 9999
- rs := rand.New(rand.NewSource(nt.UnixNano()))
- bs = strconv.AppendInt(bs, 1000+rs.Int63n(8999), 10)
+ // rs := rand.New(rand.NewSource(nt.UnixNano()))
+ bs = strconv.AppendInt(bs, 1000+rand.Int63n(8999), 10)
return string(bs)
}
-// DateSNV2 generate date serial number.
+// 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 {
+func DateSNv2(prefix string, extBase ...int) string {
pl := len(prefix)
bs := make([]byte, 0, 22+pl)
if pl > 0 {
@@ -108,14 +241,51 @@ func DateSNV2(prefix string, extBase ...int) string {
nt := time.Now()
bs = nt.AppendFormat(bs, "20060102150405.000000")
- // rand 10000 - 99999
- rs := rand.New(rand.NewSource(nt.UnixNano()))
- // 6bit micro + 5bit rand
- ext := strconv.AppendInt(bs[16+pl:], 10000+rs.Int63n(89999), 10)
-
- base := basefn.FirstOr(extBase, 16)
+ // 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)
+}
diff --git a/vendor/github.com/gookit/goutil/strutil/hash.go b/vendor/github.com/gookit/goutil/strutil/hash.go
index a9435220a1..7038e3dcb2 100644
--- a/vendor/github.com/gookit/goutil/strutil/hash.go
+++ b/vendor/github.com/gookit/goutil/strutil/hash.go
@@ -2,8 +2,10 @@ package strutil
import (
"crypto/hmac"
+ "crypto/rand"
"crypto/sha256"
"encoding/hex"
+ "fmt"
"math/big"
"strings"
@@ -64,6 +66,33 @@ 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 -----------------------------
//
@@ -78,7 +107,7 @@ func HashPasswd(pwd, key string) string {
// VerifyPasswd for quick verify input password is valid
//
-// - pwdMAC from db or config, generated by EncryptPasswd()
+// - pwdMAC from db or config, generated by HashPasswd()
func VerifyPasswd(pwdMAC, pwd, key string) bool {
decBts, err := hex.DecodeString(pwdMAC)
if err != nil {
diff --git a/vendor/github.com/gookit/goutil/strutil/padding.go b/vendor/github.com/gookit/goutil/strutil/padding.go
index dea9cbe0f3..cfb01d1445 100644
--- a/vendor/github.com/gookit/goutil/strutil/padding.go
+++ b/vendor/github.com/gookit/goutil/strutil/padding.go
@@ -1,43 +1,59 @@
package strutil
import (
- "fmt"
"strings"
+
+ "github.com/gookit/goutil/comdef"
)
// PosFlag type
-type PosFlag uint8
+type PosFlag = comdef.Position
// Position for padding/resize string
const (
PosLeft PosFlag = iota
- PosRight
PosMiddle
+ PosRight
+ PosAuto
)
/*************************************************************
* String padding operation
*************************************************************/
-// Padding a string.
-func Padding(s, pad string, length int, pos PosFlag) string {
- diff := len(s) - length
+// 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
}
- if pad == "" || pad == " " {
- mark := ""
- if pos == PosRight { // to right
- mark = "-"
+ // pad space.
+ if len(s) == sLen && (pad == "" || pad == " ") {
+ // Sprintf: 是按字数来填充的,不管中英文都是一个字符 - 有问题
+ if padPos == PosRight { // to right
+ return s + strings.Repeat(" ", -diff)
}
-
- // padding left: "%7s", padding right: "%-7s"
- tpl := fmt.Sprintf("%s%d", mark, length)
- return fmt.Sprintf(`%`+tpl+`s`, s)
+ return strings.Repeat(" ", -diff) + s
}
- if pos == PosRight { // to right
+ // other character.
+ if padPos == PosRight { // to right
return s + Repeat(pad, -diff)
}
return Repeat(pad, -diff) + s
@@ -53,26 +69,6 @@ func PadRight(s, pad string, length int) string {
return Padding(s, pad, length, PosRight)
}
-// Resize a string by given length and align settings. padding space.
-func Resize(s string, length int, align PosFlag) string {
- diff := len(s) - length
- if diff >= 0 { // do not need padding.
- return s
- }
-
- if align == PosMiddle {
- padLn := (length - len(s)) / 2
- if diff := length - padLn*2; diff > 0 {
- s += " "
- }
-
- padStr := string(RepeatBytes(' ', padLn))
- return padStr + s + padStr
- }
-
- return Padding(s, " ", length, align)
-}
-
// 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)
@@ -130,6 +126,59 @@ 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
*************************************************************/
diff --git a/vendor/github.com/gookit/goutil/strutil/parse.go b/vendor/github.com/gookit/goutil/strutil/parse.go
index c03cb125d2..580b66baae 100644
--- a/vendor/github.com/gookit/goutil/strutil/parse.go
+++ b/vendor/github.com/gookit/goutil/strutil/parse.go
@@ -2,6 +2,7 @@ package strutil
import (
"errors"
+ "regexp"
"strconv"
"strings"
"time"
@@ -10,6 +11,13 @@ import (
"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...)
diff --git a/vendor/github.com/gookit/goutil/strutil/runes.go b/vendor/github.com/gookit/goutil/strutil/runes.go
index 6a3debba02..85048cd715 100644
--- a/vendor/github.com/gookit/goutil/strutil/runes.go
+++ b/vendor/github.com/gookit/goutil/strutil/runes.go
@@ -31,13 +31,29 @@ func IsSpaceRune(r rune) bool {
return r <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
}
-// Utf8Len count of the string
+// 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 of the string
+// Utf8len count rune of the string.
func Utf8len(s string) int { return utf8.RuneCountInString(s) }
-// RuneCount of the string
+// 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.
@@ -75,8 +91,8 @@ func Utf8Width(s string) int { return RunesWidth([]rune(s)) }
// str := "hi,你好"
//
// len(str) // 9
-// strutil.Utf8Width(str) // 7
-// len([]rune(str)) = utf8.RuneCountInString(s) // 5
+// strutil.RunesWidth(str) // 7 一个中文字占两个字符
+// len([]rune(str)) = utf8.RuneCountInString(s) // 5 按字算
func RunesWidth(rs []rune) (w int) {
if len(rs) == 0 {
return
@@ -95,8 +111,11 @@ func Truncate(s string, w int, tail string) string { return Utf8Truncate(s, w, t
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 {
- if sw := Utf8Width(s); sw <= w {
+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
}
@@ -161,10 +180,14 @@ func Utf8Split(s string, w int) []string {
return ss
}
-// TextWrap a string by "\n"
+// 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 := ""
diff --git a/vendor/github.com/gookit/goutil/strutil/split.go b/vendor/github.com/gookit/goutil/strutil/split.go
index bb1abb07e6..76898ccb5d 100644
--- a/vendor/github.com/gookit/goutil/strutil/split.go
+++ b/vendor/github.com/gookit/goutil/strutil/split.go
@@ -148,7 +148,7 @@ func SplitByWhitespace(s string) []string {
return whitespaceRegexp.Split(s, -1)
}
-// Substr for a string.
+// 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)
diff --git a/vendor/github.com/gookit/goutil/strutil/strutil.go b/vendor/github.com/gookit/goutil/strutil/strutil.go
index 9e46773b94..2e15436f90 100644
--- a/vendor/github.com/gookit/goutil/strutil/strutil.go
+++ b/vendor/github.com/gookit/goutil/strutil/strutil.go
@@ -3,7 +3,6 @@ package strutil
import (
"errors"
- "fmt"
"strings"
"github.com/gookit/goutil/comdef"
@@ -77,34 +76,6 @@ func Valid(ss ...string) string {
return ""
}
-// 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)
-}
-
-// 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)
-}
-
// SubstrCount returns the number of times the substr substring occurs in the s string.
// Actually, it comes from strings.Count().
//
diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go b/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go
index bcfff8d248..ea91b5b4f9 100644
--- a/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go
+++ b/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go
@@ -15,8 +15,8 @@ import (
// 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')}
+// - 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
diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go b/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go
index 7ade12c091..931e9221da 100644
--- a/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go
+++ b/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go
@@ -13,14 +13,14 @@ import (
// ReplaceVars by regex replace given tpl vars.
//
-// If a format is empty, will use {const defaultVarFormat}
+// 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}
+// 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)
}
diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go b/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go
index 40044f300f..798e0b25b6 100644
--- a/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go
+++ b/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go
@@ -1,6 +1,7 @@
package textutil
import (
+ "os"
"reflect"
"regexp"
"strings"
@@ -11,7 +12,8 @@ import (
"github.com/gookit/goutil/strutil"
)
-const defaultVarFormat = "{{,}}"
+// DefaultVarFormat var template
+const DefaultVarFormat = "{{,}}"
// FallbackFn type
type FallbackFn = func(name string) (val string, ok bool)
@@ -28,7 +30,7 @@ type VarReplacer struct {
//
// eg: {name: {a: 1, b: 2}} => {name.a: 1, name.b: 2}
flatSubs bool
- // do parse env value. default: false
+ // do parse env value in var-value and tpl var-name. default: false
parseEnv bool
// do parse default value. default: false
//
@@ -46,7 +48,7 @@ type VarReplacer struct {
RenderFn func(s string, vs map[string]string) string
}
-// NewVarReplacer instance.
+// NewVarReplacer instance. default format is: DefaultVarFormat
//
// Usage:
//
@@ -104,7 +106,7 @@ func (r *VarReplacer) OnNotFound(fn FallbackFn) *VarReplacer {
// WithFormat custom var template
func (r *VarReplacer) WithFormat(format string) *VarReplacer {
- r.Left, r.Right = strutil.QuietCut(strutil.OrElse(format, defaultVarFormat), ",")
+ r.Left, r.Right = strutil.QuietCut(strutil.OrElse(format, DefaultVarFormat), ",")
r.Init()
return r
}
@@ -183,7 +185,11 @@ func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string {
if r.parseEnv {
for name, val := range varMap {
- varMap[name] = varexpr.SafeParse(val)
+ if strings.Contains(val, "${") {
+ varMap[name] = varexpr.SafeParse(val)
+ } else {
+ varMap[name] = val
+ }
}
}
@@ -223,6 +229,11 @@ func (r *VarReplacer) doReplace(s string, varMap map[string]string) string {
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 {
diff --git a/vendor/github.com/gookit/goutil/syncs/chan.go b/vendor/github.com/gookit/goutil/syncs/chan.go
index 458d1aedfb..f9be49fd3c 100644
--- a/vendor/github.com/gookit/goutil/syncs/chan.go
+++ b/vendor/github.com/gookit/goutil/syncs/chan.go
@@ -1,10 +1,21 @@
package syncs
+import "fmt"
+
// Go is a basic promise implementation: it wraps calls a function in a goroutine
// and returns a channel which will later return the function's return value.
+//
+// if panic happen, it will be recovered and return as error
func Go(f func() error) error {
ch := make(chan error)
go func() {
+ // add recovery handle
+ defer func() {
+ if r := recover(); r != nil {
+ ch <- fmt.Errorf("panic recover: %v", r)
+ }
+ }()
+
ch <- f()
}()
return <-ch
diff --git a/vendor/github.com/gookit/goutil/syncs/group.go b/vendor/github.com/gookit/goutil/syncs/group.go
index 2a41aa5bbb..c393837f59 100644
--- a/vendor/github.com/gookit/goutil/syncs/group.go
+++ b/vendor/github.com/gookit/goutil/syncs/group.go
@@ -2,6 +2,7 @@ package syncs
import (
"context"
+ "fmt"
"golang.org/x/sync/errgroup"
)
@@ -44,3 +45,39 @@ func (g *ErrGroup) Add(handlers ...func() error) {
g.Go(handler)
}
}
+
+// Go add a handler function. if panic occurs, will catch it and return as error
+func (g *ErrGroup) Go(fn func() error) {
+ // Group.Go: 调用给定函数的新流程。
+ // 第一次“离开”的调用必须在等待之前发生。它会一直阻塞,直到新 goroutine 能够被添加,而该组中活跃 goroutine 数量不超过配置限制。
+ // 第一次返回非零错误的调用会取消该组的上下文,如果该组是由调用 WithContext 创建的。错误将由等待返回。
+ g.Group.Go(func() (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("panic recover: %v", r)
+ }
+ }()
+ err = fn()
+ return
+ })
+}
+
+// TryGo calls the given function in a new goroutine only if the number of
+// active goroutines in the group is currently below the configured limit.
+//
+// The return value reports whether the goroutine was started.
+//
+// If panic occurs, will catch it and return as error
+func (g *ErrGroup) TryGo(fn func() error) bool {
+ // 只有当组中当前活跃的 goroutine 数量低于配置限制时,才会在新的 Goroutine 中调用该函数。
+ // 返回值报告是否启动了goroutine。
+ return g.Group.TryGo(func() (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("panic recover: %v", r)
+ }
+ }()
+ err = fn()
+ return
+ })
+}
diff --git a/vendor/github.com/gookit/goutil/syncs/syncs.go b/vendor/github.com/gookit/goutil/syncs/syncs.go
index e8933afdc7..9b900974e9 100644
--- a/vendor/github.com/gookit/goutil/syncs/syncs.go
+++ b/vendor/github.com/gookit/goutil/syncs/syncs.go
@@ -1,9 +1,21 @@
// Package syncs provides synchronization primitives util functions.
package syncs
-import "sync"
+import (
+ "context"
+ "sync"
+)
// WaitGroup is a wrapper of sync.WaitGroup.
+//
+// Usage:
+//
+// wg := syncs.WaitGroup{}
+// wg.Go(func() {
+// time.Sleep(time.Second)
+// })
+// wg.Wait()
+//
type WaitGroup struct {
sync.WaitGroup
}
@@ -16,3 +28,64 @@ func (wg *WaitGroup) Go(fn func()) {
fn()
}()
}
+
+// ContextValue create a new context with given value
+func ContextValue(key, value any) context.Context {
+ return context.WithValue(context.Background(), key, value)
+}
+
+// SafeMap is a goroutine-safe map.
+type SafeMap[K comparable, V any] struct {
+ mu sync.RWMutex
+ data map[K]V
+}
+
+// NewSafeMap create a new SafeMap instance.
+func NewSafeMap[K comparable, V any]() *SafeMap[K, V] {
+ return &SafeMap[K, V]{
+ data: make(map[K]V),
+ }
+}
+
+// Set value to map.
+func (m *SafeMap[K, V]) Set(key K, value V) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ m.data[key] = value
+}
+
+// Get value from map.
+func (m *SafeMap[K, V]) Get(key K) (value V, ok bool) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ value, ok = m.data[key]
+ return value, ok
+}
+
+// Delete value from map.
+func (m *SafeMap[K, V]) Delete(key K) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ delete(m.data, key)
+}
+
+// Range iterate map values.
+func (m *SafeMap[K, V]) Range(fn func(key K, value V)) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ for key, value := range m.data {
+ fn(key, value)
+ }
+}
+
+// Clear do clear all map values.
+func (m *SafeMap[K, V]) Clear() {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ m.data = make(map[K]V)
+}
+
+// Len get map length.
+func (m *SafeMap[K, V]) Len() int {
+ return len(m.data)
+}
diff --git a/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go b/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go
index a9bb0c9834..279d6027c4 100644
--- a/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go
+++ b/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go
@@ -43,7 +43,7 @@ func VersionInfoBySys() *OSVersionInfo {
// OsVersionByParse Get Windows system version information by parse string
//
-// cmdOut eg: "Microsoft Windows [Version 10.0.22631.4391]"
+// cmdOut eg: "Microsoft Windows [Version 10.0.22631.4391]"
func OsVersionByParse(cmdOut string) (*OSVersionInfo, error) {
return parseOsVersionString(cmdOut)
}
@@ -153,4 +153,3 @@ func parseOsVersionString(out string) (*OSVersionInfo, error) {
}
return &ovi, nil
}
-
diff --git a/vendor/github.com/gookit/goutil/sysutil/sysenv.go b/vendor/github.com/gookit/goutil/sysutil/sysenv.go
index 0aac4d61c4..1d623a4864 100644
--- a/vendor/github.com/gookit/goutil/sysutil/sysenv.go
+++ b/vendor/github.com/gookit/goutil/sysutil/sysenv.go
@@ -203,7 +203,7 @@ func SearchPath(keywords string, limit int, opts ...SearchPathOption) []string {
// if windows, will limit with .exe, .bat, .cmd
for _, fPath := range matches {
fExt := filepath.Ext(fPath)
- if checkfn.StringsContains(opt.LimitExt, fExt) {
+ if !checkfn.StringsContains(opt.LimitExt, fExt) {
continue
}
list = append(list, fPath)
diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go
index f0962d8a2d..f8bb1503c7 100644
--- a/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go
+++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go
@@ -1,4 +1,5 @@
//go:build darwin
+
package sysutil
import "os/exec"
diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_unix.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_unix.go
new file mode 100644
index 0000000000..86be67a123
--- /dev/null
+++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_unix.go
@@ -0,0 +1,60 @@
+//go:build freebsd || openbsd || netbsd || dragonfly
+
+package sysutil
+
+import (
+ "os/exec"
+ "runtime"
+ "strings"
+)
+
+// OsName system name. like runtime.GOOS.
+// For Unix systems, this will be the actual GOOS value (freebsd, openbsd, netbsd, dragonfly)
+var OsName = runtime.GOOS
+
+// IsWin system. linux windows darwin
+func IsWin() bool { return false }
+
+// IsWindows system. linux windows darwin
+func IsWindows() bool { return false }
+
+// IsMac system
+func IsMac() bool { return false }
+
+// IsDarwin system
+func IsDarwin() bool { return false }
+
+// IsLinux system
+func IsLinux() bool { return false }
+
+// There are multiple possible providers to open a browser on Unix systems
+// Similar to Linux, try xdg-open and other common browser launchers
+var openBins = []string{"xdg-open", "x-www-browser", "www-browser", "firefox", "chrome", "chromium"}
+
+// OpenURL Open file or browser URL
+//
+// Mac:
+//
+// open 'https://github.com/inhere'
+//
+// Linux:
+//
+// xdg-open URL
+// x-www-browser 'https://github.com/inhere'
+//
+// Windows:
+//
+// cmd /c start https://github.com/inhere
+//
+// Unix (FreeBSD, OpenBSD, etc.):
+//
+// Try xdg-open, x-www-browser, or fallback browsers
+func OpenURL(URL string) error {
+ for _, bin := range openBins {
+ if _, err := exec.LookPath(bin); err == nil {
+ return exec.Command(bin, URL).Run()
+ }
+ }
+
+ return &exec.Error{Name: strings.Join(openBins, ","), Err: exec.ErrNotFound}
+}
diff --git a/vendor/github.com/gookit/goutil/x/basefn/basefn.go b/vendor/github.com/gookit/goutil/x/basefn/basefn.go
index ec9e8956e0..3ab144eee7 100644
--- a/vendor/github.com/gookit/goutil/x/basefn/basefn.go
+++ b/vendor/github.com/gookit/goutil/x/basefn/basefn.go
@@ -4,42 +4,20 @@ package basefn
import (
"errors"
"fmt"
+
+ "github.com/gookit/goutil/internal/comfunc"
)
// Panicf format panic message use fmt.Sprintf
-func Panicf(format string, v ...any) {
- panic(fmt.Sprintf(format, v...))
-}
+func Panicf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
// PanicIf if cond = true, panics with an error message
func PanicIf(cond bool, fmtAndArgs ...any) {
if cond {
- panic(errors.New(formatWithArgs(fmtAndArgs)))
+ panic(errors.New(comfunc.FormatWithArgs(fmtAndArgs)))
}
}
-func formatWithArgs(fmtAndArgs []any) string {
- ln := len(fmtAndArgs)
- if ln == 0 {
- return ""
- }
-
- first := fmtAndArgs[0]
-
- if ln == 1 {
- if msgAsStr, ok := first.(string); ok {
- return msgAsStr
- }
- return fmt.Sprintf("%+v", first)
- }
-
- // is template string.
- if tplStr, ok := first.(string); ok {
- return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
- }
- return fmt.Sprint(fmtAndArgs...)
-}
-
// PanicErr panics if error is not empty
func PanicErr(err error) {
if err != nil {
diff --git a/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go b/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go
index 69e554342f..9162dce844 100644
--- a/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go
+++ b/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go
@@ -4,8 +4,6 @@ import (
"fmt"
"regexp"
"strings"
-
- "github.com/gookit/goutil/x/termenv"
)
// output colored text like uses custom tag.
@@ -67,6 +65,7 @@ var colorTags = map[string]string{
"normal": "0;39", // no color
"brown": "0;33", // #A52A2A
"yellow": "0;33",
+ "ylw": "0;33",
"ylw0": "0;33",
"yellowB": "1;33", // with bold
"ylw1": "1;33",
@@ -178,7 +177,7 @@ func ReplaceTag(str string) string { return ParseTagByEnv(str) }
// ParseTagByEnv parse given string. will check package setting.
func ParseTagByEnv(str string) string {
// disable OR not support color
- if termenv.NoColor() || !termenv.IsSupportColor() {
+ if shouldCleanColor() {
return ClearTag(str)
}
return ParseTag(str)
diff --git a/vendor/github.com/gookit/goutil/x/ccolor/style.go b/vendor/github.com/gookit/goutil/x/ccolor/style.go
index 5a80891202..2fd9f5791f 100644
--- a/vendor/github.com/gookit/goutil/x/ccolor/style.go
+++ b/vendor/github.com/gookit/goutil/x/ccolor/style.go
@@ -8,8 +8,9 @@ import (
// String color style string. TODO
// eg:
-// s := String("red,bold")
-// s.Println("some text message")
+//
+// s := String("red,bold")
+// s.Println("some text message")
type String string
// Style for color render.
diff --git a/vendor/github.com/gookit/goutil/x/ccolor/util.go b/vendor/github.com/gookit/goutil/x/ccolor/util.go
index 46ed2b4bbc..a00110dc29 100644
--- a/vendor/github.com/gookit/goutil/x/ccolor/util.go
+++ b/vendor/github.com/gookit/goutil/x/ccolor/util.go
@@ -22,6 +22,10 @@ func ColorsToCode(colors ...Color) string {
return strings.Join(codes, ";")
}
+func shouldCleanColor() bool {
+ return termenv.NoColor() || !termenv.IsSupportColor()
+}
+
/*************************************************************
* render color code
*************************************************************/
@@ -43,7 +47,7 @@ func RenderCode(code string, args ...any) string {
}
// disabled OR not support color
- if !termenv.IsSupportColor() {
+ if shouldCleanColor() {
return ClearCode(message)
}
@@ -62,7 +66,7 @@ func RenderString(code string, str string) string {
}
// disabled OR not support color
- if !termenv.IsSupportColor() {
+ if shouldCleanColor() {
return ClearCode(str)
}
@@ -79,7 +83,7 @@ func RenderWithSpaces(code string, args ...any) string {
}
// disabled OR not support color
- if !termenv.IsSupportColor() {
+ if shouldCleanColor() {
return ClearCode(msg)
}
return StartSet + code + "m" + msg + ResetSet
@@ -128,4 +132,3 @@ func formatLikePrintln(args []any) (message string) {
}
return
}
-
diff --git a/vendor/github.com/gookit/goutil/x/goinfo/stack.go b/vendor/github.com/gookit/goutil/x/goinfo/stack.go
index 3912b7aa9b..e6183569d7 100644
--- a/vendor/github.com/gookit/goutil/x/goinfo/stack.go
+++ b/vendor/github.com/gookit/goutil/x/goinfo/stack.go
@@ -124,12 +124,12 @@ type CallerFilterFunc func(file string, fc *runtime.Func) bool
//
// Usage:
//
-// cs := sysutil.CallersInfos(3, 2)
-// for _, ci := range cs {
-// fc := runtime.FuncForPC(pc)
-// // maybe need check fc = nil
-// fnName = fc.Name()
-// }
+// cs := sysutil.CallersInfos(3, 2)
+// for _, ci := range cs {
+// fc := runtime.FuncForPC(pc)
+// // maybe need check fc = nil
+// fnName = fc.Name()
+// }
func CallersInfos(skip, num int, filters ...CallerFilterFunc) []*CallerInfo {
filterLn := len(filters)
callers := make([]*CallerInfo, 0, num)
diff --git a/vendor/github.com/gookit/goutil/x/stdio/stdio.go b/vendor/github.com/gookit/goutil/x/stdio/stdio.go
index c15253e685..d58d7044f3 100644
--- a/vendor/github.com/gookit/goutil/x/stdio/stdio.go
+++ b/vendor/github.com/gookit/goutil/x/stdio/stdio.go
@@ -61,6 +61,11 @@ func NewScanner(in any) *bufio.Scanner {
}
}
+// SafeClose close io.Closer, ignore error
+func SafeClose(c io.Closer) {
+ _ = c.Close()
+}
+
// WriteByte to stdout, will ignore error
func WriteByte(b byte) {
_, _ = os.Stdout.Write([]byte{b})
diff --git a/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go b/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go
index 39f8e36787..ec157d5694 100644
--- a/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go
+++ b/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go
@@ -41,6 +41,6 @@ func detectSpecialTermColor(termVal string) (ColorLevel, bool) {
return TermColor16, false
}
-func syscallStdinFd() int {
- return syscall.Stdin
-}
\ No newline at end of file
+func syscallStdinFd() int { return syscall.Stdin }
+
+func syscallStdoutFd() int { return syscall.Stdout }
diff --git a/vendor/github.com/gookit/goutil/x/termenv/detect_windows.go b/vendor/github.com/gookit/goutil/x/termenv/detect_windows.go
index 7206625947..212ead033b 100644
--- a/vendor/github.com/gookit/goutil/x/termenv/detect_windows.go
+++ b/vendor/github.com/gookit/goutil/x/termenv/detect_windows.go
@@ -169,3 +169,8 @@ func EnableVTProcessing(stream syscall.Handle, enable bool) error {
func syscallStdinFd() int {
return int(syscall.Stdin)
}
+
+// on Windows, must convert to int
+func syscallStdoutFd() int {
+ return int(syscall.Stdout)
+}
diff --git a/vendor/github.com/gookit/goutil/x/termenv/termenv.go b/vendor/github.com/gookit/goutil/x/termenv/termenv.go
index dbc82ca5c2..2dd1095348 100644
--- a/vendor/github.com/gookit/goutil/x/termenv/termenv.go
+++ b/vendor/github.com/gookit/goutil/x/termenv/termenv.go
@@ -5,6 +5,7 @@ package termenv
import (
"fmt"
"os"
+ "strconv"
"golang.org/x/term"
)
@@ -60,20 +61,37 @@ func setLastErr(err error) {
// )
var terminalWidth, terminalHeight int
-// GetTermSize for current console terminal.
+// GetTermSize for current console terminal. will first try to get from environment variables COLUMNS and LINES.
func GetTermSize(refresh ...bool) (w int, h int) {
- if terminalWidth > 0 && len(refresh) > 0 && !refresh[0] {
+ if terminalWidth > 0 && (len(refresh) == 0 || !refresh[0]) {
+ return terminalWidth, terminalHeight
+ }
+
+ // 首先尝试从环境变量获取
+ if cols := os.Getenv("COLUMNS"); cols != "" {
+ if width, err := strconv.Atoi(cols); err == nil && width > 0 {
+ terminalWidth = width
+ }
+ }
+ if rows := os.Getenv("LINES"); rows != "" {
+ if height, err := strconv.Atoi(rows); err == nil && height > 0 {
+ terminalHeight = height
+ }
+ }
+ if terminalWidth > 0 && terminalHeight > 0 {
return terminalWidth, terminalHeight
}
var err error
- w, h, err = term.GetSize(syscallStdinFd())
+ w, h, err = term.GetSize(syscallStdoutFd())
if err != nil {
+ debugf("get terminal size error: %v", err)
return
}
// cache result
terminalWidth, terminalHeight = w, h
+ debugf("get terminal size: %d,%d", w, h)
return
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 28a619a997..630b3ae618 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -744,7 +744,7 @@ github.com/google/uuid
## explicit; go 1.19
github.com/gookit/config/v2
github.com/gookit/config/v2/yaml
-# github.com/gookit/goutil v0.7.1
+# github.com/gookit/goutil v0.7.4
## explicit; go 1.19
github.com/gookit/goutil
github.com/gookit/goutil/arrutil