Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
*.log
*.swp
.idea
.vscode/
*.patch
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
*.cov
.DS_Store
.xenv.toml
*~
.claude/
testdata/
vendor/
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2016 inhere
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+40
View File
@@ -0,0 +1,40 @@
# link https://github.com/humbug/box/blob/master/Makefile
#SHELL = /bin/sh
.DEFAULT_GOAL := help
# 每行命令之前必须有一个tab键。如果想用其他键,可以用内置变量.RECIPEPREFIX 声明
# mac 下这条声明 没起作用 !!
#.RECIPEPREFIX = >
.PHONY: all usage help clean
# 需要注意的是,每行命令在一个单独的shell中执行。这些Shell之间没有继承关系。
# - 解决办法是将两行命令写在一行,中间用分号分隔。
# - 或者在换行符前加反斜杠转义 \
# 接收命令行传入参数 make COMMAND tag=v2.0.4
# TAG=$(tag)
##There some make command for the project
##
help:
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' | sed -e 's/: / /'
##Available Commands:
readme: ## Generate or update README file by ./internal/gendoc
readme:
go run ./internal/gendoc -o README.md
go run ./internal/gendoc -o README.zh-CN.md -l zh-CN
readme-c: ## Generate or update README file and commit change to git
readme-c: readme
git add README.* internal
git commit -m ":memo: doc: update and re-generate README docs"
csfix: ## Fix code style for all files by go fmt
csfix:
go fmt ./...
csdiff: ## Display code style error files by gofmt
csdiff:
gofmt -l ./
+1886
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
# Array/Slice Utils
## Install
```shell
go get github.com/gookit/goutil/arrutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/arrutil)
## Functions API
> **Note**: doc by run `go doc ./arrutil`
```go
func AnyToString(arr any) string
func CloneSlice(data any) interface{}
func Contains(arr, val any) bool
func ExceptWhile(data any, fn Predicate) interface{}
func Excepts(first, second any, fn Comparer) interface{}
func Find(source any, fn Predicate) (interface{}, error)
func FindOrDefault(source any, fn Predicate, defaultValue any) interface{}
func FormatIndent(arr any, indent string) string
func GetRandomOne(arr any) interface{}
func HasValue(arr, val any) bool
func InStrings(elem string, ss []string) bool
func Int64sHas(ints []int64, val int64) bool
func Intersects(first any, second any, fn Comparer) interface{}
func IntsHas(ints []int, val int) bool
func JoinSlice(sep string, arr ...any) string
func JoinStrings(sep string, ss ...string) string
func MakeEmptySlice(itemType reflect.Type) interface{}
func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V
func MustToInt64s(arr any) []int64
func MustToStrings(arr any) []string
func NotContains(arr, val any) bool
func RandomOne(arr any) interface{}
func Reverse(ss []string)
func SliceToInt64s(arr []any) []int64
func SliceToString(arr ...any) string
func SliceToStrings(arr []any) []string
func StringsFilter(ss []string, filter ...func(s string) bool) []string
func StringsHas(ss []string, val string) bool
func StringsJoin(sep string, ss ...string) string
func StringsMap(ss []string, mapFn func(s string) string) []string
func StringsRemove(ss []string, s string) []string
func StringsToInts(ss []string) (ints []int, err error)
func StringsToSlice(ss []string) []interface{}
func TakeWhile(data any, fn Predicate) interface{}
func ToInt64s(arr any) (ret []int64, err error)
func ToString(arr []any) string
func ToStrings(arr any) (ret []string, err error)
func TrimStrings(ss []string, cutSet ...string) []string
func TwowaySearch(data any, item any, fn Comparer) (int, error)
func Union(first, second any, fn Comparer) interface{}
func Unique(arr any) interface{}
type ArrFormatter struct{ ... }
func NewFormatter(arr any) *ArrFormatter
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./cliutil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./cliutil/...
```
## Refers
- https://github.com/elliotchance/pie
+50
View File
@@ -0,0 +1,50 @@
## ArrUtil
`arrutil` 是一个用于操作数组和切片的工具包,提供了丰富的功能来简化 Go 语言中数组和切片的处理。
## Install
```shell
go get github.com/gookit/goutil/arrutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/arrutil)
## 基本功能
主要包括以下功能:
1. **数组/切片的基本操作**
- `RandomOne`:从数组或切片中随机获取一个元素。
- `Reverse`:反转数组或切片中的元素顺序。
2. **检查和查找**
- `Contains``HasValue`:检查数组或切片是否包含特定值。
- `InStrings``StringsHas`:检查字符串切片中是否包含特定字符串。
- `IntsHas``Int64sHas`:检查整数切片中是否包含特定整数值。
- `Find``FindOrDefault`:根据谓词函数查找元素,如果没有找到则返回默认值。
3. **集合操作**
- `Union`:计算两个切片的并集。
- `Intersects`:计算两个切片的交集。
- `Excepts``Differences`:计算两个切片的差集。
- `TwowaySearch`:在切片中双向搜索特定元素。
4. **转换和格式化**
- `ToInt64s``ToStrings`:将任意类型的切片转换为整数或字符串切片。
- `JoinSlice``JoinStrings`:将切片中的元素连接成一个字符串。
- `FormatIndent`:将数组或切片格式化为带有缩进的字符串。
5. **排序和过滤**
- `Sort`:对切片进行排序。
- `Filter`:根据条件过滤切片中的元素。
- `Remove`:从切片中移除特定元素。
6. **其他实用功能**
- `Unique`:去除切片中的重复元素。
- `FirstOr`:获取切片的第一个元素,如果切片为空则返回默认值。
这些功能使得在 Go 语言中处理数组和切片变得更加方便和高效。无论是进行数据处理、集合运算还是字符串操作,`arrutil` 都提供了一系列简洁且易于使用的函数来帮助开发者完成任务。
+18
View File
@@ -0,0 +1,18 @@
// Package arrutil provides some util functions for array, slice
package arrutil
import (
"github.com/gookit/goutil/mathutil"
)
// GetRandomOne get random element from an array/slice
func GetRandomOne[T any](arr []T) T { return RandomOne(arr) }
// RandomOne get random element from an array/slice
func RandomOne[T any](arr []T) T {
if ln := len(arr); ln > 0 {
i := mathutil.RandomInt(0, len(arr))
return arr[i]
}
panic("cannot get value from nil or empty slice")
}
+136
View File
@@ -0,0 +1,136 @@
package arrutil
import (
"reflect"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/mathutil"
)
// SliceHas check the slice contains the given value
func SliceHas[T comdef.ScalarType](slice []T, val T) bool {
for _, ele := range slice {
if ele == val {
return true
}
}
return false
}
// IntsHas check the []comdef.Integer contains the given value
func IntsHas[T comdef.Integer](ints []T, val T) bool {
for _, ele := range ints {
if ele == val {
return true
}
}
return false
}
// Int64sHas check the []int64 contains the given value
func Int64sHas(ints []int64, val int64) bool {
for _, ele := range ints {
if ele == val {
return true
}
}
return false
}
// StringsHas check the []string contains the given element
func StringsHas[T ~string](ss []T, val T) bool {
for _, ele := range ss {
if ele == val {
return true
}
}
return false
}
// InStrings check elem in the ss. alias of StringsHas()
func InStrings[T ~string](elem T, ss []T) bool {
return StringsHas(ss, elem)
}
// NotIn check the given value whether not in the list
func NotIn[T comdef.ScalarType](value T, list []T) bool {
return !In(value, list)
}
// In check the given value whether in the list
func In[T comdef.ScalarType](value T, list []T) bool {
for _, elem := range list {
if elem == value {
return true
}
}
return false
}
// ContainsAll check given values is sub-list of sample list.
func ContainsAll[T comdef.ScalarType](list, values []T) bool {
return IsSubList(values, list)
}
// IsSubList check given values is sub-list of sample list.
func IsSubList[T comdef.ScalarType](values, list []T) bool {
for _, value := range values {
if !In(value, list) {
return false
}
}
return true
}
// IsParent check given values is parent-list of samples.
func IsParent[T comdef.ScalarType](values, list []T) bool {
return IsSubList(list, values)
}
// HasValue check array(strings, intXs, uintXs) should be contained the given value(int(X),string).
func HasValue(arr, val any) bool { return Contains(arr, val) }
// Contains check slice/array(strings, intXs, uintXs) should be contained the given value(int(X),string).
//
// TIP: Difference the In(), Contains() will try to convert value type,
// and Contains() support array type.
func Contains(arr, val any) bool {
if val == nil || arr == nil {
return false
}
// if is string value
if strVal, ok := val.(string); ok {
if ss, ok := arr.([]string); ok {
return StringsHas(ss, strVal)
}
rv := reflect.ValueOf(arr)
if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {
for i := 0; i < rv.Len(); i++ {
if v, ok := rv.Index(i).Interface().(string); ok && strings.EqualFold(v, strVal) {
return true
}
}
}
return false
}
// as int value
intVal, err := mathutil.Int64(val)
if err != nil {
return false
}
if int64s, err := ToInt64s(arr); err == nil {
return Int64sHas(int64s, intVal)
}
return false
}
// NotContains check array(strings, ints, uints) should be not contains the given value.
func NotContains(arr, val any) bool {
return !Contains(arr, val)
}
+338
View File
@@ -0,0 +1,338 @@
package arrutil
import (
"errors"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/reflects"
)
// ErrElementNotFound is the error returned when the element is not found.
var ErrElementNotFound = errors.New("element not found")
// Comparer Function to compare two elements.
type Comparer[T any] func(a, b T) int
// type Comparer func(a, b any) int
// Predicate Function to predicate a struct/value satisfies a condition.
type Predicate[T any] func(v T) bool
// StringEqualsComparer Comparer for string. It will compare the string by their value.
//
// returns: 0 if equal, -1 if a != b
func StringEqualsComparer(a, b string) int {
if a == b {
return 0
}
return -1
}
// ValueEqualsComparer Comparer for comdef.Compared type. It will compare by their value.
//
// returns: 0 if equal, -1 if a != b
func ValueEqualsComparer[T comdef.Compared](a, b T) int {
if a == b {
return 0
}
return -1
}
// ReflectEqualsComparer Comparer for struct ptr. It will compare by reflect.Value
//
// returns: 0 if equal, -1 if a != b
func ReflectEqualsComparer[T any](a, b T) int {
if reflects.IsEqual(a, b) {
return 0
}
return -1
}
// ElemTypeEqualsComparer Comparer for struct/value. It will compare the struct by their element type.
//
// returns: 0 if same type, -1 if not.
func ElemTypeEqualsComparer[T any](a, b T) int {
at := reflects.TypeOf(a).SafeElem()
bt := reflects.TypeOf(b).SafeElem()
if at == bt {
return 0
}
return -1
}
// TwowaySearch find specialized element in a slice forward and backward in the same time, should be more quickly.
//
// - data: the slice to search in. MUST BE A SLICE.
// - item: the element to search.
// - fn: the comparer function.
// - return: the index of the element, or -1 if not found.
func TwowaySearch[T any](data []T, item T, fn Comparer[T]) (int, error) {
if data == nil {
return -1, errors.New("collections.TwowaySearch: data is nil")
}
if fn == nil {
return -1, errors.New("collections.TwowaySearch: fn is nil")
}
if len(data) == 0 {
return -1, errors.New("collections.TwowaySearch: data is empty")
}
forward := 0
backward := len(data) - 1
for forward <= backward {
if fn(data[forward], item) == 0 {
return forward, nil
}
if fn(data[backward], item) == 0 {
return backward, nil
}
forward++
backward--
}
return -1, ErrElementNotFound
}
// CloneSlice Clone a slice.
//
// data: the slice to clone.
// returns: the cloned slice.
func CloneSlice[T any](data []T) []T {
nt := make([]T, 0, len(data))
nt = append(nt, data...)
return nt
}
// Diff Produces the set difference of two slice according to a comparer function. alias of Differences
func Diff[T any](first, second []T, fn Comparer[T]) []T {
return Differences(first, second, fn)
}
// Differences Produces the set difference of two slice according to a comparer function.
//
// - first: the first slice. MUST BE A SLICE.
// - second: the second slice. MUST BE A SLICE.
// - fn: the comparer function.
// - returns: the difference of the two slices.
//
// Example:
//
// // Output: []string{"c"}
// Differences([]string{"a", "b", "c"}, []string{"a", "b"}, arrutil.StringEqualsComparer
func Differences[T any](first, second []T, fn Comparer[T]) []T {
firstLen := len(first)
if firstLen == 0 {
return CloneSlice(second)
}
secondLen := len(second)
if secondLen == 0 {
return CloneSlice(first)
}
maxLn := firstLen
if secondLen > firstLen {
maxLn = secondLen
}
result := make([]T, 0)
for i := 0; i < maxLn; i++ {
if i < firstLen {
s := first[i]
if i, _ := TwowaySearch(second, s, fn); i < 0 {
result = append(result, s)
}
}
if i < secondLen {
t := second[i]
if i, _ := TwowaySearch(first, t, fn); i < 0 {
result = append(result, t)
}
}
}
return result
}
// Excepts Produces the set difference of two slice according to a comparer function.
//
// - first: the first slice. MUST BE A SLICE.
// - second: the second slice. MUST BE A SLICE.
// - fn: the comparer function.
// - returns: the difference of the two slices.
//
// Example:
//
// // Output: []string{"c"}
// Excepts([]string{"a", "b", "c"}, []string{"a", "b"}, arrutil.StringEqualsComparer)
func Excepts[T any](first, second []T, fn Comparer[T]) []T {
if len(first) == 0 {
return make([]T, 0)
}
if len(second) == 0 {
return CloneSlice(first)
}
result := make([]T, 0)
for _, s := range first {
if i, _ := TwowaySearch(second, s, fn); i < 0 {
result = append(result, s)
}
}
return result
}
// Intersects Produces to intersect of two slice according to a comparer function.
//
// - first: the first slice. MUST BE A SLICE.
// - second: the second slice. MUST BE A SLICE.
// - fn: the comparer function.
// - returns: to intersect of the two slices.
//
// Example:
//
// // Output: []string{"a", "b"}
// Intersects([]string{"a", "b", "c"}, []string{"a", "b"}, arrutil.ValueEqualsComparer)
func Intersects[T any](first, second []T, fn Comparer[T]) []T {
if len(first) == 0 || len(second) == 0 {
return make([]T, 0)
}
result := make([]T, 0)
for _, s := range first {
if i, _ := TwowaySearch(second, s, fn); i >= 0 {
result = append(result, s)
}
}
return result
}
// Union Produces the set union of two slice according to a comparer function
//
// - first: the first slice. MUST BE A SLICE.
// - second: the second slice. MUST BE A SLICE.
// - fn: the comparer function.
// - returns: the union of the two slices.
//
// Example:
//
// // Output: []string{"a", "b", "c"}
// sl := Union([]string{"a", "b", "c"}, []string{"a", "b"}, arrutil.ValueEqualsComparer)
func Union[T any](first, second []T, fn Comparer[T]) []T {
if len(first) == 0 {
return CloneSlice(second)
}
excepts := Excepts(second, first, fn)
nt := make([]T, 0, len(first)+len(second))
nt = append(nt, first...)
return append(nt, excepts...)
}
// Find Produces the value of a slice according to a predicate function.
//
// - source: the slice. MUST BE A SLICE.
// - fn: the predicate function.
// - returns: the struct/value of the slice.
//
// Example:
//
// // Output: "c"
// val := Find([]string{"a", "b", "c"}, func(s string) bool {
// return s == "c"
// })
func Find[T any](source []T, fn Predicate[T]) (v T, err error) {
err = ErrElementNotFound
if len(source) == 0 {
return
}
for _, s := range source {
if fn(s) {
return s, nil
}
}
return
}
// FindOrDefault Produce the value f a slice to a predicate function,
// Produce default value when predicate function not found.
//
// - source: the slice. MUST BE A SLICE.
// - fn: the predicate function.
// - defaultValue: the default value.
// - returns: the value of the slice.
//
// Example:
//
// // Output: "d"
// val := FindOrDefault([]string{"a", "b", "c"}, func(s string) bool {
// return s == "d"
// }, "d")
func FindOrDefault[T any](source []T, fn Predicate[T], defaultValue T) T {
item, err := Find(source, fn)
if err != nil {
return defaultValue
}
return item
}
// TakeWhile Produce the set of a slice according to a predicate function,
// Produce empty slice when predicate function not matched.
//
// - data: the slice. MUST BE A SLICE.
// - fn: the predicate function.
// - returns: the set of the slice.
//
// Example:
//
// // Output: []string{"a", "b"}
// sl := TakeWhile([]string{"a", "b", "c"}, func(s string) bool {
// return s != "c"
// })
func TakeWhile[T any](data []T, fn Predicate[T]) []T {
result := make([]T, 0)
if len(data) == 0 {
return result
}
for _, v := range data {
if fn(v) {
result = append(result, v)
}
}
return result
}
// ExceptWhile Produce the set of a slice except with a predicate function,
// Produce original slice when predicate function not match.
//
// - data: the slice. MUST BE A SLICE.
// - fn: the predicate function.
// - returns: the set of the slice.
//
// Example:
//
// // Output: []string{"a", "b"}
// sl := ExceptWhile([]string{"a", "b", "c"}, func(s string) bool {
// return s == "c"
// })
func ExceptWhile[T any](data []T, fn Predicate[T]) []T {
result := make([]T, 0)
if len(data) == 0 {
return result
}
for _, v := range data {
if !fn(v) {
result = append(result, v)
}
}
return result
}
+309
View File
@@ -0,0 +1,309 @@
package arrutil
import (
"errors"
"reflect"
"strconv"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/strutil"
)
// ErrInvalidType error
var ErrInvalidType = errors.New("the input param type is invalid")
/*************************************************************
* Join func for slice
*************************************************************/
// JoinStrings alias of strings.Join
func JoinStrings(sep string, ss ...string) string {
return strings.Join(ss, sep)
}
// StringsJoin alias of strings.Join
func StringsJoin(sep string, ss ...string) string {
return strings.Join(ss, sep)
}
// JoinTyped join typed []T slice to string.
//
// Usage:
//
// JoinTyped(",", 1,2,3) // "1,2,3"
// JoinTyped(",", "a","b","c") // "a,b,c"
// JoinTyped[any](",", "a",1,"c") // "a,1,c"
func JoinTyped[T any](sep string, arr ...T) string {
if arr == nil {
return ""
}
var sb strings.Builder
for i, v := range arr {
if i > 0 {
sb.WriteString(sep)
}
sb.WriteString(strutil.QuietString(v))
}
return sb.String()
}
// JoinSlice join []any slice to string.
func JoinSlice(sep string, arr ...any) string {
if arr == nil {
return ""
}
var sb strings.Builder
for i, v := range arr {
if i > 0 {
sb.WriteString(sep)
}
sb.WriteString(strutil.QuietString(v))
}
return sb.String()
}
/*************************************************************
* convert func for ints
*************************************************************/
// IntsToString convert []T to string
func IntsToString[T comdef.Integer](ints []T) string {
if len(ints) == 0 {
return "[]"
}
var sb strings.Builder
sb.WriteByte('[')
for i, v := range ints {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(strconv.FormatInt(int64(v), 10))
}
sb.WriteByte(']')
return sb.String()
}
// ToInt64s convert any(allow: array,slice) to []int64
func ToInt64s(arr any) (ret []int64, err error) {
rv := reflect.ValueOf(arr)
if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
err = ErrInvalidType
return
}
for i := 0; i < rv.Len(); i++ {
i64, err := mathutil.Int64(rv.Index(i).Interface())
if err != nil {
return []int64{}, err
}
ret = append(ret, i64)
}
return
}
// MustToInt64s convert any(allow: array,slice) to []int64
func MustToInt64s(arr any) []int64 {
ret, _ := ToInt64s(arr)
return ret
}
// SliceToInt64s convert []any to []int64
func SliceToInt64s(arr []any) []int64 {
i64s := make([]int64, len(arr))
for i, v := range arr {
i64s[i] = mathutil.QuietInt64(v)
}
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
*************************************************************/
// AnyToSlice convert any(allow: array,slice) to []any
func AnyToSlice(sl any) (ls []any, err error) {
rfKeys := reflect.ValueOf(sl)
if rfKeys.Kind() != reflect.Slice && rfKeys.Kind() != reflect.Array {
return nil, ErrInvalidType
}
for i := 0; i < rfKeys.Len(); i++ {
ls = append(ls, rfKeys.Index(i).Interface())
}
return
}
// AnyToStrings convert array or slice to []string
func AnyToStrings(arr any) []string {
ret, _ := ToStrings(arr)
return ret
}
// MustToStrings convert array or slice to []string
func MustToStrings(arr any) []string {
ret, err := ToStrings(arr)
if err != nil {
panic(err)
}
return ret
}
// ToStrings convert any(allow: array,slice) to []string
func ToStrings(arr any) (ret []string, err error) {
// try direct convert
switch typVal := arr.(type) {
case string:
return []string{typVal}, nil
case []string:
return typVal, nil
case []any:
return SliceToStrings(typVal), nil
}
// try use reflect to convert
rv := reflect.ValueOf(arr)
if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
err = ErrInvalidType
return
}
for i := 0; i < rv.Len(); i++ {
str, err1 := strutil.ToString(rv.Index(i).Interface())
if err1 != nil {
return nil, err1
}
ret = append(ret, str)
}
return
}
// SliceToStrings safe convert []any to []string
func SliceToStrings(arr []any) []string {
ss := make([]string, len(arr))
for i, v := range arr {
ss[i] = strutil.SafeString(v)
}
return ss
}
// QuietStrings safe convert []any to []string
func QuietStrings(arr []any) []string { return SliceToStrings(arr) }
// ConvType convert type of slice elements to new type slice, by the given newElemTyp type.
//
// Supports conversion between []string, []intX, []uintX, []floatX.
//
// Usage:
//
// ints, _ := arrutil.ConvType([]string{"12", "23"}, 1) // []int{12, 23}
func ConvType[T any, R any](arr []T, newElemTyp R) ([]R, error) {
newArr := make([]R, len(arr))
elemTyp := reflect.TypeOf(newElemTyp)
for i, elem := range arr {
var anyElem any = elem
// type is same.
if _, ok := anyElem.(R); ok {
newArr[i] = anyElem.(R)
continue
}
// need conv type.
rfVal, err := reflects.ValueByType(elem, elemTyp)
if err != nil {
return nil, err
}
newArr[i] = rfVal.Interface().(R)
}
return newArr, nil
}
// AnyToString simple and quickly convert any array, slice to string
func AnyToString(arr any) string {
return NewFormatter(arr).Format()
}
// SliceToString convert []any to string
func SliceToString(arr ...any) string { return ToString(arr) }
// ToString simple and quickly convert []T to string
func ToString[T any](arr []T) string {
// like fmt.Println([]any(nil))
if arr == nil {
return "[]"
}
var sb strings.Builder
sb.WriteByte('[')
for i, v := range arr {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(strutil.SafeString(v))
}
sb.WriteByte(']')
return sb.String()
}
// CombineToMap combine []K and []V slice to map[K]V.
//
// If keys length is greater than values, the extra keys will be ignored.
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
ln := len(values)
mp := make(map[K]V, len(keys))
for i, key := range keys {
if i >= ln {
break
}
mp[key] = values[i]
}
return mp
}
// CombineToSMap combine two string-slice to map[string]string
func CombineToSMap(keys, values []string) map[string]string {
ln := len(values)
mp := make(map[string]string, len(keys))
for i, key := range keys {
if ln > i {
mp[key] = values[i]
} else {
mp[key] = ""
}
}
return mp
}
+123
View File
@@ -0,0 +1,123 @@
package arrutil
import (
"io"
"reflect"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/strutil"
)
// ArrFormatter struct
type ArrFormatter struct {
comdef.BaseFormatter
// Prefix string for each element
Prefix string
// Indent string for format each element
Indent string
// ClosePrefix on before end char: ]
ClosePrefix string
}
// NewFormatter instance
func NewFormatter(arr any) *ArrFormatter {
f := &ArrFormatter{}
f.Src = arr
return f
}
// FormatIndent array data to string.
func FormatIndent(arr any, indent string) string {
return NewFormatter(arr).WithIndent(indent).Format()
}
// WithFn for config self
func (f *ArrFormatter) WithFn(fn func(f *ArrFormatter)) *ArrFormatter {
fn(f)
return f
}
// WithIndent string
func (f *ArrFormatter) WithIndent(indent string) *ArrFormatter {
f.Indent = indent
return f
}
// FormatTo to custom buffer
func (f *ArrFormatter) FormatTo(w io.Writer) {
f.SetOutput(w)
f.doFormat()
}
// Format to string
func (f *ArrFormatter) String() string {
return f.Format()
}
// Format to string
func (f *ArrFormatter) Format() string {
f.doFormat()
return f.BsWriter().String()
}
// Format to string
//
//goland:noinspection GoUnhandledErrorResult
func (f *ArrFormatter) doFormat() {
if f.Src == nil {
return
}
rv, ok := f.Src.(reflect.Value)
if !ok {
rv = reflect.ValueOf(f.Src)
}
rv = reflect.Indirect(rv)
if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
return
}
writer := f.BsWriter()
arrLn := rv.Len()
if arrLn == 0 {
writer.WriteString("[]")
return
}
// if f.AfterReset {
// defer f.Reset()
// }
// sb.Grow(arrLn * 4)
writer.WriteByte('[')
indentLn := len(f.Indent)
if indentLn > 0 {
writer.WriteByte('\n')
}
for i := 0; i < arrLn; i++ {
if indentLn > 0 {
writer.WriteString(f.Indent)
}
writer.WriteString(strutil.QuietString(rv.Index(i).Interface()))
if i < arrLn-1 {
writer.WriteByte(',')
// no indent, with space
if indentLn == 0 {
writer.WriteByte(' ')
}
}
if indentLn > 0 {
writer.WriteByte('\n')
}
}
if f.ClosePrefix != "" {
writer.WriteString(f.ClosePrefix)
}
writer.WriteByte(']')
}
+216
View File
@@ -0,0 +1,216 @@
package arrutil
import (
"sort"
"strings"
"github.com/gookit/goutil/comdef"
)
// Ints type
type Ints[T comdef.Integer] []T
// String to string
func (is Ints[T]) String() string {
return ToString(is)
}
// Has given element
func (is Ints[T]) Has(i T) bool {
for _, iv := range is {
if i == iv {
return true
}
}
return false
}
// First element value.
func (is Ints[T]) First(defVal ...T) T {
if len(is) > 0 {
return is[0]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty integer slice")
}
// Last element value.
func (is Ints[T]) Last(defVal ...T) T {
if len(is) > 0 {
return is[len(is)-1]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty integer slice")
}
// Sort the int slice
func (is Ints[T]) Sort() {
sort.Sort(is)
}
// Len get length
func (is Ints[T]) Len() int {
return len(is)
}
// Less compare two elements
func (is Ints[T]) Less(i, j int) bool {
return is[i] < is[j]
}
// Swap elements by indexes
func (is Ints[T]) Swap(i, j int) {
is[i], is[j] = is[j], is[i]
}
// Strings type
type Strings []string
// String to string
func (ss Strings) String() string {
return strings.Join(ss, ",")
}
// Join to string
func (ss Strings) Join(sep string) string {
return strings.Join(ss, sep)
}
// Has given element
func (ss Strings) Has(sub string) bool {
return ss.Contains(sub)
}
// Contains given element
func (ss Strings) Contains(sub string) bool {
for _, s := range ss {
if s == sub {
return true
}
}
return false
}
// First element value.
func (ss Strings) First(defVal ...string) string {
if len(ss) > 0 {
return ss[0]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty string list")
}
// Last element value.
func (ss Strings) Last(defVal ...string) string {
if len(ss) > 0 {
return ss[len(ss)-1]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty string list")
}
// Sort the string slice
func (ss Strings) Sort() {
sort.Strings(ss)
}
// SortedList definition for compared type
type SortedList[T comdef.Compared] []T
// Len get length
func (ls SortedList[T]) Len() int {
return len(ls)
}
// Less compare two elements
func (ls SortedList[T]) Less(i, j int) bool {
return ls[i] < ls[j]
}
// Swap elements by indexes
func (ls SortedList[T]) Swap(i, j int) {
ls[i], ls[j] = ls[j], ls[i]
}
// IsEmpty check
func (ls SortedList[T]) IsEmpty() bool {
return len(ls) == 0
}
// String to string
func (ls SortedList[T]) String() string {
return ToString(ls)
}
// Has given element
func (ls SortedList[T]) Has(el T) bool {
return ls.Contains(el)
}
// Contains given element
func (ls SortedList[T]) Contains(el T) bool {
for _, v := range ls {
if v == el {
return true
}
}
return false
}
// First element value.
func (ls SortedList[T]) First(defVal ...T) T {
if len(ls) > 0 {
return ls[0]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty list")
}
// Last element value.
func (ls SortedList[T]) Last(defVal ...T) T {
if ln := len(ls); ln > 0 {
return ls[ln-1]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty list")
}
// Remove given element
func (ls SortedList[T]) Remove(el T) SortedList[T] {
return Filter(ls, func(v T) bool {
return v != el
})
}
// Filter the slice, default will filter zero value.
func (ls SortedList[T]) Filter(filter ...comdef.MatchFunc[T]) SortedList[T] {
return Filter(ls, filter...)
}
// Map the slice to new slice. TODO syntax ERROR: Method cannot have type parameters
// func (ls SortedList[T]) Map[V any](mapFn MapFn[T, V]) SortedList[V] {
// return Map(ls, mapFn)
// }
// Sort the slice
func (ls SortedList[T]) Sort() {
sort.Sort(ls)
}
+222
View File
@@ -0,0 +1,222 @@
package arrutil
import (
"reflect"
"github.com/gookit/goutil/comdef"
)
// Reverse any T slice.
//
// eg: []string{"site", "user", "info", "0"} -> []string{"0", "info", "user", "site"}
func Reverse[T any](ls []T) {
ln := len(ls)
for i := 0; i < ln/2; i++ {
li := ln - i - 1
ls[i], ls[li] = ls[li], ls[i]
}
}
// Remove give element from slice []T.
//
// eg: []string{"site", "user", "info", "0"} -> []string{"site", "user", "info"}
func Remove[T comdef.Compared](ls []T, val T) []T {
return Filter(ls, func(el T) bool {
return el != val
})
}
// Filter given slice, default will filter zero value.
//
// Usage:
//
// // output: [a, b]
// ss := arrutil.Filter([]string{"a", "", "b", ""})
func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T {
var fn comdef.MatchFunc[T]
if len(filter) > 0 && filter[0] != nil {
fn = filter[0]
} else {
fn = func(el T) bool {
// if el == nil { // Filter nil value
// return false
// }
return !reflect.ValueOf(el).IsZero()
}
}
newLs := make([]T, 0, len(ls))
for _, el := range ls {
if fn(el) {
newLs = append(newLs, el)
}
}
return newLs
}
// Map a list to new list with map filter function.
//
// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
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 := mapFilter(obj); ok {
flatArr = append(flatArr, target)
}
}
return flatArr
}
// 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)
}
// Unique value in the given slice data.
func Unique[T comdef.NumberOrString](list []T) []T {
if len(list) < 2 {
return list
}
valMap := make(map[T]struct{}, len(list))
uniArr := make([]T, 0, len(list))
for _, t := range list {
if _, ok := valMap[t]; !ok {
valMap[t] = struct{}{}
uniArr = append(uniArr, t)
}
}
return uniArr
}
// IndexOf value in given slice.
func IndexOf[T comdef.NumberOrString](val T, list []T) int {
for i, v := range list {
if v == val {
return i
}
}
return -1
}
// FirstOr get first value of slice, if slice is empty, return the default value.
func FirstOr[T any](list []T, defVal ...T) T {
if len(list) > 0 {
return list[0]
}
if len(defVal) > 0 {
return defVal[0]
}
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
}
+141
View File
@@ -0,0 +1,141 @@
package arrutil
import (
"strconv"
"strings"
"github.com/gookit/goutil/comdef"
)
// StringsToAnys convert []string to []any
func StringsToAnys(ss []string) []any {
args := make([]any, len(ss))
for i, s := range ss {
args[i] = s
}
return args
}
// StringsToSlice convert []string to []any. alias of StringsToAnys()
func StringsToSlice(ss []string) []any {
return StringsToAnys(ss)
}
// StringsAsInts convert and ignore error
func StringsAsInts(ss []string) []int {
ints, _ := StringsTryInts(ss)
return ints
}
// StringsToInts string slice to int slice
func StringsToInts(ss []string) (ints []int, err error) {
return StringsTryInts(ss)
}
// StringsTryInts string slice to int slice
func StringsTryInts(ss []string) (ints []int, err error) {
for _, str := range ss {
iVal, err := strconv.Atoi(str)
if err != nil {
return nil, err
}
ints = append(ints, iVal)
}
return
}
// StringsUnique unique string slice
func StringsUnique(ss []string) []string {
if len(ss) == 0 {
return ss
}
var unique []string
for _, s := range ss {
if !StringsContains(unique, s) {
unique = append(unique, s)
}
}
return unique
}
// StringsContains check string slice contains string
func StringsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// StringsRemove value form a string slice
func StringsRemove(ss []string, s string) []string {
return StringsFilter(ss, func(el string) bool {
return s != el
})
}
// StringsFilter given strings, default will filter emtpy string.
//
// Usage:
//
// // output: [a, b]
// ss := arrutil.StringsFilter([]string{"a", "", "b", ""})
func StringsFilter(ss []string, filter ...comdef.StringMatchFunc) []string {
var fn comdef.StringMatchFunc
if len(filter) > 0 && filter[0] != nil {
fn = filter[0]
} else {
fn = func(s string) bool {
return s != ""
}
}
ns := make([]string, 0, len(ss))
for _, s := range ss {
if fn(s) {
ns = append(ns, s)
}
}
return ns
}
// StringsMap handle each string item, map to new strings
func StringsMap(ss []string, mapFn func(s string) string) []string {
ns := make([]string, 0, len(ss))
for _, s := range ss {
ns = append(ns, mapFn(s))
}
return ns
}
// TrimStrings trim string slice item.
//
// Usage:
//
// // output: [a, b, c]
// ss := arrutil.TrimStrings([]string{",a", "b.", ",.c,"}, ",.")
func TrimStrings(ss []string, cutSet ...string) []string {
cutSetLn := len(cutSet)
hasCutSet := cutSetLn > 0 && cutSet[0] != ""
var trimSet string
if hasCutSet {
trimSet = cutSet[0]
}
if cutSetLn > 1 {
trimSet = strings.Join(cutSet, "")
}
ns := make([]string, 0, len(ss))
for _, str := range ss {
if hasCutSet {
ns = append(ns, strings.Trim(str, trimSet))
} else {
ns = append(ns, strings.TrimSpace(str))
}
}
return ns
}
+55
View File
@@ -0,0 +1,55 @@
# Bytes Util
Provide some common bytes util functions.
## Install
```shell
go get github.com/gookit/goutil/byteutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/byteutil)
## Functions API
> **Note**: doc by run `go doc ./byteutil`
```go
func AppendAny(dst []byte, v any) []byte
func FirstLine(bs []byte) []byte
func IsNumChar(c byte) bool
func Md5(src any) []byte
func Random(length int) ([]byte, error)
func SafeString(bs []byte, err error) string
func StrOrErr(bs []byte, err error) (string, error)
func String(b []byte) string
func ToString(b []byte) string
type Buffer struct{ ... }
func NewBuffer() *Buffer
type BytesEncoder interface{ ... }
type ChanPool struct{ ... }
func NewChanPool(maxSize int, width int, capWidth int) *ChanPool
type StdEncoder struct{ ... }
func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./byteutil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./byteutil/...
```
+118
View File
@@ -0,0 +1,118 @@
package byteutil
import (
"bytes"
"fmt"
)
// Buffer wrap and extends the bytes.Buffer, add some useful methods
// and implements the io.Writer, io.Closer and stdio.Flusher interfaces
type Buffer struct {
bytes.Buffer
// custom error for testing
CloseErr error
FlushErr error
SyncErr error
}
// NewBuffer instance
func NewBuffer() *Buffer { return &Buffer{} }
// PrintByte to buffer, ignore error. alias of WriteByte()
func (b *Buffer) PrintByte(c byte) {
_ = b.WriteByte(c)
}
// WriteStr1 quiet write one string to buffer
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) }
// writeStringNl quiet write one string and end with newline
func (b *Buffer) writeStringNl(s string, nl bool) {
_, _ = b.Buffer.WriteString(s)
if nl {
_ = b.WriteByte('\n')
}
}
// WriteStr quiet write strings to buffer
func (b *Buffer) WriteStr(ss ...string) {
b.writeStringsNl(ss, false)
}
// WriteStrings to buffer, ignore error.
func (b *Buffer) WriteStrings(ss []string) {
b.writeStringsNl(ss, false)
}
// WriteStringNl write message to buffer and end with newline
func (b *Buffer) WriteStringNl(ss ...string) {
b.writeStringsNl(ss, true)
}
// writeStringsNl to buffer, ignore error.
func (b *Buffer) writeStringsNl(ss []string, nl bool) {
for _, s := range ss {
_, _ = b.Buffer.WriteString(s)
}
if nl {
_ = b.WriteByte('\n')
}
}
// WriteAny type value to buffer
func (b *Buffer) WriteAny(vs ...any) {
b.writeAnysWithNl(vs, false)
}
// Writeln write values to buffer and end with newline
func (b *Buffer) Writeln(vs ...any) {
b.writeAnysWithNl(vs, true)
}
// WriteAnyNl type value to buffer and end with newline
func (b *Buffer) WriteAnyNl(vs ...any) {
b.writeAnysWithNl(vs, true)
}
// WriteAnyLn type value to buffer and end with newline
func (b *Buffer) writeAnysWithNl(vs []any, nl bool) {
for _, v := range vs {
_, _ = b.Buffer.WriteString(fmt.Sprint(v))
}
if nl {
_ = b.WriteByte('\n')
}
}
// 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...) }
// Println quick write message with newline to buffer, will ignore error.
func (b *Buffer) Println(vs ...any) { _, _ = fmt.Fprintln(b, vs...) }
// ResetGet buffer string. alias of ResetAndGet()
func (b *Buffer) ResetGet() string {
return b.ResetAndGet()
}
// ResetAndGet buffer string.
func (b *Buffer) ResetAndGet() string {
s := b.String()
b.Reset()
return s
}
// Close buffer
func (b *Buffer) Close() error { return b.CloseErr }
// Flush buffer
func (b *Buffer) Flush() error { return b.FlushErr }
// Sync anf flush buffer
func (b *Buffer) Sync() error { return b.SyncErr }
+125
View File
@@ -0,0 +1,125 @@
// Package byteutil provides some useful functions for byte slice.
package byteutil
import (
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"strconv"
"time"
)
// Md5 Generate a 32-bit md5 bytes
func Md5(src any) []byte {
bs := Md5Sum(src)
dst := make([]byte, hex.EncodedLen(len(bs)))
hex.Encode(dst, bs)
return dst
}
// Md5Sum Generate a md5 bytes
func Md5Sum(src any) []byte {
h := md5.New()
switch val := src.(type) {
case []byte:
h.Write(val)
case string:
h.Write([]byte(val))
default:
h.Write([]byte(fmt.Sprint(src)))
}
return h.Sum(nil) // cap(bs) == 16
}
// ShortMd5 Generate a 16-bit md5 bytes. remove the first 8 and last 8 bytes from 32-bit md5.
func ShortMd5(src any) []byte { return Md5(src)[8:24] }
// Random bytes generate
func Random(length int) ([]byte, error) {
b := make([]byte, length)
// Note that err == nil only if we read len(b) bytes.
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
// FirstLine from command output
func FirstLine(bs []byte) []byte {
if i := bytes.IndexByte(bs, '\n'); i >= 0 {
return bs[0:i]
}
return bs
}
// AppendAny append any value to byte slice
func AppendAny(dst []byte, v any) []byte {
if v == nil {
return append(dst, "<nil>"...)
}
switch val := v.(type) {
case []byte:
dst = append(dst, val...)
case string:
dst = append(dst, val...)
case int:
dst = strconv.AppendInt(dst, int64(val), 10)
case int8:
dst = strconv.AppendInt(dst, int64(val), 10)
case int16:
dst = strconv.AppendInt(dst, int64(val), 10)
case int32:
dst = strconv.AppendInt(dst, int64(val), 10)
case int64:
dst = strconv.AppendInt(dst, val, 10)
case uint:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint8:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint16:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint32:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint64:
dst = strconv.AppendUint(dst, val, 10)
case float32:
dst = strconv.AppendFloat(dst, float64(val), 'f', -1, 32)
case float64:
dst = strconv.AppendFloat(dst, val, 'f', -1, 64)
case bool:
dst = strconv.AppendBool(dst, val)
case time.Time:
dst = val.AppendFormat(dst, time.RFC3339)
case time.Duration:
dst = strconv.AppendInt(dst, int64(val), 10)
case error:
dst = append(dst, val.Error()...)
case fmt.Stringer:
dst = append(dst, val.String()...)
default:
dst = append(dst, fmt.Sprint(v)...)
}
return dst
}
// Cut bytes by one byte char. like bytes.Cut(), but sep is byte.
func Cut(bs []byte, sep byte) (before, after []byte, found bool) {
return bytes.Cut(bs, []byte{sep})
}
// SafeCut bytes by one byte char. always return before and after
func SafeCut(bs []byte, sep byte) (before, after []byte) {
before, after, _ = bytes.Cut(bs, []byte{sep})
return
}
// SafeCuts bytes by sub bytes. like the bytes.Cut(), but always return before and after
func SafeCuts(bs []byte, sep []byte) (before, after []byte) {
before, after, _ = bytes.Cut(bs, sep)
return
}
+9
View File
@@ -0,0 +1,9 @@
package byteutil
// IsNumChar returns true if the given character is a numeric, otherwise false.
func IsNumChar(c byte) bool { return c >= '0' && c <= '9' }
// IsAlphaChar returns true if the given character is a alphabet, otherwise false.
func IsAlphaChar(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
+116
View File
@@ -0,0 +1,116 @@
package byteutil
import (
"fmt"
"strconv"
"time"
"unsafe"
"github.com/gookit/goutil/comdef"
)
// StrOrErr convert to string, return empty string on error.
func StrOrErr(bs []byte, err error) (string, error) {
if err != nil {
return "", err
}
return string(bs), err
}
// SafeString convert to string, return empty string on error.
func SafeString(bs []byte, err error) string {
if err != nil {
return ""
}
return string(bs)
}
// String unsafe convert bytes to string
func String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// ToString convert bytes to string
func ToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// ToBytes convert any value to []byte. return error on convert failed.
func ToBytes(v any) ([]byte, error) {
return ToBytesWithFunc(v, nil)
}
// SafeBytes convert any value to []byte. use fmt.Sprint() on convert failed.
func SafeBytes(v any) []byte {
bs, _ := ToBytesWithFunc(v, func(v any) ([]byte, error) {
return []byte(fmt.Sprint(v)), nil
})
return bs
}
// ToBytesFunc convert any value to []byte
type ToBytesFunc = func(v any) ([]byte, error)
// ToBytesWithFunc convert any value to []byte with custom fallback func.
//
// refer the strutil.ToStringWithFunc
//
// On not convert:
// - If usrFn is nil, will return comdef.ErrConvType.
// - If usrFn is not nil, will call it to convert.
func ToBytesWithFunc(v any, usrFn ToBytesFunc) ([]byte, error) {
if v == nil {
return nil, nil
}
switch val := v.(type) {
case []byte:
return val, nil
case string:
return []byte(val), nil
case int:
return []byte(strconv.Itoa(val)), nil
case int8:
return []byte(strconv.Itoa(int(val))), nil
case int16:
return []byte(strconv.Itoa(int(val))), nil
case int32: // same as `rune`
return []byte(strconv.Itoa(int(val))), nil
case int64:
return []byte(strconv.FormatInt(val, 10)), nil
case uint:
return []byte(strconv.FormatUint(uint64(val), 10)), nil
case uint8:
return []byte(strconv.FormatUint(uint64(val), 10)), nil
case uint16:
return []byte(strconv.FormatUint(uint64(val), 10)), nil
case uint32:
return []byte(strconv.FormatUint(uint64(val), 10)), nil
case uint64:
return []byte(strconv.FormatUint(val, 10)), nil
case float32:
return []byte(strconv.FormatFloat(float64(val), 'f', -1, 32)), nil
case float64:
return []byte(strconv.FormatFloat(val, 'f', -1, 64)), nil
case bool:
return []byte(strconv.FormatBool(val)), nil
case time.Duration:
return []byte(strconv.FormatInt(int64(val), 10)), nil
case fmt.Stringer:
return []byte(val.String()), nil
case error:
return []byte(val.Error()), nil
default:
if usrFn == nil {
return nil, comdef.ErrConvType
}
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]
}
}
+69
View File
@@ -0,0 +1,69 @@
package byteutil
import (
"encoding/base64"
"encoding/hex"
)
// BytesEncodeFunc type
type BytesEncodeFunc func(src []byte) []byte
// BytesDecodeFunc type
type BytesDecodeFunc func(src []byte) ([]byte, error)
// BytesEncoder interface
type BytesEncoder interface {
Encode(src []byte) []byte
Decode(src []byte) ([]byte, error)
}
// StdEncoder implement the BytesEncoder
type StdEncoder struct {
encodeFn BytesEncodeFunc
decodeFn BytesDecodeFunc
}
// NewStdEncoder instance
func NewStdEncoder(encFn BytesEncodeFunc, decFn BytesDecodeFunc) *StdEncoder {
return &StdEncoder{
encodeFn: encFn,
decodeFn: decFn,
}
}
// Encode input
func (e *StdEncoder) Encode(src []byte) []byte {
return e.encodeFn(src)
}
// Decode input
func (e *StdEncoder) Decode(src []byte) ([]byte, error) {
return e.decodeFn(src)
}
var (
// HexEncoder instance
HexEncoder = NewStdEncoder(func(src []byte) []byte {
dst := make([]byte, hex.EncodedLen(len(src)))
hex.Encode(dst, src)
return dst
}, func(src []byte) ([]byte, error) {
n, err := hex.Decode(src, src)
return src[:n], err
})
// B64Encoder instance
B64Encoder = NewStdEncoder(func(src []byte) []byte {
b64Dst := make([]byte, base64.StdEncoding.EncodedLen(len(src)))
base64.StdEncoding.Encode(b64Dst, src)
return b64Dst
}, func(src []byte) ([]byte, error) {
dBuf := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
n, err := base64.StdEncoding.Decode(dBuf, src)
if err != nil {
return nil, err
}
return dBuf[:n], err
})
)
+63
View File
@@ -0,0 +1,63 @@
package byteutil
// ChanPool struct
//
// Usage:
//
// bp := strutil.NewByteChanPool(500, 1024, 1024)
// buf:=bp.Get()
// defer bp.Put(buf)
// // use buf do something ...
//
// refer https://www.flysnow.org/2020/08/21/golang-chan-byte-pool.html
// from https://github.com/minio/minio/blob/master/internal/bpool/bpool.go
type ChanPool struct {
c chan []byte
w int // init byte width
wcap int // set byte cap
}
// NewChanPool instance
func NewChanPool(chSize int, width int, capWidth int) *ChanPool {
return &ChanPool{
c: make(chan []byte, chSize),
w: width,
wcap: capWidth,
}
}
// Get gets a []byte from the BytePool, or creates a new one if none are
// available in the pool.
func (bp *ChanPool) Get() (b []byte) {
select {
case b = <-bp.c: // reuse existing buffer
default:
// create new buffer
if bp.wcap > 0 {
b = make([]byte, bp.w, bp.wcap)
} else {
b = make([]byte, bp.w)
}
}
return
}
// Put returns the given Buffer to the BytePool.
func (bp *ChanPool) Put(b []byte) {
select {
case bp.c <- b:
// buffer went back into pool
default:
// buffer didn't go back into pool, just discard
}
}
// Width returns the width of the byte arrays in this pool.
func (bp *ChanPool) Width() (n int) {
return bp.w
}
// WidthCap returns the cap width of the byte arrays in this pool.
func (bp *ChanPool) WidthCap() (n int) {
return bp.wcap
}
+67
View File
@@ -0,0 +1,67 @@
package cmdline
import (
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
// LineBuilder build command line string.
// codes refer from strings.Builder
type LineBuilder struct {
strings.Builder
}
// NewBuilder create
func NewBuilder(binFile string, args ...string) *LineBuilder {
b := &LineBuilder{}
if binFile != "" {
b.AddArg(binFile)
}
b.AddArray(args)
return b
}
// ResetGet value, will reset after get.
func (b *LineBuilder) ResetGet() string {
defer b.Reset()
return b.String()
}
// AddArg to builder
func (b *LineBuilder) AddArg(arg string) {
_, _ = b.WriteString(arg)
}
// AddArgs to builder
func (b *LineBuilder) AddArgs(args ...string) {
b.AddArray(args)
}
// AddArray to builder
func (b *LineBuilder) AddArray(args []string) {
for _, arg := range args {
_, _ = b.WriteString(arg)
}
}
// AddAny args to builder
func (b *LineBuilder) AddAny(args ...any) {
for _, arg := range args {
_, _ = b.WriteString(strutil.SafeString(arg))
}
}
// WriteString arg string to the builder, will auto quote special string.
// refer strconv.Quote()
func (b *LineBuilder) WriteString(a string) (int, error) {
// add sep on not-first write.
if b.Len() != 0 {
_ = b.WriteByte(' ')
}
return b.Builder.WriteString(comfunc.ShellQuote(a))
}
+15
View File
@@ -0,0 +1,15 @@
// Package cmdline provide quick build and parse cmd line string.
package cmdline
import "github.com/gookit/goutil/internal/comfunc"
// LineBuild build command line string by given args.
func LineBuild(binFile string, args []string) string {
return NewBuilder(binFile, args...).String()
}
// ParseLine input command line text. alias of the StringToOSArgs()
func ParseLine(line string) []string { return NewParser(line).Parse() }
// Quote string in shell command env
func Quote(s string) string { return comfunc.ShellQuote(s) }
+174
View File
@@ -0,0 +1,174 @@
package cmdline
import (
"bytes"
"os/exec"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/varexpr"
"github.com/gookit/goutil/strutil"
)
// LineParser struct
// parse input command line to []string, such as cli os.Args
type LineParser struct {
parsed bool
// Line the full input command line text
// eg `kite top sub -a "this is a message" --foo val1 --bar "val 2"`
Line string
// ParseEnv parse ENV var on the line.
ParseEnv bool
// the exploded nodes by space.
nodes []string
// the parsed args
args []string
// temp value
quoteChar byte
quoteIndex int // if > 0, mark is not on start
tempNode bytes.Buffer
}
// NewParser create
func NewParser(line string) *LineParser {
return &LineParser{Line: line}
}
// WithParseEnv with parse ENV var
func (p *LineParser) WithParseEnv() *LineParser {
p.ParseEnv = true
return p
}
// AlsoEnvParse input command line text to os.Args, will parse ENV var
func (p *LineParser) AlsoEnvParse() []string {
p.ParseEnv = true
return p.Parse()
}
// NewExecCmd quick create exec.Cmd by cmdline string
func (p *LineParser) NewExecCmd() *exec.Cmd {
// parse get bin and args
binName, args := p.BinAndArgs()
// create a new Cmd instance
return exec.Command(binName, args...)
}
// BinAndArgs get binName and args
func (p *LineParser) BinAndArgs() (bin string, args []string) {
p.Parse() // ensure parsed.
ln := len(p.args)
if ln == 0 {
return
}
bin = p.args[0]
if ln > 1 {
args = p.args[1:]
}
return
}
// Parse input command line text to os.Args
func (p *LineParser) Parse() []string {
if p.parsed {
return p.args
}
p.parsed = true
p.Line = strings.TrimSpace(p.Line)
if p.Line == "" {
return p.args
}
// enable parse Env var
if p.ParseEnv {
p.Line = varexpr.SafeParse(p.Line)
}
p.nodes = strings.Split(p.Line, " ")
if len(p.nodes) == 1 {
p.args = p.nodes
return p.args
}
for i := 0; i < len(p.nodes); i++ {
node := p.nodes[i]
if node == "" {
continue
}
p.parseNode(node)
}
p.nodes = p.nodes[:0]
if p.tempNode.Len() > 0 {
p.appendTempNode()
}
return p.args
}
func (p *LineParser) parseNode(node string) {
maxIdx := len(node) - 1
start, end := node[0], node[maxIdx]
// in quotes
if p.quoteChar != 0 {
p.tempNode.WriteByte(' ')
// end quotes
if end == p.quoteChar {
if p.quoteIndex > 0 {
p.tempNode.WriteString(node) // eg: node="--pretty=format:'one two'"
} else {
p.tempNode.WriteString(node[:maxIdx]) // remove last quote
}
p.appendTempNode()
} else { // goon ... write to temp node
p.tempNode.WriteString(node)
}
return
}
// quote start
if start == comdef.DoubleQuote || start == comdef.SingleQuote {
// only one words. eg: `-m "msg"`
if end == start {
p.args = append(p.args, node[1:maxIdx])
return
}
p.quoteChar = start
p.tempNode.WriteString(node[1:])
} else if end == comdef.DoubleQuote || end == comdef.SingleQuote {
p.args = append(p.args, node) // only one node: `msg"`
} else {
// eg: --pretty=format:'one two three'
if strutil.ContainsByte(node, comdef.DoubleQuote) {
p.quoteIndex = 1 // mark is not on start
p.quoteChar = comdef.DoubleQuote
} else if strutil.ContainsByte(node, comdef.SingleQuote) {
p.quoteIndex = 1
p.quoteChar = comdef.SingleQuote
}
// in quote, append to temp-node
if p.quoteChar != 0 {
p.tempNode.WriteString(node)
} else {
p.args = append(p.args, node)
}
}
}
func (p *LineParser) appendTempNode() {
p.args = append(p.args, p.tempNode.String())
// reset context value
p.quoteChar = 0
p.quoteIndex = 0
p.tempNode.Reset()
}
+17
View File
@@ -0,0 +1,17 @@
// Package comdef provide some common type or constant definitions
package comdef
// ToTypeFunc convert value to defined type
type ToTypeFunc[T any] func(any) (T, error)
// IntCheckFunc check func
type IntCheckFunc func(val int) error
// StrCheckFunc check func
type StrCheckFunc func(val string) error
// ToStringFunc try to convert value to string, return error on fail
type ToStringFunc func(v any) (string, error)
// SafeStringFunc safe convert value to string
type SafeStringFunc func(v any) string
+39
View File
@@ -0,0 +1,39 @@
package comdef
// constants for compare operation
const (
OpEq = "="
OpNeq = "!="
OpLt = "<"
OpLte = "<="
OpGt = ">"
OpGte = ">="
)
// constants quote chars
const (
SingleQuote = '\''
DoubleQuote = '"'
SlashQuote = '\\'
SingleQuoteStr = "'"
DoubleQuoteStr = `"`
SlashQuoteStr = "\\"
)
// NoIdx invalid index or length
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
)
+6
View File
@@ -0,0 +1,6 @@
//go:build !windows
package comdef
// Newline string for non-windows
const Newline = "\n"
+4
View File
@@ -0,0 +1,4 @@
package comdef
// Newline string for windows
const Newline = "\r\n"
+38
View File
@@ -0,0 +1,38 @@
package comdef
import (
"errors"
"strings"
)
// ErrConvType error
var ErrConvType = errors.New("convert value type error")
// Errors multi error list
type Errors []error
// Error string
func (es Errors) Error() string {
var sb strings.Builder
for _, err := range es {
sb.WriteString(err.Error())
sb.WriteByte('\n')
}
return sb.String()
}
// ErrOrNil error
func (es Errors) ErrOrNil() error {
if len(es) == 0 {
return nil
}
return es
}
// First error
func (es Errors) First() error {
if len(es) > 0 {
return es[0]
}
return nil
}
+63
View File
@@ -0,0 +1,63 @@
package comdef
import (
"bytes"
"io"
"github.com/gookit/goutil/x/stdio"
)
// DataFormatter interface
type DataFormatter interface {
Format() string
FormatTo(w io.Writer)
}
// BaseFormatter struct
//
// Usage:
//
// type YourFormatter struct {
// comdef.BaseFormatter
// }
// // implement the DataFormatter interface...
type BaseFormatter struct {
ow ByteStringWriter
// Out formatted to the writer
Out io.Writer
// Src data(array, map, struct) for format
Src any
// MaxDepth limit depth for array, map data TODO
MaxDepth int
// Prefix string for each element
Prefix string
// Indent string for format each element
Indent string
// ClosePrefix string for last "]", "}"
ClosePrefix string
}
// Reset after format
func (f *BaseFormatter) Reset() {
f.Out = nil
f.Src = nil
}
// SetOutput writer
func (f *BaseFormatter) SetOutput(out io.Writer) {
f.Out = out
}
// BsWriter warp the Out, build a ByteStringWriter
func (f *BaseFormatter) BsWriter() ByteStringWriter {
if f.ow == nil {
if f.Out == nil {
f.ow = new(bytes.Buffer)
} else if ow, ok := f.Out.(ByteStringWriter); ok {
f.ow = ow
} else {
f.ow = stdio.NewWriteWrapper(f.Out)
}
}
return f.ow
}
+78
View File
@@ -0,0 +1,78 @@
package comdef
import (
"fmt"
"io"
)
// ByteStringWriter interface
type ByteStringWriter interface {
io.Writer
io.ByteWriter
io.StringWriter
fmt.Stringer
}
// StringWriteStringer interface
type StringWriteStringer interface {
io.StringWriter
fmt.Stringer
}
// Int64able interface
type Int64able interface {
Int64() (int64, error)
}
// Float64able interface
type Float64able interface {
Float64() (float64, error)
}
// MapFunc definition
type MapFunc func(val any) (any, error)
//
//
// Matcher type
//
//
// Matcher interface
type Matcher[T any] interface {
Match(s T) bool
}
// MatchFunc definition. implements Matcher interface
type MatchFunc[T any] func(v T) bool
// Match satisfies the Matcher interface
func (fn MatchFunc[T]) Match(v T) bool {
return fn(v)
}
// StringMatcher interface
type StringMatcher interface {
Match(s string) bool
}
// StringMatchFunc definition
type StringMatchFunc func(s string) bool
// Match satisfies the StringMatcher interface
func (fn StringMatchFunc) Match(s string) bool {
return fn(s)
}
// StringHandler interface
type StringHandler interface {
Handle(s string) string
}
// StringHandleFunc definition
type StringHandleFunc func(s string) string
// Handle satisfies the StringHandler interface
func (fn StringHandleFunc) Handle(s string) string {
return fn(s)
}
+27
View File
@@ -0,0 +1,27 @@
package comdef
type (
// MarshalFunc define
MarshalFunc func(v any) ([]byte, error)
// UnmarshalFunc define
UnmarshalFunc func(bts []byte, ptr any) error
)
// Serializer interface definition
type Serializer interface {
Serialize(v any) ([]byte, error)
Deserialize(data []byte, v any) error
}
// GoSerializer interface definition
type GoSerializer interface {
Marshal(v any) ([]byte, error)
Unmarshal(v []byte, ptr any) error
}
// Codec interface definition
type Codec interface {
Decode(blob []byte, v any) (err error)
Encode(v any) (out []byte, err error)
}
+41
View File
@@ -0,0 +1,41 @@
package comdef
const (
// CommaStr const define
CommaStr = ","
// CommaChar define
CommaChar = ','
// EqualStr define
EqualStr = "="
// EqualChar define
EqualChar = '='
// ColonStr define
ColonStr = ":"
// ColonChar define
ColonChar = ':'
// SemicolonStr semicolon define
SemicolonStr = ";"
// SemicolonChar define
SemicolonChar = ';'
// PathStr define const
PathStr = "/"
// PathChar define
PathChar = '/'
// DefaultSep comma string
DefaultSep = ","
// SpaceChar char
SpaceChar = ' '
// SpaceStr string
SpaceStr = " "
// NewlineChar char
NewlineChar = '\n'
// NewlineStr string
NewlineStr = "\n"
)
+90
View File
@@ -0,0 +1,90 @@
package comdef
// Int interface type
type Int interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Uint interface type
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Xint interface type. alias of Integer
type Xint interface {
Int | Uint
}
// Integer interface type. all int or uint types
type Integer interface {
Int | Uint
}
// Float interface type
type Float interface {
~float32 | ~float64
}
// IntOrFloat interface type. all int and float types, but NOT uint types
type IntOrFloat interface {
Int | Float
}
// Number interface type. contains all int, uint and float types
type Number interface {
Int | Uint | Float
}
// XintOrFloat interface type. all int, uint and float types. alias of Number
//
// Deprecated: use Number instead.
type XintOrFloat interface {
Int | Uint | Float
}
// NumberOrString interface type for (x)int, float, ~string types
type NumberOrString interface {
Int | Uint | Float | ~string
}
// SortedType interface type. same of constraints.Ordered
//
// it can be ordered, that supports the operators < <= >= >.
//
// contains: (x)int, float, ~string types
type SortedType interface {
Int | Uint | Float | ~string
}
// Compared type. alias of constraints.SortedType
//
// TODO: use type alias, will error on go1.18 Error: types.go:50: interface contains type constraints
// type Compared = SortedType
type Compared interface {
Int | Uint | Float | ~string
}
// SimpleType interface type. alias of ScalarType
//
// contains: (x)int, float, ~string, ~bool types
type SimpleType interface {
Int | Uint | Float | ~string | ~bool
}
// ScalarType basic interface type.
//
// TIP: has bool type, it cannot be ordered
//
// contains: (x)int, float, ~string, ~bool types
type ScalarType interface {
Int | Uint | Float | ~string | ~bool
}
// StrMap is alias of map[string]string
type StrMap map[string]string
// AnyMap is alias of map[string]any
type AnyMap map[string]any
// L2StrMap is alias of map[string]map[string]string
type L2StrMap map[string]map[string]string
+231
View File
@@ -0,0 +1,231 @@
package goutil
import (
"fmt"
"math"
"reflect"
"strconv"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/strutil"
)
// Bool convert value to bool
func Bool(v any) bool {
bl, _ := comfunc.ToBool(v)
return bl
}
// ToBool try to convert type to bool
func ToBool(v any) (bool, error) { return comfunc.ToBool(v) }
// String func. always converts value to string, will ignore error
func String(v any) string {
s, _ := strutil.AnyToString(v, false)
return s
}
// ToString convert value to string, will return error on fail.
func ToString(v any) (string, error) { return strutil.AnyToString(v, true) }
// Int convert value to int
func Int(v any) int {
iv, _ := mathutil.ToInt(v)
return iv
}
// ToInt try to convert value to int
func ToInt(v any) (int, error) { return mathutil.ToInt(v) }
// Int64 convert value to int64
func Int64(v any) int64 {
iv, _ := mathutil.ToInt64(v)
return iv
}
// ToInt64 try to convert value to int64
func ToInt64(v any) (int64, error) { return mathutil.ToInt64(v) }
// Uint convert value to uint
func Uint(v any) uint {
iv, _ := mathutil.ToUint(v)
return iv
}
// ToUint try to convert value to uint
func ToUint(v any) (uint, error) { return mathutil.ToUint(v) }
// Uint64 convert value to uint64
func Uint64(v any) uint64 {
iv, _ := mathutil.ToUint64(v)
return iv
}
// ToUint64 try to convert value to uint64
func ToUint64(v any) (uint64, error) { return mathutil.ToUint64(v) }
// BoolString convert bool to string
func BoolString(bl bool) string { return strconv.FormatBool(bl) }
// BaseTypeVal convert custom type or intX,uintX,floatX to generic base type.
//
// intX => int64
// unitX => uint64
// floatX => float64
// string => string
//
// returns int64,uint64,string,float or error
func BaseTypeVal(val any) (value any, err error) {
return reflects.BaseTypeVal(reflect.ValueOf(val))
}
// SafeKind convert input any value to given reflect.Kind type.
func SafeKind(val any, kind reflect.Kind) (newVal any) {
newVal, _ = ToKind(val, kind, nil)
return
}
// SafeConv convert input any value to given reflect.Kind type.
func SafeConv(val any, kind reflect.Kind) (newVal any) {
newVal, _ = ToKind(val, kind, nil)
return
}
// ConvTo convert input any value to given reflect.Kind.
func ConvTo(val any, kind reflect.Kind) (newVal any, err error) {
return ToKind(val, kind, nil)
}
// ConvOrDefault convert input any value to given reflect.Kind.
// if fail will return default value.
func ConvOrDefault(val any, kind reflect.Kind, defVal any) any {
newVal, err := ToKind(val, kind, nil)
if err != nil {
return defVal
}
return newVal
}
// ToType
// func ToType[T any](val any, kind reflect.Kind, fbFunc func(val any) (T, error)) (newVal T, err error) {
// switch typVal.(type) { // assert ERROR
// case string:
// }
// }
// ToKind convert input any value to given reflect.Kind type.
//
// TIPs: Only support kind: string, bool, intX, uintX, floatX
//
// Examples:
//
// 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:
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt {
return nil, fmt.Errorf("value overflow int. val: %v", val)
}
newVal = dstV
}
case reflect.Int8:
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt8 {
return nil, fmt.Errorf("value overflow int8. val: %v", val)
}
newVal = int8(dstV)
}
case reflect.Int16:
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt16 {
return nil, fmt.Errorf("value overflow int16. val: %v", val)
}
newVal = int16(dstV)
}
case reflect.Int32:
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt32 {
return nil, fmt.Errorf("value overflow int32. val: %v", val)
}
newVal = int32(dstV)
}
case reflect.Int64:
var dstV int64
if dstV, err = mathutil.ToInt64(val); err == nil {
newVal = dstV
}
case reflect.Uint:
var dstV uint
if dstV, err = mathutil.ToUint(val); err == nil {
newVal = dstV
}
case reflect.Uint8:
var dstV uint
if dstV, err = mathutil.ToUint(val); err == nil {
if dstV > math.MaxUint8 {
return nil, fmt.Errorf("value overflow uint8. val: %v", val)
}
newVal = uint8(dstV)
}
case reflect.Uint16:
var dstV uint
if dstV, err = mathutil.ToUint(val); err == nil {
if dstV > math.MaxUint16 {
return nil, fmt.Errorf("value overflow uint16. val: %v", val)
}
newVal = uint16(dstV)
}
case reflect.Uint32:
var dstV uint
if dstV, err = mathutil.ToUint(val); err == nil {
if dstV > math.MaxUint32 {
return nil, fmt.Errorf("value overflow uint32. val: %v", val)
}
newVal = uint32(dstV)
}
case reflect.Uint64:
var dstV uint64
if dstV, err = mathutil.ToUint64(val); err == nil {
newVal = dstV
}
case reflect.Float32:
var dstV float64
if dstV, err = mathutil.ToFloat(val); err == nil {
if dstV > math.MaxFloat32 {
return nil, fmt.Errorf("value overflow float32. val: %v", val)
}
newVal = float32(dstV)
}
case reflect.Float64:
var dstV float64
if dstV, err = mathutil.ToFloat(val); err == nil {
newVal = dstV
}
case reflect.String:
var dstV string
if dstV, err = strutil.ToString(val); err == nil {
newVal = dstV
}
case reflect.Bool:
if bl, err1 := comfunc.ToBool(val); err1 == nil {
newVal = bl
} else {
err = err1
}
default:
if fbFunc != nil {
newVal, err = fbFunc(val)
} else {
err = comdef.ErrConvType
}
}
return
}
+62
View File
@@ -0,0 +1,62 @@
# Env Util
Provide some commonly system or go ENV util functions.
## Install
```shell
go get github.com/gookit/goutil/envutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/envutil)
## Functions API
> **Note**: doc by run `go doc ./envutil`
```go
func Environ() map[string]string
func GetBool(name string, def ...bool) bool
func GetInt(name string, def ...int) int
func Getenv(name string, def ...string) string
func HasShellEnv(shell string) bool
func IsConsole(out io.Writer) bool
func IsGithubActions() bool
func IsLinux() bool
func IsMSys() bool
func IsMac() bool
func IsSupport256Color() bool
func IsSupportColor() bool
func IsSupportTrueColor() bool
func IsTerminal(fd uintptr) bool
func IsWSL() bool
func IsWin() bool
func IsWindows() bool
func ParseEnvValue(val string) string
func ParseValue(val string) (newVal string)
func SetEnvs(mp map[string]string)
func StdIsTerminal() bool
func VarParse(val string) string
func VarReplace(s string) string
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./envutil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./envutil/...
```
+198
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()
}
+66
View File
@@ -0,0 +1,66 @@
// Package envutil provide some commonly ENV util functions.
package envutil
import (
"os"
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/internal/varexpr"
)
// ValueGetter Env value provider func.
//
// TIPS: you can custom provide data.
var ValueGetter = os.Getenv
// VarReplace replaces ${var} or $var in the string according to the values.
//
// is alias of the os.ExpandEnv()
func VarReplace(s string) string { return os.ExpandEnv(s) }
// ParseOrErr parse ENV var value from input string, support default value.
//
// Diff with the ParseValue, this support return error.
//
// With error format: ${VAR_NAME | ?error}
func ParseOrErr(val string) (string, error) {
return varexpr.Parse(val)
}
// ParseValue parse ENV var value from input string, support default value.
//
// Format:
//
// ${var_name} Only var name
// ${var_name | default} With default value
//
// Usage:
//
// envutil.ParseValue("${ APP_NAME }")
// envutil.ParseValue("${ APP_ENV | dev }")
func ParseValue(val string) string {
return varexpr.SafeParse(val)
}
// VarParse alias of the ParseValue
func VarParse(val string) string { return varexpr.SafeParse(val) }
// ParseEnvValue alias of the ParseValue
func ParseEnvValue(val string) string { return varexpr.SafeParse(val) }
// SplitText2map parse ENV text to map. Can use to parse .env file contents.
func SplitText2map(text string) map[string]string {
envMp, _ := comfunc.ParseEnvLines(text, comfunc.ParseEnvLineOption{
SkipOnErrorLine: true,
})
return envMp
}
// SplitLineToKv parse ENV line to k-v. eg: 'DEBUG=true' => ['DEBUG', 'true']
func SplitLineToKv(line string) (string, string) {
if line = strings.TrimSpace(line); line == "" {
return "", ""
}
return comfunc.SplitLineToKv(line, "=")
}
+115
View File
@@ -0,0 +1,115 @@
package envutil
import (
"os"
"path/filepath"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
"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)
if val == "" && len(def) > 0 {
val = def[0]
}
return val
}
// MustGet get ENV value by key name, if not exists or empty, will panic
func MustGet(name string) string {
if val := os.Getenv(name); val != "" {
return val
}
panic("ENV key '" + name + "' not exists")
}
// GetInt get int ENV value by key name, can with default value
func GetInt(name string, def ...int) int {
if val := os.Getenv(name); val != "" {
return strutil.QuietInt(val)
}
return basefn.FirstOr(def, 0)
}
// GetBool get bool ENV value by key name, can with default value
func GetBool(name string, def ...bool) bool {
if val := os.Getenv(name); val != "" {
return strutil.QuietBool(val)
}
return basefn.FirstOr(def, false)
}
// GetOne get one not empty ENV value by input names.
func GetOne(names []string, defVal ...string) string {
for _, name := range names {
if val := os.Getenv(name); val != "" {
return val
}
}
if len(defVal) > 0 {
return defVal[0]
}
return ""
}
// GetMulti ENV values by input names.
func GetMulti(names ...string) map[string]string {
valMap := make(map[string]string, len(names))
for _, name := range names {
if val := os.Getenv(name); val != "" {
valMap[name] = val
}
}
return valMap
}
// 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)
return true
}
return false
}
// EnvPaths get and split $PATH to []string
func EnvPaths() []string {
return filepath.SplitList(os.Getenv("PATH"))
}
// EnvMap like os.Environ, but will returns key-value map[string]string data.
func EnvMap() map[string]string { return comfunc.Environ() }
// Environ like os.Environ, but will returns key-value map[string]string data.
func Environ() map[string]string { return comfunc.Environ() }
// SearchEnvKeys values by given keywords
func SearchEnvKeys(keywords string) map[string]string {
return SearchEnv(keywords, false)
}
// SearchEnv values by given keywords
func SearchEnv(keywords string, matchValue bool) map[string]string {
founded := make(map[string]string)
for name, val := range comfunc.Environ() {
if strutil.IContains(name, keywords) {
founded[name] = val
} else if matchValue && strutil.IContains(val, keywords) {
founded[name] = val
}
}
return founded
}
+143
View File
@@ -0,0 +1,143 @@
package envutil
import (
"io"
"os"
"runtime"
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/sysutil"
"golang.org/x/term"
)
// IsWin system. linux windows darwin
func IsWin() bool {
return runtime.GOOS == "windows"
}
// IsWindows system. alias of IsWin
func IsWindows() bool {
return runtime.GOOS == "windows"
}
// IsMac system
func IsMac() bool {
return runtime.GOOS == "darwin"
}
// IsLinux system
func IsLinux() bool {
return runtime.GOOS == "linux"
}
// IsMSys msys(MINGW64) env. alias of the sysutil.IsMSys()
func IsMSys() bool {
return sysutil.IsMSys()
}
// IsTerminal isatty check
//
// Usage:
//
// envutil.IsTerminal(os.Stdout.Fd())
func IsTerminal(fd uintptr) bool {
// return isatty.IsTerminal(fd) // "github.com/mattn/go-isatty"
return term.IsTerminal(int(fd))
}
// StdIsTerminal os.Stdout is terminal
func StdIsTerminal() bool {
return IsTerminal(os.Stdout.Fd())
}
// IsConsole check out is console env. alias of the sysutil.IsConsole()
func IsConsole(out io.Writer) bool {
return sysutil.IsConsole(out)
}
// HasShellEnv has shell env check.
//
// Usage:
//
// HasShellEnv("sh")
// HasShellEnv("bash")
func HasShellEnv(shell string) bool {
return comfunc.HasShellEnv(shell)
}
// Support color:
//
// "TERM=xterm"
// "TERM=xterm-vt220"
// "TERM=xterm-256color"
// "TERM=screen-256color"
// "TERM=tmux-256color"
// "TERM=rxvt-unicode-256color"
//
// Don't support color:
//
// "TERM=cygwin"
var specialColorTerms = map[string]bool{
"alacritty": true,
}
// IsSupportColor check current console is support color.
//
// Supported:
//
// linux, mac, or Windows's ConEmu, Cmder, putty, git-bash.exe
//
// Not support:
//
// windows cmd.exe, powerShell.exe
func IsSupportColor() bool {
envTerm := os.Getenv("TERM")
if strings.Contains(envTerm, "xterm") {
return true
}
// it's special color term
if _, ok := specialColorTerms[envTerm]; ok {
return true
}
// like on ConEmu software, e.g "ConEmuANSI=ON"
if os.Getenv("ConEmuANSI") == "ON" {
return true
}
// like on ConEmu software, e.g "ANSICON=189x2000 (189x43)"
if os.Getenv("ANSICON") != "" {
return true
}
// up: if support 256-color, can also support basic color.
return IsSupport256Color()
}
// IsSupport256Color render
func IsSupport256Color() bool {
// "TERM=xterm-256color"
// "TERM=screen-256color"
// "TERM=tmux-256color"
// "TERM=rxvt-unicode-256color"
supported := strings.Contains(os.Getenv("TERM"), "256color")
if !supported {
// up: if support true-color, can also support 256-color.
supported = IsSupportTrueColor()
}
return supported
}
// IsSupportTrueColor render. IsSupportRGBColor
func IsSupportTrueColor() bool {
// "COLORTERM=truecolor"
return strings.Contains(os.Getenv("COLORTERM"), "truecolor")
}
// IsGithubActions env
func IsGithubActions() bool {
return os.Getenv("GITHUB_ACTIONS") == "true"
}
+53
View File
@@ -0,0 +1,53 @@
package envutil
import (
"os"
"github.com/gookit/goutil/internal/comfunc"
)
// SetEnvMap set multi ENV(string-map) to os
func SetEnvMap(mp map[string]string) {
for key, value := range mp {
_ = os.Setenv(key, value)
}
}
// SetEnvs set multi k-v ENV pairs to os
func SetEnvs(kvPairs ...string) {
if len(kvPairs)%2 == 1 {
panic("envutil.SetEnvs: odd argument count")
}
for i := 0; i < len(kvPairs); i += 2 {
_ = os.Setenv(kvPairs[i], kvPairs[i+1])
}
}
// UnsetEnvs from os
func UnsetEnvs(keys ...string) {
for _, key := range keys {
_ = os.Unsetenv(key)
}
}
// LoadText parse multiline text to ENV. Can use to load .env file contents.
//
// Usage:
//
// envutil.LoadText(fsutil.ReadFile(".env"))
func LoadText(text string) {
envMp := SplitText2map(text)
for key, value := range envMp {
_ = os.Setenv(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 {
return os.Setenv(k, v) == nil
}
return false
}
+221
View File
@@ -0,0 +1,221 @@
# ErrorX
`errorx` provide an enhanced error implements for go, allow with stacktraces and wrap another error.
## Install
```go
go get github.com/gookit/goutil/errorx
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/errorx)
## Usage
### Create error with call stack info
- use the `errorx.New` instead `errors.New`
```go
func doSomething() error {
if false {
// return errors.New("a error happen")
return errorx.New("a error happen")
}
}
```
- use the `errorx.Newf` or `errorx.Errorf` instead `fmt.Errorf`
```go
func doSomething() error {
if false {
// return fmt.Errorf("a error %s", "happen")
return errorx.Newf("a error %s", "happen")
}
}
```
### Wrap the previous error
used like this before:
```go
if err := SomeFunc(); err != nil {
return err
}
```
can be replaced with:
```go
if err := SomeFunc(); err != nil {
return errors.Stacked(err)
}
```
## Output details
error output details for use `errorx`
### Use errorx.New
`errorx` functions for new error:
```go
func New(msg string) error
func Newf(tpl string, vars ...interface{}) error
func Errorf(tpl string, vars ...interface{}) error
```
Examples:
```go
err := errorx.New("the error message")
fmt.Println(err)
// fmt.Printf("%v\n", err)
// fmt.Printf("%#v\n", err)
```
> from the test: `errorx_test.TestNew()`
**Output**:
```text
the error message
STACK:
github.com/gookit/goutil/errorx_test.returnXErr()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:21
github.com/gookit/goutil/errorx_test.returnXErrL2()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:25
github.com/gookit/goutil/errorx_test.TestNew()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:29
testing.tRunner()
/usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1439
runtime.goexit()
/usr/local/Cellar/go/1.18/libexec/src/runtime/asm_amd64.s:1571
```
### Use errorx.With
`errorx` functions for with another error:
```go
func With(err error, msg string) error
func Withf(err error, tpl string, vars ...interface{}) error
```
With a go raw error:
```go
err1 := returnErr("first error message")
err2 := errorx.With(err1, "second error message")
fmt.Println(err2)
```
> from the test: `errorx_test.TestWith_goerr()`
**Output**:
```text
second error message
STACK:
github.com/gookit/goutil/errorx_test.TestWith_goerr()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:51
testing.tRunner()
/usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1439
runtime.goexit()
/usr/local/Cellar/go/1.18/libexec/src/runtime/asm_amd64.s:1571
Previous: first error message
```
With a `errorx` error:
```go
err1 := returnXErr("first error message")
err2 := errorx.With(err1, "second error message")
fmt.Println(err2)
```
> from the test: `errorx_test.TestWith_errorx()`
**Output**:
```text
second error message
STACK:
github.com/gookit/goutil/errorx_test.TestWith_errorx()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:64
testing.tRunner()
/usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1439
runtime.goexit()
/usr/local/Cellar/go/1.18/libexec/src/runtime/asm_amd64.s:1571
Previous: first error message
STACK:
github.com/gookit/goutil/errorx_test.returnXErr()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:21
github.com/gookit/goutil/errorx_test.TestWith_errorx()
/Users/inhere/Workspace/godev/gookit/goutil/errorx/errorx_test.go:61
testing.tRunner()
/usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1439
runtime.goexit()
/usr/local/Cellar/go/1.18/libexec/src/runtime/asm_amd64.s:1571
```
### Use errorx.Wrap
```go
err := errors.New("first error message")
err = errorx.Wrap(err, "second error message")
err = errorx.Wrap(err, "third error message")
// fmt.Println(err)
// fmt.Println(err.Error())
```
Direct print the `err`:
```text
third error message
Previous: second error message
Previous: first error message
```
Print the `err.Error()`:
```text
third error message; second error message; first error message
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./errorx/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./errorx/...
```
## Refers
- golang errors
- https://github.com/joomcode/errorx
- https://github.com/pkg/errors
- https://github.com/juju/errors
- https://github.com/go-errors/errors
+61
View File
@@ -0,0 +1,61 @@
package errorx
import (
"errors"
"fmt"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/comfunc"
)
// IsTrue assert result is true, otherwise will return error
func IsTrue(result bool, fmtAndArgs ...any) error {
if !result {
return errors.New(formatErrMsg("result should be True", fmtAndArgs))
}
return nil
}
// IsFalse assert result is false, otherwise will return error
func IsFalse(result bool, fmtAndArgs ...any) error {
if result {
return errors.New(formatErrMsg("result should be False", fmtAndArgs))
}
return nil
}
// IsIn value should be in the list, otherwise will return error
func IsIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error {
if arrutil.NotIn(value, list) {
var errMsg string
if len(fmtAndArgs) > 0 {
errMsg = comfunc.FormatWithArgs(fmtAndArgs)
} else {
errMsg = fmt.Sprintf("value should be in the %v", list)
}
return errors.New(errMsg)
}
return nil
}
// NotIn value should not be in the list, otherwise will return error
func NotIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error {
if arrutil.In(value, list) {
var errMsg string
if len(fmtAndArgs) > 0 {
errMsg = comfunc.FormatWithArgs(fmtAndArgs)
} else {
errMsg = fmt.Sprintf("value should not be in the %v", list)
}
return errors.New(errMsg)
}
return nil
}
func formatErrMsg(defMsg string, fmtAndArgs []any) string {
if len(fmtAndArgs) > 0 {
return comfunc.FormatWithArgs(fmtAndArgs)
}
return defMsg
}
+154
View File
@@ -0,0 +1,154 @@
package errorx
import (
"fmt"
"strconv"
"strings"
)
// ErrorCoder interface
type ErrorCoder interface {
error
Code() int
}
// ErrorR useful for web service replay/response.
// code == 0 is successful. otherwise, is failed.
type ErrorR interface {
ErrorCoder
fmt.Stringer
IsSuc() bool
IsFail() bool
}
// error reply struct
type errorR struct {
code int
msg string
}
// NewR code with error response
func NewR(code int, msg string) ErrorR {
return &errorR{code: code, msg: msg}
}
// Fail code with error response
func Fail(code int, msg string) ErrorR {
return &errorR{code: code, msg: msg}
}
// Failf code with error response
func Failf(code int, tpl string, v ...any) ErrorR {
return &errorR{code: code, msg: fmt.Sprintf(tpl, v...)}
}
// Suc success response reply
func Suc(msg string) ErrorR {
return &errorR{code: 0, msg: msg}
}
// IsSuc code value check
func (e *errorR) IsSuc() bool {
return e.code == 0
}
// IsFail code value check
func (e *errorR) IsFail() bool {
return e.code != 0
}
// Code value
func (e *errorR) Code() int {
return e.code
}
// Error string
func (e *errorR) Error() string {
return e.msg
}
// String get
func (e *errorR) String() string {
return e.msg + "(code: " + strconv.FormatInt(int64(e.code), 10) + ")"
}
// GoString get.
func (e *errorR) GoString() string {
return e.String()
}
// ErrorM multi error map
type ErrorM map[string]error
// ErrMap alias of ErrorM
type ErrMap = ErrorM
// Error string
func (e ErrorM) Error() string {
var sb strings.Builder
for name, err := range e {
sb.WriteString(name)
sb.WriteByte(':')
sb.WriteString(err.Error())
sb.WriteByte('\n')
}
return sb.String()
}
// ErrorOrNil error
func (e ErrorM) ErrorOrNil() error {
if len(e) == 0 {
return nil
}
return e
}
// IsEmpty error
func (e ErrorM) IsEmpty() bool {
return len(e) == 0
}
// One error
func (e ErrorM) One() error {
for _, err := range e {
return err
}
return nil
}
// Errors multi error list
type Errors []error
// ErrList alias for Errors
type ErrList = Errors
// Error string
func (es Errors) Error() string {
var sb strings.Builder
for _, err := range es {
sb.WriteString(err.Error())
sb.WriteByte('\n')
}
return sb.String()
}
// ErrorOrNil error
func (es Errors) ErrorOrNil() error {
if len(es) == 0 {
return nil
}
return es
}
// IsEmpty error
func (es Errors) IsEmpty() bool {
return len(es) == 0
}
// First error
func (es Errors) First() error {
if len(es) > 0 {
return es[0]
}
return nil
}
+336
View File
@@ -0,0 +1,336 @@
// Package errorx provide an enhanced error implements for go,
// allow with stacktraces and wrap another error.
package errorx
import (
"bytes"
"errors"
"fmt"
"io"
)
// Causer interface for get first cause error
type Causer interface {
// Cause returns the first cause error by call err.Cause().
// Otherwise, will returns current error.
Cause() error
}
// Unwrapper interface for get previous error
type Unwrapper interface {
// Unwrap returns previous error by call err.Unwrap().
// Otherwise, will returns nil.
Unwrap() error
}
// XErrorFace interface
type XErrorFace interface {
error
Causer
Unwrapper
}
// Exception interface
// type Exception interface {
// XErrorFace
// Code() string
// Message() string
// StackString() string
// }
/*************************************************************
* implements XErrorFace interface
*************************************************************/
// ErrorX struct
//
// TIPS:
//
// fmt pkg call order: Format > GoString > Error > String
type ErrorX struct {
// trace stack
*stack
prev error
msg string
}
// Cause implements Causer.
func (e *ErrorX) Cause() error {
if e.prev == nil {
return e
}
if ex, ok := e.prev.(*ErrorX); ok {
return ex.Cause()
}
return e.prev
}
// Unwrap implements Unwrapper.
func (e *ErrorX) Unwrap() error {
return e.prev
}
// Format error, will output stack information.
func (e *ErrorX) Format(s fmt.State, verb rune) {
// format current error: only output on have msg
if len(e.msg) > 0 {
_, _ = io.WriteString(s, e.msg)
if e.stack != nil {
e.stack.Format(s, verb)
}
}
// format prev error
if e.prev == nil {
return
}
_, _ = s.Write([]byte("\nPrevious: "))
if ex, ok := e.prev.(*ErrorX); ok {
ex.Format(s, verb)
} else {
_, _ = s.Write([]byte(e.prev.Error()))
}
}
// GoString to GO string, contains stack information.
// printing an error with %#v will produce useful information.
func (e *ErrorX) GoString() string {
// var sb strings.Builder
var buf bytes.Buffer
_, _ = e.WriteTo(&buf)
return buf.String()
}
// Error msg string, not contains stack information.
func (e *ErrorX) Error() string {
var buf bytes.Buffer
e.writeMsgTo(&buf)
return buf.String()
}
// String error to string, contains stack information.
func (e *ErrorX) String() string {
return e.GoString()
}
// WriteTo write the error to a writer, contains stack information.
func (e *ErrorX) WriteTo(w io.Writer) (n int64, err error) {
// current error: only output on have msg
if len(e.msg) > 0 {
_, _ = w.Write([]byte(e.msg))
// with stack
if e.stack != nil {
_, _ = e.stack.WriteTo(w)
}
}
// with prev error
if e.prev != nil {
_, _ = io.WriteString(w, "\nPrevious: ")
if ex, ok := e.prev.(*ErrorX); ok {
_, _ = ex.WriteTo(w)
} else {
_, _ = io.WriteString(w, e.prev.Error())
}
}
return
}
// Message error message of current
func (e *ErrorX) Message() string {
return e.msg
}
// StackString returns error stack string of current.
func (e *ErrorX) StackString() string {
if e.stack != nil {
return e.stack.String()
}
return ""
}
// writeMsgTo write the error msg to a writer
func (e *ErrorX) writeMsgTo(w io.Writer) {
// current error
if len(e.msg) > 0 {
_, _ = w.Write([]byte(e.msg))
}
// with prev error
if e.prev != nil {
_, _ = w.Write([]byte("; "))
if ex, ok := e.prev.(*ErrorX); ok {
ex.writeMsgTo(w)
} else {
_, _ = io.WriteString(w, e.prev.Error())
}
}
}
// CallerFunc returns the error caller func. if stack is nil, will return nil
func (e *ErrorX) CallerFunc() *Func {
if e.stack == nil {
return nil
}
return FuncForPC(e.stack.CallerPC())
}
// Location information for the caller func. more please see CallerFunc
//
// Returns eg:
//
// github.com/gookit/goutil/errorx_test.TestWithPrev(), errorx_test.go:34
func (e *ErrorX) Location() string {
if e.stack == nil {
return "unknown"
}
return e.CallerFunc().Location()
}
/*************************************************************
* new error with call stacks
*************************************************************/
// New error message and with caller stacks
func New(msg string) error {
return &ErrorX{
msg: msg,
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// Newf error with format message, and with caller stacks.
// alias of Errorf()
func Newf(tpl string, vars ...any) error {
return &ErrorX{
msg: fmt.Sprintf(tpl, vars...),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// Errorf error with format message, and with caller stacks
func Errorf(tpl string, vars ...any) error {
return &ErrorX{
msg: fmt.Sprintf(tpl, vars...),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// With prev error and error message, and with caller stacks
func With(err error, msg string) error {
return &ErrorX{
msg: msg,
prev: err,
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// Withf error and with format message, and with caller stacks
func Withf(err error, tpl string, vars ...any) error {
return &ErrorX{
msg: fmt.Sprintf(tpl, vars...),
prev: err,
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// WithPrev error and message, and with caller stacks. alias of With()
func WithPrev(err error, msg string) error {
return &ErrorX{
msg: msg,
prev: err,
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// WithPrevf error and with format message, and with caller stacks. alias of Withf()
func WithPrevf(err error, tpl string, vars ...any) error {
return &ErrorX{
msg: fmt.Sprintf(tpl, vars...),
prev: err,
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
/*************************************************************
* wrap go error with call stacks
*************************************************************/
// WithStack wrap a go error with a stacked trace. If err is nil, will return nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &ErrorX{
msg: err.Error(),
// prev: err,
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// Traced warp a go error and with caller stacks. alias of WithStack()
func Traced(err error) error {
if err == nil {
return nil
}
return &ErrorX{
msg: err.Error(),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// Stacked warp a go error and with caller stacks. alias of WithStack()
func Stacked(err error) error {
if err == nil {
return nil
}
return &ErrorX{
msg: err.Error(),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
}
}
// WithOptions new error with some option func
func WithOptions(msg string, fns ...func(opt *ErrStackOpt)) error {
opt := newErrOpt()
for _, fn := range fns {
fn(opt)
}
return &ErrorX{
msg: msg,
stack: callersStack(opt.SkipDepth, opt.TraceDepth),
}
}
/*************************************************************
* helper func for wrap error without stacks
*************************************************************/
// Wrap error and with message, but not with stack
func Wrap(err error, msg string) error {
if err == nil {
return errors.New(msg)
}
return &ErrorX{
msg: msg,
prev: err,
}
}
// Wrapf error with format message, but not with stack
func Wrapf(err error, tpl string, vars ...any) error {
if err == nil {
return fmt.Errorf(tpl, vars...)
}
return &ErrorX{
msg: fmt.Sprintf(tpl, vars...),
prev: err,
}
}
+187
View File
@@ -0,0 +1,187 @@
package errorx
import (
"bytes"
"fmt"
"io"
"path/filepath"
"runtime"
"strconv"
)
// stack represents a stack of program counters.
type stack []uintptr
// Format stack trace
func (s *stack) Format(fs fmt.State, verb rune) {
switch verb {
// case 'v', 's':
case 'v':
_, _ = s.WriteTo(fs)
}
}
// StackLen for error
func (s *stack) StackLen() int {
return len(*s)
}
// WriteTo for error
func (s *stack) WriteTo(w io.Writer) (int64, error) {
if len(*s) == 0 {
return 0, nil
}
nn, _ := w.Write([]byte("\nSTACK:\n"))
for _, pc := range *s {
// For historical reasons if pc is interpreted as a uintptr
// its value represents the program counter + 1.
fc := runtime.FuncForPC(pc - 1)
if fc == nil {
continue
}
// file eg: workspace/godev/gookit/goutil/errorx/errorx_test.go
file, line := fc.FileLine(pc - 1)
// f.Name() eg: github.com/gookit/goutil/errorx_test.TestWithPrev()
location := fc.Name() + "()\n " + file + ":" + strconv.Itoa(line) + "\n"
n, _ := w.Write([]byte(location))
nn += n
}
return int64(nn), nil
}
// String format to string
func (s *stack) String() string {
var buf bytes.Buffer
_, _ = s.WriteTo(&buf)
return buf.String()
}
// StackFrames stack frame list
func (s *stack) StackFrames() *runtime.Frames {
return runtime.CallersFrames(*s)
}
// CallerPC the caller PC value in the stack. it is first frame.
func (s *stack) CallerPC() uintptr {
if len(*s) == 0 {
return 0
}
// For historical reasons if pc is interpreted as a uintptr
// its value represents the program counter + 1.
return (*s)[0] - 1
}
/*************************************************************
* For error caller func
*************************************************************/
// Func struct
type Func struct {
*runtime.Func
pc uintptr
}
// FuncForPC create.
func FuncForPC(pc uintptr) *Func {
fc := runtime.FuncForPC(pc)
if fc == nil {
return nil
}
return &Func{
pc: pc,
Func: fc,
}
}
// FileLine returns the file name and line number of the source code
func (f *Func) FileLine() (file string, line int) {
return f.Func.FileLine(f.pc)
}
// Location simple location info for the func
//
// Returns eg:
//
// "github.com/gookit/goutil/errorx_test.TestWithPrev(), errorx_test.go:34"
func (f *Func) Location() string {
file, line := f.FileLine()
return f.Name() + "(), " + filepath.Base(file) + ":" + strconv.Itoa(line)
}
// String of the func
//
// Returns eg:
//
// github.com/gookit/goutil/errorx_test.TestWithPrev()
// At /path/to/github.com/gookit/goutil/errorx_test.go:34
func (f *Func) String() string {
file, line := f.FileLine()
return f.Name() + "()\n At " + file + ":" + strconv.Itoa(line)
}
// MarshalText handle
func (f *Func) MarshalText() ([]byte, error) {
return []byte(f.String()), nil
}
/*************************************************************
* helper func for callers stacks
*************************************************************/
// ErrStackOpt struct
type ErrStackOpt struct {
SkipDepth int
TraceDepth int
}
// default option
var stdOpt = newErrOpt()
// ResetStdOpt config
func ResetStdOpt() {
stdOpt = newErrOpt()
}
func newErrOpt() *ErrStackOpt {
return &ErrStackOpt{
SkipDepth: 3,
TraceDepth: 8,
}
}
// Config the stdOpt setting
func Config(fns ...func(opt *ErrStackOpt)) {
for _, fn := range fns {
fn(stdOpt)
}
}
// SkipDepth setting
func SkipDepth(skipDepth int) func(opt *ErrStackOpt) {
return func(opt *ErrStackOpt) {
opt.SkipDepth = skipDepth
}
}
// TraceDepth setting
func TraceDepth(traceDepth int) func(opt *ErrStackOpt) {
return func(opt *ErrStackOpt) {
opt.TraceDepth = traceDepth
}
}
func callersStack(skip, depth int) *stack {
pcs := make([]uintptr, depth)
num := runtime.Callers(skip, pcs[:])
var st stack = pcs[0:num]
return &st
}
+114
View File
@@ -0,0 +1,114 @@
package errorx
import (
"errors"
"fmt"
)
// E new a raw go error. alias of errors.New()
func E(msg string) error { return errors.New(msg) }
// Err new a raw go error. alias of errors.New()
func Err(msg string) error { return errors.New(msg) }
// Raw new a raw go error. alias of errors.New()
func Raw(msg string) error { return errors.New(msg) }
// Ef new a raw go error. alias of fmt.Errorf
func Ef(tpl string, vars ...any) error { return fmt.Errorf(tpl, vars...) }
// Errf new a raw go error. alias of fmt.Errorf
func Errf(tpl string, vars ...any) error { return fmt.Errorf(tpl, vars...) }
// Rf new a raw go error. alias of fmt.Errorf
func Rf(tpl string, vs ...any) error { return fmt.Errorf(tpl, vs...) }
// Rawf new a raw go error. alias of fmt.Errorf
func Rawf(tpl string, vs ...any) error { return fmt.Errorf(tpl, vs...) }
/*************************************************************
* helper func for error
*************************************************************/
// Cause returns the first cause error by call err.Cause().
// Otherwise, will returns current error.
func Cause(err error) error {
if err == nil {
return nil
}
if err, ok := err.(Causer); ok {
return err.Cause()
}
return err
}
// Unwrap returns previous error by call err.Unwrap().
// Otherwise, will returns nil.
func Unwrap(err error) error {
if err == nil {
return nil
}
if err, ok := err.(Unwrapper); ok {
return err.Unwrap()
}
return nil
}
// Previous alias of Unwrap()
func Previous(err error) error { return Unwrap(err) }
// IsErrorX check
func IsErrorX(err error) (ok bool) {
_, ok = err.(*ErrorX)
return
}
// ToErrorX convert check. like errors.As()
func ToErrorX(err error) (ex *ErrorX, ok bool) {
ex, ok = err.(*ErrorX)
return
}
// MustEX convert error to *ErrorX, panic if err check failed.
func MustEX(err error) *ErrorX {
ex, ok := err.(*ErrorX)
if !ok {
panic("errorx: error is not *ErrorX")
}
return ex
}
// Has contains target error, or err is eq target.
// alias of errors.Is()
func Has(err, target error) bool {
return errors.Is(err, target)
}
// Is alias of errors.Is()
func Is(err, target error) bool {
return errors.Is(err, target)
}
// To try convert err to target, returns is result.
//
// NOTICE: target must be ptr and not nil. alias of errors.As()
//
// Usage:
//
// var ex *errorx.ErrorX
// err := doSomething()
// if errorx.To(err, &ex) {
// fmt.Println(ex.GoString())
// }
func To(err error, target any) bool {
return errors.As(err, target)
}
// As same of the To(), alias of errors.As()
//
// NOTICE: target must be ptr and not nil
func As(err error, target any) bool {
return errors.As(err, target)
}
+138
View File
@@ -0,0 +1,138 @@
# FileSystem Util
`fsutil` Provide some commonly file system util functions.
## Install
```shell
go get github.com/gookit/goutil/fsutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/fsutil)
## Find files
More see [./finder](./finder)
```go
// find all files in dir
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {
fmt.Println(filePath)
return nil
})
// find files with filters
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {
fmt.Println(filePath)
return nil
}, fsutil.ExcludeDotFile)
```
## Functions API
> **Note**: doc by run `go doc ./fsutil`
```go
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool
func CopyFile(srcPath, dstPath string) error
func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error)
func DeleteIfExist(fPath string) error
func DeleteIfFileExist(fPath string) error
func Dir(fpath string) string
func DiscardReader(src io.Reader)
func ExcludeDotFile(_ string, ent fs.DirEntry) bool
func Expand(pathStr string) string
func ExpandPath(pathStr string) string
func Extname(fpath string) string
func FileExists(path string) bool
func FileExt(fpath string) string
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
func GetContents(in any) []byte
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error)
func IsAbsPath(aPath string) bool
func IsDir(path string) bool
func IsFile(path string) bool
func IsImageFile(path string) bool
func IsZipFile(filepath string) bool
func JoinPaths(elem ...string) string
func JoinSubPaths(basePath string, elem ...string) string
func LineScanner(in any) *bufio.Scanner
func MimeType(path string) (mime string)
func MkDirs(perm os.FileMode, dirPaths ...string) error
func MkParentDir(fpath string) error
func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error
func Mkdir(dirPath string, perm os.FileMode) error
func MustCopyFile(srcPath, dstPath string)
func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File
func MustReadFile(filePath string) []byte
func MustReadReader(r io.Reader) []byte
func MustRemove(fPath string)
func Name(fpath string) string
func NewIOReader(in any) (r io.Reader, err error)
func OSTempDir(pattern string) (string, error)
func OSTempFile(pattern string) (*os.File, error)
func OnlyFindDir(_ string, ent fs.DirEntry) bool
func OnlyFindFile(_ string, ent fs.DirEntry) bool
func OpenAppendFile(filepath string) (*os.File, error)
func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error)
func OpenReadFile(filepath string) (*os.File, error)
func OpenTruncFile(filepath string) (*os.File, error)
func PathExists(path string) bool
func PathMatch(pattern, s string) bool
func PathName(fpath string) string
func PutContents(filePath string, data any, fileFlag ...int) (int, error)
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error)
func QuietRemove(fPath string)
func ReadAll(in any) []byte
func ReadExistFile(filePath string) []byte
func ReadFile(filePath string) []byte
func ReadOrErr(in any) ([]byte, error)
func ReadReader(r io.Reader) []byte
func ReadString(in any) string
func ReadStringOrErr(in any) (string, error)
func ReaderMimeType(r io.Reader) (mime string)
func Realpath(pathStr string) string
func Remove(fPath string) error
func ResolvePath(pathStr string) string
func RmFileIfExist(fPath string) error
func RmIfExist(fPath string) error
func SearchNameUp(dirPath, name string) string
func SearchNameUpx(dirPath, name string) (string, bool)
func SlashPath(path string) string
func SplitPath(pathStr string) (dir, name string)
func Suffix(fpath string) string
func TempDir(dir, pattern string) (string, error)
func TempFile(dir, pattern string) (*os.File, error)
func TextScanner(in any) *scanner.Scanner
func ToAbsPath(p string) string
func UnixPath(path string) string
func Unzip(archive, targetDir string) (err error)
func WalkDir(dir string, fn fs.WalkDirFunc) error
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error
func WriteOSFile(f *os.File, data any) (n int, err error)
type FilterFunc func(fPath string, ent fs.DirEntry) bool
func ExcludeSuffix(ss ...string) FilterFunc
func IncludeSuffix(ss ...string) FilterFunc
type HandleFunc func(fPath string, ent fs.DirEntry) error
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./fsutil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./fsutil/...
```
+162
View File
@@ -0,0 +1,162 @@
package fsutil
import (
"bytes"
"io"
"os"
"path"
"path/filepath"
)
// perm for create dir or file
var (
DefaultDirPerm os.FileMode = 0775
DefaultFilePerm os.FileMode = 0665
OnlyReadFilePerm os.FileMode = 0444
)
var (
// DefaultFileFlags for create and write
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
// OnlyReadFileFlags open file for read
OnlyReadFileFlags = os.O_RDONLY
)
// alias methods
var (
DirExist = IsDir
FileExist = IsFile
PathExist = PathExists
)
// PathExists reports whether the named file or directory exists.
func PathExists(path string) bool {
if path == "" {
return false
}
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// IsDir reports whether the named directory exists.
func IsDir(path string) bool {
if path == "" || len(path) > 468 {
return false
}
if fi, err := os.Stat(path); err == nil {
return fi.IsDir()
}
return false
}
// FileExists reports whether the named file or directory exists.
func FileExists(path string) bool {
return IsFile(path)
}
// 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
}
if fi, err := os.Stat(path); err == nil {
return !fi.IsDir()
}
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 {
if aPath[0] == '/' {
return true
}
return filepath.IsAbs(aPath)
}
return false
}
// IsEmptyDir reports whether the named directory is empty.
func IsEmptyDir(dirPath string) bool {
f, err := os.Open(dirPath)
if err != nil {
return false
}
defer f.Close()
_, err = f.Readdirnames(1)
return err == io.EOF
}
// ImageMimeTypes refer net/http package
var ImageMimeTypes = map[string]string{
"bmp": "image/bmp",
"gif": "image/gif",
"ief": "image/ief",
"jpg": "image/jpeg",
// "jpe": "image/jpeg",
"jpeg": "image/jpeg",
"png": "image/png",
"svg": "image/svg+xml",
"ico": "image/x-icon",
"webp": "image/webp",
}
// IsImageFile check file is image file.
func IsImageFile(path string) bool {
mime := MimeType(path)
if mime == "" {
return false
}
for _, imgMime := range ImageMimeTypes {
if imgMime == mime {
return true
}
}
return false
}
// IsZipFile check is zip file.
// from https://blog.csdn.net/wangshubo1989/article/details/71743374
func IsZipFile(filepath string) bool {
f, err := os.Open(filepath)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, 4)
if n, err := f.Read(buf); err != nil || n < 4 {
return false
}
return bytes.Equal(buf, []byte("PK\x03\x04"))
}
// PathMatch check for a string. alias of path.Match()
func PathMatch(pattern, s string) bool {
ok, err := path.Match(pattern, s)
if err != nil {
ok = false
}
return ok
}
+109
View File
@@ -0,0 +1,109 @@
package fsutil
import (
"io/fs"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/strutil"
)
const (
// MimeSniffLen sniff Length, use for detect file mime type
MimeSniffLen = 512
)
// NameMatchFunc name matches func, alias of comdef.StringMatchFunc
type NameMatchFunc = comdef.StringMatchFunc
// PathMatchFunc path matches func. alias of comdef.StringMatchFunc
type PathMatchFunc = comdef.StringMatchFunc
// Entry extends fs.DirEntry, add some useful methods
type Entry interface {
fs.DirEntry
// Path gets file/dir full path. eg: "/path/to/file.go"
Path() string
// Info get file info. like fs.DirEntry.Info(), but will cache result.
Info() (fs.FileInfo, error)
}
type entry struct {
fs.DirEntry
path string
stat fs.FileInfo
sErr error
}
// NewEntry create a new Entry instance
func NewEntry(fPath string, ent fs.DirEntry) Entry {
return &entry{
path: fPath,
DirEntry: ent,
}
}
// Path gets full file/dir path. eg: "/path/to/file.go"
func (e *entry) Path() string {
return e.path
}
// Info gets file info, will cache result
func (e *entry) Info() (fs.FileInfo, error) {
if e.stat == nil {
e.stat, e.sErr = e.DirEntry.Info()
}
return e.stat, e.sErr
}
// String get string representation
func (e *entry) String() string {
return strutil.OrCond(e.IsDir(), "dir: ", "file: ") + e.Path()
}
// FileInfo extends fs.FileInfo, add some useful methods
type FileInfo interface {
fs.FileInfo
// Path gets file full path. eg: "/path/to/file.go"
Path() string
}
type fileInfo struct {
fs.FileInfo
fullPath string
}
// NewFileInfo create a new FileInfo instance
func NewFileInfo(fPath string, info fs.FileInfo) FileInfo {
return &fileInfo{
fullPath: fPath,
FileInfo: info,
}
}
// Path gets file full path. eg: "/path/to/file.go"
func (fi *fileInfo) Path() string {
return fi.fullPath
}
// FileInfos type for FileInfo slice
//
// implements sort.Interface:
//
// sorts by oldest time modified in the file info.
// eg: [old_220211, old_220212, old_220213]
type FileInfos []FileInfo
// Len get length
func (fis FileInfos) Len() int {
return len(fis)
}
// Swap swap values
func (fis FileInfos) Swap(i, j int) {
fis[i], fis[j] = fis[j], fis[i]
}
// Less check by mod time
func (fis FileInfos) Less(i, j int) bool {
return fis[j].ModTime().After(fis[i].ModTime())
}
+345
View File
@@ -0,0 +1,345 @@
package fsutil
import (
"io/fs"
"os"
"path"
"path/filepath"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
// FilePathInDirs get full file path in dirs. return empty string if not found.
//
// Params:
// - file: can be relative path, file name, full path.
// - dirs: dir paths
func FilePathInDirs(fPath string, dirs ...string) string {
fPath = comfunc.ExpandHome(fPath)
if FileExists(fPath) {
return fPath
}
for _, dirPath := range dirs {
fPath = JoinSubPaths(dirPath, fPath)
if FileExists(fPath) {
return fPath
}
}
return "" // not found
}
// FirstExists check multi paths and return first exists path.
func FirstExists(paths ...string) string {
return MatchFirst(paths, PathExists, "")
}
// FirstExistsDir check multi paths and return first exists dir.
func FirstExistsDir(paths ...string) string {
return MatchFirst(paths, IsDir, "")
}
// FirstExistsFile check multi paths and return first exists file.
func FirstExistsFile(paths ...string) string {
return MatchFirst(paths, IsFile, "")
}
// MatchPaths given paths by custom mather func.
func MatchPaths(paths []string, matcher PathMatchFunc) []string {
var ret []string
for _, p := range paths {
if matcher(p) {
ret = append(ret, p)
}
}
return ret
}
// MatchFirst filter paths by filter func and return first match path.
func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) string {
for _, p := range paths {
if matcher(p) {
return p
}
}
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
//
// Usage:
//
// repoDir := fsutil.SearchNameUp("/path/to/dir", ".git")
func SearchNameUp(dirPath, name string) string {
dir, _ := SearchNameUpx(dirPath, name)
return dir
}
// SearchNameUpx find file/dir name in dirPath or parent dirs,
// return the name of directory path and dir is changed.
func SearchNameUpx(dirPath, name string) (string, bool) {
var level int
dirPath = ToAbsPath(dirPath)
for {
namePath := filepath.Join(dirPath, name)
if PathExists(namePath) {
return dirPath, level > 0
}
level++
prevLn := len(dirPath)
dirPath = filepath.Dir(dirPath)
if prevLn == len(dirPath) {
return "", false
}
}
}
// WalkDir walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root.
//
// TIP: will recursively found in sub dirs.
func WalkDir(dir string, fn fs.WalkDirFunc) error {
return filepath.WalkDir(dir, fn)
}
// Glob finds files by glob path pattern. alias of filepath.Glob()
// and support filter matched files by name.
//
// Usage:
//
// files := fsutil.Glob("/path/to/dir/*.go")
func Glob(pattern string, fls ...NameMatchFunc) []string {
files, _ := filepath.Glob(pattern)
if len(fls) == 0 || len(files) == 0 {
return files
}
var matched []string
for _, file := range files {
for _, fn := range fls {
if fn(path.Base(file)) {
matched = append(matched, file)
break
}
}
}
return matched
}
// GlobWithFunc find files by glob path pattern, then handle matched file
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) {
files, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, filePath := range files {
err = fn(filePath)
if err != nil {
break
}
}
return
}
type (
// FilterFunc type for FindInDir
//
// - return False will skip handle the file.
FilterFunc func(fPath string, ent fs.DirEntry) bool
// HandleFunc type for FindInDir
HandleFunc func(fPath string, ent fs.DirEntry) error
)
// OnlyFindDir on find
func OnlyFindDir(_ string, ent fs.DirEntry) bool { return ent.IsDir() }
// OnlyFindFile on find
func OnlyFindFile(_ string, ent fs.DirEntry) bool { return !ent.IsDir() }
// ExcludeNames on find
func ExcludeNames(names ...string) FilterFunc {
return func(_ string, ent fs.DirEntry) bool {
return !arrutil.StringsHas(names, ent.Name())
}
}
// IncludeSuffix on find
func IncludeSuffix(ss ...string) FilterFunc {
return func(_ string, ent fs.DirEntry) bool {
return strutil.HasOneSuffix(ent.Name(), ss)
}
}
// ExcludeDotFile on find
func ExcludeDotFile(_ string, ent fs.DirEntry) bool { return ent.Name()[0] != '.' }
// ExcludeSuffix on find
func ExcludeSuffix(ss ...string) FilterFunc {
return func(_ string, ent fs.DirEntry) bool {
return !strutil.HasOneSuffix(ent.Name(), ss)
}
}
// ApplyFilters handle
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool {
for _, filter := range filters {
if !filter(fPath, ent) {
return true
}
}
return false
}
// FindInDir code refer the go pkg: path/filepath.glob()
//
// - 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) {
fi, err := os.Stat(dir)
if err != nil || !fi.IsDir() {
return // ignore I/O error
}
des, err := os.ReadDir(dir)
if err != nil {
return
}
// remove the last '/' char
dirLn := len(dir)
if dirLn > 1 && dir[dirLn-1] == '/' {
dir = dir[:dirLn-1]
}
for _, ent := range des {
filePath := dir + "/" + ent.Name()
// apply filters
if len(filters) > 0 && ApplyFilters(filePath, ent, filters) {
continue
}
if err1 := handleFn(filePath, ent); err1 != nil {
return err1
}
}
return nil
}
// FileInDirs returns the first file path in the given dirs.
func FileInDirs(paths []string, names ...string) string {
for _, pathDir := range paths {
for _, name := range names {
file := pathDir + "/" + name
if IsFile(file) {
return file
}
}
}
return ""
}
+75
View File
@@ -0,0 +1,75 @@
// Package fsutil Filesystem util functions, quick create, read and write file. eg: file and dir check, operate
package fsutil
import (
"os"
"path/filepath"
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/x/basefn"
)
// PathSep alias of os.PathSeparator
const PathSep = os.PathSeparator
// JoinPaths elements, alias of filepath.Join()
func JoinPaths(elem ...string) string {
return filepath.Join(elem...)
}
// JoinPaths3 elements, like the filepath.Join()
func JoinPaths3(basePath, secPath string, elems ...string) string {
return comfunc.JoinPaths3(basePath, secPath, elems)
}
// JoinSubPaths elements, like the filepath.Join()
func JoinSubPaths(basePath string, elems ...string) string {
return comfunc.JoinPaths2(basePath, elems)
}
// SlashPath alias of filepath.ToSlash
func SlashPath(path string) string {
return filepath.ToSlash(path)
}
// UnixPath like of filepath.ToSlash, but always replace
func UnixPath(path string) string {
if !strings.ContainsRune(path, '\\') {
return path
}
return strings.ReplaceAll(path, "\\", "/")
}
// ToAbsPath convert path to absolute path.
// Will expand home dir, if empty will return current work dir
//
// TIP: will don't check path is really exists
func ToAbsPath(p string) string {
// return current work dir
if len(p) == 0 {
wd, err := os.Getwd()
if err != nil {
return p
}
return wd
}
if IsAbsPath(p) {
return p
}
// expand home dir
if p[0] == '~' {
return comfunc.ExpandHome(p)
}
wd, err := os.Getwd()
if err != nil {
return p
}
return filepath.Join(wd, p)
}
// Must2 ok for (any, error) result. if it has error, will panic
func Must2(_ any, err error) { basefn.MustOK(err) }
+112
View File
@@ -0,0 +1,112 @@
package fsutil
import (
"os"
"path/filepath"
"strings"
"github.com/gookit/goutil/internal/comfunc"
)
// 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.
// eg: "/foo/bar/baz.js" => "baz.js"
func PathName(fPath string) string { return filepath.Base(fPath) }
// PathNoExt get path from full path, without ext.
//
// eg: path/to/main.go => "path/to/main"
func PathNoExt(fPath string) string {
ext := filepath.Ext(fPath)
if el := len(ext); el > 0 {
return fPath[:len(fPath)-el]
}
return fPath
}
// Name get file/dir name from full path.
//
// eg:
// "path/to/main.go" => "main.go"
// "/foo/bar/baz" => "baz"
func Name(fPath string) string {
if fPath == "" {
return ""
}
return filepath.Base(fPath)
}
// NameNoExt get file name from a full path, without an ext.
//
// eg: path/to/main.go => "main"
func NameNoExt(fPath string) string {
if fPath == "" {
return ""
}
fName := filepath.Base(fPath)
if pos := strings.LastIndexByte(fName, '.'); pos > 0 {
return fName[:pos]
}
return fName
}
// FileExt get filename ext. alias of filepath.Ext()
//
// eg: path/to/main.go => ".go"
func FileExt(fPath string) string { return filepath.Ext(fPath) }
// Extname get filename ext. alias of filepath.Ext()
//
// eg: path/to/main.go => "go"
func Extname(fPath string) string {
if ext := filepath.Ext(fPath); len(ext) > 0 {
return ext[1:]
}
return ""
}
// Suffix get filename ext. alias of filepath.Ext()
//
// eg: path/to/main.go => ".go"
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) }
// 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) }
// ResolvePath will parse `~` and ENV var in path
func ResolvePath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
// return comfunc.ParseEnvVar()
return os.ExpandEnv(pathStr)
}
// 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) }
// 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() }
+20
View File
@@ -0,0 +1,20 @@
//go:build !windows
package fsutil
import (
"path"
"github.com/gookit/goutil/internal/comfunc"
)
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
// Will expand ~ to home dir, and join with workdir if path is relative path.
func Realpath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
if !IsAbsPath(pathStr) {
pathStr = JoinSubPaths(comfunc.Workdir(), pathStr)
}
return path.Clean(pathStr)
}
+17
View File
@@ -0,0 +1,17 @@
package fsutil
import (
"path/filepath"
"github.com/gookit/goutil/internal/comfunc"
)
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
func Realpath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
if !IsAbsPath(pathStr) {
pathStr = JoinSubPaths(comfunc.Workdir(), pathStr)
}
return filepath.Clean(pathStr)
}
+40
View File
@@ -0,0 +1,40 @@
package fsutil
import (
"io"
"net/http"
"os"
)
// DetectMime detect a file mime type. alias of MimeType()
func DetectMime(path string) string {
return MimeType(path)
}
// MimeType get file mime type name. eg "image/png"
func MimeType(path string) (mime string) {
file, err := os.Open(path)
if err != nil {
return
}
return ReaderMimeType(file)
}
// ReaderMimeType get the io.Reader mimeType
//
// Usage:
//
// file, err := os.Open(filepath)
// if err != nil {
// return
// }
// mime := ReaderMimeType(file)
func ReaderMimeType(r io.Reader) (mime string) {
var buf [MimeSniffLen]byte
n, _ := io.ReadFull(r, buf[:])
if n == 0 {
return ""
}
return http.DetectContentType(buf[:n])
}
+337
View File
@@ -0,0 +1,337 @@
package fsutil
import (
"archive/zip"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/gookit/goutil/x/basefn"
)
// Mkdir alias of os.MkdirAll()
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 fs.FileMode, dirPaths ...string) error {
for _, dirPath := range dirPaths {
if err := os.MkdirAll(dirPath, perm); err != nil {
return err
}
}
return nil
}
// MkSubDirs batch makes multi sub-dirs at once
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 {
return err
}
}
return nil
}
// MkParentDir quickly create parent dir for a given path.
func MkParentDir(fpath string) error {
dirPath := filepath.Dir(fpath)
if !IsDir(dirPath) {
return os.MkdirAll(dirPath, 0775)
}
return nil
}
// ************************************************************
// options for open file
// ************************************************************
// OpenOption for open file
type OpenOption struct {
// file open flag. see FsCWTFlags
Flag int
// file perm. see DefaultFilePerm
Perm os.FileMode
}
// OpenOptionFunc for open/write file
type OpenOptionFunc func(*OpenOption)
// NewOpenOption create a new OpenOption instance
//
// Defaults:
// - open flags: FsCWTFlags (override write)
// - file Perm: DefaultFilePerm
func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption {
opt := &OpenOption{
Flag: FsCWTFlags,
Perm: DefaultFilePerm,
}
for _, fn := range optFns {
fn(opt)
}
return opt
}
// OpenOptOrNew create a new OpenOption instance if opt is nil
func OpenOptOrNew(opt *OpenOption) *OpenOption {
if opt == nil {
return NewOpenOption()
}
return opt
}
// WithFlag set file open flag
func WithFlag(flag int) OpenOptionFunc {
return func(opt *OpenOption) {
opt.Flag = flag
}
}
// WithPerm set file perm
func WithPerm(perm os.FileMode) OpenOptionFunc {
return func(opt *OpenOption) {
opt.Perm = perm
}
}
// ************************************************************
// open/create files
// ************************************************************
// some commonly flag consts for open file.
const (
FsCWAFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // create, append write-only
FsCWTFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC // create, override write-only
FsCWFlags = os.O_CREATE | os.O_WRONLY // create, write-only
FsRWFlags = os.O_RDWR // read-write, dont create.
FsRFlags = os.O_RDONLY // read-only
)
// OpenFile like os.OpenFile, but will auto create dir.
//
// Usage:
//
// file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666)
func OpenFile(filePath string, flag int, perm os.FileMode) (*os.File, error) {
fileDir := filepath.Dir(filePath)
if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil {
return nil, err
}
file, err := os.OpenFile(filePath, flag, perm)
if err != nil {
return nil, err
}
return file, nil
}
// MustOpenFile like os.OpenFile, but will auto create dir.
//
// Usage:
//
// file := MustOpenFile("path/to/file.txt", FsCWFlags, 0666)
func MustOpenFile(filePath string, flag int, perm os.FileMode) *os.File {
file, err := OpenFile(filePath, flag, perm)
if err != nil {
panic(err)
}
return file
}
// QuickOpenFile like os.OpenFile, open for append write. if not exists, will create it.
//
// Alias of OpenAppendFile()
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) {
flag := basefn.FirstOr(fileFlag, FsCWAFlags)
return OpenFile(filepath, flag, DefaultFilePerm)
}
// OpenAppendFile like os.OpenFile, open for append write. if not exists, will create it.
func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) {
perm := basefn.FirstOr(filePerm, DefaultFilePerm)
return OpenFile(filepath, FsCWAFlags, perm)
}
// OpenTruncFile like os.OpenFile, open for override write. if not exists, will create it.
func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) {
perm := basefn.FirstOr(filePerm, DefaultFilePerm)
return OpenFile(filepath, FsCWTFlags, perm)
}
// OpenReadFile like os.OpenFile, open file for read contents
func OpenReadFile(filepath string) (*os.File, error) {
return os.OpenFile(filepath, FsRFlags, OnlyReadFilePerm)
}
// CreateFile create file if not exists
//
// Usage:
//
// CreateFile("path/to/file.txt", 0664, 0666)
func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) {
dirPath := filepath.Dir(fpath)
if !IsDir(dirPath) {
err := os.MkdirAll(dirPath, dirPerm)
if err != nil {
return nil, err
}
}
flag := basefn.FirstOr(fileFlag, FsCWAFlags)
return os.OpenFile(fpath, flag, filePerm)
}
// MustCreateFile create file, will panic on error
func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File {
file, err := CreateFile(filePath, filePerm, dirPerm)
if err != nil {
panic(err)
}
return file
}
// ************************************************************
// remove files
// ************************************************************
// alias methods
var (
// MustRm removes the named file or (empty) directory.
MustRm = MustRemove
// QuietRm removes the named file or (empty) directory.
QuietRm = QuietRemove
)
// Remove removes the named file or (empty) directory.
func Remove(fPath string) error {
return os.Remove(fPath)
}
// MustRemove removes the named file or (empty) directory.
// NOTICE: will panic on error
func MustRemove(fPath string) {
if err := os.Remove(fPath); err != nil {
panic(err)
}
}
// QuietRemove removes the named file or (empty) directory.
//
// NOTICE: will ignore error
func QuietRemove(fPath string) { _ = os.Remove(fPath) }
// SafeRemoveAll removes path and any children it contains. will ignore error
func SafeRemoveAll(path string) {
_ = os.RemoveAll(path)
}
// RmIfExist removes the named file or (empty) directory on existing.
func RmIfExist(fPath string) error { return DeleteIfExist(fPath) }
// DeleteIfExist removes the named file or (empty) directory on existing.
func DeleteIfExist(fPath string) error {
if PathExists(fPath) {
return os.Remove(fPath)
}
return nil
}
// RmFileIfExist removes the named file on existing.
func RmFileIfExist(fPath string) error { return DeleteIfFileExist(fPath) }
// DeleteIfFileExist removes the named file on existing.
func DeleteIfFileExist(fPath string) error {
if IsFile(fPath) {
return os.Remove(fPath)
}
return nil
}
// RemoveSub removes all sub files and dirs of dirPath, but not remove dirPath.
func RemoveSub(dirPath string, fns ...FilterFunc) error {
return FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error {
if ent.IsDir() {
if err := RemoveSub(fPath, fns...); err != nil {
return err
}
// skip rm not empty subdir
if !IsEmptyDir(fPath) {
return nil
}
}
return os.Remove(fPath)
}, fns...)
}
// ************************************************************
// other operates
// ************************************************************
// Unzip a zip archive
// from https://blog.csdn.net/wangshubo1989/article/details/71743374
func Unzip(archive, targetDir string) (err error) {
reader, err := zip.OpenReader(archive)
if err != nil {
return err
}
if err = os.MkdirAll(targetDir, DefaultDirPerm); err != nil {
return
}
for _, file := range reader.File {
if strings.Contains(file.Name, "..") {
return fmt.Errorf("illegal file path in zip: %v", file.Name)
}
fullPath := filepath.Join(targetDir, file.Name)
if file.FileInfo().IsDir() {
err = os.MkdirAll(fullPath, file.Mode())
if err != nil {
return err
}
continue
}
fileReader, err := file.Open()
if err != nil {
return err
}
targetFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
_ = fileReader.Close()
return err
}
_, err = io.Copy(targetFile, fileReader)
// close all
_ = fileReader.Close()
targetFile.Close()
if err != nil {
return err
}
}
return
}
+142
View File
@@ -0,0 +1,142 @@
package fsutil
import (
"bufio"
"errors"
"io"
"os"
"text/scanner"
"github.com/gookit/goutil/x/basefn"
)
// NewIOReader instance by input file path or io.Reader
func NewIOReader(in any) (r io.Reader, err error) {
switch typIn := in.(type) {
case string: // as file path
return OpenReadFile(typIn)
case io.Reader:
return typIn, nil
}
return nil, errors.New("invalid input type, allow: string, io.Reader")
}
// DiscardReader anything from the reader
func DiscardReader(src io.Reader) {
_, _ = io.Copy(io.Discard, src)
}
// ReadFile read file contents, will panic on error
func ReadFile(filePath string) []byte { return MustReadFile(filePath) }
// MustReadFile read file contents, will panic on error
func MustReadFile(filePath string) []byte {
bs, err := os.ReadFile(filePath)
if err != nil {
panic(err)
}
return bs
}
// ReadReader read contents from io.Reader, will panic on error
func ReadReader(r io.Reader) []byte { return MustReadReader(r) }
// MustReadReader read contents from io.Reader, will panic on error
func MustReadReader(r io.Reader) []byte {
bs, err := io.ReadAll(r)
if err != nil {
panic(err)
}
return bs
}
// ReadString read contents from path or io.Reader, will panic on in type error
func ReadString(in any) string { return string(GetContents(in)) }
// ReadStringOrErr read contents from path or io.Reader, will panic on in type error
func ReadStringOrErr(in any) (string, error) {
r, err := NewIOReader(in)
if err != nil {
return "", err
}
bs, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(bs), nil
}
// ReadAll read contents from path or io.Reader, will panic on in type error
func ReadAll(in any) []byte { return MustRead(in) }
// GetContents read contents from path or io.Reader, will panic on in type error
func GetContents(in any) []byte { return MustRead(in) }
// MustRead read contents from path or io.Reader, will panic on in type error
func MustRead(in any) []byte { return basefn.Must(ReadOrErr(in)) }
// ReadOrErr read contents from path or io.Reader, will panic on in type error
func ReadOrErr(in any) ([]byte, error) {
r, err := NewIOReader(in)
defer func() {
if r != nil {
if file, ok := r.(*os.File); ok {
err = file.Close()
}
}
}()
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// ReadExistFile read file contents if existed, will panic on error
func ReadExistFile(filePath string) []byte {
if IsFile(filePath) {
bs, err := os.ReadFile(filePath)
if err != nil {
panic(err)
}
return bs
}
return nil
}
// TextScanner from filepath or io.Reader, will panic on in type error.
// Will scan parse text to tokens: Ident, Int, Float, Char, String, RawString, Comment, etc.
//
// Usage:
//
// s := fsutil.TextScanner("/path/to/file")
// for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
// fmt.Printf("%s: %s\n", s.Position, s.TokenText())
// }
func TextScanner(in any) *scanner.Scanner {
var s scanner.Scanner
r, err := NewIOReader(in)
if err != nil {
panic(err)
}
s.Init(r)
s.Filename = "text-scanner"
return &s
}
// LineScanner create from filepath or io.Reader, will panic on in type error.
// Will scan and parse text to lines.
//
// s := fsutil.LineScanner("/path/to/file")
// for s.Scan() {
// fmt.Println(s.Text())
// }
func LineScanner(in any) *bufio.Scanner {
r, err := NewIOReader(in)
if err != nil {
panic(err)
}
return bufio.NewScanner(r)
}
+201
View File
@@ -0,0 +1,201 @@
package fsutil
import (
"fmt"
"io"
"os"
"github.com/gookit/goutil/x/basefn"
)
// ************************************************************
// temp file or dir
// ************************************************************
// OSTempFile create a temp file on os.TempDir()
//
// Usage:
//
// fsutil.OSTempFile("example.*.txt")
func OSTempFile(pattern string) (*os.File, error) {
return os.CreateTemp(os.TempDir(), pattern)
}
// TempFile is like os.CreateTemp, but can custom temp dir.
//
// Usage:
//
// // create temp file on os.TempDir()
// fsutil.TempFile("", "example.*.txt")
// // create temp file on "testdata" dir
// fsutil.TempFile("testdata", "example.*.txt")
func TempFile(dir, pattern string) (*os.File, error) {
return os.CreateTemp(dir, pattern)
}
// OSTempDir creates a new temp dir on os.TempDir and return the temp dir path
//
// Usage:
//
// fsutil.OSTempDir("example.*")
func OSTempDir(pattern string) (string, error) {
return os.MkdirTemp(os.TempDir(), pattern)
}
// TempDir creates a new temp dir and return the temp dir path
//
// Usage:
//
// fsutil.TempDir("", "example.*")
// fsutil.TempDir("testdata", "example.*")
func TempDir(dir, pattern string) (string, error) {
return os.MkdirTemp(dir, pattern)
}
// ************************************************************
// write, copy files
// ************************************************************
// MustSave create file and write contents to file, panic on error.
//
// - data type allow: string, []byte, io.Reader
//
// default option see NewOpenOption()
func MustSave(filePath string, data any, optFns ...OpenOptionFunc) {
basefn.MustOK(SaveFile(filePath, data, optFns...))
}
// SaveFile create file and write contents to file. will auto create dir.
//
// - data type allow: string, []byte, io.Reader
//
// default option see NewOpenOption()
func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error {
opt := NewOpenOption(optFns...)
return WriteFile(filePath, data, opt.Perm, opt.Flag)
}
// WriteData Quick write any data to file, alias of PutContents
func WriteData(filePath string, data any, fileFlag ...int) (int, error) {
return PutContents(filePath, data, fileFlag...)
}
// PutContents create file and write contents to file at once. Will auto create dir
//
// data type allows: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags (override write)
//
// Usage:
//
// fsutil.PutContents(filePath, contents, fsutil.FsCWAFlags) // append write
// fsutil.Must2(fsutil.PutContents(filePath, contents)) // panic on error
func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
f, err := QuickOpenFile(filePath, basefn.FirstOr(fileFlag, FsCWTFlags))
if err != nil {
return 0, err
}
return WriteOSFile(f, data)
}
// WriteFile create file and write contents to file, can set perm for a file.
//
// data type allows: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags (override write)
//
// Usage:
//
// fsutil.WriteFile(filePath, contents, fsutil.DefaultFilePerm, fsutil.FsCWAFlags)
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error {
flag := basefn.FirstOr(fileFlag, FsCWTFlags)
f, err := OpenFile(filePath, flag, perm)
if err != nil {
return err
}
_, err = WriteOSFile(f, data)
return err
}
// WriteOSFile write data to give os.File, then close file.
//
// data type allows: string, []byte, io.Reader
func WriteOSFile(f *os.File, data any) (n int, err error) {
switch typData := data.(type) {
case []byte:
n, err = f.Write(typData)
case string:
n, err = f.WriteString(typData)
case io.Reader: // eg: buffer
var n64 int64
n64, err = io.Copy(f, typData)
n = int(n64)
default:
_ = f.Close()
panic("WriteFile: data type only allow: []byte, string, io.Reader")
}
if err1 := f.Close(); err1 != nil && err == nil {
err = err1
}
return n, err
}
// CopyFile copy a file to another file path.
func CopyFile(srcPath, dstPath string) error {
srcFile, err := os.OpenFile(srcPath, FsRFlags, 0)
if err != nil {
return err
}
defer srcFile.Close()
// create and open file
dstFile, err := QuickOpenFile(dstPath, FsCWTFlags)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}
// MustCopyFile copy file to another path.
func MustCopyFile(srcPath, dstPath string) {
err := CopyFile(srcPath, dstPath)
if err != nil {
panic(err)
}
}
// UpdateContents read file contents, call handleFn(contents) handle, then write updated contents to file
func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error {
osFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer osFile.Close()
// read file contents
if bs, err1 := io.ReadAll(osFile); err1 == nil {
bs = handleFn(bs)
_, err = osFile.Write(bs)
} else {
err = err1
}
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)
}
+90
View File
@@ -0,0 +1,90 @@
package goutil
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.
func Go(f func() error) error {
ch := make(chan error)
go func() {
ch <- f()
}()
return <-ch
}
// ErrFunc type
type ErrFunc func() error
// CallOn call func on condition is true
func CallOn(cond bool, fn ErrFunc) error {
if cond {
return fn()
}
return nil
}
// IfElseFn call okFunc() on condition is true, else call elseFn()
func IfElseFn(cond bool, okFn, elseFn ErrFunc) error {
if cond {
return okFn()
}
return elseFn()
}
// CallOrElse call okFunc() on condition is true, else call elseFn()
func CallOrElse(cond bool, okFn, elseFn ErrFunc) error {
if cond {
return okFn()
}
return elseFn()
}
// SafeRun sync run a func. If the func panics, the panic value is returned as an error.
func SafeRun(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
fn()
return err
}
// SafeRunWithError sync run a func with error.
// If the func panics, the panic value is returned as an error.
func SafeRunWithError(fn func() error) (err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
return fn()
}
// SafeGo async run a func.
// If the func panics, the panic value will be handle by errHandler.
func SafeGo(fn func(), errHandler func(error)) {
go func() {
if err := SafeRun(fn); err != nil {
errHandler(err)
}
}()
}
// SafeGoWithError async run a func with error.
// If the func panics, the panic value will be handle by errHandler.
func SafeGoWithError(fn func() error, errHandler func(error)) {
go func() {
if err := SafeRunWithError(fn); err != nil {
errHandler(err)
}
}()
}
+208
View File
@@ -0,0 +1,208 @@
// Package goutil 💪 Useful utils for Go: byte, int, string, array/slice, map, struct, reflect, error, time, format, CLI, ENV, filesystem,
// system, testing, debug and more.
package goutil
import (
"fmt"
"reflect"
"github.com/gookit/goutil/internal/checkfn"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/x/basefn"
"github.com/gookit/goutil/x/goinfo"
)
// Value alias of structs.Value
type Value = structs.Value
// Panicf format panic message use fmt.Sprintf
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) {
basefn.PanicIf(cond, fmtAndArgs...)
}
// PanicErr if error is not empty, will panic.
// Alias of basefn.PanicErr()
func PanicErr(err error) {
if err != nil {
panic(err)
}
}
// PanicIfErr if error is not empty, will panic.
// Alias of basefn.PanicErr()
func PanicIfErr(err error) { PanicErr(err) }
// MustOK if error is not empty, will panic.
// Alias of basefn.MustOK()
func MustOK(err error) { PanicErr(err) }
// MustIgnore for return like (v, error). Ignore return v and will panic on error.
//
// Useful for io, file operation func: (n int, err error)
//
// Usage:
//
// // old
// _, err := fn()
// if err != nil {
// panic(err)
// }
//
// // new
// goutil.MustIgnore(fn())
func MustIgnore(_ any, err error) { PanicErr(err) }
// Must return like (v, error). will panic on error, otherwise return v.
//
// Usage:
//
// // old
// v, err := fn()
// if err != nil {
// panic(err)
// }
//
// // new
// v := goutil.Must(fn())
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
// ErrOnFail return input error on cond is false, otherwise return nil
func ErrOnFail(cond bool, err error) error {
return OrError(cond, err)
}
// OrError return input error on cond is false, otherwise return nil
func OrError(cond bool, err error) error {
if !cond {
return err
}
return nil
}
// OrValue get. like: if cond { okVal } else { elVal }
func OrValue[T any](cond bool, okVal, elVal T) T {
if cond {
return okVal
}
return elVal
}
// OrReturn call okFunc() on condition is true, else call elseFn()
func OrReturn[T any](cond bool, okFn, elseFn func() T) T {
if cond {
return okFn()
}
return elseFn()
}
//
// ------------------------- check functions -------------------------
//
// IsNil value check
func IsNil(v any) bool {
if v == nil {
return true
}
return reflects.IsNil(reflect.ValueOf(v))
}
// IsZero value check, alias of the IsEmpty()
var IsZero = IsEmpty
// IsEmpty value check
func IsEmpty(v any) bool {
if v == nil {
return true
}
return reflects.IsEmpty(reflect.ValueOf(v))
}
// IsZeroReal Alias of the IsEmptyReal()
var IsZeroReal = IsEmptyReal
// IsEmptyReal checks for empty given value and also real empty value if the passed value is a pointer
func IsEmptyReal(v any) bool {
if v == nil {
return true
}
return reflects.IsEmptyReal(reflect.ValueOf(v))
}
// IsFunc value
func IsFunc(val any) bool {
if val == nil {
return false
}
return reflect.TypeOf(val).Kind() == reflect.Func
}
// IsEqual determines if two objects are considered equal.
//
// TIP: cannot compare a function type
func IsEqual(src, dst any) bool {
if src == nil || dst == nil {
return src == dst
}
// cannot compare a function type
if IsFunc(src) || IsFunc(dst) {
return false
}
return reflects.IsEqual(src, dst)
}
// Contains try loop over the data check if the data includes the element.
// alias of the IsContains
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func Contains(data, elem any) bool {
_, found := checkfn.Contains(data, elem)
return found
}
// IsContains try loop over the data check if the data includes the element.
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func IsContains(data, elem any) bool {
_, found := checkfn.Contains(data, elem)
return found
}
//
// ------------------------- goinfo functions -------------------------
//
// FuncName get func name
func FuncName(f any) string {
return goinfo.FuncName(f)
}
// PkgName get the current package name. alias of goinfo.PkgName()
//
// Usage:
//
// funcName := goutil.FuncName(fn)
// pgkName := goutil.PkgName(funcName)
func PkgName(funcName string) string {
return goinfo.PkgName(funcName)
}
+60
View File
@@ -0,0 +1,60 @@
package goutil
import (
"context"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/syncs"
)
// ErrGroup is a collection of goroutines working on subtasks that
// are part of the same overall task.
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...)
}
// RunFn func
type RunFn func(ctx *structs.Data) error
// QuickRun struct
type QuickRun struct {
ctx *structs.Data
// err error
fns []RunFn
}
// NewQuickRun instance
func NewQuickRun() *QuickRun {
return &QuickRun{
ctx: structs.NewData(),
}
}
// Add func for run
func (p *QuickRun) Add(fns ...RunFn) *QuickRun {
p.fns = append(p.fns, fns...)
return p
}
// Run all func
func (p *QuickRun) Run() error {
for i, fn := range p.fns {
p.ctx.Set("_index", i)
if err := fn(p.ctx); err != nil {
return err
}
}
return nil
}
+164
View File
@@ -0,0 +1,164 @@
package checkfn
import (
"bytes"
"fmt"
"reflect"
"regexp"
"strings"
)
// IsNil value check
func IsNil(v any) bool {
if v == nil {
return true
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return rv.IsNil()
default:
return false
}
}
// IsSimpleKind kind in: string, bool, intX, uintX, floatX
func IsSimpleKind(k reflect.Kind) bool {
if reflect.String == k {
return true
}
return k > reflect.Invalid && k <= reflect.Float64
}
// IsEqual determines if two objects are considered equal.
//
// TIP: cannot compare function type
func IsEqual(src, dst any) bool {
if src == nil || dst == nil {
return src == dst
}
bs1, ok := src.([]byte)
if !ok {
return reflect.DeepEqual(src, dst)
}
bs2, ok := dst.([]byte)
if !ok {
return false
}
if bs1 == nil || bs2 == nil {
return bs1 == nil && bs2 == nil
}
return bytes.Equal(bs1, bs2)
}
// Contains try loop over the data check if the data includes the element.
//
// data allow types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
//
// Returns:
// - valid: data is valid
// - found: element was found
//
// return (false, false) if impossible.
// return (true, false) if element was not found.
// return (true, true) if element was found.
func Contains(data, elem any) (valid, found bool) {
if data == nil {
return false, false
}
dataRv := reflect.ValueOf(data)
dataRt := reflect.TypeOf(data)
dataKind := dataRt.Kind()
// string
if dataKind == reflect.String {
return true, strings.Contains(dataRv.String(), fmt.Sprint(elem))
}
// map
if dataKind == reflect.Map {
mapKeys := dataRv.MapKeys()
for i := 0; i < len(mapKeys); i++ {
if IsEqual(mapKeys[i].Interface(), elem) {
return true, true
}
}
return true, false
}
// array, slice - other return false
if dataKind != reflect.Slice && dataKind != reflect.Array {
return false, false
}
for i := 0; i < dataRv.Len(); i++ {
if IsEqual(dataRv.Index(i).Interface(), elem) {
return true, true
}
}
return true, false
}
// StringsContains check string slice contains sub-string
func StringsContains(ss []string, sub string) bool {
for _, v := range ss {
if v == sub {
return true
}
}
return false
}
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 {
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
}
+40
View File
@@ -0,0 +1,40 @@
package checkfn
import (
"os"
"strings"
)
var detectedWSL bool
var detectedWSLContents string
// IsWSL system env
// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
func IsWSL() bool {
// ENV:
// WSL_DISTRO_NAME=Debian
if len(os.Getenv("WSL_DISTRO_NAME")) > 0 {
return true
}
if !detectedWSL {
b := make([]byte, 1024)
// `cat /proc/version`
// on mac:
// !not the file!
// on linux(debian,ubuntu,alpine):
// Linux version 4.19.121-linuxkit (root@18b3f92ade35) (gcc version 9.2.0 (Alpine 9.2.0)) #1 SMP Thu Jan 21 15:36:34 UTC 2021
// on win git bash, conEmu:
// MINGW64_NT-10.0-19042 version 3.1.7-340.x86_64 (@WIN-N0G619FD3UK) (gcc version 9.3.0 (GCC) ) 2020-10-23 13:08 UTC
// on WSL:
// Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020
f, err := os.Open("/proc/version")
if err == nil {
_, _ = f.Read(b) // ignore error
f.Close()
detectedWSLContents = string(b)
}
detectedWSL = true
}
return strings.Contains(detectedWSLContents, "Microsoft")
}
+3
View File
@@ -0,0 +1,3 @@
# Common func for internal use
- don't depend on other external packages
+93
View File
@@ -0,0 +1,93 @@
package comfunc
import (
"fmt"
"strings"
)
// Cmdline build
func Cmdline(args []string, binName ...string) string {
b := new(strings.Builder)
if len(binName) > 0 {
b.WriteString(binName[0])
b.WriteByte(' ')
}
for i, a := range args {
if i > 0 {
b.WriteByte(' ')
}
if strings.ContainsRune(a, '"') {
b.WriteString(fmt.Sprintf(`'%s'`, a))
} else if a == "" || strings.ContainsRune(a, '\'') || strings.ContainsRune(a, ' ') {
b.WriteString(fmt.Sprintf(`"%s"`, a))
} else {
b.WriteString(a)
}
}
return b.String()
}
// ShellQuote quote a string on contains ', ", SPACE. refer strconv.Quote()
func ShellQuote(a string) string {
if a == "" {
return `""`
}
// use quote char
var quote byte
// has double quote
if pos := strings.IndexByte(a, '"'); pos > -1 {
if !checkNeedQuote(a, pos, '"') {
return a
}
quote = '\''
} else if pos := strings.IndexByte(a, '\''); pos > -1 {
// single quote
if !checkNeedQuote(a, pos, '\'') {
return a
}
quote = '"'
} else if strings.IndexByte(a, ' ') > -1 {
quote = '"'
}
// no quote char OR not need quote
if quote == 0 {
return a
}
return fmt.Sprintf("%c%s%c", quote, a, quote)
}
func checkNeedQuote(a string, pos int, char byte) bool {
// end with char. eg: "
lastIsQ := a[len(a)-1] == char
// start with char. eg: "
if pos == 0 {
if lastIsQ {
return false
}
if pos1 := strings.IndexByte(a[pos+1:], char); pos1 > -1 {
// eg: `"one two" three four`
lastS := a[pos1+pos+1:]
if !strings.ContainsRune(lastS, ' ') {
return false
}
}
} else {
startS := a[:pos]
// eg: `--one="two three"`
if lastIsQ && strings.IndexByte(startS, ' ') == -1 {
return false
}
}
return true
}
+22
View File
@@ -0,0 +1,22 @@
package comfunc
import (
"os"
"strings"
)
// Environ like os.Environ, but will returns key-value map[string]string data.
func Environ() map[string]string {
envList := os.Environ()
envMap := make(map[string]string, len(envList))
for _, str := range envList {
nodes := strings.SplitN(str, "=", 2)
if len(nodes) < 2 {
envMap[nodes[0]] = ""
} else {
envMap[nodes[0]] = nodes[1]
}
}
return envMap
}
+185
View File
@@ -0,0 +1,185 @@
package comfunc
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/checkfn"
)
// Bool try to convert type to bool
func Bool(v any) bool {
bl, _ := ToBool(v)
return bl
}
// ToBool try to convert type to bool
func ToBool(v any) (bool, error) {
if bl, ok := v.(bool); ok {
return bl, nil
}
if str, ok := v.(string); ok {
return StrToBool(str)
}
return false, comdef.ErrConvType
}
// StrToBool parse string to bool. like strconv.ParseBool()
func StrToBool(s string) (bool, error) {
lower := strings.ToLower(s)
switch lower {
case "1", "on", "yes", "true":
return true, nil
case "0", "off", "no", "false":
return false, nil
}
return false, fmt.Errorf("'%s' cannot convert to bool", s)
}
// 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 {
return ""
}
first := fmtAndArgs[0]
if ln == 1 {
if str, ok := first.(string); ok {
return str
}
return fmt.Sprintf("%+v", first)
}
// is template string.
if tplStr, ok := first.(string); ok && strings.IndexByte(tplStr, '%') >= 0 {
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
}
return fmt.Sprint(fmtAndArgs...)
}
// ConvOption convert options
type ConvOption struct {
// if ture: value is nil, will return convert error;
// if false(default): value is nil, will convert to zero value
NilAsFail bool
// HandlePtr auto convert ptr type(int,float,string) value. eg: *int to int
// - if true: will use real type try convert. default is false
// - NOTE: current T type's ptr is default support.
HandlePtr bool
// set custom fallback convert func for not supported type.
UserConvFn comdef.ToStringFunc
}
// ConvOptionFn convert option func
type ConvOptionFn func(opt *ConvOption)
// StrBySprintFn convert any value to string by fmt.Sprint
var StrBySprintFn = func(v any) (string, error) {
return fmt.Sprint(v), nil
}
// WithUserConvFn set ConvOption.UserConvFn option
func WithUserConvFn(fn comdef.ToStringFunc) ConvOptionFn {
return func(opt *ConvOption) {
opt.UserConvFn = fn
}
}
// NewConvOption create a new ConvOption
func NewConvOption(optFns ...ConvOptionFn) *ConvOption {
opt := &ConvOption{}
opt.WithOption(optFns...)
return opt
}
// WithOption set convert option
func (opt *ConvOption) WithOption(optFns ...ConvOptionFn) {
for _, fn := range optFns {
if fn != nil {
fn(opt)
}
}
}
// 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) {
switch value := in.(type) {
case int:
str = strconv.Itoa(value)
case int8:
str = strconv.Itoa(int(value))
case int16:
str = strconv.Itoa(int(value))
case int32: // same as `rune`
str = strconv.Itoa(int(value))
case int64:
str = strconv.FormatInt(value, 10)
case uint:
str = strconv.FormatUint(uint64(value), 10)
case uint8:
str = strconv.FormatUint(uint64(value), 10)
case uint16:
str = strconv.FormatUint(uint64(value), 10)
case uint32:
str = strconv.FormatUint(uint64(value), 10)
case uint64:
str = strconv.FormatUint(value, 10)
case float32:
str = strconv.FormatFloat(float64(value), 'f', -1, 32)
case float64:
str = strconv.FormatFloat(value, 'f', -1, 64)
case bool:
str = strconv.FormatBool(value)
case string:
str = value
case *string:
str = *value
case []byte:
str = string(value)
case time.Duration:
str = strconv.FormatInt(int64(value), 10)
case fmt.Stringer:
str = value.String()
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()
if checkfn.IsSimpleKind(rv.Kind()) {
return ToStringWith(rv.Interface(), optFns...)
}
}
}
if opt.UserConvFn != nil {
str, err = opt.UserConvFn(in)
} else {
err = comdef.ErrConvType
}
}
return
}
+20
View File
@@ -0,0 +1,20 @@
package comfunc
import "path/filepath"
// JoinPaths2 elements, like the filepath.Join()
func JoinPaths2(basePath string, elems []string) string {
paths := make([]string, len(elems)+1)
paths[0] = basePath
copy(paths[1:], elems)
return filepath.Join(paths...)
}
// JoinPaths3 elements, like the filepath.Join()
func JoinPaths3(basePath, secPath string, elems []string) string {
paths := make([]string, len(elems)+2)
paths[0] = basePath
paths[1] = secPath
copy(paths[2:], elems)
return filepath.Join(paths...)
}
+135
View File
@@ -0,0 +1,135 @@
package comfunc
import (
"fmt"
"strings"
)
var commentsPrefixes = []string{"#", ";", "//"}
// ParseEnvLineOption parse env line options
type ParseEnvLineOption struct {
// NotInlineComments dont parse inline comments.
// - default: false. will parse inline comments
NotInlineComments bool
// SkipOnErrorLine skip error line, continue parse next line
// - False: return error, clear parsed map
SkipOnErrorLine bool
}
// ParseEnvLines parse simple multiline k-v string to a string-map.
// Can use to parse simple INI or DOTENV file contents.
//
// NOTE:
//
// - It's like INI/ENV format contents.
// - Support comments line starts with: "#", ";", "//"
// - 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")
ln := len(lines)
if ln == 0 {
return
}
strMap := make(map[string]string, ln)
for _, line := range lines {
if line = strings.TrimSpace(line); line == "" {
continue
}
// skip comments line
if line[0] == '#' || line[0] == ';' || strings.HasPrefix(line, "//") {
continue
}
key, val := splitLineByChar(line, '=', !opt.NotInlineComments)
// invalid line
if key == "" {
if opt.SkipOnErrorLine {
continue
}
strMap = nil
err = fmt.Errorf("invalid line contents: must match `KEY=VAL`(line: %s)", line)
return
}
strMap[key] = val
}
return strMap, nil
}
// 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) {
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
}
+142
View File
@@ -0,0 +1,142 @@
package comfunc
import (
"os"
"os/exec"
"path/filepath"
"strings"
)
// Workdir get
func Workdir() string {
dir, _ := os.Getwd()
return dir
}
// ExpandHome will parse first `~` as user home dir path.
func ExpandHome(pathStr string) string {
if len(pathStr) == 0 {
return pathStr
}
if pathStr[0] != '~' {
return pathStr
}
if len(pathStr) > 1 && pathStr[1] != '/' && pathStr[1] != '\\' {
return pathStr
}
homeDir, err := os.UserHomeDir()
if err != nil {
return pathStr
}
return homeDir + pathStr[1:]
}
// ExecCmd an command and return output.
//
// Usage:
//
// ExecCmd("ls", []string{"-al"})
func ExecCmd(binName string, args []string, workDir ...string) (string, error) {
// create a new Cmd instance
cmd := exec.Command(binName, args...)
if len(workDir) > 0 {
cmd.Dir = workDir[0]
}
bs, err := cmd.Output()
return string(bs), err
}
var (
cmdList = []string{"cmd", "cmd.exe"}
pwshList = []string{"powershell", "powershell.exe", "pwsh", "pwsh.exe"}
)
// ShellExec exec command by shell
// cmdLine e.g. "ls -al"
func ShellExec(cmdLine string, shells ...string) (string, error) {
// shell := "/bin/sh"
shell := "sh"
if len(shells) > 0 {
shell = shells[0]
}
cmd := exec.Command(shell, "-c", cmdLine)
bs, err := cmd.Output()
return string(bs), err
}
// curShellCache value
var curShellCache string
// CurrentShell get current used shell env file.
//
// return like: "/bin/zsh" "/bin/bash". if onlyName=true, will return "zsh", "bash"
func CurrentShell(onlyName bool, fallbackShell ...string) (binPath string) {
var err error
fbShell := ""
if len(fallbackShell) > 0 {
fbShell = fallbackShell[0]
}
if curShellCache == "" {
// 检查父进程名称
parentProcess := os.Getenv("GOPROCESS")
if parentProcess != "" {
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]
}
// cache result
curShellCache = binPath
} else {
binPath = curShellCache
}
if onlyName && len(binPath) > 0 {
binPath = filepath.Base(binPath)
} else if len(binPath) == 0 {
binPath = fbShell
}
return
}
func checkWinCurrentShell() string {
// 在 Windows 上,可以检查 COMSPEC 环境变量
comSpec := os.Getenv("COMSPEC")
// 没法检查 pwsh, 返回的还是 cmd
return comSpec
}
// HasShellEnv has shell env check.
//
// Usage:
//
// HasShellEnv("sh")
// HasShellEnv("bash")
func HasShellEnv(shell string) bool {
// can also use: "echo $0"
out, err := ShellExec("echo OK", shell)
if err != nil {
return false
}
return strings.TrimSpace(out) == "OK"
}
+149
View File
@@ -0,0 +1,149 @@
package comfunc
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"time"
)
var (
// check is duration string. TIP: extend unit d,w. eg: "1d", "2w"
//
// time.ParseDuration() is max support hour "h".
durStrReg = regexp.MustCompile(`^-?([0-9]+(?:\.[0-9]*)?(ns|us|µs|ms|s|m|h|d|w))+$`)
// check long duration string. 验证整体格式是否符合
//
// eg: "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks", "1month"
//
// time.ParseDuration() is not support long unit.
durStrRegL = regexp.MustCompile(`^-?([0-9]+(?:\.[0-9]*)?[nuµsmhdw][a-zA-Z]{0,8})+$`)
// use for parse duration string. see ToDuration()
//
// NOTE: 解析时,不能加最后的 `+` 会导致只匹配了最后一组 时间单位
durStrRegL2 = regexp.MustCompile(`-?([0-9]+(?:\.[0-9]*)?)([nuµsmhdw][a-z]{0,8})`)
)
// IsDuration check the string is a duration string.
func IsDuration(s string) bool {
if s == "0" || durStrReg.MatchString(s) {
return true
}
return durStrRegL.MatchString(s)
}
// ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
//
// Diff of time.ParseDuration:
// - support extends unit d, w at the end of string. such as "1d", "2w".
// - support extends unit: month, week, day
// - support long string unit at the end. such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks".
//
// If the string is not a valid duration string, it will return an error.
func ToDuration(s string) (time.Duration, error) {
ln := len(s)
if ln == 0 {
return 0, fmt.Errorf("empty duration string")
}
s = strings.ToLower(s)
if s == "0" {
return 0, nil
}
// check duration string is valid
if !durStrRegL.MatchString(s) {
return 0, fmt.Errorf("invalid duration string: %s", s)
}
// if ln < 4 AND end != d|w, directly call time.ParseDuration()
if ln < 4 && s[ln-1] != 'd' && s[ln-1] != 'w' {
return time.ParseDuration(s)
}
// time.ParseDuration() is not support long unit.
ssList := durStrRegL2.FindAllStringSubmatch(s, -1)
// fmt.Println(ssList)
bts := make([]byte, 0, ln)
if s[0] == '-' {
bts = append(bts, '-')
}
// only one element. eg: "1day"
if len(ssList) == 1 {
bts = parseLongUnit(ssList[0], bts)
} else {
// more than one element. eg: "1day2hour3min"
for _, ss := range ssList {
if len(ss) == 3 {
bts = parseLongUnit(ss, bts)
}
}
}
return time.ParseDuration(string(bts))
}
// convert to short unit
func parseLongUnit(ss []string, bts []byte) []byte {
// eg: "3sec" -> ss=[3sec, -3, sec]
num, unit := ss[1], ss[2]
switch unit {
case "month", "months":
// time lib max unit is hour, so need convert by 24 * 30*n
bts = appendNumToBytes(bts, num, 24*30)
bts = append(bts, 'h')
case "w", "week", "weeks":
// time lib max unit is hour, so need convert by 24 * 7*n
bts = appendNumToBytes(bts, num, 24*7)
bts = append(bts, 'h')
case "d", "day", "days":
// time lib max unit is hour, so need convert by 24*n
bts = appendNumToBytes(bts, num, 24)
bts = append(bts, 'h')
case "hour", "hours":
bts = append(bts, num...)
bts = append(bts, 'h')
case "min", "mins", "minute", "minutes":
bts = append(bts, num...)
bts = append(bts, 'm')
case "sec", "secs", "second", "seconds":
bts = append(bts, num...)
bts = append(bts, 's')
default:
first := ss[0]
// '-' has been added on ToDuration()
if first[0] == '-' {
bts = append(bts, first[1:]...)
} else {
bts = append(bts, first...)
}
}
return bts
}
func appendNumToBytes(bts []byte, num string, multiple int) []byte {
if strings.ContainsRune(num, '.') {
f, _ := strconv.ParseFloat(num, 64) // is float number
val := f * float64(multiple)
// 使用 Float 保留两位小数 -> 会始终有两位小数,即使是N.00
// bts = strconv.AppendFloat(bts, val, 'f', 2, 64)
// 四舍五入到两位小数
rounded := math.Round(val*100) / 100
// 使用 AppendFloat 自动去除末尾的 .0 或 .00
bts = strconv.AppendFloat(bts, rounded, 'f', -1, 64)
} else {
n, _ := strconv.Atoi(num)
bts = strconv.AppendInt(bts, int64(n*multiple), 10)
}
return bts
}
+166
View File
@@ -0,0 +1,166 @@
// Package varexpr provides some commonly ENV var parse functions.
//
// parse env value, allow expressions:
//
// ${VAR_NAME} Only var name
// ${VAR_NAME | default} With default value, if value is empty.
// ${VAR_NAME | ?error} With error on value is empty.
//
// Examples:
//
// only key - "${SHELL}"
// with default - "${NotExist | defValue}"
// multi key - "${GOPATH}/${APP_ENV | prod}/dir"
package varexpr
import (
"errors"
"os"
"regexp"
"strings"
)
const (
// SepChar separator char split var name and default value
SepChar = "|"
VarLeft = "${" // default var left format chars
VarRight = "}" // default var right format chars
mustPrefix = '?' // must prefix char
)
// ParseOptFn option func
type ParseOptFn func(o *ParseOpts)
// ParseOpts parse options for ParseValue
type ParseOpts struct {
// Getter Env value provider func.
Getter func(string) string
// ParseFn custom parse expr func. expr like "${SHELL}" "${NotExist|defValue}"
ParseFn func(string) (string, error)
// Regexp custom expression regex.
Regexp *regexp.Regexp
// var format chars for expression.
// default left="${", right="}"
VarLeft, VarRight string
}
func (opt *ParseOpts) useDefaultRegex() {
opt.Regexp = envRegex
opt.VarLeft = VarLeft
opt.VarRight = VarRight
}
// must add "?" - To ensure that there is no greedy match
var envRegex = regexp.MustCompile(`\${.+?}`)
var std = New()
// Parse parse ENV var value from input string, support default value.
//
// Format:
//
// ${var_name} Only var name
// ${var_name | default} With default value
// ${var_name | ?error} With error on value is empty.
//
// see Parser.Parse
func Parse(val string) (string, error) {
return std.Parse(val)
}
// SafeParse parse ENV var value from input string, support default value.
//
// see Parser.Parse
func SafeParse(val string) string {
s, _ := std.Parse(val)
return s
}
// ParseWith parse ENV var value from input string, support default value.
func ParseWith(val string, optFns ...ParseOptFn) (string, error) {
return New(optFns...).Parse(val)
}
// Parser parse ENV var value from input string, support default value.
type Parser struct {
ParseOpts
}
// New create a new Parser
func New(optFns ...ParseOptFn) *Parser {
opts := &ParseOpts{Getter: os.Getenv}
opts.useDefaultRegex()
for _, fn := range optFns {
fn(opts)
}
return &Parser{ParseOpts: *opts}
}
// Parse parse ENV var value from input string, support default value.
//
// Format:
//
// ${var_name} Only var name
// ${var_name | default} With default value
// ${var_name | ?error} With error on value is empty.
// ${VAR_NAME1}/path/${VAR_NAME2} Allow multi var name.
func (p *Parser) Parse(val string) (newVal string, err error) {
if p.Regexp == nil {
p.useDefaultRegex()
}
times := strings.Count(val, p.VarLeft)
if times == 0 {
return val, nil
}
// enhance: see https://github.com/gookit/goutil/issues/135
if times == 1 && strings.HasPrefix(val, p.VarLeft) && strings.HasSuffix(val, p.VarRight) {
return p.parseOne(val)
}
// parse expression
newVal = p.Regexp.ReplaceAllStringFunc(val, func(s string) string {
if err != nil {
return s
}
s, err = p.parseOne(s)
return s
})
return
}
// parse one node expression.
func (p *Parser) parseOne(eVar string) (val string, err error) {
if p.ParseFn != nil {
return p.ParseFn(eVar)
}
// like "${NotExist | defValue}". first remove "${" and "}", then split it
ss := strings.SplitN(eVar[2:len(eVar)-1], SepChar, 2)
var name, def string
// with default value.
if len(ss) == 2 {
name, def = strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
} else {
name = strings.TrimSpace(ss[0])
}
// get ENV value by name
val = p.Getter(name)
if val == "" && def != "" {
// check def is "?error"
if def[0] == mustPrefix {
msg := "value is required for var: " + name
if len(def) > 1 {
msg = def[1:]
}
err = errors.New(msg)
} else {
val = def
}
}
return
}
+75
View File
@@ -0,0 +1,75 @@
package jsonutil
import (
"bytes"
"encoding/json"
"io"
"os"
)
// MustString encode data to json string, will panic on error
func MustString(v any) string {
bs, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(bs)
}
// Encode data to json bytes. alias of json.Marshal
func Encode(v any) ([]byte, error) {
return json.Marshal(v)
}
// EncodePretty encode data to pretty JSON bytes.
func EncodePretty(v any) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
// EncodeString encode data to JSON string.
func EncodeString(v any) (string, error) {
bs, err := json.MarshalIndent(v, "", " ")
return string(bs), err
}
// EncodeToWriter encode data to json and write to writer.
func EncodeToWriter(v any, w io.Writer) error {
return json.NewEncoder(w).Encode(v)
}
// EncodeUnescapeHTML data to json bytes. will close escape HTML
func EncodeUnescapeHTML(v any) ([]byte, error) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Decode json bytes to data ptr. alias of json.Unmarshal
func Decode(bts []byte, ptr any) error {
return json.Unmarshal(bts, ptr)
}
// DecodeString json string to data ptr.
func DecodeString(str string, ptr any) error {
return json.Unmarshal([]byte(str), ptr)
}
// DecodeReader decode JSON from io reader.
func DecodeReader(r io.Reader, ptr any) error {
return json.NewDecoder(r).Decode(ptr)
}
// DecodeFile decode JSON from file, bind data to ptr.
func DecodeFile(file string, ptr any) error {
bs, err := os.ReadFile(file)
if err != nil {
return err
}
return json.Unmarshal(bs, ptr)
}
+21
View File
@@ -0,0 +1,21 @@
package jsonutil
/*
TODO json build
type JsonBuilder struct {
Indent string
// mu sync.Mutex
// cfg *CConfig
buf bytes.Buffer
out io.Writer
}
// AddField add field to json
func (b *JsonBuilder) AddField(key string, value any) *JsonBuilder {
b.buf.WriteString(`,"`)
b.buf.WriteString(key)
b.buf.WriteString(`":`)
b.encode(value)
return b
}
*/
+154
View File
@@ -0,0 +1,154 @@
// Package jsonutil provide some util functions for quick operate JSON data
package jsonutil
import (
"bytes"
"encoding/json"
"os"
"regexp"
"strings"
"text/scanner"
)
// WriteFile write data to JSON file
func WriteFile(filePath string, data any) error {
jsonBytes, err := json.Marshal(data)
if err != nil {
return err
}
return os.WriteFile(filePath, jsonBytes, 0664)
}
// WritePretty write pretty data to JSON file
func WritePretty(filePath string, data any) error {
bs, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
return os.WriteFile(filePath, bs, 0664)
}
// ReadFile Read JSON file data
func ReadFile(filePath string, v any) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
return json.NewDecoder(file).Decode(v)
}
// Pretty JSON string and return
func Pretty(v any) (string, error) {
out, err := json.MarshalIndent(v, "", " ")
return string(out), err
}
// MustPretty data to JSON string, will panic on error
func MustPretty(v any) string {
out, err := json.MarshalIndent(v, "", " ")
if err != nil {
panic(err)
}
return string(out)
}
// Mapping src data(map,struct) to dst struct use json tags.
//
// On src, dst both is struct, equivalent to merging two structures (src should be a subset of dsc)
func Mapping(src, dst any) error {
bts, err := json.Marshal(src)
if err != nil {
return err
}
return Decode(bts, dst)
}
// IsJSON check if the string is valid JSON. (Note: uses json.Valid)
func IsJSON(s string) bool {
if s == "" {
return false
}
return json.Valid([]byte(s))
}
// IsJSONFast simple and fast check input is valid JSON array or object.
func IsJSONFast(s string) bool {
ln := len(s)
if ln < 2 {
return false
}
if ln == 2 {
return s == "{}" || s == "[]"
}
// object
if s[0] == '{' {
return s[ln-1] == '}' && s[1] == '"'
}
// array
return s[0] == '[' && s[ln-1] == ']'
}
// IsArray check if the string is valid JSON array.
func IsArray(s string) bool {
ln := len(s)
if ln < 2 {
return false
}
return s[0] == '[' && s[ln-1] == ']'
}
// IsObject check if the string is valid JSON object.
func IsObject(s string) bool {
ln := len(s)
if ln < 2 {
return false
}
if ln == 2 {
return s == "{}"
}
// object
if s[0] == '{' {
return s[ln-1] == '}' && s[1] == '"'
}
return false
}
// `(?s:` enable match multi line
var jsonMLComments = regexp.MustCompile(`(?s:/\*.*?\*/\s*)`)
// StripComments strip comments for a JSON string
func StripComments(src string) string {
// multi line comments
if strings.Contains(src, "/*") {
src = jsonMLComments.ReplaceAllString(src, "")
}
// single line comments
if !strings.Contains(src, "//") {
return strings.TrimSpace(src)
}
// strip inline comments
var s scanner.Scanner
s.Init(strings.NewReader(src))
s.Filename = "comments"
s.Mode ^= scanner.SkipComments // don't skip comments
buf := new(bytes.Buffer)
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
txt := s.TokenText()
if !strings.HasPrefix(txt, "//") && !strings.HasPrefix(txt, "/*") {
buf.WriteString(txt)
// } else {
// fmt.Printf("%s: %s\n", s.Position, txt)
}
}
return buf.String()
}
+72
View File
@@ -0,0 +1,72 @@
# Map Utils
`maputil` provide map data util functions. eg: convert, sub-value get, simple merge
- use `map[string]any` as Data
- deep get value by key path
- deep set value by key path
## Install
```bash
go get github.com/gookit/goutil/maputil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/maputil)
## Usage
### Deep get value
```go
mp := map[string]any {
"top1": "val1",
"arr1": []string{"ab", "cd"}
"map1": map[string]any{
"sub1": "val2",
},
}
fmt.Println(maputil.DeepGet(mp, "map1.sub1")) // Output: VAL3
// get value from slice.
fmt.Println(maputil.DeepGet(mp, "arr1.1")) // Output: cd
fmt.Println(maputil.DeepGet(mp, "arr1[1]")) // Output: cd
```
### Deep set value
```go
mp := map[string]any {
"top1": "val1",
"arr1": []string{"ab"}
"map1": map[string]any{
"sub1": "val2",
},
}
err := maputil.SetByPath(&mp, "map1.newKey", "VAL3")
fmt.Println(maputil.DeepGet(mp, "map1.newKey")) // Output: VAL3
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./maputil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./maputil/...
```
+69
View File
@@ -0,0 +1,69 @@
package maputil
import (
"fmt"
"sort"
)
// Aliases implemented a simple string alias map.
// - key: alias, value: real name
type Aliases map[string]string
// 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))
}
as[alias] = real
}
// 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 map
func (as Aliases) AddAliasMap(alias2real map[string]string) {
for a, r := range alias2real {
as.AddAlias(a, r)
}
}
// HasAlias in the Aliases map
func (as Aliases) HasAlias(alias string) bool {
if _, ok := as[alias]; ok {
return true
}
return false
}
// ResolveAlias by given name.
func (as Aliases) ResolveAlias(alias string) string {
if name, ok := as[alias]; ok {
return name
}
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
}
+64
View File
@@ -0,0 +1,64 @@
package maputil
import (
"reflect"
"github.com/gookit/goutil/reflects"
)
// HasKey check of the given map.
func HasKey(mp, key any) (ok bool) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
return
}
for _, keyRv := range rftVal.MapKeys() {
if reflects.IsEqual(keyRv.Interface(), key) {
return true
}
}
return
}
// HasOneKey check of the given map. return the first exist key
func HasOneKey(mp any, keys ...any) (ok bool, key any) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
return
}
for _, key = range keys {
for _, keyRv := range rftVal.MapKeys() {
if reflects.IsEqual(keyRv.Interface(), key) {
return true, key
}
}
}
return false, nil
}
// HasAllKeys check of the given map. return the first not exist key
func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
return
}
for _, key := range keys {
var exist bool
for _, keyRv := range rftVal.MapKeys() {
if reflects.IsEqual(keyRv.Interface(), key) {
exist = true
break
}
}
if !exist {
return false, key
}
}
return true, nil
}
+279
View File
@@ -0,0 +1,279 @@
package maputil
import (
"errors"
"reflect"
"strings"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/strutil"
)
// alias functions
var (
// ToStrMap convert map[string]any to map[string]string
ToStrMap = ToStringMap
// ToL2StrMap convert map[string]any to map[string]map[string]string
ToL2StrMap = ToL2StringMap
)
// KeyToLower convert keys to lower case.
func KeyToLower(src map[string]string) map[string]string {
if len(src) == 0 {
return src
}
newMp := make(map[string]string, len(src))
for k, v := range src {
k = strings.ToLower(k)
newMp[k] = v
}
return newMp
}
// AnyToStrMap try convert any(map[string]any, map[string]string) to map[string]string
func AnyToStrMap(src any) map[string]string {
if src == nil {
return nil
}
if m, ok := src.(map[string]string); ok {
return m
}
if m, ok := src.(map[string]any); ok {
return ToStringMap(m)
}
return nil
}
// ToStringMap simple convert map[string]any to map[string]string
func ToStringMap(src map[string]any) map[string]string {
strMp := make(map[string]string, len(src))
for k, v := range src {
strMp[k] = strutil.SafeString(v)
}
return strMp
}
// ToL2StringMap convert map[string]any to map[string]map[string]string
func ToL2StringMap(groupsMap map[string]any) map[string]map[string]string {
if len(groupsMap) == 0 {
return nil
}
l2sMap := make(map[string]map[string]string, len(groupsMap))
for k, v := range groupsMap {
if mp, ok := v.(map[string]any); ok {
l2sMap[k] = ToStringMap(mp)
} else if smp, ok := v.(map[string]string); ok {
l2sMap[k] = smp
}
}
return l2sMap
}
// CombineToSMap combine two string-slices to SMap(map[string]string)
func CombineToSMap(keys, values []string) SMap {
return arrutil.CombineToSMap(keys, values)
}
// CombineToMap combine two any slice to map[K]V. alias of arrutil.CombineToMap
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
return arrutil.CombineToMap(keys, values)
}
// SliceToSMap convert string k-v pairs slice to map[string]string
// - 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
if ln == 0 || ln%2 != 0 {
return nil
}
sMap := make(map[string]string, ln/2)
for i := 0; i < ln; i += 2 {
sMap[kvPairs[i]] = kvPairs[i+1]
}
return sMap
}
// SliceToMap convert any k-v pairs slice to map[string]any
func SliceToMap(kvPairs ...any) map[string]any {
ln := len(kvPairs)
// check kvPairs length must be even
if ln == 0 || ln%2 != 0 {
return nil
}
mp := make(map[string]any, ln/2)
for i := 0; i < ln; i += 2 {
kStr := strutil.SafeString(kvPairs[i])
mp[kStr] = kvPairs[i+1]
}
return mp
}
// SliceToTypeMap convert k-v pairs slice to map[string]T
func SliceToTypeMap[T any](valFunc func(any) T, kvPairs ...any) map[string]T {
ln := len(kvPairs)
// check kvPairs length must be even
if ln == 0 || ln%2 != 0 {
return nil
}
mp := make(map[string]T, ln/2)
for i := 0; i < ln; i += 2 {
kStr := strutil.SafeString(kvPairs[i])
mp[kStr] = valFunc(kvPairs[i+1])
}
return mp
}
// ToAnyMap convert map[TYPE1]TYPE2 to map[string]any
func ToAnyMap(mp any) map[string]any {
amp, _ := TryAnyMap(mp)
return amp
}
// TryAnyMap convert map[TYPE1]TYPE2 to map[string]any
func TryAnyMap(mp any) (map[string]any, error) {
if aMp, ok := mp.(map[string]any); ok {
return aMp, nil
}
if sMp, ok := mp.(map[string]string); ok {
anyMp := make(map[string]any, len(sMp))
for k, v := range sMp {
anyMp[k] = v
}
return anyMp, nil
}
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
return nil, errors.New("input is not a map value type")
}
anyMp := make(map[string]any, rv.Len())
for _, key := range rv.MapKeys() {
keyStr := strutil.SafeString(key.Interface())
anyMp[keyStr] = rv.MapIndex(key).Interface()
}
return anyMp, nil
}
// HTTPQueryString convert map[string]any data to http query string.
func HTTPQueryString(data map[string]any) string {
ss := make([]string, 0, len(data))
for k, v := range data {
ss = append(ss, k+"="+strutil.QuietString(v))
}
return strings.Join(ss, "&")
}
// StringsMapToAnyMap convert map[string][]string to map[string]any
//
// Example:
// {"k1": []string{"v1", "v2"}, "k2": []string{"v3"}}
// =>
// {"k": []string{"v1", "v2"}, "k2": "v3"}
//
// mp := StringsMapToAnyMap(httpReq.Header)
func StringsMapToAnyMap(ssMp map[string][]string) map[string]any {
if len(ssMp) == 0 {
return nil
}
anyMp := make(map[string]any, len(ssMp))
for k, v := range ssMp {
if len(v) == 1 {
anyMp[k] = v[0]
continue
}
anyMp[k] = v
}
return anyMp
}
// ToString simple and quickly convert map[string]any to string.
func ToString(mp map[string]any) string {
if mp == nil {
return ""
}
if len(mp) == 0 {
return "{}"
}
buf := make([]byte, 0, len(mp)*16)
buf = append(buf, '{')
for k, val := range mp {
buf = append(buf, k...)
buf = append(buf, ':')
str := strutil.QuietString(val)
buf = append(buf, str...)
buf = append(buf, ',', ' ')
}
// remove last ', '
buf = append(buf[:len(buf)-2], '}')
return strutil.Byte2str(buf)
}
// ToString2 simple and quickly convert a map to string.
func ToString2(mp any) string { return NewFormatter(mp).Format() }
// FormatIndent format map data to string with newline and indent.
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.
*************************************************************/
// Flatten convert tree map to flat key-value map.
//
// Examples:
//
// {"top": {"sub": "value", "sub2": "value2"} }
// ->
// {"top.sub": "value", "top.sub2": "value2" }
func Flatten(mp map[string]any) map[string]any {
if mp == nil {
return nil
}
flatMp := make(map[string]any, len(mp)*2)
reflects.FlatMap(reflect.ValueOf(mp), func(path string, val reflect.Value) {
flatMp[path] = val.Interface()
})
return flatMp
}
// FlatWithFunc flat a tree-map with custom collect handle func
func FlatWithFunc(mp map[string]any, fn reflects.FlatFunc) {
if mp == nil || fn == nil {
return
}
reflects.FlatMap(reflect.ValueOf(mp), fn)
}
+359
View File
@@ -0,0 +1,359 @@
package maputil
import (
"strings"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/strutil"
)
// 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)
return ok
}
// IsEmpty if the data map
func (d Data) IsEmpty() bool {
return len(d) == 0
}
//
// endregion
// region T: set value(s)
//
// Set value to the data map
func (d Data) Set(key string, val any) {
d[key] = val
}
// SetByPath sets a value in the map.
// Supports dot syntax to set deep values.
//
// Example:
//
// d.SetByPath("name.first", "Mat")
func (d Data) SetByPath(path string, value any) error {
if path == "" {
return nil
}
return d.SetByKeys(strings.Split(path, KeySepStr), value)
}
// SetByKeys sets a value in the map by path keys.
// Supports dot syntax to set deep values.
//
// Example:
//
// d.SetByKeys([]string{"name", "first"}, "Mat")
func (d Data) SetByKeys(keys []string, value any) error {
kln := len(keys)
if kln == 0 {
return nil
}
// special handle d is empty.
if len(d) == 0 {
if kln == 1 {
d.Set(keys[0], value)
} else {
d.Set(keys[0], MakeByKeys(keys[1:], value))
}
return nil
}
return SetByKeys((*map[string]any)(&d), keys, value)
// It's ok, but use `func (d *Data)`
// return SetByKeys((*map[string]any)(d), keys, value)
}
//
// endregion
// region T: read value(s)
//
// Value get from the data map
func (d Data) Value(key string) (any, bool) {
val, ok := d.GetByPath(key)
return val, ok
}
// Get value from the data map.
// Supports dot syntax to get deep values. eg: top.sub
func (d Data) Get(key string) any {
if val, ok := d.GetByPath(key); ok {
return val
}
return nil
}
// One get value from the data by multi paths. will return first founded value
func (d Data) One(keys ...string) any {
if val, ok := d.TryOne(keys...); ok {
return val
}
return nil
}
// TryOne get value from the data by multi paths. will return first founded value
func (d Data) TryOne(keys ...string) (any, bool) {
for _, path := range keys {
if val, ok := d.GetByPath(path); ok {
return val, true
}
}
return nil, false
}
// GetByPath get value from the data map by path. eg: top.sub
// Supports dot syntax to get deep values.
func (d Data) GetByPath(path string) (any, bool) {
if val, ok := d[path]; ok {
return val, true
}
// is a key path.
if strings.ContainsRune(path, '.') {
val, ok := GetByPath(path, d)
if ok {
return val, true
}
}
return nil, false
}
// Default get value from the data map with default value
func (d Data) Default(key string, def any) any {
if val, ok := d.GetByPath(key); ok {
return val
}
return def
}
// Int value get, or default value
func (d Data) Int(key string, defVal ...int) int {
if val, ok := d.GetByPath(key); ok {
return mathutil.SafeInt(val)
}
if len(defVal) > 0 {
return defVal[0]
}
return 0
}
// Int64 value get, or default value
func (d Data) Int64(key string, defVal ...int64) int64 {
if val, ok := d.GetByPath(key); ok {
return mathutil.SafeInt64(val)
}
if len(defVal) > 0 {
return defVal[0]
}
return 0
}
// 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)
}
if len(defVal) > 0 {
return defVal[0]
}
return 0
}
// 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 ""
}
// StrOne value gets by multi keys, will return first value
func (d Data) StrOne(keys ...string) string {
for _, key := range keys {
if val, ok := d.GetByPath(key); ok {
return strutil.SafeString(val)
}
}
return ""
}
// Bool value get
func (d Data) Bool(key string) bool {
val, ok := d.GetByPath(key)
if !ok {
return false
}
return comfunc.Bool(val)
}
// BoolOne value gets from multi keys, return first value
func (d Data) BoolOne(keys ...string) bool {
for _, key := range keys {
if val, ok := d.GetByPath(key); ok {
return comfunc.Bool(val)
}
}
return false
}
// StringsOne get []string value by multi keys, return first founded value
func (d Data) StringsOne(keys ...string) []string {
for _, key := range keys {
if val, ok := d.GetByPath(key); ok {
return arrutil.AnyToStrings(val)
}
}
return nil
}
// Strings get []string value by key
func (d Data) Strings(key string) []string {
if val, ok := d.GetByPath(key); ok {
return arrutil.AnyToStrings(val)
}
return nil
}
// StrSplit get strings by split string value
func (d Data) StrSplit(key, sep string) []string {
if val, ok := d.GetByPath(key); ok {
return strings.Split(strutil.SafeString(val), sep)
}
return nil
}
// StringsByStr value gets by key, will split string value by ","
func (d Data) StringsByStr(key string) []string {
return d.StrSplit(key, ",")
}
// StrMap get map[string]string value
func (d Data) StrMap(key string) map[string]string {
return d.StringMap(key)
}
// StringMap get map[string]string value
func (d Data) StringMap(key string) map[string]string {
val, ok := d.GetByPath(key)
if !ok {
return nil
}
switch tv := val.(type) {
case map[string]string:
return tv
case map[string]any:
return ToStringMap(tv)
default:
return nil
}
}
// Sub get sub value(map[string]any) as new Data
func (d Data) Sub(key string) Data {
if val, ok := d.GetByPath(key); ok {
return d.toAnyMap(val)
}
return nil
}
// AnyMap get sub value as map[string]any
func (d Data) AnyMap(key string) map[string]any {
if val, ok := d.GetByPath(key); ok {
return d.toAnyMap(val)
}
return nil
}
// AnyMap get sub value as map[string]any
func (d Data) toAnyMap(val any) map[string]any {
switch tv := val.(type) {
case map[string]string:
return ToAnyMap(tv)
case map[string]any:
return tv
default:
return nil
}
}
// Slice get []any value from data map
func (d Data) Slice(key string) ([]any, error) {
val, ok := d.GetByPath(key)
if !ok {
return nil, nil
}
return arrutil.AnyToSlice(val)
}
// Keys of the data map
func (d Data) Keys() []string {
keys := make([]string, 0, len(d))
for k := range d {
keys = append(keys, k)
}
return keys
}
// ToStringMap convert to map[string]string
func (d Data) ToStringMap() map[string]string {
return ToStringMap(d)
}
// String data to string
func (d Data) String() string {
return ToString(d)
}
// Load other data to current data map
func (d Data) Load(sub map[string]any) {
for name, val := range sub {
d[name] = val
}
}
// LoadSMap to data
func (d Data) LoadSMap(smp map[string]string) {
for name, val := range smp {
d[name] = val
}
}
+125
View File
@@ -0,0 +1,125 @@
package maputil
import (
"io"
"reflect"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/strutil"
)
// MapFormatter struct
type MapFormatter struct {
comdef.BaseFormatter
// Prefix string for each element
Prefix string
// Indent string for each element
Indent string
// ClosePrefix string for last "}"
ClosePrefix string
// AfterReset after reset on call Format().
// AfterReset bool
}
// NewFormatter instance
func NewFormatter(mp any) *MapFormatter {
f := &MapFormatter{}
f.Src = mp
return f
}
// WithFn for config self
func (f *MapFormatter) WithFn(fn func(f *MapFormatter)) *MapFormatter {
fn(f)
return f
}
// WithIndent string
func (f *MapFormatter) WithIndent(indent string) *MapFormatter {
f.Indent = indent
return f
}
// FormatTo to custom buffer
func (f *MapFormatter) FormatTo(w io.Writer) {
f.SetOutput(w)
f.doFormat()
}
// Format to string
func (f *MapFormatter) String() string {
return f.Format()
}
// Format to string
func (f *MapFormatter) Format() string {
f.doFormat()
return f.BsWriter().String()
}
// Format map data to string.
//
//goland:noinspection GoUnhandledErrorResult
func (f *MapFormatter) doFormat() {
if f.Src == nil {
return
}
rv, ok := f.Src.(reflect.Value)
if !ok {
rv = reflect.ValueOf(f.Src)
}
rv = reflect.Indirect(rv)
if rv.Kind() != reflect.Map {
return
}
buf := f.BsWriter()
ln := rv.Len()
if ln == 0 {
buf.WriteString("{}")
return
}
// buf.Grow(ln * 16)
buf.WriteByte('{')
indentLn := len(f.Indent)
if indentLn > 0 {
buf.WriteByte('\n')
}
for i, key := range rv.MapKeys() {
strK := strutil.SafeString(key.Interface())
if indentLn > 0 {
buf.WriteString(f.Indent)
}
buf.WriteString(strK)
buf.WriteByte(':')
strV := strutil.SafeString(rv.MapIndex(key).Interface())
buf.WriteString(strV)
if i < ln-1 {
buf.WriteByte(',')
// no indent, with space
if indentLn == 0 {
buf.WriteByte(' ')
}
}
// with newline
if indentLn > 0 {
buf.WriteByte('\n')
}
}
if f.ClosePrefix != "" {
buf.WriteString(f.ClosePrefix)
}
buf.WriteByte('}')
}
+252
View File
@@ -0,0 +1,252 @@
package maputil
import (
"reflect"
"strconv"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/reflects"
)
// some consts for separators
const (
Wildcard = "*"
PathSep = "."
)
// DeepGet value by key path. eg "top" "top.sub"
func DeepGet(mp map[string]any, path string) (val any) {
val, _ = GetByPath(path, mp)
return
}
// QuietGet value by key path. eg "top" "top.sub"
func QuietGet(mp map[string]any, path string) (val any) {
val, _ = GetByPath(path, mp)
return
}
// GetFromAny get value by key path from any(map,slice) data. eg "top" "top.sub"
func GetFromAny(path string, data any) (val any, ok bool) {
// empty data
if data == nil {
return nil, false
}
if len(path) == 0 {
return data, true
}
return getByPathKeys(data, strings.Split(path, "."))
}
// GetByPath get value by key path from a map(map[string]any). eg "top" "top.sub"
func GetByPath(path string, mp map[string]any) (val any, ok bool) {
if len(path) == 0 {
return mp, true
}
if val, ok := mp[path]; ok {
return val, true
}
// no sub key
if len(mp) == 0 || strings.IndexByte(path, '.') < 1 {
return nil, false
}
// key is path. eg: "top.sub"
return GetByPathKeys(mp, strings.Split(path, "."))
}
// GetByPathKeys get value by path keys from a map(map[string]any). eg "top" "top.sub"
//
// Example:
//
// mp := map[string]any{
// "top": map[string]any{
// "sub": "value",
// },
// }
// val, ok := GetByPathKeys(mp, []string{"top", "sub"}) // return "value", true
func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
kl := len(keys)
if kl == 0 {
return mp, true
}
// find top item data use top key
var item any
topK := keys[0]
if item, ok = mp[topK]; !ok {
return
}
// find sub item data use sub key
return getByPathKeys(item, keys[1:])
}
func getByPathKeys(item any, keys []string) (val any, ok bool) {
kl := len(keys)
for i, k := range keys {
switch tData := item.(type) {
case map[string]string: // is string map
if item, ok = tData[k]; !ok {
return
}
case map[string]any: // is map(decode from toml/json/yaml)
if item, ok = tData[k]; !ok {
return
}
case map[any]any: // is map(decode from yaml.v2)
if item, ok = tData[k]; !ok {
return
}
case []map[string]any: // is an any-map slice
if k == Wildcard {
if kl == i+1 { // * is last key
return tData, true
}
// * is not last key, find sub item data
sl := make([]any, 0, len(tData))
for _, v := range tData {
if val, ok = getByPathKeys(v, keys[i+1:]); ok {
sl = append(sl, val)
}
}
if len(sl) > 0 {
return sl, true
}
return nil, false
}
// k is index number
idx, err := strconv.Atoi(k)
if err != nil || idx >= len(tData) {
return nil, false
}
item = tData[idx]
default:
if k == Wildcard && kl == i+1 { // * is last key
return tData, true
}
rv := reflect.ValueOf(tData)
// check is slice
if rv.Kind() == reflect.Slice {
if k == Wildcard {
// * is not last key, find sub item data
sl := make([]any, 0, rv.Len())
for si := 0; si < rv.Len(); si++ {
el := reflects.Indirect(rv.Index(si))
if el.Kind() != reflect.Map {
return nil, false
}
// el is map value.
if val, ok = getByPathKeys(el.Interface(), keys[i+1:]); ok {
sl = append(sl, val)
}
}
if len(sl) > 0 {
return sl, true
}
return nil, false
}
// check k is index number
ii, err := strconv.Atoi(k)
if err != nil || ii >= rv.Len() {
return nil, false
}
item = rv.Index(ii).Interface()
continue
}
// as error
return nil, false
}
// next is last key and it is *
if kl == i+2 && keys[i+1] == Wildcard {
return item, true
}
}
return item, true
}
// Keys get all keys of the given map.
func Keys(mp any) (keys []string) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
return
}
keys = make([]string, 0, rftVal.Len())
for _, key := range rftVal.MapKeys() {
keys = append(keys, key.String())
}
return
}
// TypedKeys get all keys of the given typed map.
func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K) {
for key := range mp {
keys = append(keys, key)
}
return
}
// FirstKey returns the first key of the given map.
func FirstKey[T any](mp map[string]T) string {
for key := range mp {
return key
}
return ""
}
// Values get all values from the given map.
func Values(mp any) (values []any) {
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
return
}
values = make([]any, 0, rv.Len())
for _, key := range rv.MapKeys() {
values = append(values, rv.MapIndex(key).Interface())
}
return
}
// TypedValues get all values from the given typed map.
func TypedValues[K comdef.SimpleType, V any](mp map[K]V) (values []V) {
for _, val := range mp {
values = append(values, val)
}
return
}
// EachAnyMap iterates the given map and calls the given function for each item.
func EachAnyMap(mp any, fn func(key string, val any)) {
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
panic("not a map value")
}
for _, key := range rv.MapKeys() {
fn(key.String(), rv.MapIndex(key).Interface())
}
}
// EachTypedMap iterates the given map and calls the given function for each item.
func EachTypedMap[K comdef.SimpleType, V any](mp map[K]V, fn func(key K, val V)) {
for key, val := range mp {
fn(key, val)
}
}
+215
View File
@@ -0,0 +1,215 @@
// Package maputil provide map data util functions. eg: convert, sub-value get, simple merge
package maputil
import (
"reflect"
"strings"
"github.com/gookit/goutil/arrutil"
)
// Key, value sep char consts
const (
ValSepStr = ","
ValSepChar = ','
KeySepStr = "."
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 {
return dst
}
if len(dst) == 0 {
return src
}
for key, val := range src {
if mp, ok := val.(map[string]any); ok {
if dmp, ok := dst[key].(map[string]any); ok {
dst[key] = SimpleMerge(mp, dmp)
continue
}
}
// simple merge
dst[key] = val
}
return dst
}
// Merge1level merge multi any map[string]any data. only merge one level data.
func Merge1level(mps ...map[string]any) map[string]any {
newMp := make(map[string]any)
for _, mp := range mps {
for k, v := range mp {
newMp[k] = v
}
}
return newMp
}
// func DeepMerge(src, dst map[string]any, deep int) map[string]any { TODO
// }
// MergeSMap simple merge two string map. merge src to dst map
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string {
return MergeStringMap(src, dst, ignoreCase)
}
// MergeStrMap simple merge two string map. merge src to dst map
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 {
return dst
}
if len(dst) == 0 {
return src
}
for k, v := range src {
if ignoreCase {
k = strings.ToLower(k)
}
dst[k] = v
}
return dst
}
// MergeMultiSMap quick merge multi string-map data.
func MergeMultiSMap(mps ...map[string]string) map[string]string {
newMp := make(map[string]string)
for _, mp := range mps {
for k, v := range mp {
newMp[k] = v
}
}
return newMp
}
// MergeL2StrMap merge multi level2 string-map data. The back map covers the front.
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string {
newMp := make(map[string]map[string]string)
for _, mp := range mps {
for k, v := range mp {
// merge level 2 value
if oldV, ok := newMp[k]; ok {
for k1, v1 := range v {
oldV[k1] = v1
}
newMp[k] = oldV
} else {
newMp[k] = v
}
}
}
return newMp
}
// FilterSMap filter empty elem for the string map.
func FilterSMap(sm map[string]string) map[string]string {
for key, val := range sm {
if val == "" {
delete(sm, key)
}
}
return sm
}
// MakeByPath build new value by key names
//
// Example:
//
// "site.info"
// ->
// map[string]any {
// site: {info: val}
// }
//
// // case 2, last key is slice:
// "site.tags[1]"
// ->
// map[string]any {
// site: {tags: [val]}
// }
func MakeByPath(path string, val any) (mp map[string]any) {
return MakeByKeys(strings.Split(path, KeySepStr), val)
}
// MakeByKeys build new value by key names
//
// Example:
//
// // case 1:
// []string{"site", "info"}
// ->
// map[string]any {
// site: {info: val}
// }
//
// // case 2, last key is slice:
// []string{"site", "tags[1]"}
// ->
// map[string]any {
// site: {tags: [val]}
// }
func MakeByKeys(keys []string, val any) (mp map[string]any) {
size := len(keys)
// if last key contains slice index, make slice wrap the val
lastKey := keys[size-1]
if newK, idx, ok := parseArrKeyIndex(lastKey); ok {
// valTyp := reflect.TypeOf(val)
sliTyp := reflect.SliceOf(reflect.TypeOf(val))
sliVal := reflect.MakeSlice(sliTyp, idx+1, idx+1)
sliVal.Index(idx).Set(reflect.ValueOf(val))
// update val and last key
val = sliVal.Interface()
keys[size-1] = newK
}
if size == 1 {
return map[string]any{keys[0]: val}
}
// multi nodes
arrutil.Reverse(keys)
for _, p := range keys {
if mp == nil {
mp = map[string]any{p: val}
} else {
mp = map[string]any{p: mp}
}
}
return
}
+340
View File
@@ -0,0 +1,340 @@
package maputil
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/gookit/goutil/strutil"
)
// SetByPath set sub-map value by key path.
// Supports dot syntax to set deep values.
//
// For example:
//
// SetByPath("name.first", "Mat")
func SetByPath(mp *map[string]any, path string, val any) error {
return SetByKeys(mp, strings.Split(path, KeySepStr), val)
}
// SetByKeys set sub-map value by path keys.
// Supports dot syntax to set deep values.
//
// For example:
//
// SetByKeys([]string{"name", "first"}, "Mat")
func SetByKeys(mp *map[string]any, keys []string, val any) (err error) {
kln := len(keys)
if kln == 0 {
return nil
}
mpv := *mp
if len(mpv) == 0 {
*mp = MakeByKeys(keys, val)
return nil
}
topK := keys[0]
if kln == 1 {
mpv[topK] = val
return nil
}
if _, ok := mpv[topK]; !ok {
mpv[topK] = MakeByKeys(keys[1:], val)
return nil
}
rv := reflect.ValueOf(mp).Elem()
return setMapByKeys(rv, keys, reflect.ValueOf(val))
}
func setMapByKeys(rv reflect.Value, keys []string, nv reflect.Value) (err error) {
if rv.Kind() != reflect.Map {
return fmt.Errorf("input parameter#rv must be a Map, but was %s", rv.Kind())
}
// If the map is nil, make a new map
if rv.IsNil() {
mapType := reflect.MapOf(rv.Type().Key(), rv.Type().Elem())
rv.Set(reflect.MakeMap(mapType))
}
var ok bool
maxI := len(keys) - 1
for i, key := range keys {
idx := -1
isMap := rv.Kind() == reflect.Map
isSlice := rv.Kind() == reflect.Slice
isLast := i == len(keys)-1
// slice index key must be ended on the keys.
// eg: "top.arr[2]" -> "arr[2]"
if pos := strings.IndexRune(key, '['); pos > 0 {
var realKey string
if realKey, idx, ok = parseArrKeyIndex(key); ok {
// update value
key = realKey
if !isMap {
err = fmt.Errorf(
"current value#%s type is %s, cannot get sub-value by key: %s",
strings.Join(keys[i:], "."),
rv.Kind(),
key,
)
break
}
rftK := reflect.ValueOf(key)
tmpV := rv.MapIndex(rftK)
if !tmpV.IsValid() {
if isLast {
sliVal := reflect.MakeSlice(reflect.SliceOf(nv.Type()), idx+1, idx+1)
sliVal.Index(idx).Set(nv)
rv.SetMapIndex(rftK, sliVal)
} else {
// deep make map by keys
newVal := MakeByKeys(keys[i+1:], nv.Interface())
mpVal := reflect.ValueOf(newVal)
sliVal := reflect.MakeSlice(reflect.SliceOf(mpVal.Type()), idx+1, idx+1)
sliVal.Index(idx).Set(mpVal)
rv.SetMapIndex(rftK, sliVal)
}
break
}
// get real type: any -> map
if tmpV.Kind() == reflect.Interface {
tmpV = tmpV.Elem()
}
if tmpV.Kind() != reflect.Slice {
err = fmt.Errorf(
"current value#%s type is %s, cannot set sub by index: %d",
strings.Join(keys[i:], "."),
tmpV.Kind(),
idx,
)
break
}
wantLen := idx + 1
sliLen := tmpV.Len()
elemTyp := tmpV.Type().Elem()
if wantLen > sliLen {
newAdd := reflect.MakeSlice(tmpV.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
tmpV = reflect.AppendSlice(tmpV, newAdd)
}
if !isLast {
if elemTyp.Kind() == reflect.Map {
err := setMapByKeys(tmpV.Index(idx), keys[i+1:], nv)
if err != nil {
return err
}
// tmpV.Index(idx).Set(elemV)
rv.SetMapIndex(rftK, tmpV)
} else {
err = fmt.Errorf(
"key %s[%d] elem must be map for set sub-value by remain path: %s",
key,
idx,
strings.Join(keys[i:], "."),
)
}
} else {
// last - set value
tmpV.Index(idx).Set(nv)
rv.SetMapIndex(rftK, tmpV)
}
break
}
}
// set value on last key
if isLast {
if isMap {
rv.SetMapIndex(reflect.ValueOf(key), nv)
break
}
if isSlice {
// key is slice index
if strutil.IsInt(key) {
idx, _ = strconv.Atoi(key)
}
if idx > -1 {
wantLen := idx + 1
sliLen := rv.Len()
if wantLen > sliLen {
elemTyp := rv.Type().Elem()
newAdd := reflect.MakeSlice(rv.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
if !rv.CanAddr() {
err = fmt.Errorf("cannot set value to a cannot addr slice, key: %s", key)
break
}
rv.Set(reflect.AppendSlice(rv, newAdd))
}
rv.Index(idx).Set(nv)
} else {
err = fmt.Errorf("cannot set slice value by named key %q", key)
}
} else {
err = fmt.Errorf(
"cannot set sub-value for type %q(path %q, key %q)",
rv.Kind(),
strings.Join(keys[:i], "."),
key,
)
}
break
}
if isMap {
rftK := reflect.ValueOf(key)
if tmpV := rv.MapIndex(rftK); tmpV.IsValid() {
var isPtr bool
// get real type: any -> map
tmpV, isPtr = getRealVal(tmpV)
if tmpV.Kind() == reflect.Map {
rv = tmpV
continue
}
// sub is slice and is not ptr
if tmpV.Kind() == reflect.Slice {
if isPtr {
rv = tmpV
continue // to (E)
}
// next key is index number.
nxtKey := keys[i+1]
if strutil.IsInt(nxtKey) {
idx, _ = strconv.Atoi(nxtKey)
sliLen := tmpV.Len()
wantLen := idx + 1
if wantLen > sliLen {
elemTyp := tmpV.Type().Elem()
newAdd := reflect.MakeSlice(tmpV.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
tmpV = reflect.AppendSlice(tmpV, newAdd)
}
// rv = tmpV.Index(idx) // TODO
if i+1 == maxI {
tmpV.Index(idx).Set(nv)
} else {
err := setMapByKeys(tmpV.Index(idx), keys[i+1:], nv)
if err != nil {
return err
}
}
rv.SetMapIndex(rftK, tmpV)
} else {
err = fmt.Errorf("cannot set slice value by named key %s(parent: %s)", nxtKey, key)
}
} else {
err = fmt.Errorf(
"map item type is %s(path:%q), cannot set sub-value by path %q",
tmpV.Kind(),
strings.Join(keys[0:i+1], "."),
strings.Join(keys[i+1:], "."),
)
}
} else {
// deep make map by keys
newVal := MakeByKeys(keys[i+1:], nv.Interface())
rv.SetMapIndex(rftK, reflect.ValueOf(newVal))
}
break
} else if isSlice && strutil.IsInt(key) { // (E). slice from ptr slice
idx, _ = strconv.Atoi(key)
sliLen := rv.Len()
wantLen := idx + 1
if wantLen > sliLen {
elemTyp := rv.Type().Elem()
newAdd := reflect.MakeSlice(rv.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
rv = reflect.AppendSlice(rv, newAdd)
}
rv = rv.Index(idx)
} else {
err = fmt.Errorf(
"map item type is %s, cannot set sub-value by path %q",
rv.Kind(),
strings.Join(keys[i:], "."),
)
}
}
return
}
func getRealVal(rv reflect.Value) (reflect.Value, bool) {
// get real type: any -> map
if rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
isPtr := false
if rv.Kind() == reflect.Ptr {
isPtr = true
rv = rv.Elem()
}
return rv, isPtr
}
// "arr[2]" => "arr", 2, true
func parseArrKeyIndex(key string) (string, int, bool) {
pos := strings.IndexRune(key, '[')
if pos < 1 || !strings.HasSuffix(key, "]") {
return key, 0, false
}
var idx int
var err error
idxStr := key[pos+1 : len(key)-1]
if idxStr != "" {
idx, err = strconv.Atoi(idxStr)
if err != nil {
return key, 0, false
}
}
key = key[:pos]
return key, idx, true
}
+165
View File
@@ -0,0 +1,165 @@
package maputil
import (
"github.com/gookit/goutil/mathutil"
"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
// IsEmpty of the data map
func (m StrMap) IsEmpty() bool {
return len(m) == 0
}
// Has key on the data map
func (m StrMap) Has(key string) bool {
_, ok := m[key]
return ok
}
// HasValue on the data map
func (m StrMap) HasValue(val string) bool {
for _, v := range m {
if v == val {
return true
}
}
return false
}
// Load data to the map
func (m StrMap) Load(data map[string]string) {
for k, v := range data {
m[k] = v
}
}
// Set value to the data map
func (m StrMap) Set(key string, val any) {
m[key] = strutil.MustString(val)
}
// Value get from the data map
func (m StrMap) Value(key string) (string, bool) {
val, ok := m[key]
return val, ok
}
// Default get value by key. if not found, return defVal
func (m StrMap) Default(key, defVal string) string {
if val, ok := m[key]; ok {
return val
}
return defVal
}
// Get value by key
func (m StrMap) Get(key string) string {
return m[key]
}
// Int value get
func (m StrMap) Int(key string) int {
if val, ok := m[key]; ok {
return mathutil.SafeInt(val)
}
return 0
}
// Int64 value get
func (m StrMap) Int64(key string) int64 {
if val, ok := m[key]; ok {
return mathutil.SafeInt64(val)
}
return 0
}
// Str value get
func (m StrMap) Str(key string) string {
return m[key]
}
// StrOne get first founded value by keys
func (m SMap) StrOne(keys ...string) string {
for _, key := range keys {
if val, ok := m[key]; ok {
return val
}
}
return ""
}
// Bool value get
func (m StrMap) Bool(key string) bool {
if val, ok := m[key]; ok {
return strutil.SafeBool(val)
}
return false
}
// Ints value to []int
func (m StrMap) Ints(key string) []int {
if val, ok := m[key]; ok {
return strutil.Ints(val, ValSepStr)
}
return nil
}
// Strings value to []string
func (m StrMap) Strings(key string) (ss []string) {
if val, ok := m[key]; ok {
return strutil.ToSlice(val, ValSepStr)
}
return
}
// IfExist key, then call the fn with value.
func (m StrMap) IfExist(key string, fn func(val string)) {
if val, ok := m[key]; ok {
fn(val)
}
}
// IfValid value is not empty, then call the fn
func (m StrMap) IfValid(key string, fn func(val string)) {
if val, ok := m[key]; ok && val != "" {
fn(val)
}
}
// Keys of the string-map
func (m StrMap) Keys() []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
// Values of the string-map
func (m StrMap) Values() []string {
ss := make([]string, 0, len(m))
for _, v := range m {
ss = append(ss, v)
}
return ss
}
// ToKVPairs slice convert. eg: {k1:v1,k2:v2} => {k1,v1,k2,v2}
func (m StrMap) ToKVPairs() []string {
pairs := make([]string, 0, len(m)*2)
for k, v := range m {
pairs = append(pairs, k, v)
}
return pairs
}
// String data to string
func (m StrMap) String() string {
return ToString2(m)
}
+51
View File
@@ -0,0 +1,51 @@
package maputil
import "github.com/gookit/goutil/strutil"
// L2StrMap is alias of map[string]map[string]string
type L2StrMap map[string]map[string]string
// Load data, merge new data to old
func (m L2StrMap) Load(mp map[string]map[string]string) {
for k, v := range mp {
if oldV, ok := m[k]; ok {
for k1, v1 := range v {
oldV[k1] = v1
}
m[k] = oldV
} else {
m[k] = v
}
}
}
// Value get by key path. eg: "top.sub"
func (m L2StrMap) Value(key string) (val string, ok bool) {
top, sub, found := strutil.Cut(key, KeySepStr)
if !found {
return "", false
}
if vals, ok1 := m[top]; ok1 {
val, ok = vals[sub]
return
}
return "", false
}
// Get value by key path. eg: "top.sub"
func (m L2StrMap) Get(key string) string {
val, _ := m.Value(key)
return val
}
// Exists check key path exists. eg: "top.sub"
func (m L2StrMap) Exists(key string) bool {
_, ok := m.Value(key)
return ok
}
// StrMap get by top key. eg: "top"
func (m L2StrMap) StrMap(top string) StrMap {
return m[top]
}
+108
View File
@@ -0,0 +1,108 @@
# Math Utils
- some features
## Install
```bash
go get github.com/gookit/goutil/mathutil
```
## Go docs
- [Go Docs](https://pkg.go.dev/github.com/gookit/goutil)
## Usage
## Functions
```go
func CompFloat[T comdef.Float](first, second T, op string) (ok bool)
func CompInt[T comdef.Xint](first, second T, op string) (ok bool)
func CompInt64(first, second int64, op string) bool
func CompValue[T comdef.XintOrFloat](first, second T, op string) (ok bool)
func Compare(first, second any, op string) (ok bool)
func DataSize(size uint64) string
func ElapsedTime(startTime time.Time) string
func Float(in any) (float64, error)
func FloatOr(in any, defVal float64) float64
func FloatOrDefault(in any, defVal float64) float64
func FloatOrErr(in any) (float64, error)
func FloatOrPanic(in any) float64
func GreaterOr[T comdef.XintOrFloat](val, min, defVal T) T
func GteOr[T comdef.XintOrFloat](val, min, defVal T) T
func HowLongAgo(sec int64) string
func InRange[T comdef.IntOrFloat](val, min, max T) bool
func InUintRange[T comdef.Uint](val, min, max T) bool
func Int(in any) (int, error)
func Int64(in any) (int64, error)
func Int64OrErr(in any) (int64, error)
func IntOr(in any, defVal int) int
func IntOrDefault(in any, defVal int) int
func IntOrErr(in any) (iVal int, err error)
func IntOrPanic(in any) int
func IsNumeric(c byte) bool
func LessOr[T comdef.XintOrFloat](val, max, devVal T) T
func LteOr[T comdef.XintOrFloat](val, max, devVal T) T
func Max[T comdef.XintOrFloat](x, y T) T
func MaxFloat(x, y float64) float64
func MaxI64(x, y int64) int64
func MaxInt(x, y int) int
func Min[T comdef.XintOrFloat](x, y T) T
func MustFloat(in any) float64
func MustInt(in any) int
func MustInt64(in any) int64
func MustString(val any) string
func MustUint(in any) uint64
func OrElse[T comdef.XintOrFloat](val, defVal T) T
func OutRange[T comdef.IntOrFloat](val, min, max T) bool
func Percent(val, total int) float64
func QuietFloat(in any) float64
func QuietInt(in any) int
func QuietInt64(in any) int64
func QuietString(val any) string
func QuietUint(in any) uint64
func RandInt(min, max int) int
func RandIntWithSeed(min, max int, seed int64) int
func RandomInt(min, max int) int
func RandomIntWithSeed(min, max int, seed int64) int
func SafeFloat(in any) float64
func SafeInt(in any) int
func SafeInt64(in any) int64
func SafeUint(in any) uint64
func StrInt(s string) int
func StrIntOr(s string, defVal int) int
func String(val any) string
func StringOrErr(val any) (string, error)
func StringOrPanic(val any) string
func SwapMax[T comdef.XintOrFloat](x, y T) (T, T)
func SwapMaxI64(x, y int64) (int64, int64)
func SwapMaxInt(x, y int) (int, int)
func SwapMin[T comdef.XintOrFloat](x, y T) (T, T)
func ToFloat(in any) (f64 float64, err error)
func ToFloatWithFunc(in any, usrFn func(any) (float64, error)) (f64 float64, err error)
func ToInt(in any) (iVal int, err error)
func ToInt64(in any) (i64 int64, err error)
func ToString(val any) (string, error)
func ToUint(in any) (u64 uint64, err error)
func ToUintWithFunc(in any, usrFn func(any) (uint64, error)) (u64 uint64, err error)
func TryToString(val any, defaultAsErr bool) (str string, err error)
func Uint(in any) (uint64, error)
func UintOrErr(in any) (uint64, error)
func ZeroOr[T comdef.XintOrFloat](val, defVal T) T
```
## Testings
```shell
go test -v ./mathutil/...
```
Test limit by regexp:
```shell
go test -v -run ^TestSetByKeys ./mathutil/...
```
+11
View File
@@ -0,0 +1,11 @@
package mathutil
import "github.com/gookit/goutil/comdef"
// Abs get absolute value of given value
func Abs[T comdef.Int](val T) T {
if val >= 0 {
return val
}
return -val
}
+138
View File
@@ -0,0 +1,138 @@
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' }
// 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`
//
// Usage:
//
// mathutil.Compare(2, 3, ">") // false
// mathutil.Compare(2, 1.3, ">") // true
// mathutil.Compare(2.2, 1.3, ">") // true
// mathutil.Compare(2.1, 2, ">") // true
func Compare(first, second any, op string) bool {
if first == nil || second == nil {
return false
}
switch fVal := first.(type) {
case float64:
if sVal, err := ToFloat(second); err == nil {
return CompFloat(fVal, sVal, op)
}
case float32:
if sVal, err := ToFloat(second); err == nil {
return CompFloat(float64(fVal), sVal, op)
}
default: // as int64
if int1, err := ToInt64(first); err == nil {
if int2, err := ToInt64(second); err == nil {
return CompInt64(int1, int2, op)
}
}
}
return false
}
// CompInt compare all intX,uintX type value. returns `first op(=,!=,<,<=,>,>=) second`
func CompInt[T comdef.Xint](first, second T, op string) (ok bool) {
return CompValue(first, second, op)
}
// CompInt64 compare int64 value. returns `first op(=,!=,<,<=,>,>=) second`
func CompInt64(first, second int64, op string) bool {
return CompValue(first, second, op)
}
// CompFloat compare float64,float32 value. returns `first op(=,!=,<,<=,>,>=) second`
func CompFloat[T comdef.Float](first, second T, op string) (ok bool) {
return CompValue(first, second, op)
}
// CompValue compare intX,uintX,floatX value. returns `first op(=,!=,<,<=,>,>=) second`
func CompValue[T comdef.Number](first, second T, op string) (ok bool) {
switch op {
case "<", "lt":
ok = first < second
case "<=", "lte":
ok = first <= second
case ">", "gt":
ok = first > second
case ">=", "gte":
ok = first >= second
case "=", "eq":
ok = first == second
case "!=", "ne", "neq":
ok = first != second
}
return
}
// InRange check if val in int/float range [min, max]
func InRange[T comdef.Number](val, min, max T) bool {
return val >= min && val <= max
}
// OutRange check if val not in int/float range [min, max]
func OutRange[T comdef.Number](val, min, max T) bool {
return val < min || val > max
}
// InUintRange check if val in unit range [min, max]
func InUintRange[T comdef.Uint](val, min, max T) bool {
if max == 0 {
return val >= min
}
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)
}
+143
View File
@@ -0,0 +1,143 @@
package mathutil
import (
"math"
"github.com/gookit/goutil/comdef"
)
// Min compare two value and return max value
func Min[T comdef.Number](x, y T) T {
if x < y {
return x
}
return y
}
// Max compare two value and return max value
func Max[T comdef.Number](x, y T) T {
if x > y {
return x
}
return y
}
// SwapMin compare and always return [min, max] value
func SwapMin[T comdef.Number](x, y T) (T, T) {
if x < y {
return x, y
}
return y, x
}
// SwapMax compare and always return [max, min] value
func SwapMax[T comdef.Number](x, y T) (T, T) {
if x > y {
return x, y
}
return y, x
}
// MaxInt compare and return max value
func MaxInt(x, y int) int {
if x > y {
return x
}
return y
}
// SwapMaxInt compare and return max, min value
func SwapMaxInt(x, y int) (int, int) {
if x > y {
return x, y
}
return y, x
}
// MaxI64 compare and return max value
func MaxI64(x, y int64) int64 {
if x > y {
return x
}
return y
}
// SwapMaxI64 compare and return max, min value
func SwapMaxI64(x, y int64) (int64, int64) {
if x > y {
return x, y
}
return y, x
}
// MaxFloat compare and return max value
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
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
}
+303
View File
@@ -0,0 +1,303 @@
package mathutil
import (
"reflect"
"strconv"
"strings"
"time"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/checkfn"
"github.com/gookit/goutil/internal/comfunc"
)
// ConvOption convert options
type ConvOption[T any] struct {
// if ture: value is nil, will return convert error;
// if false(default): value is nil, will convert to zero value
NilAsFail bool
// HandlePtr auto convert ptr type(int,float,string) value. eg: *int to int
// - 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]
}
// NewConvOption create a new ConvOption
func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T] {
opt := &ConvOption[T]{}
opt.WithOption(optFns...)
return opt
}
// WithOption set convert option
func (opt *ConvOption[T]) WithOption(optFns ...ConvOptionFn[T]) {
for _, fn := range optFns {
if fn != nil {
fn(opt)
}
}
}
// ConvOptionFn convert option func
type ConvOptionFn[T any] func(opt *ConvOption[T])
// WithNilAsFail set ConvOption.NilAsFail option
//
// Example:
//
// ToIntWithFunc(val, mathutil.WithNilAsFail[int])
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 }
// 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] {
return func(opt *ConvOption[T]) {
opt.UserConvFn = fn
}
}
/*************************************************************
* region Strict to int/uint
*************************************************************/
// 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:
return int64(tVal), true
case int8:
return int64(tVal), true
case int16:
return int64(tVal), true
case int32:
return int64(tVal), true
case int64:
return tVal, true
case uint:
return int64(tVal), true
case uint8:
return int64(tVal), true
case uint16:
return int64(tVal), true
case uint32:
return int64(tVal), true
case uint64:
return int64(tVal), true
case uintptr:
return int64(tVal), true
default:
return 0, false
}
}
// 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
}
}
/*************************************************************
* region convert to float64
*************************************************************/
// QuietFloat convert value to float64, will ignore error. alias of SafeFloat
func QuietFloat(in any) float64 { return SafeFloat(in) }
// SafeFloat convert value to float64, will ignore error
func SafeFloat(in any) float64 {
val, _ := ToFloatWith(in)
return val
}
// FloatOrPanic convert value to float64, will panic on error
func FloatOrPanic(in any) float64 { return MustFloat(in) }
// MustFloat convert value to float64, will panic on error
func MustFloat(in any) float64 {
val, err := ToFloatWith(in)
if err != nil {
panic(err)
}
return val
}
// FloatOrDefault convert value to float64, will return default value on error
func FloatOrDefault(in any, defVal float64) float64 { return FloatOr(in, defVal) }
// FloatOr convert value to float64, will return default value on error
func FloatOr(in any, defVal float64) float64 {
val, err := ToFloatWith(in)
if err != nil {
return defVal
}
return val
}
// Float convert value to float64, return error on failed
func Float(in any) (float64, error) { return ToFloatWith(in) }
// FloatOrErr convert value to float64, return error on failed
func FloatOrErr(in any) (float64, error) { return ToFloatWith(in) }
// ToFloat convert value to float64, return error on failed
func ToFloat(in any) (float64, error) { return ToFloatWith(in) }
// ToFloatWith try to convert value to float64. can with some option func, more see ConvOption.
func ToFloatWith(in any, optFns ...ConvOptionFn[float64]) (f64 float64, err error) {
opt := NewConvOption(optFns...)
if !opt.NilAsFail && in == nil {
return 0, nil
}
switch tVal := in.(type) {
case string:
f64, err = strconv.ParseFloat(strings.TrimSpace(tVal), 64)
case int:
f64 = float64(tVal)
case int8:
f64 = float64(tVal)
case int16:
f64 = float64(tVal)
case int32:
f64 = float64(tVal)
case int64:
f64 = float64(tVal)
case uint:
f64 = float64(tVal)
case uint8:
f64 = float64(tVal)
case uint16:
f64 = float64(tVal)
case uint32:
f64 = float64(tVal)
case uint64:
f64 = float64(tVal)
case float32:
f64 = float64(tVal)
case float64:
f64 = tVal
case *float64: // default support float64 ptr type
f64 = *tVal
case time.Duration:
f64 = float64(tVal)
case comdef.Float64able: // eg: json.Number
f64, err = tVal.Float64()
default:
if opt.HandlePtr {
if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer {
rv = rv.Elem()
if checkfn.IsSimpleKind(rv.Kind()) {
return ToFloatWith(rv.Interface(), optFns...)
}
}
}
if opt.UserConvFn != nil {
f64, err = opt.UserConvFn(in)
} else {
err = comdef.ErrConvType
}
}
return
}
/*************************************************************
* region intX/floatX to string
*************************************************************/
// MustString convert intX/floatX value to string, will panic on error
func MustString(val any) string {
str, err := ToStringWith(val)
if err != nil {
panic(err)
}
return str
}
// StringOrPanic convert intX/floatX value to string, will panic on error
func StringOrPanic(val any) string { return MustString(val) }
// StringOrDefault convert intX/floatX value to string, will return default value on error
func StringOrDefault(val any, defVal string) string { return StringOr(val, defVal) }
// StringOr convert intX/floatX value to string, will return default value on error
func StringOr(val any, defVal string) string {
str, err := ToStringWith(val)
if err != nil {
return defVal
}
return str
}
// ToString convert intX/floatX value to string, return error on failed
func ToString(val any) (string, error) { return ToStringWith(val) }
// StringOrErr convert intX/floatX value to string, return error on failed
func StringOrErr(val any) (string, error) { return ToStringWith(val) }
// QuietString convert intX/floatX value to string, other type convert by fmt.Sprint
func QuietString(val any) string { return SafeString(val) }
// String convert intX/floatX value to string, other type convert by fmt.Sprint
func String(val any) string {
str, _ := TryToString(val, false)
return str
}
// SafeString convert intX/floatX value to string, other type convert by fmt.Sprint
func SafeString(val any) string {
str, _ := TryToString(val, false)
return str
}
// TryToString try convert intX/floatX value to string
//
// if defaultAsErr is False, will use fmt.Sprint convert other type
func TryToString(val any, defaultAsErr bool) (string, error) {
var optFn comfunc.ConvOptionFn
if !defaultAsErr {
optFn = comfunc.WithUserConvFn(comfunc.StrBySprintFn)
}
return ToStringWith(val, optFn)
}
// ToStringWith try to convert value to string. can with some option func, more see comfunc.ConvOption.
func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error) {
return comfunc.ToStringWith(in, optFns...)
}
+87
View File
@@ -0,0 +1,87 @@
package mathutil
import "fmt"
// DataSize format bytes number friendly. eg: 1024 => 1KB, 1024*1024 => 1MB
//
// Usage:
//
// file, err := os.Open(path)
// fl, err := file.Stat()
// fmtSize := DataSize(fl.Size())
func DataSize(size uint64) string {
switch {
case size < 1024:
return fmt.Sprintf("%dB", size)
case size < 1024*1024:
return fmt.Sprintf("%.2fK", float64(size)/1024)
case size < 1024*1024*1024:
return fmt.Sprintf("%.2fM", float64(size)/1024/1024)
default:
return fmt.Sprintf("%.2fG", float64(size)/1024/1024/1024)
}
}
// 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},
{2, 1},
{60},
{120, 60},
{3600},
{7200, 3600},
{86400},
{172800, 86400}, // second elem is unit.
{2592000},
{2592000 * 2, 2592000},
}
var timeMessages = []string{
"< 1 sec", "1 sec", "secs", "1 min", "mins", "1 hr", "hrs", "1 day", "days", "1 month", "months",
}
// HowLongAgo format a seconds, get how lang ago. eg: 1 day, 1 week
func HowLongAgo(sec int64) string {
intVal := int(sec)
length := len(timeFormats)
for i, item := range timeFormats {
if intVal >= item[0] {
ni := i + 1
match := false
if ni < length { // next exists
next := timeFormats[ni]
if intVal < next[0] { // current <= intVal < next
match = true
}
} else if ni == length { // current is last
match = true
}
if match { // match success
if len(item) == 1 {
return timeMessages[i]
}
return fmt.Sprintf("%d %s", intVal/item[1], timeMessages[i])
}
}
}
return "unknown" // He should never happen
}
+140
View File
@@ -0,0 +1,140 @@
// Package mathutil provide math(int, number) util functions. eg: convert, math calc, random
package mathutil
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/checkfn"
)
// 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))
}
// MulF2i computes the float64 type a * b value, rounding the result to an integer.
func MulF2i(a, b float64) int {
return int(math.Round(a * b))
}
// Div computes the `a/b` value, result uses a round handle.
func Div[T1, T2 comdef.Number](a T1, b T2) float64 {
return math.Round(SafeFloat(a) / SafeFloat(b))
}
// DivInt computes the int type a / b value, rounding the result to an integer.
func DivInt[T comdef.Integer](a, b T) int {
fv := math.Round(float64(a) / float64(b))
return int(fv)
}
// DivF2i computes the float64 type a / b value, rounding the result to an integer.
func DivF2i(a, b float64) int {
return int(math.Round(a / b))
}
// 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
}
+37
View File
@@ -0,0 +1,37 @@
package mathutil
import (
"math/rand"
"time"
)
// RandomInt return a random int at the [min, max)
//
// Usage:
//
// RandomInt(10, 99)
// RandomInt(100, 999)
// RandomInt(1000, 9999)
func RandomInt(min, max int) int {
rr := rand.New(rand.NewSource(time.Now().UnixNano()))
return min + rr.Intn(max-min)
}
// RandInt alias of RandomInt()
func RandInt(min, max int) int { return RandomInt(min, max) }
// RandIntWithSeed alias of RandomIntWithSeed()
func RandIntWithSeed(min, max int, seed int64) int {
return RandomIntWithSeed(min, max, seed)
}
// RandomIntWithSeed return a random int at the [min, max)
//
// Usage:
//
// seed := time.Now().UnixNano()
// RandomIntWithSeed(1000, 9999, seed)
func RandomIntWithSeed(min, max int, seed int64) int {
rr := rand.New(rand.NewSource(seed))
return min + rr.Intn(max-min)
}
+19
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)
+82
View File
@@ -0,0 +1,82 @@
# Reflects
`reflects` Provide extends reflect util functions.
- some features
## Install
```bash
go get github.com/gookit/goutil/reflects
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/reflects)
## Usage
```go
import "github.com/gookit/goutil/reflects"
// get struct field value
reflects.GetFieldValue(obj, "Name")
```
## Functions API
> **Note**: doc by run `go doc ./reflects`
```go
func BaseTypeVal(v reflect.Value) (value any, err error)
func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error)
func EachMap(mp reflect.Value, fn func(key, val reflect.Value))
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any))
func Elem(v reflect.Value) reflect.Value
func FlatMap(rv reflect.Value, fn FlatFunc)
func HasChild(v reflect.Value) bool
func Indirect(v reflect.Value) reflect.Value
func IsAnyInt(k reflect.Kind) bool
func IsArrayOrSlice(k reflect.Kind) bool
func IsEmpty(v reflect.Value) bool
func IsEmptyValue(v reflect.Value) bool
func IsEqual(src, dst any) bool
func IsFunc(val any) bool
func IsIntx(k reflect.Kind) bool
func IsNil(v reflect.Value) bool
func IsSimpleKind(k reflect.Kind) bool
func IsUintX(k reflect.Kind) bool
func Len(v reflect.Value) int
func SetRValue(rv, val reflect.Value)
func SetUnexportedValue(rv reflect.Value, value any)
func SetValue(rv reflect.Value, val any) error
func SliceElemKind(typ reflect.Type) reflect.Kind
func SliceSubKind(typ reflect.Type) reflect.Kind
func String(rv reflect.Value) string
func ToString(rv reflect.Value) (str string, err error)
func UnexportedValue(rv reflect.Value) any
func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error)
func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error)
func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error)
type BKind uint
func ToBKind(kind reflect.Kind) BKind
func ToBaseKind(kind reflect.Kind) BKind
type FlatFunc func(path string, val reflect.Value)
type Type interface{ ... }
func TypeOf(v any) Type
type Value struct{ ... }
func ValueOf(v any) Value
func Wrap(rv reflect.Value) Value
```
## Testings
```shell
go test -v ./reflects/...
```
Test limit by regexp:
```shell
go test -v -run ^TestSetByKeys ./reflects/...
```
+189
View File
@@ -0,0 +1,189 @@
package reflects
import (
"bytes"
"reflect"
)
// IsTimeType check is or alias of time.Time type
func IsTimeType(t reflect.Type) bool {
if t == nil || t.Kind() != reflect.Struct {
return false
}
// t == timeType - 无法判断自定义类型
return t == timeType || t.ConvertibleTo(timeType)
}
// IsDurationType check is or alias of time.Duration type
func IsDurationType(t reflect.Type) bool {
if t == nil || t.Kind() != reflect.Int64 {
return false
}
// t == durationType - 无法判断自定义类型
return t == durationType || t.ConvertibleTo(durationType)
}
// HasChild type check. eg: array, slice, map, struct
func HasChild(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct:
return true
default:
return false
}
}
// IsArrayOrSlice type check. eg: array, slice
func IsArrayOrSlice(k reflect.Kind) bool {
return k == reflect.Slice || k == reflect.Array
}
// IsSimpleKind kind in: string, bool, intX, uintX, floatX
func IsSimpleKind(k reflect.Kind) bool {
if reflect.String == k {
return true
}
return k > reflect.Invalid && k <= reflect.Float64
}
// IsAnyInt check is intX or uintX type. alias of the IsIntLike()
func IsAnyInt(k reflect.Kind) bool {
return k >= reflect.Int && k <= reflect.Uintptr
}
// IsIntLike reports whether the type is int-like(intX, uintX).
func IsIntLike(k reflect.Kind) bool {
return k >= reflect.Int && k <= reflect.Uintptr
}
// IsIntx check is intX type
func IsIntx(k reflect.Kind) bool {
return k >= reflect.Int && k <= reflect.Int64
}
// IsUintX check is uintX type
func IsUintX(k reflect.Kind) bool {
return k >= reflect.Uint && k <= reflect.Uintptr
}
// IsNil reflect value
func IsNil(v reflect.Value) bool {
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return v.IsNil()
default:
return false
}
}
// IsValidPtr check variable is a valid pointer.
func IsValidPtr(v reflect.Value) bool {
return v.IsValid() && (v.Kind() == reflect.Ptr) && !v.IsNil()
}
// CanBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
func CanBeNil(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return true
case reflect.Struct:
return typ == reflectValueType
default:
return false
}
}
// IsFunc value
func IsFunc(val any) bool {
if val == nil {
return false
}
return reflect.TypeOf(val).Kind() == reflect.Func
}
// IsEqual determines if two objects are considered equal.
//
// TIP: cannot compare a function type
func IsEqual(src, dst any) bool {
if src == nil || dst == nil {
return src == dst
}
bs1, ok := src.([]byte)
if !ok {
return reflect.DeepEqual(src, dst)
}
bs2, ok := dst.([]byte)
if !ok {
return false
}
if bs1 == nil || bs2 == nil {
return bs1 == nil && bs2 == nil
}
return bytes.Equal(bs1, bs2)
}
// IsZero reflect value check, alias of the IsEmpty()
var IsZero = IsEmpty
// IsEmpty reflect value check. if is ptr, check if is nil
func IsEmpty(v reflect.Value) bool {
switch v.Kind() {
case reflect.Invalid:
return true
case reflect.String, reflect.Array:
return v.Len() == 0
case reflect.Map, reflect.Slice:
return v.Len() == 0 || v.IsNil()
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr, reflect.Func:
return v.IsNil()
default:
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}
}
// IsEmptyValue reflect value check, alias of the IsEmptyReal()
var IsEmptyValue = IsEmptyReal
// IsEmptyReal reflect value check.
//
// Note:
//
// Difference the IsEmpty(), if value is ptr or interface, will check real elem.
//
// From src/pkg/encoding/json/encode.go.
func IsEmptyReal(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if v.IsNil() {
return true
}
return IsEmptyReal(v.Elem())
case reflect.Func:
return v.IsNil()
case reflect.Invalid:
return true
default:
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}
}

Some files were not shown because too many files have changed in this diff Show More