Initial QSfera import
This commit is contained in:
+26
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+1874
File diff suppressed because it is too large
Load Diff
+85
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package comdef
|
||||
|
||||
// Newline string for non-windows
|
||||
const Newline = "\n"
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package comdef
|
||||
|
||||
// Newline string for windows
|
||||
const Newline = "\r\n"
|
||||
+38
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
# Common func for internal use
|
||||
|
||||
- don't depend on other external packages
|
||||
+93
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user