Update github.com/gookit/goutil to v0.7.4 for FreeBSD compatibility

The goutil that OpenCloud currently uses is one version from the release that adds FreeBSD support, this now compiles successfully on FreeBSD.
This commit is contained in:
PC Kitty
2026-04-23 19:45:16 -07:00
committed by Ralf Haferkamp
parent 10e54ca717
commit d97217f22c
74 changed files with 2942 additions and 1005 deletions

2
go.mod
View File

@@ -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

4
go.sum
View File

@@ -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=

View File

@@ -18,7 +18,9 @@
*.out
*.cov
.DS_Store
.xenv.toml
*~
.claude/
testdata/
vendor/

View File

@@ -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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### Cflag
> Package `github.com/gookit/goutil/cflag`
`cflag` - Wraps and extends go `flag.FlagSet` to build simple command line applications
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### `cflag` Usage
`cflag` usage please see [cflag/README.md](cflag/README.md)
### CLI Utils
> Package `github.com/gookit/goutil/cliutil`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### Examples
example code:
@@ -456,10 +484,13 @@ Preview:
![](dump/_examples/preview-nested-struct.png)
### ENV/Environment
> Package `github.com/gookit/goutil/envutil`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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.
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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`
<details><summary>Click to see functions 👈</summary>
```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)
```
</details>
### Math/Number
> Package `github.com/gookit/goutil/mathutil`
Package `mathutil` provide math(int, number) util functions. eg: convert, math calc, random
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### Reflects
> Package `github.com/gookit/goutil/reflects`
Package `reflects` Provide extends reflection util functions. eg: check, convert, value set, etc.
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### 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
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### Strings
### String Utils
> Package `github.com/gookit/goutil/strutil`
<details><summary>Click to see functions 👈</summary>
```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)
```
</details>
### 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`
<details><summary>Click to see functions 👈</summary>
```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)
```
</details>
### Testing Utils
> Package `github.com/gookit/goutil/testutil`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### 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.
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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

View File

@@ -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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### Cflag
> Package `github.com/gookit/goutil/cflag`
`cflag` - Wraps and extends go `flag.FlagSet` to build simple command line applications
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### `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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### Examples
example code:
@@ -456,10 +484,13 @@ Preview:
![](dump/_examples/preview-nested-struct.png)
### ENV/Environment
> Package `github.com/gookit/goutil/envutil`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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.
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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`
<details><summary>Click to see functions 👈</summary>
```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)
```
</details>
### Math/Number
> Package `github.com/gookit/goutil/mathutil`
Package `mathutil` provide math(int, number) util functions. eg: convert, math calc, random
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### Reflects
> Package `github.com/gookit/goutil/reflects`
Package `reflects` Provide extends reflection util functions. eg: check, convert, value set, etc.
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### 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
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### Strings
### String Utils
> Package `github.com/gookit/goutil/strutil`
<details><summary>Click to see functions 👈</summary>
```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)
```
</details>
### 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`
<details><summary>Click to see functions 👈</summary>
```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)
```
</details>
### Testing Utils
> Package `github.com/gookit/goutil/testutil`
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
### 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.
<details><summary>Click to see functions 👈</summary>
```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
```
</details>
#### 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

View File

@@ -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
*************************************************************/

View File

@@ -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
}

View File

@@ -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 }

View File

@@ -4,9 +4,9 @@ package byteutil
import (
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"math/rand"
"strconv"
"time"
)

View File

@@ -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')
}

View File

@@ -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]
}
}

View File

@@ -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
)

View File

@@ -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:

198
vendor/github.com/gookit/goutil/envutil/dotenv.go generated vendored Normal file
View File

@@ -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()
}

View File

@@ -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)

View File

@@ -36,7 +36,6 @@ func IsMSys() bool {
return sysutil.IsMSys()
}
// IsTerminal isatty check
//
// Usage:

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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() }

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -206,4 +206,3 @@ func FuncName(f any) string {
func PkgName(funcName string) string {
return goinfo.PkgName(funcName)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -72,4 +72,4 @@ func DecodeFile(file string, ptr any) error {
}
return json.Unmarshal(bs, ptr)
}
}

