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 ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/goutil?style=flat-square) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/goutil)](https://github.com/gookit/goutil) @@ -7,7 +7,7 @@ [![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](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: ![](dump/_examples/preview-nested-struct.png) + ### 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 ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/goutil?style=flat-square) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/goutil)](https://github.com/gookit/goutil) @@ -7,7 +7,7 @@ [![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](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: ![](dump/_examples/preview-nested-struct.png) + ### 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", 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", 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