View File

@@ -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
}

View File

@@ -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.
*************************************************************/

View File

@@ -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 ""
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
}

648
vendor/github.com/gookit/goutil/mathutil/conv2int.go generated vendored Normal file
View File

@@ -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
}

View File

@@ -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

View File

@@ -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},

View File

@@ -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
}

19
vendor/github.com/gookit/goutil/mathutil/types.go generated vendored Normal file
View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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))

View File

@@ -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) {

View File

@@ -1,6 +1,7 @@
package strutil
import (
"fmt"
"regexp"
"strings"
"unicode"
@@ -184,3 +185,52 @@ func IndentBytes(b, prefix []byte) []byte {
}
return res
}
// Replaces replace multi strings
//
// pairs: {old1: new1, old2: new2, ...}
//
// Can also use:
//
// strings.NewReplacer("old1", "new1", "old2", "new2").Replace(str)
func Replaces(str string, pairs map[string]string) string {
return NewReplacer(pairs).Replace(str)
}
// ReplaceVars replaces simple variables in a string. format: {varName}
//
// Usage:
// strutil.ReplaceVars("{name}, age is {age}", map[string]string{
// "name": "Joe",
// "age": "18"
// })
func ReplaceVars(s string, vars map[string]string) string {
if !ContainsByte(s, '{') {
return s
}
// format var name to {name}
pairs := make(map[string]string)
for k, v := range vars {
vName := "{" + k + "}"
pairs[vName] = v
}
return NewReplacer(pairs).Replace(s)
}
// NewReplacer instance
func NewReplacer(pairs map[string]string) *strings.Replacer {
ss := make([]string, len(pairs)*2)
for old, newVal := range pairs {
ss = append(ss, old, newVal)
}
return strings.NewReplacer(ss...)
}
// WrapTag for given string.
func WrapTag(s, tag string) string {
if s == "" {
return s
}
return fmt.Sprintf("<%s>%s</%s>", tag, s, tag)
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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
*************************************************************/

View File

@@ -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...)

View File

@@ -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 := ""

View File

@@ -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)

View File

@@ -3,7 +3,6 @@ package strutil
import (
"errors"
"fmt"
"strings"
"github.com/gookit/goutil/comdef"
@@ -77,34 +76,6 @@ func Valid(ss ...string) string {
return ""
}
// Replaces replace multi strings
//
// pairs: {old1: new1, old2: new2, ...}
//
// Can also use:
//
// strings.NewReplacer("old1", "new1", "old2", "new2").Replace(str)
func Replaces(str string, pairs map[string]string) string {
return NewReplacer(pairs).Replace(str)
}
// NewReplacer instance
func NewReplacer(pairs map[string]string) *strings.Replacer {
ss := make([]string, len(pairs)*2)
for old, newVal := range pairs {
ss = append(ss, old, newVal)
}
return strings.NewReplacer(ss...)
}
// WrapTag for given string.
func WrapTag(s, tag string) string {
if s == "" {
return s
}
return fmt.Sprintf("<%s>%s</%s>", tag, s, tag)
}
// SubstrCount returns the number of times the substr substring occurs in the s string.
// Actually, it comes from strings.Count().
//

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
})
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -1,4 +1,5 @@
//go:build darwin
package sysutil
import "os/exec"

View File

@@ -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}
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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})

View File

@@ -41,6 +41,6 @@ func detectSpecialTermColor(termVal string) (ColorLevel, bool) {
return TermColor16, false
}
func syscallStdinFd() int {
return syscall.Stdin
}
func syscallStdinFd() int { return syscall.Stdin }
func syscallStdoutFd() int { return syscall.Stdout }

View File

@@ -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)
}

View File

@@ -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
}

2
vendor/modules.txt vendored
View File

@@ -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