Bump github.com/gookit/config/v2 from 2.2.3 to 2.2.4

Bumps [github.com/gookit/config/v2](https://github.com/gookit/config) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/gookit/config/releases)
- [Commits](https://github.com/gookit/config/compare/v2.2.3...v2.2.4)

---
updated-dependencies:
- dependency-name: github.com/gookit/config/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-11-03 08:57:34 +00:00
committed by Ralf Haferkamp
parent 30784affc4
commit 0df009eae0
141 changed files with 7314 additions and 4044 deletions

8
go.mod
View File

@@ -44,7 +44,7 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/go-tika v0.3.0
github.com/google/uuid v1.4.0
github.com/gookit/config/v2 v2.2.3
github.com/gookit/config/v2 v2.2.4
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0
github.com/jellydator/ttlcache/v2 v2.11.1
@@ -204,7 +204,7 @@ require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.0.4 // indirect
github.com/goccy/go-yaml v1.11.0 // indirect
github.com/goccy/go-yaml v1.11.2 // indirect
github.com/godbus/dbus/v5 v5.0.6 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -217,8 +217,8 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/gookit/goutil v0.6.10 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gookit/goutil v0.6.14 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect

16
go.sum
View File

@@ -1244,8 +1244,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54=
github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng=
github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ=
github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -1404,12 +1404,12 @@ github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5i
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/gookit/config/v2 v2.2.3 h1:GlnYPduYeY7lRgWQmGld9juy0xpFUo06BUC9Pzyjuew=
github.com/gookit/config/v2 v2.2.3/go.mod h1:FhmMu+2wg0UhyOjVGo+DZ1+ov34q4G4aWXzh86boEsY=
github.com/gookit/goutil v0.6.10 h1:iq7CXOf+fYLvrVAh3+ZoLgufGfK65TwbzE8NpnPGtyk=
github.com/gookit/goutil v0.6.10/go.mod h1:qqrPoX+Pm6YmxqqccgkNLPirTFX7UYMES1SK+fokqQU=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/config/v2 v2.2.4 h1:uLHNzFzREe5gDBP4Gb1+WOC9LB6vauPvq4eolp32Dcg=
github.com/gookit/config/v2 v2.2.4/go.mod h1:k1ofSAuJnW6n1kTriFMSzFDC8ZT20tAPQ+1iGI3QOrU=
github.com/gookit/goutil v0.6.14 h1:96elyOG4BvVoDaiT7vx1vHPrVyEtFfYlPPBODR0/FGQ=
github.com/gookit/goutil v0.6.14/go.mod h1:YyDBddefmjS+mU2PDPgCcjVzTDM5WgExiDv5ZA/b8I8=
github.com/gookit/ini/v2 v2.2.2 h1:3B8abZJrVH1vi/7TU4STuTBxdhiAq1ORSt6NJZCahaI=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=

View File

@@ -1,3 +1,26 @@
# 1.11.2 - 2023-09-15
### Fix bugs
- Fix quoted comments ( #370 )
- Fix handle of space at start or last ( #376 )
- Fix sequence with comment ( #390 )
# 1.11.1 - 2023-09-14
### Fix bugs
- Handle `\r` in a double-quoted string the same as `\n` ( #372 )
- Replace loop with n.Values = append(n.Values, target.Values...) ( #380 )
- Skip encoding an inline field if it is null ( #386 )
- Fix comment parsing with null value ( #388 )
# 1.11.0 - 2023-04-03
### Features
- Supports dynamically switch encode and decode processing for a given type
# 1.10.1 - 2023-03-28
### Features

View File

@@ -1506,9 +1506,7 @@ func (n *SequenceNode) Replace(idx int, value Node) error {
func (n *SequenceNode) Merge(target *SequenceNode) {
column := n.Start.Position.Column - target.Start.Position.Column
target.AddColumn(column)
for _, value := range target.Values {
n.Values = append(n.Values, value)
}
n.Values = append(n.Values, target.Values...)
}
// SetIsFlowStyle set value to IsFlowStyle field recursively.

View File

@@ -823,6 +823,10 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column
}
mapNode, ok := value.(ast.MapNode)
if !ok {
// if an inline field is null, skip encoding it
if _, ok := value.(*ast.NullNode); ok {
continue
}
return nil, xerrors.Errorf("inline value is must be map or struct type")
}
mapIter := mapNode.MapRange()

View File

@@ -13,7 +13,6 @@ type context struct {
idx int
size int
tokens token.Tokens
mode Mode
path string
}
@@ -56,7 +55,6 @@ func (c *context) copy() *context {
idx: c.idx,
size: c.size,
tokens: append(token.Tokens{}, c.tokens...),
mode: c.mode,
path: c.path,
}
}
@@ -145,10 +143,6 @@ func (c *context) afterNextNotCommentToken() *token.Token {
return nil
}
func (c *context) enabledComment() bool {
return c.mode&ParseComments != 0
}
func (c *context) isCurrentCommentToken() bool {
tk := c.currentToken()
if tk == nil {
@@ -193,7 +187,6 @@ func newContext(tokens token.Tokens, mode Mode) *context {
idx: 0,
size: len(filteredTokens),
tokens: token.Tokens(filteredTokens),
mode: mode,
path: "$",
}
}

View File

@@ -156,15 +156,38 @@ func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken
ctx.insertToken(ctx.idx, nullToken)
return ast.Null(nullToken), nil
}
var comment *ast.CommentGroupNode
if tk.Type == token.CommentType {
comment = p.parseCommentOnly(ctx)
if comment != nil {
comment.SetPath(ctx.withChild(key.GetToken().Value).path)
}
tk = ctx.currentToken()
}
if tk.Position.Column == key.GetToken().Position.Column && tk.Type == token.StringType {
// in this case,
// ----
// key: <value does not defined>
// next
nullToken := p.createNullToken(colonToken)
ctx.insertToken(ctx.idx, nullToken)
return ast.Null(nullToken), nil
nullNode := ast.Null(nullToken)
if comment != nil {
nullNode.SetComment(comment)
} else {
// If there is a comment, it is already bound to the key node,
// so remove the comment from the key to bind it to the null value.
keyComment := key.GetComment()
if keyComment != nil {
if err := key.SetComment(nil); err != nil {
return nil, err
}
nullNode.SetComment(keyComment)
}
}
return nullNode, nil
}
if tk.Position.Column < key.GetToken().Position.Column {
@@ -174,13 +197,20 @@ func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken
// next
nullToken := p.createNullToken(colonToken)
ctx.insertToken(ctx.idx, nullToken)
return ast.Null(nullToken), nil
nullNode := ast.Null(nullToken)
if comment != nil {
nullNode.SetComment(comment)
}
return nullNode, nil
}
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, errors.Wrapf(err, "failed to parse mapping 'value' node")
}
if comment != nil {
value.SetComment(comment)
}
return value, nil
}
@@ -304,10 +334,9 @@ func (p *parser) parseSequenceEntry(ctx *context) (*ast.SequenceNode, error) {
if tk.Type == token.CommentType {
comment = p.parseCommentOnly(ctx)
tk = ctx.currentToken()
if tk.Type != token.SequenceEntryType {
break
if tk.Type == token.SequenceEntryType {
ctx.progress(1) // skip sequence token
}
ctx.progress(1) // skip sequence token
}
value, err := p.parseToken(ctx.withIndex(uint(len(sequenceNode.Values))), ctx.currentToken())
if err != nil {

View File

@@ -500,11 +500,29 @@ func newSelectorNode(selector string) *selectorNode {
}
func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
selector := n.selector
if len(selector) > 1 && selector[0] == '\'' && selector[len(selector)-1] == '\'' {
selector = selector[1 : len(selector)-1]
}
switch node.Type() {
case ast.MappingType:
for _, value := range node.(*ast.MappingNode).Values {
key := value.Key.GetToken().Value
if key == n.selector {
if len(key) > 0 {
switch key[0] {
case '"':
var err error
key, err = strconv.Unquote(key)
if err != nil {
return nil, errors.Wrapf(err, "failed to unquote")
}
case '\'':
if len(key) > 1 && key[len(key)-1] == '\'' {
key = key[1 : len(key)-1]
}
}
}
if key == selector {
if n.child == nil {
return value.Value, nil
}
@@ -518,7 +536,7 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
case ast.MappingValueType:
value := node.(*ast.MappingValueNode)
key := value.Key.GetToken().Value
if key == n.selector {
if key == selector {
if n.child == nil {
return value.Value, nil
}
@@ -571,7 +589,9 @@ func (n *selectorNode) replace(node ast.Node, target ast.Node) error {
}
func (n *selectorNode) String() string {
s := fmt.Sprintf(".%s", n.selector)
var builder PathBuilder
selector := builder.normalizeSelectorName(n.selector)
s := fmt.Sprintf(".%s", selector)
if n.child != nil {
s += n.child.String()
}

View File

@@ -339,6 +339,11 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) {
value = append(value, '\n')
idx++
continue
case 'r':
ctx.addOriginBuf(nextChar)
value = append(value, '\r')
idx++
continue
case 'v':
ctx.addOriginBuf(nextChar)
value = append(value, '\v')

View File

@@ -623,12 +623,12 @@ func IsNeedQuoted(value string) bool {
}
first := value[0]
switch first {
case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"', '@':
case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"', '@', ' ':
return true
}
last := value[len(value)-1]
switch last {
case ':':
case ':', ' ':
return true
}
if looksLikeTimeValue(value) {

View File

@@ -89,43 +89,42 @@ func (s MapSlice) ToMap() map[interface{}]interface{} {
//
// The field tag format accepted is:
//
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
//
// The following flags are currently supported:
//
// omitempty Only include the field if it's not set to the zero
// value for the type or to empty slices or maps.
// Zero valued structs will be omitted if all their public
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be included if that method returns true.
// omitempty Only include the field if it's not set to the zero
// value for the type or to empty slices or maps.
// Zero valued structs will be omitted if all their public
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be included if that method returns true.
//
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
//
// inline Inline the field, which must be a struct or a map,
// causing all of its fields or keys to be processed as if
// they were part of the outer struct. For maps, keys must
// not conflict with the yaml keys of other struct fields.
// inline Inline the field, which must be a struct or a map,
// causing all of its fields or keys to be processed as if
// they were part of the outer struct. For maps, keys must
// not conflict with the yaml keys of other struct fields.
//
// anchor Marshal with anchor. If want to define anchor name explicitly, use anchor=name style.
// Otherwise, if used 'anchor' name only, used the field name lowercased as the anchor name
// anchor Marshal with anchor. If want to define anchor name explicitly, use anchor=name style.
// Otherwise, if used 'anchor' name only, used the field name lowercased as the anchor name
//
// alias Marshal with alias. If want to define alias name explicitly, use alias=name style.
// Otherwise, If omitted alias name and the field type is pointer type,
// assigned anchor name automatically from same pointer address.
// alias Marshal with alias. If want to define alias name explicitly, use alias=name style.
// Otherwise, If omitted alias name and the field type is pointer type,
// assigned anchor name automatically from same pointer address.
//
// In addition, if the key is "-", the field is ignored.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}) // Returns "a: 1\nb: 0\n"
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}) // Returns "a: 1\nb: 0\n"
func Marshal(v interface{}) ([]byte, error) {
return MarshalWithOptions(v)
}
@@ -167,16 +166,15 @@ func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
//
// See the documentation of Marshal for the format of tags and a list of
// supported tag options.
//
func Unmarshal(data []byte, v interface{}) error {
return UnmarshalWithOptions(data, v)
}

View File

@@ -41,15 +41,27 @@ func (o Opts) String() string {
* Basic 16 color definition
*************************************************************/
// Base value for foreground/background color
// base: fg 30~37, bg 40~47
// light: fg 90~97, bg 100~107
const (
// OptMax max option value. range: 0 - 9
OptMax = 10
// DiffFgBg diff foreground and background color
DiffFgBg = 10
)
// Boundary value for foreground/background color 16
//
// - base: fg 30~37, bg 40~47
// - light: fg 90~97, bg 100~107
const (
FgBase uint8 = 30
FgMax uint8 = 37
BgBase uint8 = 40
BgMax uint8 = 47
HiFgBase uint8 = 90
HiFgMax uint8 = 97
HiBgBase uint8 = 100
HiBgMax uint8 = 107
)
// Foreground colors. basic foreground colors 30 - 37
@@ -94,7 +106,7 @@ const (
BgDefault Color = 49
)
// Extra background color 100 - 107(非标准)
// Extra background color 100 - 107 (non-standard)
const (
BgDarkGray Color = iota + 100
BgLightRed
@@ -108,7 +120,7 @@ const (
BgGray Color = 100
)
// Option settings
// Option settings. range: 0 - 9
const (
OpReset Color = iota // 0 重置所有设置
OpBold // 1 加粗
@@ -248,9 +260,9 @@ func (c Color) Println(a ...any) { doPrintlnV2(c.String(), a) }
// lightCyan := Cyan.Light()
// lightCyan.Print("message")
func (c Color) Light() Color {
val := int(c)
val := uint8(c)
if val >= 30 && val <= 47 {
return Color(uint8(c) + 60)
return Color(val + 60)
}
// don't change
@@ -264,9 +276,9 @@ func (c Color) Light() Color {
// cyan := LightCyan.Darken()
// cyan.Print("message")
func (c Color) Darken() Color {
val := int(c)
val := uint8(c)
if val >= 90 && val <= 107 {
return Color(uint8(c) - 60)
return Color(val - 60)
}
// don't change
@@ -324,7 +336,7 @@ func (c Color) RGB() RGBColor {
return emptyRGBColor
}
return HEX(Basic2hex(val))
return HEX(Basic2hex(val), c.IsBg())
}
// Code convert to code string. eg "35"
@@ -337,8 +349,23 @@ func (c Color) String() string {
return strconv.FormatInt(int64(c), 10)
}
// IsBg check is background color
func (c Color) IsBg() bool {
val := uint8(c)
return val >= BgBase && val <= BgMax || val >= HiBgBase && val <= HiBgMax
}
// IsFg check is foreground color
func (c Color) IsFg() bool {
val := uint8(c)
return val >= FgBase && val <= FgMax || val >= HiFgBase && val <= HiFgMax
}
// IsOption check is option code: 0-9
func (c Color) IsOption() bool { return uint8(c) < OptMax }
// IsValid color value
func (c Color) IsValid() bool { return c < 107 }
func (c Color) IsValid() bool { return uint8(c) < HiBgMax }
/*************************************************************
* basic color maps

View File

@@ -43,7 +43,8 @@ const (
* 8bit(256) Color: Bit8Color Color256
*************************************************************/
// Color256 256 color (8 bit), uint8 range at 0 - 255
// Color256 256 color (8 bit), uint8 range at 0 - 255.
// Support 256 color on windows CMD, PowerShell
//
// 颜色值使用10进制和16进制都可 0x98 = 152
//
@@ -54,10 +55,9 @@ const (
//
// example:
//
// fg color: [152, 0]
// bg color: [152, 1]
// fg color: [152, 0]
// bg color: [152, 1]
//
// NOTICE: now support 256 color on windows CMD, PowerShell
// lint warn - Name starts with package name
type Color256 [2]uint8
type Bit8Color = Color256 // alias
@@ -164,9 +164,7 @@ func (c Color256) String() string {
}
// IsFg color
func (c Color256) IsFg() bool {
return c[1] == AsFg
}
func (c Color256) IsFg() bool { return c[1] == AsFg }
// ToFg 256 color
func (c Color256) ToFg() Color256 {
@@ -175,9 +173,7 @@ func (c Color256) ToFg() Color256 {
}
// IsBg color
func (c Color256) IsBg() bool {
return c[1] == AsBg
}
func (c Color256) IsBg() bool { return c[1] == AsBg }
// ToBg 256 color
func (c Color256) ToBg() Color256 {
@@ -186,9 +182,7 @@ func (c Color256) ToBg() Color256 {
}
// IsEmpty value
func (c Color256) IsEmpty() bool {
return c[1] > 1
}
func (c Color256) IsEmpty() bool { return c[1] > 1 }
/*************************************************************
* 8bit(256) Style

View File

@@ -44,6 +44,7 @@ const (
*************************************************************/
// RGBColor definition.
// Support RGB color on Windows CMD, PowerShell
//
// The first to third digits represent the color value.
// The last digit represents the foreground(0), background(1), >1 is unset value
@@ -54,8 +55,6 @@ const (
// // 3rd: Fg=0, Bg=1, >1: unset value
// RGBColor{30,144,255, 0}
// RGBColor{30,144,255, 1}
//
// NOTICE: now support RGB color on Windows CMD, PowerShell
type RGBColor [4]uint8
// create an empty RGBColor
@@ -251,6 +250,18 @@ func (c RGBColor) String() string {
return ""
}
// ToBg convert to background color
func (c RGBColor) ToBg() RGBColor {
c[3] = AsBg
return c
}
// ToFg convert to foreground color
func (c RGBColor) ToFg() RGBColor {
c[3] = AsFg
return c
}
// IsEmpty value
func (c RGBColor) IsEmpty() bool {
return c[3] > AsBg

View File

@@ -52,6 +52,7 @@ var (
// ---------- basic(16) <=> RGB color convert ----------
// refer from Hyper app
// Tip: only keep foreground color, background color need convert to foreground color for convert to RGB
basic2hexMap = map[uint8]string{
30: "000000", // black
31: "c51e14", // red
@@ -61,7 +62,7 @@ var (
35: "c839c5", // magenta
36: "20c5c6", // cyan
37: "c7c7c7", // white
// - don't add bg color
// - don't add bg color, convert to fg color for convert to RGB
// 40: "000000", // black
// 41: "c51e14", // red
// 42: "1dc121", // green
@@ -428,10 +429,11 @@ func HexToRGB(hex string) []int { return HexToRgb(hex) }
// HexToRgb convert hex color string to RGB numbers
//
// Usage:
// rgb := HexToRgb("ccc") // rgb: [204 204 204]
// rgb := HexToRgb("aabbcc") // rgb: [170 187 204]
// rgb := HexToRgb("#aabbcc") // rgb: [170 187 204]
// rgb := HexToRgb("0xad99c0") // rgb: [170 187 204]
//
// rgb := HexToRgb("ccc") // rgb: [204 204 204]
// rgb := HexToRgb("aabbcc") // rgb: [170 187 204]
// rgb := HexToRgb("#aabbcc") // rgb: [170 187 204]
// rgb := HexToRgb("0xad99c0") // rgb: [170 187 204]
func HexToRgb(hex string) (rgb []int) {
hex = strings.TrimSpace(hex)
if hex == "" {
@@ -474,6 +476,7 @@ func Rgb2hex(rgb []int) string { return RgbToHex(rgb) }
// RgbToHex convert RGB-code to hex-code
//
// Usage:
//
// hex := RgbToHex([]int{170, 187, 204}) // hex: "aabbcc"
func RgbToHex(rgb []int) string {
hexNodes := make([]string, len(rgb))
@@ -488,10 +491,15 @@ func RgbToHex(rgb []int) string {
* 4bit(16) color <=> RGB/True color
*************************************************************/
// BasicToHex convert basic color to hex string.
func BasicToHex(val uint8) string {
val = Bg2Fg(val)
return basic2hexMap[val]
}
// Basic2hex convert basic color to hex string.
func Basic2hex(val uint8) string {
val = Fg2Bg(val)
return basic2hexMap[val]
return BasicToHex(val)
}
// Hex2basic convert hex string to basic color code.
@@ -663,6 +671,7 @@ func C256ToRgbV1(val uint8) (rgb []uint8) {
// returns r, g, and b in the set [0, 255].
//
// Usage:
//
// HslIntToRgb(0, 100, 50) // red
// HslIntToRgb(120, 100, 50) // lime
// HslIntToRgb(120, 100, 25) // dark green
@@ -677,6 +686,7 @@ func HslIntToRgb(h, s, l int) (rgb []uint8) {
// returns r, g, and b in the set [0, 255].
//
// Usage:
//
// rgbVals := HslToRgb(0, 1, 0.5) // red
func HslToRgb(h, s, l float64) (rgb []uint8) {
var r, g, b float64

View File

@@ -37,7 +37,8 @@ func (s *Style) Add(cs ...Color) {
*s = append(*s, cs...)
}
// Render render text
// Render colored text
//
// Usage:
//
// color.New(color.FgGreen).Render("text")
@@ -46,8 +47,9 @@ func (s Style) Render(a ...any) string {
return RenderCode(s.String(), a...)
}
// Renderln render text line.
// Renderln render text with newline.
// like Println, will add spaces for each argument
//
// Usage:
//
// color.New(color.FgGreen).Renderln("text", "more")

View File

@@ -76,10 +76,21 @@ func (c *Config) MapOnExists(key string, dst any) error {
//
// dbInfo := Db{}
// config.Structure("db", &dbInfo)
func (c *Config) Structure(key string, dst any) error {
func (c *Config) Structure(key string, dst any) (err error) {
var data any
// binding all data
// binding all data on key is empty.
if key == "" {
// fix: if c.data is nil, don't need to apply map structure
if len(c.data) == 0 {
// init default value by tag: default
if c.opts.ParseDefault {
err = structs.InitDefaults(dst, func(opt *structs.InitOptions) {
opt.ParseEnv = c.opts.ParseEnv
})
}
return
}
data = c.data
} else {
// binding sub-data of the config
@@ -90,6 +101,7 @@ func (c *Config) Structure(key string, dst any) error {
}
}
// map structure from data
bindConf := c.opts.makeDecoderConfig()
// set result struct ptr
bindConf.Result = dst

View File

@@ -99,6 +99,21 @@ func (c *Config) Data() map[string]any {
return c.data
}
// Sub return sub config data by key
func Sub(key string) map[string]any { return dc.Sub(key) }
// Sub get sub config data by key
//
// Note: will don't apply any options, like ParseEnv
func (c *Config) Sub(key string) map[string]any {
if mp, ok := c.GetValue(key); ok {
if mmp, ok := mp.(map[string]any); ok {
return mmp
}
}
return nil
}
// Keys return all config data
func Keys() []string { return dc.Keys() }

View File

@@ -18,16 +18,21 @@ func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc {
return data, nil
}
var err error
str := data.(string)
if parseEnv {
str = envutil.ParseEnvValue(str)
// https://docs.docker.com/compose/environment-variables/env-file/
str, err = envutil.ParseOrErr(str)
if err != nil {
return nil, err
}
}
if len(str) < 2 {
return str, nil
}
// start char is number(1-9)
if str[0] > '0' && str[0] < '9' {
if str[0] > '0' && str[0] <= '9' {
// parse time string. eg: 10s
if parseTime && t.Kind() == reflect.Int64 {
dur, err := time.ParseDuration(str)

View File

@@ -20,3 +20,4 @@
.DS_Store
testdata/
vendor/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,95 +2,9 @@
package arrutil
import (
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/mathutil"
)
// Reverse string slice [site user info 0] -> [0 info user site]
func Reverse(ss []string) {
ln := len(ss)
for i := 0; i < ln/2; i++ {
li := ln - i - 1
ss[i], ss[li] = ss[li], ss[i]
}
}
// StringsRemove a value form a string slice
func StringsRemove(ss []string, s string) []string {
ns := make([]string, 0, len(ss))
for _, v := range ss {
if v != s {
ns = append(ns, v)
}
}
return ns
}
// StringsFilter given strings, default will filter emtpy string.
//
// Usage:
//
// // output: [a, b]
// ss := arrutil.StringsFilter([]string{"a", "", "b", ""})
func StringsFilter(ss []string, filter ...func(s string) bool) []string {
var fn func(s string) bool
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
}
// GetRandomOne get random element from an array/slice
func GetRandomOne[T any](arr []T) T { return RandomOne(arr) }
@@ -102,31 +16,3 @@ func RandomOne[T any](arr []T) T {
}
panic("cannot get value from nil or empty slice")
}
// Unique value in the given slice data.
func Unique[T ~string | comdef.XintOrFloat](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 ~string | comdef.XintOrFloat](val T, list []T) int {
for i, v := range list {
if v == val {
return i
}
}
return -1
}

View File

@@ -8,8 +8,18 @@ import (
"github.com/gookit/goutil/mathutil"
)
// IntsHas check the []int contains the given value
func IntsHas(ints []int, val int) bool {
// 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
@@ -28,11 +38,8 @@ func Int64sHas(ints []int64, val int64) bool {
return false
}
// InStrings alias of StringsHas()
func InStrings(elem string, ss []string) bool { return StringsHas(ss, elem) }
// StringsHas check the []string contains the given element
func StringsHas(ss []string, val string) bool {
func StringsHas[T ~string](ss []T, val T) bool {
for _, ele := range ss {
if ele == val {
return true
@@ -41,6 +48,11 @@ func StringsHas(ss []string, val string) bool {
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)

View File

@@ -2,97 +2,72 @@ package arrutil
import (
"errors"
"reflect"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/reflects"
)
// ErrElementNotFound is the error returned when the element is not found.
const ErrElementNotFound = "element not found"
var ErrElementNotFound = errors.New("element not found")
// Comparer Function to compare two elements.
type Comparer func(a, b any) int
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 func(a any) bool
type Predicate[T any] func(v T) bool
var (
// StringEqualsComparer Comparer for string. It will compare the string by their value.
// returns: 0 if equal, -1 if a != b
StringEqualsComparer Comparer = func(a, b any) int {
typeOfA := reflect.TypeOf(a)
if typeOfA.Kind() == reflect.Ptr {
typeOfA = typeOfA.Elem()
}
typeOfB := reflect.TypeOf(b)
if typeOfB.Kind() == reflect.Ptr {
typeOfB = typeOfB.Elem()
}
if typeOfA != typeOfB {
return -1
}
strA := ""
strB := ""
if val, ok := a.(string); ok {
strA = val
} else if val, ok := a.(*string); ok {
strA = *val
} else {
return -1
}
if val, ok := b.(string); ok {
strB = val
} else if val, ok := b.(*string); ok {
strB = *val
} else {
return -1
}
if strA == strB {
return 0
}
return -1
}
// ReferenceEqualsComparer Comparer for strcut ptr. It will compare the struct by their ptr addr.
// returns: 0 if equal, -1 if a != b
ReferenceEqualsComparer Comparer = func(a, b any) int {
if a == b {
return 0
}
return -1
}
// ElemTypeEqualsComparer Comparer for struct/value. It will compare the struct by their element type (reflect.Type.Elem()).
// returns: 0 if same type, -1 if not.
ElemTypeEqualsComparer Comparer = func(a, b any) int {
at := reflect.TypeOf(a)
bt := reflect.TypeOf(b)
if at.Kind() == reflect.Ptr {
at = at.Elem()
}
if bt.Kind() == reflect.Ptr {
bt = bt.Elem()
}
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.
// StringEqualsComparer Comparer for string. It will compare the string by their value.
//
// 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(data any, item any, fn Comparer) (int, error) {
// 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")
}
@@ -100,35 +75,19 @@ func TwowaySearch(data any, item any, fn Comparer) (int, error) {
return -1, errors.New("collections.TwowaySearch: fn is nil")
}
dataType := reflect.TypeOf(data)
if dataType.Kind() != reflect.Slice {
return -1, errors.New("collections.TwowaySearch: data is not a slice")
}
dataVal := reflect.ValueOf(data)
if dataVal.Len() == 0 {
if len(data) == 0 {
return -1, errors.New("collections.TwowaySearch: data is empty")
}
itemType := dataType.Elem()
if itemType.Kind() == reflect.Ptr {
itemType = itemType.Elem()
}
if itemType != dataVal.Index(0).Type() {
return -1, errors.New("collections.TwowaySearch: item type is not the same as data type")
}
forward := 0
backward := dataVal.Len() - 1
backward := len(data) - 1
for forward <= backward {
forwardVal := dataVal.Index(forward).Interface()
if fn(forwardVal, item) == 0 {
if fn(data[forward], item) == 0 {
return forward, nil
}
backwardVal := dataVal.Index(backward).Interface()
if fn(backwardVal, item) == 0 {
if fn(data[backward], item) == 0 {
return backward, nil
}
@@ -136,55 +95,44 @@ func TwowaySearch(data any, item any, fn Comparer) (int, error) {
backward--
}
return -1, errors.New(ErrElementNotFound)
}
// MakeEmptySlice Create a new slice with the elements of the source that satisfy the predicate.
//
// itemType: the type of the elements in the source.
// returns: the new slice.
func MakeEmptySlice(itemType reflect.Type) any {
ret := reflect.MakeSlice(reflect.SliceOf(itemType), 0, 0).Interface()
return ret
return -1, ErrElementNotFound
}
// CloneSlice Clone a slice.
//
// data: the slice to clone.
// returns: the cloned slice.
func CloneSlice(data any) any {
typeOfData := reflect.TypeOf(data)
if typeOfData.Kind() != reflect.Slice {
panic("collections.CloneSlice: data must be a slice")
}
return reflect.AppendSlice(reflect.New(reflect.SliceOf(typeOfData.Elem())).Elem(), reflect.ValueOf(data)).Interface()
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.
func Differences[T any](first, second []T, fn Comparer) []T {
typeOfFirst := reflect.TypeOf(first)
if typeOfFirst.Kind() != reflect.Slice {
panic("collections.Excepts: first must be a slice")
}
typeOfSecond := reflect.TypeOf(second)
if typeOfSecond.Kind() != reflect.Slice {
panic("collections.Excepts: second must be a slice")
}
// - 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).([]T)
return CloneSlice(second)
}
secondLen := len(second)
if secondLen == 0 {
return CloneSlice(first).([]T)
return CloneSlice(first)
}
max := firstLen
@@ -214,139 +162,123 @@ func Differences[T any](first, second []T, fn Comparer) []T {
// 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.
func Excepts(first, second any, fn Comparer) any {
typeOfFirst := reflect.TypeOf(first)
if typeOfFirst.Kind() != reflect.Slice {
panic("collections.Excepts: first must be a slice")
// - 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)
}
valOfFirst := reflect.ValueOf(first)
if valOfFirst.Len() == 0 {
return MakeEmptySlice(typeOfFirst.Elem())
}
typeOfSecond := reflect.TypeOf(second)
if typeOfSecond.Kind() != reflect.Slice {
panic("collections.Excepts: second must be a slice")
}
valOfSecond := reflect.ValueOf(second)
if valOfSecond.Len() == 0 {
if len(second) == 0 {
return CloneSlice(first)
}
result := reflect.New(reflect.SliceOf(typeOfFirst.Elem())).Elem()
for i := 0; i < valOfFirst.Len(); i++ {
s := valOfFirst.Index(i).Interface()
result := make([]T, 0)
for _, s := range first {
if i, _ := TwowaySearch(second, s, fn); i < 0 {
result = reflect.Append(result, reflect.ValueOf(s))
result = append(result, s)
}
}
return result.Interface()
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.
func Intersects(first any, second any, fn Comparer) any {
typeOfFirst := reflect.TypeOf(first)
if typeOfFirst.Kind() != reflect.Slice {
panic("collections.Intersects: first must be a slice")
}
valOfFirst := reflect.ValueOf(first)
if valOfFirst.Len() == 0 {
return MakeEmptySlice(typeOfFirst.Elem())
// - 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)
}
typeOfSecond := reflect.TypeOf(second)
if typeOfSecond.Kind() != reflect.Slice {
panic("collections.Intersects: second must be a slice")
}
valOfSecond := reflect.ValueOf(second)
if valOfSecond.Len() == 0 {
return MakeEmptySlice(typeOfFirst.Elem())
}
result := reflect.New(reflect.SliceOf(typeOfFirst.Elem())).Elem()
for i := 0; i < valOfFirst.Len(); i++ {
s := valOfFirst.Index(i).Interface()
result := make([]T, 0)
for _, s := range first {
if i, _ := TwowaySearch(second, s, fn); i >= 0 {
result = reflect.Append(result, reflect.ValueOf(s))
result = append(result, s)
}
}
return result.Interface()
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.
func Union(first, second any, fn Comparer) any {
excepts := Excepts(second, first, fn)
typeOfFirst := reflect.TypeOf(first)
if typeOfFirst.Kind() != reflect.Slice {
panic("collections.Intersects: first must be a slice")
}
valOfFirst := reflect.ValueOf(first)
if valOfFirst.Len() == 0 {
// - 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)
}
result := reflect.AppendSlice(reflect.New(reflect.SliceOf(typeOfFirst.Elem())).Elem(), valOfFirst)
result = reflect.AppendSlice(result, reflect.ValueOf(excepts))
return result.Interface()
excepts := Excepts(second, first, fn)
nt := make([]T, 0, len(first)+len(second))
nt = append(nt, first...)
return append(nt, excepts...)
}
// Find Produces the struct/value of a slice according to a predicate function.
// 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.
func Find(source any, fn Predicate) (any, error) {
aType := reflect.TypeOf(source)
if aType.Kind() != reflect.Slice {
panic("collections.Find: source must be a slice")
// - 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
}
sourceVal := reflect.ValueOf(source)
if sourceVal.Len() == 0 {
return nil, errors.New(ErrElementNotFound)
}
for i := 0; i < sourceVal.Len(); i++ {
s := sourceVal.Index(i).Interface()
for _, s := range source {
if fn(s) {
return s, nil
}
}
return nil, errors.New(ErrElementNotFound)
return
}
// FindOrDefault Produce the struct/value f a slice to a predicate function,
// 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 struct/value of the slice.
func FindOrDefault(source any, fn Predicate, defaultValue any) any {
// - 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 {
if err.Error() == ErrElementNotFound {
return defaultValue
}
return defaultValue
}
return item
}
@@ -354,53 +286,53 @@ func FindOrDefault(source any, fn Predicate, defaultValue any) any {
// 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.
func TakeWhile(data any, fn Predicate) any {
aType := reflect.TypeOf(data)
if aType.Kind() != reflect.Slice {
panic("collections.TakeWhile: data must be a slice")
// - 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
}
sourceVal := reflect.ValueOf(data)
if sourceVal.Len() == 0 {
return MakeEmptySlice(aType.Elem())
}
result := reflect.New(reflect.SliceOf(aType.Elem())).Elem()
for i := 0; i < sourceVal.Len(); i++ {
s := sourceVal.Index(i).Interface()
if fn(s) {
result = reflect.Append(result, reflect.ValueOf(s))
for _, v := range data {
if fn(v) {
result = append(result, v)
}
}
return result.Interface()
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.
func ExceptWhile(data any, fn Predicate) any {
aType := reflect.TypeOf(data)
if aType.Kind() != reflect.Slice {
panic("collections.ExceptWhile: data must be a slice")
// - 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
}
sourceVal := reflect.ValueOf(data)
if sourceVal.Len() == 0 {
return MakeEmptySlice(aType.Elem())
}
result := reflect.New(reflect.SliceOf(aType.Elem())).Elem()
for i := 0; i < sourceVal.Len(); i++ {
s := sourceVal.Index(i).Interface()
if !fn(s) {
result = reflect.Append(result, reflect.ValueOf(s))
for _, v := range data {
if !fn(v) {
result = append(result, v)
}
}
return result.Interface()
return result
}

View File

@@ -1,22 +0,0 @@
package arrutil
// type MapFn func(obj T) (target V, find bool)
// Map a list to new list
//
// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
flatArr := make([]V, 0, len(list))
for _, obj := range list {
if target, ok := mapFn(obj); ok {
flatArr = append(flatArr, target)
}
}
return flatArr
}
// Column alias of Map func
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
return Map(list, mapFn)
}

View File

@@ -29,6 +29,29 @@ 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 {
@@ -47,9 +70,27 @@ func JoinSlice(sep string, arr ...any) string {
}
/*************************************************************
* helper func for slices
* 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)
@@ -84,29 +125,9 @@ func SliceToInt64s(arr []any) []int64 {
return i64s
}
// 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
}
/*************************************************************
* convert func for anys
*************************************************************/
// AnyToSlice convert any(allow: array,slice) to []any
func AnyToSlice(sl any) (ls []any, err error) {
@@ -136,15 +157,6 @@ func MustToStrings(arr any) []string {
return ret
}
// StringsToSlice convert []string to []any
func StringsToSlice(ss []string) []any {
args := make([]any, len(ss))
for i, s := range ss {
args[i] = s
}
return args
}
// ToStrings convert any(allow: array,slice) to []string
func ToStrings(arr any) (ret []string, err error) {
rv := reflect.ValueOf(arr)
@@ -168,16 +180,16 @@ func ToStrings(arr any) (ret []string, err error) {
return
}
// SliceToStrings convert []any to []string
// SliceToStrings safe convert []any to []string
func SliceToStrings(arr []any) []string {
return QuietStrings(arr)
}
// QuietStrings convert []any to []string
// QuietStrings safe convert []any to []string
func QuietStrings(arr []any) []string {
ss := make([]string, len(arr))
for i, v := range arr {
ss[i] = strutil.QuietString(v)
ss[i] = strutil.SafeString(v)
}
return ss
}
@@ -219,8 +231,8 @@ func AnyToString(arr any) string {
// SliceToString convert []any to string
func SliceToString(arr ...any) string { return ToString(arr) }
// ToString simple and quickly convert []any to string
func ToString(arr []any) string {
// ToString simple and quickly convert []T to string
func ToString[T any](arr []T) string {
// like fmt.Println([]any(nil))
if arr == nil {
return "[]"
@@ -233,14 +245,14 @@ func ToString(arr []any) string {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(strutil.QuietString(v))
sb.WriteString(strutil.SafeString(v))
}
sb.WriteByte(']')
return sb.String()
}
// CombineToMap combine two slice to map[K]V.
// 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 {

View File

@@ -1,64 +0,0 @@
package arrutil
import (
"strconv"
"strings"
)
// Ints type
type Ints []int
// String to string
func (is Ints) String() string {
ss := make([]string, len(is))
for i, iv := range is {
ss[i] = strconv.Itoa(iv)
}
return strings.Join(ss, ",")
}
// Has given element
func (is Ints) Has(i int) bool {
for _, iv := range is {
if i == iv {
return true
}
}
return false
}
// 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() string {
if len(ss) > 0 {
return ss[0]
}
return ""
}

View File

@@ -15,7 +15,7 @@ type ArrFormatter struct {
Prefix string
// Indent string for format each element
Indent string
// ClosePrefix string for last "]"
// ClosePrefix on before end char: ]
ClosePrefix string
}
@@ -23,10 +23,14 @@ type ArrFormatter struct {
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)
@@ -47,7 +51,6 @@ func (f *ArrFormatter) FormatTo(w io.Writer) {
// Format to string
func (f *ArrFormatter) String() string {
f.Format()
return f.Format()
}
@@ -118,8 +121,3 @@ func (f *ArrFormatter) doFormat() {
}
writer.WriteByte(']')
}
// FormatIndent array data to string.
func FormatIndent(arr any, indent string) string {
return NewFormatter(arr).WithIndent(indent).Format()
}

216
vendor/github.com/gookit/goutil/arrutil/list.go generated vendored Normal file
View File

@@ -0,0 +1,216 @@
package arrutil
import (
"sort"
"strings"
"github.com/gookit/goutil/comdef"
)
// Ints type
type Ints[T comdef.Integer] []T
// String to string
func (is Ints[T]) String() string {
return ToString(is)
}
// Has given element
func (is Ints[T]) Has(i T) bool {
for _, iv := range is {
if i == iv {
return true
}
}
return false
}
// First element value.
func (is Ints[T]) First(defVal ...T) T {
if len(is) > 0 {
return is[0]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty integer slice")
}
// Last element value.
func (is Ints[T]) Last(defVal ...T) T {
if len(is) > 0 {
return is[len(is)-1]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty integer slice")
}
// Sort the int slice
func (is Ints[T]) Sort() {
sort.Sort(is)
}
// Len get length
func (is Ints[T]) Len() int {
return len(is)
}
// Less compare two elements
func (is Ints[T]) Less(i, j int) bool {
return is[i] < is[j]
}
// Swap elements by indexes
func (is Ints[T]) Swap(i, j int) {
is[i], is[j] = is[j], is[i]
}
// Strings type
type Strings []string
// String to string
func (ss Strings) String() string {
return strings.Join(ss, ",")
}
// Join to string
func (ss Strings) Join(sep string) string {
return strings.Join(ss, sep)
}
// Has given element
func (ss Strings) Has(sub string) bool {
return ss.Contains(sub)
}
// Contains given element
func (ss Strings) Contains(sub string) bool {
for _, s := range ss {
if s == sub {
return true
}
}
return false
}
// First element value.
func (ss Strings) First(defVal ...string) string {
if len(ss) > 0 {
return ss[0]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty string list")
}
// Last element value.
func (ss Strings) Last(defVal ...string) string {
if len(ss) > 0 {
return ss[len(ss)-1]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty string list")
}
// Sort the string slice
func (ss Strings) Sort() {
sort.Strings(ss)
}
// SortedList definition for compared type
type SortedList[T comdef.Compared] []T
// Len get length
func (ls SortedList[T]) Len() int {
return len(ls)
}
// Less compare two elements
func (ls SortedList[T]) Less(i, j int) bool {
return ls[i] < ls[j]
}
// Swap elements by indexes
func (ls SortedList[T]) Swap(i, j int) {
ls[i], ls[j] = ls[j], ls[i]
}
// IsEmpty check
func (ls SortedList[T]) IsEmpty() bool {
return len(ls) == 0
}
// String to string
func (ls SortedList[T]) String() string {
return ToString(ls)
}
// Has given element
func (ls SortedList[T]) Has(el T) bool {
return ls.Contains(el)
}
// Contains given element
func (ls SortedList[T]) Contains(el T) bool {
for _, v := range ls {
if v == el {
return true
}
}
return false
}
// First element value.
func (ls SortedList[T]) First(defVal ...T) T {
if len(ls) > 0 {
return ls[0]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty list")
}
// Last element value.
func (ls SortedList[T]) Last(defVal ...T) T {
if ln := len(ls); ln > 0 {
return ls[ln-1]
}
if len(defVal) > 0 {
return defVal[0]
}
panic("empty list")
}
// Remove given element
func (ls SortedList[T]) Remove(el T) SortedList[T] {
return Filter(ls, func(v T) bool {
return v != el
})
}
// Filter the slice, default will filter zero value.
func (ls SortedList[T]) Filter(filter ...comdef.MatchFunc[T]) SortedList[T] {
return Filter(ls, filter...)
}
// Map the slice to new slice. TODO syntax ERROR: Method cannot have type parameters
// func (ls SortedList[T]) Map[V any](mapFn MapFn[T, V]) SortedList[V] {
// return Map(ls, mapFn)
// }
// Sort the slice
func (ls SortedList[T]) Sort() {
sort.Sort(ls)
}

102
vendor/github.com/gookit/goutil/arrutil/process.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
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 {
return !reflect.ValueOf(el).IsZero()
}
}
newLs := make([]T, 0, len(ls))
for _, el := range ls {
if fn(el) {
newLs = append(newLs, el)
}
}
return newLs
}
// MapFn map handle function type.
type MapFn[T any, V any] func(input T) (target V, find bool)
// Map a list to new list
//
// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V {
flatArr := make([]V, 0, len(list))
for _, obj := range list {
if target, ok := mapFn(obj); ok {
flatArr = append(flatArr, target)
}
}
return flatArr
}
// Column alias of Map func
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 ~string | comdef.XintOrFloat](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 ~string | comdef.XintOrFloat](val T, list []T) int {
for i, v := range list {
if v == val {
return i
}
}
return -1
}

137
vendor/github.com/gookit/goutil/arrutil/strings.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
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 {
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
}

View File

@@ -1,13 +1,52 @@
// Package basefn provide some no-dependents util functions
package basefn
import "fmt"
import (
"errors"
"fmt"
)
// Panicf format panic message use fmt.Sprintf
func Panicf(format string, v ...any) {
panic(fmt.Sprintf(format, v...))
}
// PanicIf if cond = true, panics with error message
func PanicIf(cond bool, fmtAndArgs ...any) {
if cond {
panic(errors.New(formatWithArgs(fmtAndArgs)))
}
}
func formatWithArgs(fmtAndArgs []any) string {
ln := len(fmtAndArgs)
if ln == 0 {
return ""
}
first := fmtAndArgs[0]
if ln == 1 {
if msgAsStr, ok := first.(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", first)
}
// is template string.
if tplStr, ok := first.(string); ok {
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
}
return fmt.Sprint(fmtAndArgs...)
}
// PanicErr panics if error is not empty
func PanicErr(err error) {
if err != nil {
panic(err)
}
}
// MustOK if error is not empty, will panic
func MustOK(err error) {
if err != nil {

View File

@@ -6,11 +6,13 @@ import (
)
// 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
@@ -117,3 +119,8 @@ func (b *Buffer) Close() error {
func (b *Buffer) Flush() error {
return b.FlushErr
}
// Sync anf flush buffer
func (b *Buffer) Sync() error {
return b.SyncErr
}

View File

@@ -1,14 +1,40 @@
// Package byteutil provides some useful functions for byte slice.
package byteutil
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"strconv"
"time"
"unsafe"
)
// Md5 Generate a 32-bit md5 bytes
func Md5(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)))
}
bs := h.Sum(nil) // cap(bs) == 16
dst := make([]byte, hex.EncodedLen(len(bs)))
hex.Encode(dst, bs)
return dst
}
// ShortMd5 Generate a 16-bit md5 bytes. remove 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)
@@ -27,32 +53,6 @@ func FirstLine(bs []byte) []byte {
return bs
}
// 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))
}
// AppendAny append any value to byte slice
func AppendAny(dst []byte, v any) []byte {
if v == nil {
@@ -104,12 +104,19 @@ func AppendAny(dst []byte, v any) []byte {
return dst
}
// Cut bytes. like the strings.Cut()
// Cut bytes by one byte char. like bytes.Cut(), but sep is byte.
func Cut(bs []byte, sep byte) (before, after []byte, found bool) {
if i := bytes.IndexByte(bs, sep); i >= 0 {
return bs[:i], bs[i+1:], true
}
return bytes.Cut(bs, []byte{sep})
}
before = bs
// 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
}

View File

@@ -1,19 +0,0 @@
// Package byteutil Provide some bytes utils functions or structs
package byteutil
import (
"crypto/md5"
"fmt"
)
// Md5 Generate a 32-bit md5 bytes
func Md5(src any) []byte {
h := md5.New()
if s, ok := src.(string); ok {
h.Write([]byte(s))
} else {
h.Write([]byte(fmt.Sprint(src)))
}
return h.Sum(nil)
}

109
vendor/github.com/gookit/goutil/byteutil/conv.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
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)
}
}

View File

@@ -5,6 +5,12 @@ import (
"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
@@ -13,12 +19,12 @@ type BytesEncoder interface {
// StdEncoder implement the BytesEncoder
type StdEncoder struct {
encodeFn func(src []byte) []byte
decodeFn func(src []byte) ([]byte, error)
encodeFn BytesEncodeFunc
decodeFn BytesDecodeFunc
}
// NewStdEncoder instance
func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder {
func NewStdEncoder(encFn BytesEncodeFunc, decFn BytesDecodeFunc) *StdEncoder {
return &StdEncoder{
encodeFn: encFn,
decodeFn: decFn,

View File

@@ -13,14 +13,14 @@ package byteutil
// from https://github.com/minio/minio/blob/master/internal/bpool/bpool.go
type ChanPool struct {
c chan []byte
w int
wcap int
w int // init byte width
wcap int // set byte cap
}
// NewChanPool instance
func NewChanPool(maxSize int, width int, capWidth int) *ChanPool {
func NewChanPool(chSize int, width int, capWidth int) *ChanPool {
return &ChanPool{
c: make(chan []byte, maxSize),
c: make(chan []byte, chSize),
w: width,
wcap: capWidth,
}
@@ -30,8 +30,7 @@ func NewChanPool(maxSize int, width int, capWidth int) *ChanPool {
// available in the pool.
func (bp *ChanPool) Get() (b []byte) {
select {
case b = <-bp.c:
// reuse existing buffer
case b = <-bp.c: // reuse existing buffer
default:
// create new buffer
if bp.wcap > 0 {

View File

@@ -3,8 +3,8 @@ package goutil
import (
"reflect"
"github.com/gookit/goutil/internal/checkfn"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/stdutil"
)
// IsNil value check
@@ -15,6 +15,9 @@ func IsNil(v any) bool {
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 {
@@ -55,7 +58,7 @@ func IsEqual(src, dst any) bool {
// string - check sub-string exists
// array,slice - check sub-element exists
func Contains(data, elem any) bool {
_, found := stdutil.CheckContains(data, elem)
_, found := checkfn.Contains(data, elem)
return found
}
@@ -67,6 +70,6 @@ func Contains(data, elem any) bool {
// string - check sub-string exists
// array,slice - check sub-element exists
func IsContains(data, elem any) bool {
_, found := stdutil.CheckContains(data, elem)
_, found := checkfn.Contains(data, elem)
return found
}

View File

@@ -55,13 +55,13 @@ func (b *LineBuilder) WriteString(a string) (int, error) {
if pos := strings.IndexByte(a, '"'); pos > -1 {
quote = '\''
// fix: a = `--pretty=format:"one two three"`
if pos > 0 && '"' == a[len(a)-1] {
if pos > 0 && a[len(a)-1] == '"' {
quote = 0
}
} else if pos := strings.IndexByte(a, '\''); pos > -1 {
quote = '"'
// fix: a = "--pretty=format:'one two three'"
if pos > 0 && '\'' == a[len(a)-1] {
if pos > 0 && a[len(a)-1] == '\'' {
quote = 0
}
} else if a == "" || strings.ContainsRune(a, ' ') {

View File

@@ -6,7 +6,7 @@ import (
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/internal/varexpr"
"github.com/gookit/goutil/strutil"
)
@@ -86,7 +86,7 @@ func (p *LineParser) Parse() []string {
// enable parse Env var
if p.ParseEnv {
p.Line = comfunc.ParseEnvVar(p.Line, nil)
p.Line = varexpr.SafeParse(p.Line)
}
p.nodes = strings.Split(p.Line, " ")

View File

@@ -1,38 +1,6 @@
// Package comdef provide some common type or constant definitions
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
}
// 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)
}
type (
// MarshalFunc define
MarshalFunc func(v any) ([]byte, error)
@@ -40,3 +8,15 @@ type (
// UnmarshalFunc define
UnmarshalFunc func(bts []byte, ptr any) 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

70
vendor/github.com/gookit/goutil/comdef/interface.go generated vendored Normal file
View File

@@ -0,0 +1,70 @@
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)
}
//
//
// 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)
}

View File

@@ -10,11 +10,16 @@ type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Xint interface type. all int or uint types
// 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
@@ -30,16 +35,34 @@ type XintOrFloat interface {
Int | Uint | Float
}
// SortedType interface type.
// that supports the operators < <= >= >.
// 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 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

View File

@@ -1,9 +1,12 @@
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"
@@ -65,18 +68,167 @@ func ToUint(v any) (uint64, error) {
return mathutil.ToUint(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/unitX => int64
// intX => int64
// unitX => uint64
// floatX => float64
// string => string
//
// returns int64,string,float or error
// returns int64,uint64,string,float or error
func BaseTypeVal(val any) (value any, err error) {
return reflects.BaseTypeVal(reflect.ValueOf(val))
}
// BoolString convert
func BoolString(bl bool) string {
return strconv.FormatBool(bl)
// 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 uint64
if dstV, err = mathutil.ToUint(val); err == nil {
newVal = uint(dstV)
}
case reflect.Uint8:
var dstV uint64
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 uint64
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 uint64
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.ToUint(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
}

66
vendor/github.com/gookit/goutil/encodes/encodes.go generated vendored Normal file
View File

@@ -0,0 +1,66 @@
package encodes
import (
"encoding/base32"
"encoding/base64"
)
// BaseEncoder interface
type BaseEncoder interface {
Encode(dst []byte, src []byte)
EncodeToString(src []byte) string
Decode(dst []byte, src []byte) (n int, err error)
DecodeString(s string) ([]byte, error)
}
//
// -------------------- base encode --------------------
//
// base32 encoding with no padding
var (
B32Std = base32.StdEncoding.WithPadding(base32.NoPadding)
B32Hex = base32.HexEncoding.WithPadding(base32.NoPadding)
)
// B32Encode base32 encode
func B32Encode(str string) string {
return B32Std.EncodeToString([]byte(str))
}
// B32Decode base32 decode
func B32Decode(str string) string {
dec, _ := B32Std.DecodeString(str)
return string(dec)
}
// base64 encoding with no padding
var (
B64Std = base64.StdEncoding.WithPadding(base64.NoPadding)
B64URL = base64.URLEncoding.WithPadding(base64.NoPadding)
)
// B64Encode base64 encode
func B64Encode(str string) string {
return B64Std.EncodeToString([]byte(str))
}
// B64EncodeBytes base64 encode
func B64EncodeBytes(src []byte) []byte {
buf := make([]byte, B64Std.EncodedLen(len(src)))
B64Std.Encode(buf, src)
return buf
}
// B64Decode base64 decode
func B64Decode(str string) string {
dec, _ := B64Std.DecodeString(str)
return string(dec)
}
// B64DecodeBytes base64 decode
func B64DecodeBytes(str []byte) []byte {
dbuf := make([]byte, B64Std.DecodedLen(len(str)))
n, _ := B64Std.Decode(dbuf, str)
return dbuf[:n]
}

View File

@@ -1,6 +1,6 @@
# Env Util
Provide some commonly ENV util functions.
Provide some commonly system or go ENV util functions.
## Install

View File

@@ -4,7 +4,7 @@ package envutil
import (
"os"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/internal/varexpr"
)
// ValueGetter Env value provider func.
@@ -17,14 +17,13 @@ var ValueGetter = os.Getenv
// is alias of the os.ExpandEnv()
func VarReplace(s string) string { return os.ExpandEnv(s) }
// VarParse alias of the ParseValue
func VarParse(val string) string {
return comfunc.ParseEnvVar(val, ValueGetter)
}
// ParseEnvValue alias of the ParseValue
func ParseEnvValue(val string) string {
return comfunc.ParseEnvVar(val, ValueGetter)
// 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.
@@ -38,8 +37,18 @@ func ParseEnvValue(val string) string {
//
// envutil.ParseValue("${ APP_NAME }")
// envutil.ParseValue("${ APP_ENV | dev }")
func ParseValue(val string) (newVal string) {
return comfunc.ParseEnvVar(val, ValueGetter)
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)
}
// SetEnvMap set multi ENV(string-map) to os

View File

@@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
@@ -22,11 +23,7 @@ func GetInt(name string, def ...int) int {
if val := os.Getenv(name); val != "" {
return strutil.QuietInt(val)
}
if len(def) > 0 {
return def[0]
}
return 0
return basefn.FirstOr(def, 0)
}
// GetBool get bool ENV value by key name, can with default value
@@ -34,11 +31,7 @@ func GetBool(name string, def ...bool) bool {
if val := os.Getenv(name); val != "" {
return strutil.QuietBool(val)
}
if len(def) > 0 {
return def[0]
}
return false
return basefn.FirstOr(def, false)
}
// GetMulti ENV values by input names.

View File

@@ -30,7 +30,7 @@ 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.FormatTplAndArgs(fmtAndArgs)
errMsg = comfunc.FormatWithArgs(fmtAndArgs)
} else {
errMsg = fmt.Sprintf("value should be in the %v", list)
}
@@ -44,7 +44,7 @@ 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.FormatTplAndArgs(fmtAndArgs)
errMsg = comfunc.FormatWithArgs(fmtAndArgs)
} else {
errMsg = fmt.Sprintf("value should not be in the %v", list)
}
@@ -53,9 +53,9 @@ func NotIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error {
return nil
}
func formatErrMsg(errMsg string, fmtAndArgs []any) string {
func formatErrMsg(defMsg string, fmtAndArgs []any) string {
if len(fmtAndArgs) > 0 {
errMsg = comfunc.FormatTplAndArgs(fmtAndArgs)
return comfunc.FormatWithArgs(fmtAndArgs)
}
return errMsg
return defMsg
}

View File

@@ -77,11 +77,14 @@ func (e *errorR) GoString() string {
return e.String()
}
// ErrMap multi error map
type ErrMap map[string]error
// ErrorM multi error map
type ErrorM map[string]error
// ErrMap alias of ErrorM
type ErrMap = ErrorM
// Error string
func (e ErrMap) Error() string {
func (e ErrorM) Error() string {
var sb strings.Builder
for name, err := range e {
sb.WriteString(name)
@@ -93,7 +96,7 @@ func (e ErrMap) Error() string {
}
// ErrorOrNil error
func (e ErrMap) ErrorOrNil() error {
func (e ErrorM) ErrorOrNil() error {
if len(e) == 0 {
return nil
}
@@ -101,12 +104,12 @@ func (e ErrMap) ErrorOrNil() error {
}
// IsEmpty error
func (e ErrMap) IsEmpty() bool {
func (e ErrorM) IsEmpty() bool {
return len(e) == 0
}
// One error
func (e ErrMap) One() error {
func (e ErrorM) One() error {
for _, err := range e {
return err
}

View File

@@ -71,7 +71,7 @@ func (e *ErrorX) Unwrap() error {
return e.prev
}
// Format error
// 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 {
@@ -103,7 +103,7 @@ func (e *ErrorX) GoString() string {
return buf.String()
}
// Error to string, not contains stack information.
// Error msg string, not contains stack information.
func (e *ErrorX) Error() string {
var buf bytes.Buffer
e.writeMsgTo(&buf)
@@ -265,6 +265,7 @@ func WithStack(err error) error {
if err == nil {
return nil
}
return &ErrorX{
msg: err.Error(),
// prev: err,
@@ -277,6 +278,7 @@ func Traced(err error) error {
if err == nil {
return nil
}
return &ErrorX{
msg: err.Error(),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),
@@ -288,6 +290,7 @@ func Stacked(err error) error {
if err == nil {
return nil
}
return &ErrorX{
msg: err.Error(),
stack: callersStack(stdOpt.SkipDepth, stdOpt.TraceDepth),

View File

@@ -99,7 +99,7 @@ func FuncForPC(pc uintptr) *Func {
}
}
// FileLine of the func
// 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)
}
@@ -108,7 +108,7 @@ func (f *Func) FileLine() (file string, line int) {
//
// Returns eg:
//
// github.com/gookit/goutil/errorx_test.TestWithPrev(), errorx_test.go:34
// "github.com/gookit/goutil/errorx_test.TestWithPrev(), errorx_test.go:34"
func (f *Func) Location() string {
file, line := f.FileLine()

View File

@@ -68,12 +68,27 @@ func Unwrap(err error) error {
// 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
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 {

View File

@@ -2,6 +2,7 @@ package fsutil
import (
"bytes"
"io"
"os"
"path"
"path/filepath"
@@ -82,6 +83,18 @@ func IsAbsPath(aPath string) bool {
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",

109
vendor/github.com/gookit/goutil/fsutil/define.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
package fsutil
import (
"io/fs"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/strutil"
)
const (
// MimeSniffLen sniff Length, use for detect file mime type
MimeSniffLen = 512
)
// NameMatchFunc name match func, alias of comdef.StringMatchFunc
type NameMatchFunc = comdef.StringMatchFunc
// PathMatchFunc path match func. alias of comdef.StringMatchFunc
type PathMatchFunc = comdef.StringMatchFunc
// Entry extends fs.DirEntry, add some useful methods
type Entry interface {
fs.DirEntry
// Path get 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 get full file/dir path. eg: "/path/to/file.go"
func (e *entry) Path() string {
return e.path
}
// Info get 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 get 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 get 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())
}

View File

@@ -7,10 +7,66 @@ import (
"path/filepath"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
// FilePathInDirs get full file path in dirs.
//
// Params:
// - file: can be relative path, file name, full path.
// - dirs: dir paths
func FilePathInDirs(file string, dirs ...string) string {
file = comfunc.ExpandHome(file)
if FileExists(file) {
return file
}
for _, dirPath := range dirs {
fPath := JoinSubPaths(dirPath, file)
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
}
// SearchNameUp find file/dir name in dirPath or parent dirs,
// return the name of directory path
//
@@ -57,7 +113,7 @@ func WalkDir(dir string, fn fs.WalkDirFunc) error {
// Usage:
//
// files := fsutil.Glob("/path/to/dir/*.go")
func Glob(pattern string, fls ...comdef.StringMatchFunc) []string {
func Glob(pattern string, fls ...NameMatchFunc) []string {
files, _ := filepath.Glob(pattern)
if len(fls) == 0 || len(files) == 0 {
return files

View File

@@ -2,8 +2,6 @@
package fsutil
import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
@@ -11,77 +9,6 @@ import (
"github.com/gookit/goutil/internal/comfunc"
)
const (
// MimeSniffLen sniff Length, use for detect file mime type
MimeSniffLen = 512
)
// 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:
//
// fsutil.TempFile("", "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)
}
// 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])
}
// JoinPaths elements, alias of filepath.Join()
func JoinPaths(elem ...string) string {
return filepath.Join(elem...)

View File

@@ -8,6 +8,9 @@ import (
"github.com/gookit/goutil/internal/comfunc"
)
// DirPath get dir path from filepath, without last name.
func DirPath(fpath string) string { return filepath.Dir(fpath) }
// Dir get dir path from filepath, without last name.
func Dir(fpath string) string { return filepath.Dir(fpath) }

View File

@@ -9,6 +9,7 @@ import (
)
// 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)

40
vendor/github.com/gookit/goutil/fsutil/mime.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
package fsutil
import (
"io"
"net/http"
"os"
)
// DetectMime detect 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])
}

View File

@@ -39,7 +39,7 @@ func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error {
return nil
}
// MkParentDir quick create parent dir
// MkParentDir quick create parent dir for given path.
func MkParentDir(fpath string) error {
dirPath := filepath.Dir(fpath)
if !IsDir(dirPath) {
@@ -48,15 +48,70 @@ func MkParentDir(fpath string) error {
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
// 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
)
@@ -65,13 +120,13 @@ const (
// Usage:
//
// file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666)
func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) {
fileDir := path.Dir(filepath)
func OpenFile(filePath string, flag int, perm os.FileMode) (*os.File, error) {
fileDir := path.Dir(filePath)
if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil {
return nil, err
}
file, err := os.OpenFile(filepath, flag, perm)
file, err := os.OpenFile(filePath, flag, perm)
if err != nil {
return nil, err
}
@@ -83,8 +138,8 @@ func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) {
// 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)
func MustOpenFile(filePath string, flag int, perm os.FileMode) *os.File {
file, err := OpenFile(filePath, flag, perm)
if err != nil {
panic(err)
}
@@ -173,6 +228,11 @@ func MustRemove(fPath string) {
// 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 exists.
func RmIfExist(fPath string) error { return DeleteIfExist(fPath) }
@@ -202,6 +262,11 @@ func RemoveSub(dirPath string, fns ...FilterFunc) error {
if err := RemoveSub(fPath, fns...); err != nil {
return err
}
// skip rm not empty subdir
if !IsEmptyDir(fPath) {
return nil
}
}
return os.Remove(fPath)
}, fns...)

View File

@@ -6,6 +6,8 @@ import (
"io"
"os"
"text/scanner"
"github.com/gookit/goutil/basefn"
)
// NewIOReader instance by input file path or io.Reader
@@ -70,20 +72,27 @@ func ReadStringOrErr(in any) (string, error) {
}
// ReadAll read contents from path or io.Reader, will panic on in type error
func ReadAll(in any) []byte { return GetContents(in) }
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 {
r, err := NewIOReader(in)
if err != nil {
panic(err)
}
return MustReadReader(r)
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
}
@@ -102,7 +111,8 @@ func ReadExistFile(filePath string) []byte {
return nil
}
// TextScanner from filepath or io.Reader, will panic on in type error
// 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:
//
@@ -122,7 +132,8 @@ func TextScanner(in any) *scanner.Scanner {
return &s
}
// LineScanner create from filepath or io.Reader
// 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() {

View File

@@ -7,13 +7,73 @@ import (
"github.com/gookit/goutil/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
// ************************************************************
// PutContents create file and write contents to file at once.
// 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.
//
// default option see NewOpenOption()
func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error {
opt := NewOpenOption(optFns...)
return WriteFile(filePath, data, opt.Perm, opt.Flag)
}
// PutContents create file and write contents to file at once.
//
// data type allow: string, []byte, io.Reader. will auto create dir.
//
// Tip: file flag default is FsCWTFlags (override write)
//
@@ -25,7 +85,6 @@ func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
if err != nil {
return 0, err
}
return WriteOSFile(f, data)
}
@@ -99,3 +158,21 @@ func MustCopyFile(srcPath, dstPath string) {
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
}

View File

@@ -1,11 +1,6 @@
package goutil
import "github.com/gookit/goutil/stdutil"
// FuncName get func name
func FuncName(f any) string {
return stdutil.FuncName(f)
}
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.
@@ -35,3 +30,53 @@ func CallOrElse(cond bool, okFn, elseFn ErrFunc) error {
}
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 nil
}
// SafeRun 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)
}
}()
}

34
vendor/github.com/gookit/goutil/goinfo/README.md generated vendored Normal file
View File

@@ -0,0 +1,34 @@
# GoInfo
`goutil/goinfo` provide some useful info for golang.
> Github: https://github.com/gookit/goutil
## Install
```bash
go get github.com/gookit/goutil/goinfo
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil)
## Usage
```go
gover := goinfo.GoVersion() // eg: "1.15.6"
```
## Testings
```shell
go test -v ./goinfo/...
```
Test limit by regexp:
```shell
go test -v -run ^TestSetByKeys ./goinfo/...
```

View File

@@ -1,20 +1,21 @@
package stdutil
package goinfo
import (
"reflect"
"runtime"
"strings"
"unicode"
"github.com/gookit/goutil/strutil"
)
// FullFcName struct.
type FullFcName struct {
// FullName eg: github.com/gookit/goutil/stdutil.PanicIf
// FullName eg: "github.com/gookit/goutil/goinfo.PanicIf"
FullName string
pkgPath string
pkgName string
funcName string
pkgPath string // "github.com/gookit/goutil/goinfo"
pkgName string // "goinfo"
funcName string // "PanicIf"
}
// Parse the full func name.
@@ -28,17 +29,16 @@ func (ffn *FullFcName) Parse() {
ffn.pkgPath = ffn.FullName[:i+1]
// spilt get pkg and func name
ffn.pkgName, ffn.funcName = strutil.MustCut(ffn.FullName[i+1:], ".")
ffn.pkgPath += ffn.pkgName
}
// PkgPath string get. eg: github.com/gookit/goutil/stdutil
// PkgPath string get. eg: github.com/gookit/goutil/goinfo
func (ffn *FullFcName) PkgPath() string {
ffn.Parse()
return ffn.pkgPath
}
// PkgName string get. eg: stdutil
// PkgName string get. eg: goinfo
func (ffn *FullFcName) PkgName() string {
ffn.Parse()
return ffn.pkgName
@@ -50,7 +50,7 @@ func (ffn *FullFcName) FuncName() string {
return ffn.funcName
}
// String get full func name string.
// String get full func name string, pkg path and func name.
func (ffn *FullFcName) String() string {
return ffn.FullName
}
@@ -59,13 +59,16 @@ func (ffn *FullFcName) String() string {
//
// eg:
//
// // OUTPUT: github.com/gookit/goutil/stdutil.PanicIf
// stdutil.FuncName(stdutil.PkgName)
// // OUTPUT: github.com/gookit/goutil/goinfo.PkgName
// goinfo.FuncName(goinfo.PkgName)
func FuncName(fn any) string {
return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
}
// CutFuncName get pkg path and short func name
// eg:
//
// "github.com/gookit/goutil/goinfo.FuncName" => [github.com/gookit/goutil/goinfo, FuncName]
func CutFuncName(fullFcName string) (pkgPath, shortFnName string) {
ffn := FullFcName{FullName: fullFcName}
return ffn.PkgPath(), ffn.FuncName()
@@ -75,12 +78,13 @@ func CutFuncName(fullFcName string) (pkgPath, shortFnName string) {
//
// Usage:
//
// fullFcName := stdutil.FuncName(fn)
// pgkName := stdutil.PkgName(fullFcName)
// fullFcName := goinfo.FuncName(fn)
// pgkName := goinfo.PkgName(fullFcName)
func PkgName(fullFcName string) string {
for {
lastPeriod := strings.LastIndex(fullFcName, ".")
lastSlash := strings.LastIndex(fullFcName, "/")
if lastPeriod > lastSlash {
fullFcName = fullFcName[:lastPeriod]
} else {
@@ -89,3 +93,21 @@ func PkgName(fullFcName string) string {
}
return fullFcName
}
// GoodFuncName reports whether the function name is a valid identifier.
func GoodFuncName(name string) bool {
if name == "" {
return false
}
for i, r := range name {
switch {
case r == '_':
case i == 0 && !unicode.IsLetter(r):
return false
case !unicode.IsLetter(r) && !unicode.IsDigit(r):
return false
}
}
return true
}

78
vendor/github.com/gookit/goutil/goinfo/goinfo.go generated vendored Normal file
View File

@@ -0,0 +1,78 @@
// Package goinfo provide some standard util functions for go.
package goinfo
import (
"errors"
"os/exec"
"regexp"
"runtime"
"strings"
)
// GoVersion get go runtime version. eg: "1.18.2"
func GoVersion() string {
return runtime.Version()[2:]
}
// GoInfo define
//
// On os by:
//
// go env GOVERSION GOOS GOARCH
// go version // "go version go1.19 darwin/amd64"
type GoInfo struct {
Version string
GoOS string
Arch string
}
// match "go version go1.19 darwin/amd64"
var goVerRegex = regexp.MustCompile(`\sgo([\d.]+)\s(\w+)/(\w+)`)
// ParseGoVersion get info by parse `go version` results.
//
// Examples:
//
// line, err := sysutil.ExecLine("go version")
// if err != nil {
// return err
// }
//
// info, err := goinfo.ParseGoVersion()
// dump.P(info)
func ParseGoVersion(line string) (*GoInfo, error) {
// eg: [" go1.19 darwin/amd64", "1.19", "darwin", "amd64"]
lines := goVerRegex.FindStringSubmatch(line)
if len(lines) != 4 {
return nil, errors.New("input go version info is invalid")
}
info := &GoInfo{
Version: strings.TrimPrefix(lines[1], "go"),
}
info.GoOS = lines[2]
info.Arch = lines[3]
return info, nil
}
// OsGoInfo fetch and parse
func OsGoInfo() (*GoInfo, error) {
cmdArgs := []string{"env", "GOVERSION", "GOOS", "GOARCH"}
bs, err := exec.Command("go", cmdArgs...).Output()
if err != nil {
return nil, err
}
lines := strings.Split(strings.TrimSpace(string(bs)), "\n")
if len(lines) != len(cmdArgs)-1 {
return nil, errors.New("returns go info is not full")
}
info := &GoInfo{}
info.Version = strings.TrimPrefix(lines[0], "go")
info.GoOS = lines[1]
info.Arch = lines[2]
return info, nil
}

View File

@@ -1,10 +1,12 @@
package stdutil
package goinfo
import (
"path"
"runtime"
"strconv"
"strings"
"github.com/gookit/goutil/basefn"
)
// some commonly consts
@@ -42,15 +44,11 @@ func GetCallStacks(all bool) []byte {
//
// returns:
//
// github.com/gookit/goutil/stdutil_test.someFunc2(),stack_test.go:26
// github.com/gookit/goutil/goinfo_test.someFunc2(),stack_test.go:26
func GetCallerInfo(skip int) string {
skip++ // ignore current func
cs := GetCallersInfo(skip, skip+1)
if len(cs) > 0 {
return cs[0]
}
return ""
return basefn.FirstOr(cs, "")
}
// SimpleCallersInfo returns an array of strings containing
@@ -62,6 +60,7 @@ func SimpleCallersInfo(skip, num int) []string {
// GetCallersInfo returns an array of strings containing
// the func name, file and line number of each stack frame leading.
//
// NOTICE: max should > skip
func GetCallersInfo(skip, max int) []string {
var (
@@ -93,7 +92,7 @@ func GetCallersInfo(skip, max int) []string {
if strings.ContainsRune(file, '/') {
name = fc.Name()
file = path.Base(file)
// eg: github.com/gookit/goutil/stdutil_test.someFunc2(),stack_test.go:26
// eg: github.com/gookit/goutil/goinfo_test.someFunc2(),stack_test.go:26
callers = append(callers, name+"(),"+file+":"+strconv.Itoa(line))
}

View File

@@ -5,17 +5,24 @@ package goutil
import (
"fmt"
"github.com/gookit/goutil/stdutil"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/goinfo"
"github.com/gookit/goutil/structs"
)
// Value alias of stdutil.Value
type Value = stdutil.Value
// 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 error message
func PanicIf(cond bool, fmtAndArgs ...any) {
basefn.PanicIf(cond, fmtAndArgs...)
}
// PanicIfErr if error is not empty, will panic
func PanicIfErr(err error) {
if err != nil {
@@ -45,14 +52,19 @@ func Must[T any](v T, err error) T {
return v
}
// PkgName get current package name. alias of stdutil.PkgName()
// FuncName get func name
func FuncName(f any) string {
return goinfo.FuncName(f)
}
// PkgName get current package name. alias of goinfo.PkgName()
//
// Usage:
//
// funcName := goutil.FuncName(fn)
// pgkName := goutil.PkgName(funcName)
func PkgName(funcName string) string {
return stdutil.PkgName(funcName)
return goinfo.PkgName(funcName)
}
// ErrOnFail return input error on cond is false, otherwise return nil

View File

@@ -4,46 +4,21 @@ import (
"context"
"github.com/gookit/goutil/structs"
"golang.org/x/sync/errgroup"
"github.com/gookit/goutil/syncs"
)
// ErrGroup is a collection of goroutines working on subtasks that
// are part of the same overall task.
//
// Refers:
//
// https://github.com/neilotoole/errgroup
// https://github.com/fatih/semgroup
type ErrGroup struct {
*errgroup.Group
}
type ErrGroup = syncs.ErrGroup
// NewCtxErrGroup instance
func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context) {
egg, ctx1 := errgroup.WithContext(ctx)
if len(limit) > 0 && limit[0] > 0 {
egg.SetLimit(limit[0])
}
eg := &ErrGroup{Group: egg}
return eg, ctx1
return syncs.NewCtxErrGroup(ctx, limit...)
}
// NewErrGroup instance
func NewErrGroup(limit ...int) *ErrGroup {
eg := &ErrGroup{Group: new(errgroup.Group)}
if len(limit) > 0 && limit[0] > 0 {
eg.SetLimit(limit[0])
}
return eg
}
// Add one or more handler at once
func (g *ErrGroup) Add(handlers ...func() error) {
for _, handler := range handlers {
g.Go(handler)
}
return syncs.NewErrGroup(limit...)
}
// RunFn func

View File

@@ -0,0 +1,78 @@
package checkfn
import (
"fmt"
"reflect"
"strings"
"github.com/gookit/goutil/reflects"
)
// IsNil value check
func IsNil(v any) bool {
if v == nil {
return true
}
return reflects.IsNil(reflect.ValueOf(v))
}
// IsEmpty value check
func IsEmpty(v any) bool {
if v == nil {
return true
}
return reflects.IsEmpty(reflect.ValueOf(v))
}
// 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 reflects.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 reflects.IsEqual(dataRv.Index(i).Interface(), elem) {
return true, true
}
}
return true, false
}

View File

@@ -0,0 +1,3 @@
# common func for internal use
- don't depend on other external packages

View File

@@ -26,90 +26,14 @@ func Environ() map[string]string {
return envMap
}
// parse env value, allow:
//
// only key - "${SHELL}"
// with default - "${NotExist | defValue}"
// multi key - "${GOPATH}/${APP_ENV | prod}/dir"
//
// Notice:
//
// must add "?" - To ensure that there is no greedy match
// var envRegex = regexp.MustCompile(`\${[\w-| ]+}`)
var envRegex = regexp.MustCompile(`\${.+?}`)
// ParseEnvVar parse ENV var value from input string, support default value.
//
// Format:
//
// ${var_name} Only var name
// ${var_name | default} With default value
//
// Usage:
//
// comfunc.ParseEnvVar("${ APP_NAME }")
// comfunc.ParseEnvVar("${ APP_ENV | dev }")
func ParseEnvVar(val string, getFn func(string) string) (newVal string) {
if !strings.Contains(val, "${") {
return val
}
// default use os.Getenv
if getFn == nil {
getFn = os.Getenv
}
var name, def string
return envRegex.ReplaceAllStringFunc(val, func(eVar string) string {
// eVar like "${NotExist|defValue}", first remove "${" and "}", then split it
ss := strings.SplitN(eVar[2:len(eVar)-1], "|", 2)
// with default value. ${NotExist|defValue}
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
eVal := getFn(name)
if eVal == "" {
eVal = def
}
return eVal
})
}
// FormatTplAndArgs message
func FormatTplAndArgs(fmtAndArgs []any) string {
if len(fmtAndArgs) == 0 || fmtAndArgs == nil {
return ""
}
ln := len(fmtAndArgs)
first := fmtAndArgs[0]
if ln == 1 {
if msgAsStr, ok := first.(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", first)
}
// is template string.
if tplStr, ok := first.(string); ok {
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
}
return fmt.Sprint(fmtAndArgs...)
}
var (
// TIP: extend unit d,w
// time.ParseDuration() is not supported. eg: "1d", "2w"
// TIP: extend unit d,w. eg: "1d", "2w"
// time.ParseDuration() is max support hour "h".
durStrReg = regexp.MustCompile(`^(-?\d+)(ns|us|µs|ms|s|m|h|d|w)$`)
// match long duration string, such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks"
// match long duration string. eg: "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks", "1month"
// time.ParseDuration() is not supported.
durStrRegL = regexp.MustCompile(`^(-?\d+)([a-zA-Z]{3,})$`)
durStrRegL = regexp.MustCompile(`^(-?\d+)([hdmsw][a-zA-Z]{2,8})$`)
)
// IsDuration check the string is a duration string.
@@ -153,6 +77,10 @@ func ToDuration(s string) (time.Duration, error) {
// convert to short unit
switch unit {
case "month", "months":
// max unit is hour, so need convert by 24 * 30 * n
n, _ := strconv.Atoi(num)
s = strconv.Itoa(n*24*30) + "h"
case "week", "weeks":
// max unit is hour, so need convert by 24 * 7 * n
n, _ := strconv.Atoi(num)

View File

@@ -37,3 +37,26 @@ func StrToBool(s string) (bool, error) {
return false, fmt.Errorf("'%s' cannot convert to bool", s)
}
// FormatWithArgs format message with args
func FormatWithArgs(fmtAndArgs []any) string {
ln := len(fmtAndArgs)
if ln == 0 {
return ""
}
first := fmtAndArgs[0]
if ln == 1 {
if msgAsStr, ok := first.(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", first)
}
// is template string.
if tplStr, ok := first.(string); ok {
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
}
return fmt.Sprint(fmtAndArgs...)
}

View File

@@ -0,0 +1,145 @@
// 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"
)
// SepChar separator char
const SepChar = "|"
// 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
// Keyword check chars for expression. default is "${"
Keyword string
}
// 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.
func Parse(val string) (string, error) {
return std.Parse(val)
}
// SafeParse parse ENV var value from input string, support default value.
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,
Regexp: envRegex,
Keyword: "${",
}
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.
func (p *Parser) Parse(val string) (newVal string, err error) {
if p.Regexp == nil {
p.Regexp = envRegex
}
if p.Keyword != "" && !strings.Contains(val, p.Keyword) {
return val, nil
}
// 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)
}
// 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. ${NotExist|defValue}
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] == '?' {
msg := "value is required for var: " + name
if len(def) > 1 {
msg = def[1:]
}
err = errors.New(msg)
} else {
val = def
}
}
return
}

64
vendor/github.com/gookit/goutil/jsonutil/encoding.go generated vendored Normal file
View File

@@ -0,0 +1,64 @@
package jsonutil
import (
"bytes"
"encoding/json"
"io"
)
// 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)
}

21
vendor/github.com/gookit/goutil/jsonutil/jsonbuild.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package jsonutil
/*
TODO json build
type JsonBuilder struct {
Indent string
// mu sync.Mutex
// cfg *CConfig
buf bytes.Buffer
out io.Writer
}
// AddField add field to json
func (b *JsonBuilder) AddField(key string, value any) *JsonBuilder {
b.buf.WriteString(`,"`)
b.buf.WriteString(key)
b.buf.WriteString(`":`)
b.encode(value)
return b
}
*/

View File

@@ -4,7 +4,6 @@ package jsonutil
import (
"bytes"
"encoding/json"
"io"
"os"
"regexp"
"strings"
@@ -13,7 +12,7 @@ import (
// WriteFile write data to JSON file
func WriteFile(filePath string, data any) error {
jsonBytes, err := Encode(data)
jsonBytes, err := json.Marshal(data)
if err != nil {
return err
}
@@ -46,71 +45,35 @@ func Pretty(v any) (string, error) {
return string(out), err
}
// Encode data to json bytes.
func Encode(v any) ([]byte, error) {
return json.Marshal(v)
}
// EncodePretty encode pretty JSON data to json bytes.
func EncodePretty(v any) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
}
// EncodeToWriter encode data 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
// 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 buf.Bytes(), nil
}
// Decode json bytes to data ptr.
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)
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 := Encode(src)
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.Unmarshal)
// IsJSON check if the string is valid JSON. (Note: uses json.Valid)
func IsJSON(s string) bool {
if s == "" {
return false
}
var js json.RawMessage
return json.Unmarshal([]byte(s), &js) == nil
return json.Valid([]byte(s))
}
// IsJSONFast simple and fast check input string is valid JSON.
// IsJSONFast simple and fast check input is valid JSON array or object.
func IsJSONFast(s string) bool {
ln := len(s)
if ln < 2 {
@@ -129,6 +92,32 @@ func IsJSONFast(s string) bool {
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*)`)

View File

@@ -6,7 +6,7 @@ import "fmt"
type Aliases map[string]string
// AddAlias to the Aliases
func (as Aliases) AddAlias(real, alias string) {
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))
}
@@ -16,14 +16,14 @@ func (as Aliases) AddAlias(real, alias string) {
// AddAliases to the Aliases
func (as Aliases) AddAliases(real string, aliases []string) {
for _, a := range aliases {
as.AddAlias(real, a)
as.AddAlias(a, real)
}
}
// AddAliasMap to the Aliases
func (as Aliases) AddAliasMap(alias2real map[string]string) {
for a, r := range alias2real {
as.AddAlias(r, a)
as.AddAlias(a, r)
}
}

View File

@@ -188,10 +188,7 @@ func (d Data) StrSplit(key, sep string) []string {
// StringsByStr value get by key
func (d Data) StringsByStr(key string) []string {
if val, ok := d.GetByPath(key); ok {
return strings.Split(strutil.QuietString(val), ",")
}
return nil
return d.StrSplit(key, ",")
}
// StrMap get map[string]string value

View File

@@ -92,16 +92,16 @@ func (f *MapFormatter) doFormat() {
}
for i, key := range rv.MapKeys() {
kStr := strutil.QuietString(key.Interface())
strK := strutil.SafeString(key.Interface())
if indentLn > 0 {
buf.WriteString(f.Indent)
}
buf.WriteString(kStr)
buf.WriteString(strK)
buf.WriteByte(':')
vStr := strutil.QuietString(rv.MapIndex(key).Interface())
buf.WriteString(vStr)
strV := strutil.SafeString(rv.MapIndex(key).Interface())
buf.WriteString(strV)
if i < ln-1 {
buf.WriteByte(',')

View File

@@ -4,6 +4,8 @@ import (
"reflect"
"strconv"
"strings"
"github.com/gookit/goutil/reflects"
)
// some consts for separators
@@ -24,8 +26,24 @@ func QuietGet(mp map[string]any, path string) (val any) {
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
}
@@ -35,9 +53,8 @@ func GetByPath(path string, mp map[string]any) (val any, ok bool) {
return nil, false
}
// has sub key. eg. "top.sub"
keys := strings.Split(path, ".")
return GetByPathKeys(mp, keys)
// 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"
@@ -58,14 +75,19 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
// 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
for i, k := range keys[1:] {
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 {
@@ -81,48 +103,77 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
}
case []map[string]any: // is an any-map slice
if k == Wildcard {
if kl == i+2 {
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+2:]); ok {
if val, ok = getByPathKeys(v, keys[i+1:]); ok {
sl = append(sl, val)
}
}
return sl, true
if len(sl) > 0 {
return sl, true
}
return nil, false
}
// k is index number
idx, err := strconv.Atoi(k)
if err != nil {
return nil, false
}
if idx >= len(tData) {
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 {
i, err := strconv.Atoi(k)
if err != nil {
return nil, false
}
if i >= rv.Len() {
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
}
item = rv.Index(i).Interface()
// 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

View File

@@ -29,6 +29,18 @@ func (m SMap) HasValue(val string) bool {
return false
}
// Load data to the map
func (m SMap) Load(data map[string]string) {
for k, v := range data {
m[k] = v
}
}
// Set value to the data map
func (m SMap) Set(key string, val any) {
m[key] = strutil.MustString(val)
}
// Value get from the data map
func (m SMap) Value(key string) (string, bool) {
val, ok := m[key]

View File

@@ -10,11 +10,91 @@ go get github.com/gookit/goutil/mathutil
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/mathutil)
- [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

View File

@@ -2,7 +2,12 @@ package mathutil
import "github.com/gookit/goutil/comdef"
// Compare any intX,floatX value by given op. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
// IsNumeric returns true if the given character is a numeric, otherwise false.
func IsNumeric(c byte) bool {
return c >= '0' && c <= '9'
}
// Compare any intX,floatX value by given op. returns `first op(=,!=,<,<=,>,>=) second`
//
// Usage:
//
@@ -10,70 +15,61 @@ import "github.com/gookit/goutil/comdef"
// mathutil.Compare(2, 1.3, ">") // true
// mathutil.Compare(2.2, 1.3, ">") // true
// mathutil.Compare(2.1, 2, ">") // true
func Compare(srcVal, dstVal any, op string) (ok bool) {
if srcVal == nil || dstVal == nil {
func Compare(first, second any, op string) bool {
if first == nil || second == nil {
return false
}
// float
if srcFlt, ok := srcVal.(float64); ok {
if dstFlt, err := ToFloat(dstVal); err == nil {
return CompFloat(srcFlt, dstFlt, op)
switch fVal := first.(type) {
case float64:
if sVal, err := ToFloat(second); err == nil {
return CompFloat(fVal, sVal, op)
}
return false
}
if srcFlt, ok := srcVal.(float32); ok {
if dstFlt, err := ToFloat(dstVal); err == nil {
return CompFloat(float64(srcFlt), dstFlt, 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
}
// as int64
srcInt, err := ToInt64(srcVal)
if err != nil {
return false
}
dstInt, err := ToInt64(dstVal)
if err != nil {
return false
}
return CompInt64(srcInt, dstInt, op)
return false
}
// CompInt compare int,uint value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompInt[T comdef.Xint](srcVal, dstVal T, op string) (ok bool) {
return CompValue(srcVal, dstVal, op)
// 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 `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompInt64(srcVal, dstVal int64, op string) bool {
return CompValue(srcVal, dstVal, 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 `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompFloat[T comdef.Float](srcVal, dstVal T, op string) (ok bool) {
return CompValue(srcVal, dstVal, 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 `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompValue[T comdef.XintOrFloat](srcVal, dstVal T, op string) (ok bool) {
// CompValue compare intX,uintX,floatX value. returns `first op(=,!=,<,<=,>,>=) second`
func CompValue[T comdef.XintOrFloat](first, second T, op string) (ok bool) {
switch op {
case "<", "lt":
ok = srcVal < dstVal
ok = first < second
case "<=", "lte":
ok = srcVal <= dstVal
ok = first <= second
case ">", "gt":
ok = srcVal > dstVal
ok = first > second
case ">=", "gte":
ok = srcVal >= dstVal
ok = first >= second
case "=", "eq":
ok = srcVal == dstVal
ok = first == second
case "!=", "ne", "neq":
ok = srcVal != dstVal
ok = first != second
}
return
}

76
vendor/github.com/gookit/goutil/mathutil/compare.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
package mathutil
import (
"math"
"github.com/gookit/goutil/comdef"
)
// Min compare two value and return max value
func Min[T comdef.XintOrFloat](x, y T) T {
if x < y {
return x
}
return y
}
// Max compare two value and return max value
func Max[T comdef.XintOrFloat](x, y T) T {
if x > y {
return x
}
return y
}
// SwapMin compare and always return [min, max] value
func SwapMin[T comdef.XintOrFloat](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.XintOrFloat](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)
}

View File

@@ -1,8 +1,8 @@
package mathutil
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"time"
@@ -10,6 +10,18 @@ import (
"github.com/gookit/goutil/comdef"
)
// 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) (uint64, error)
// ToFloatFunc convert value to float
type ToFloatFunc func(any) (float64, error)
/*************************************************************
* convert value to int
*************************************************************/
@@ -19,20 +31,19 @@ func Int(in any) (int, error) {
return ToInt(in)
}
// QuietInt convert value to int, will ignore error
func QuietInt(in any) int {
// 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)
}
// MustInt convert value to int, will panic on error
func MustInt(in any) int {
val, _ := ToInt(in)
return val
}
// IntOrPanic convert value to int, will panic on error
func IntOrPanic(in any) int {
val, err := ToInt(in)
if err != nil {
panic(err)
@@ -40,16 +51,38 @@ func IntOrPanic(in any) int {
return val
}
// IntOrPanic convert value to int, will panic on error
func IntOrPanic(in any) int {
return MustInt(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 := ToIntWithFunc(in, nil)
if err != nil {
return defVal
}
return val
}
// IntOrErr convert value to int, return error on failed
func IntOrErr(in any) (iVal int, err error) {
return ToInt(in)
return ToIntWithFunc(in, nil)
}
// ToInt convert value to int, return error on failed
func ToInt(in any) (iVal int, err error) {
return ToIntWithFunc(in, nil)
}
// ToIntWithFunc convert value to int, will call usrFn on value type not supported.
func ToIntWithFunc(in any, usrFn ToIntFunc) (iVal int, err error) {
switch tVal := in.(type) {
case nil:
iVal = 0
case int:
iVal = tVal
case int8:
@@ -59,31 +92,60 @@ func ToInt(in any) (iVal int, err error) {
case int32:
iVal = int(tVal)
case int64:
iVal = int(tVal)
if tVal > math.MaxInt32 {
err = fmt.Errorf("value overflow int32. input: %v", tVal)
} else {
iVal = int(tVal)
}
case uint:
iVal = int(tVal)
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:
iVal = int(tVal)
if tVal > math.MaxInt32 {
err = fmt.Errorf("value overflow int32. input: %v", tVal)
} else {
iVal = int(tVal)
}
case uint64:
iVal = int(tVal)
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:
iVal = int(tVal)
if tVal > math.MaxInt32 {
err = fmt.Errorf("value overflow int32. input: %v", tVal)
} else {
iVal = int(tVal)
}
case string:
iVal, err = strconv.Atoi(strings.TrimSpace(tVal))
case json.Number:
case interface{ Int64() (int64, error) }: // eg: json.Number
var i64 int64
i64, err = tVal.Int64()
iVal = int(i64)
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:
err = comdef.ErrConvType
if usrFn != nil {
return usrFn(in)
} else {
err = comdef.ErrConvType
}
}
return
}
@@ -94,37 +156,71 @@ func StrInt(s string) int {
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
}
/*************************************************************
* convert value to uint
*************************************************************/
// Uint convert string to uint, return error on failed
// Uint convert any to uint, return error on failed
func Uint(in any) (uint64, error) {
return ToUint(in)
}
// QuietUint convert string to uint, will ignore error
func QuietUint(in any) uint64 {
// SafeUint convert any to uint, will ignore error
func SafeUint(in any) uint64 {
val, _ := ToUint(in)
return val
}
// MustUint convert string to uint, will panic on error
// QuietUint convert any to uint, will ignore error
func QuietUint(in any) uint64 {
return SafeUint(in)
}
// MustUint convert any to uint, will panic on error
func MustUint(in any) uint64 {
val, _ := ToUint(in)
val, err := ToUintWithFunc(in, nil)
if err != nil {
panic(err)
}
return val
}
// UintOrDefault convert any to uint, return default val on failed
func UintOrDefault(in any, defVal uint64) uint64 {
return UintOr(in, defVal)
}
// UintOr convert any to uint, return default val on failed
func UintOr(in any, defVal uint64) uint64 {
val, err := ToUintWithFunc(in, nil)
if err != nil {
return defVal
}
return val
}
// UintOrErr convert value to uint, return error on failed
func UintOrErr(in any) (uint64, error) {
return ToUint(in)
return ToUintWithFunc(in, nil)
}
// ToUint convert value to uint, return error on failed
func ToUint(in any) (u64 uint64, err error) {
return ToUintWithFunc(in, nil)
}
// ToUintWithFunc convert value to uint, will call usrFn on value type not supported.
func ToUintWithFunc(in any, usrFn ToUintFunc) (u64 uint64, err error) {
switch tVal := in.(type) {
case nil:
u64 = 0
case int:
u64 = uint64(tVal)
case int8:
@@ -151,14 +247,18 @@ func ToUint(in any) (u64 uint64, err error) {
u64 = uint64(tVal)
case time.Duration:
u64 = uint64(tVal)
case json.Number:
case interface{ Int64() (int64, error) }: // eg: json.Number
var i64 int64
i64, err = tVal.Int64()
u64 = uint64(i64)
case string:
u64, err = strconv.ParseUint(strings.TrimSpace(tVal), 10, 0)
default:
err = comdef.ErrConvType
if usrFn != nil {
u64, err = usrFn(in)
} else {
err = comdef.ErrConvType
}
}
return
}
@@ -167,41 +267,58 @@ func ToUint(in any) (u64 uint64, err error) {
* convert value to int64
*************************************************************/
// Int64 convert string to int64, return error on failed
// 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, _ := ToInt64(in)
i64, _ := ToInt64WithFunc(in, nil)
return i64
}
// QuietInt64 convert value to int64, will ignore error
func QuietInt64(in any) int64 {
i64, _ := ToInt64(in)
return i64
return SafeInt64(in)
}
// MustInt64 convert value to int64, will panic on error
func MustInt64(in any) int64 {
i64, _ := ToInt64(in)
i64, err := ToInt64WithFunc(in, nil)
if err != nil {
panic(err)
}
return i64
}
// TODO StrictInt64,AsInt64 strict convert to int64
// Int64OrDefault convert value to int64, return default val on failed
func Int64OrDefault(in any, defVal int64) int64 {
return Int64Or(in, defVal)
}
// Int64OrErr convert string to int64, return error on failed
// Int64Or convert value to int64, return default val on failed
func Int64Or(in any, defVal int64) int64 {
i64, err := ToInt64WithFunc(in, nil)
if err != nil {
return defVal
}
return i64
}
// Int64OrErr convert value to int64, return error on failed
func Int64OrErr(in any) (int64, error) {
return ToInt64(in)
}
// ToInt64 convert string to int64, return error on failed
// ToInt64 convert value to int64, return error on failed
func ToInt64(in any) (i64 int64, err error) {
return ToInt64WithFunc(in, nil)
}
// ToInt64WithFunc convert value to int64, will call usrFn on value type not supported.
func ToInt64WithFunc(in any, usrFn ToInt64Func) (i64 int64, err error) {
switch tVal := in.(type) {
case nil:
i64 = 0
case string:
i64, err = strconv.ParseInt(strings.TrimSpace(tVal), 10, 0)
case int:
@@ -230,10 +347,14 @@ func ToInt64(in any) (i64 int64, err error) {
i64 = int64(tVal)
case time.Duration:
i64 = int64(tVal)
case json.Number:
case interface{ Int64() (int64, error) }: // eg: json.Number
i64, err = tVal.Int64()
default:
err = comdef.ErrConvType
if usrFn != nil {
i64, err = usrFn(in)
} else {
err = comdef.ErrConvType
}
}
return
}
@@ -242,42 +363,63 @@ func ToInt64(in any) (i64 int64, err error) {
* convert value to float
*************************************************************/
// QuietFloat convert value to float64, will ignore error
// QuietFloat convert value to float64, will ignore error. alias of SafeFloat
func QuietFloat(in any) float64 {
val, _ := ToFloat(in)
return SafeFloat(in)
}
// SafeFloat convert value to float64, will ignore error
func SafeFloat(in any) float64 {
val, _ := ToFloatWithFunc(in, nil)
return val
}
// FloatOrPanic convert value to float64, will panic on error
func FloatOrPanic(in any) float64 {
val, err := ToFloat(in)
return MustFloat(in)
}
// MustFloat convert value to float64, will panic on error
func MustFloat(in any) float64 {
val, err := ToFloatWithFunc(in, nil)
if err != nil {
panic(err)
}
return val
}
// MustFloat convert value to float64 TODO will panic on error
func MustFloat(in any) float64 {
val, _ := ToFloat(in)
// 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 := ToFloatWithFunc(in, nil)
if err != nil {
return defVal
}
return val
}
// Float convert value to float64, return error on failed
func Float(in any) (float64, error) {
return ToFloat(in)
return ToFloatWithFunc(in, nil)
}
// FloatOrErr convert value to float64, return error on failed
func FloatOrErr(in any) (float64, error) {
return ToFloat(in)
return ToFloatWithFunc(in, nil)
}
// ToFloat convert value to float64, return error on failed
func ToFloat(in any) (f64 float64, err error) {
return ToFloatWithFunc(in, nil)
}
// ToFloatWithFunc convert value to float64, will call usrFn if value type not supported.
func ToFloatWithFunc(in any, usrFn ToFloatFunc) (f64 float64, err error) {
switch tVal := in.(type) {
case nil:
f64 = 0
case string:
f64, err = strconv.ParseFloat(strings.TrimSpace(tVal), 64)
case int:
@@ -306,10 +448,14 @@ func ToFloat(in any) (f64 float64, err error) {
f64 = tVal
case time.Duration:
f64 = float64(tVal)
case json.Number:
case interface{ Float64() (float64, error) }: // eg: json.Number
f64, err = tVal.Float64()
default:
err = comdef.ErrConvType
if usrFn != nil {
f64, err = usrFn(in)
} else {
err = comdef.ErrConvType
}
}
return
}
@@ -318,34 +464,45 @@ func ToFloat(in any) (f64 float64, err error) {
* convert intX/floatX to string
*************************************************************/
// StringOrPanic convert intX/floatX value to string, will panic on error
func StringOrPanic(val any) string {
str, err := TryToString(val, true)
// MustString convert intX/floatX value to string, will panic on error
func MustString(val any) string {
str, err := ToStringWithFunc(val, nil)
if err != nil {
panic(err)
}
return str
}
// MustString convert intX/floatX value to string, will panic on error
func MustString(val any) string {
return StringOrPanic(val)
// 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 := ToStringWithFunc(val, nil)
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 TryToString(val, true)
return ToStringWithFunc(val, nil)
}
// StringOrErr convert intX/floatX value to string, return error on failed
func StringOrErr(val any) (string, error) {
return TryToString(val, true)
return ToStringWithFunc(val, nil)
}
// QuietString convert intX/floatX value to string, other type convert by fmt.Sprint
func QuietString(val any) string {
str, _ := TryToString(val, false)
return str
return SafeString(val)
}
// String convert intX/floatX value to string, other type convert by fmt.Sprint
@@ -354,14 +511,33 @@ func String(val any) string {
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) (str string, err error) {
if val == nil {
return
var usrFn comdef.ToStringFunc
if !defaultAsErr {
usrFn = func(v any) (string, error) {
if val == nil {
return "", nil
}
return fmt.Sprint(v), nil
}
}
return ToStringWithFunc(val, usrFn)
}
// ToStringWithFunc try convert intX/floatX value to string, will call usrFn if value type not supported.
//
// if defaultAsErr is False, will use fmt.Sprint convert other type
func ToStringWithFunc(val any, usrFn comdef.ToStringFunc) (str string, err error) {
switch value := val.(type) {
case int:
str = strconv.Itoa(value)
@@ -389,14 +565,31 @@ func TryToString(val any, defaultAsErr bool) (str string, err error) {
str = strconv.FormatFloat(value, 'f', -1, 64)
case time.Duration:
str = strconv.FormatInt(int64(value), 10)
case string:
str = value
case fmt.Stringer:
str = value.String()
default:
if defaultAsErr {
err = comdef.ErrConvType
if usrFn != nil {
str, err = usrFn(val)
} else {
str = fmt.Sprint(value)
err = comdef.ErrConvType
}
}
return
}
// Percent returns a values percent of the total
func Percent(val, total int) float64 {
if total == 0 {
return float64(0)
}
return (float64(val) / float64(total)) * 100
}
// ElapsedTime calc elapsed time 计算运行时间消耗 单位 ms(毫秒)
//
// Deprecated: use timex.ElapsedTime()
func ElapsedTime(startTime time.Time) string {
return fmt.Sprintf("%.3f", time.Since(startTime).Seconds()*1000)
}

View File

@@ -1,8 +1,6 @@
package basefn
package mathutil
import (
"fmt"
)
import "fmt"
// DataSize format bytes number friendly. eg: 1024 => 1KB, 1024*1024 => 1MB
//
@@ -33,14 +31,16 @@ var timeFormats = [][]int{
{3600},
{7200, 3600},
{86400},
{172800, 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 sec", "1 sec", "secs", "1 min", "mins", "1 hr", "hrs", "1 day", "days", "1 month", "months",
}
// HowLongAgo format a seconds, get how lang ago
// HowLongAgo format a seconds, get how lang ago. eg: 1 day, 1 week
func HowLongAgo(sec int64) string {
intVal := int(sec)
length := len(timeFormats)
@@ -63,8 +63,6 @@ func HowLongAgo(sec int64) string {
if len(item) == 1 {
return timeMessages[i]
}
// len is 2
return fmt.Sprintf("%d %s", intVal/item[1], timeMessages[i])
}
}

View File

@@ -2,84 +2,72 @@
package mathutil
import (
"math"
"github.com/gookit/goutil/comdef"
)
// Min compare two value and return max value
func Min[T comdef.XintOrFloat](x, y T) T {
if x < y {
return x
}
return y
// OrElse return default value on val is zero, else return val
func OrElse[T comdef.XintOrFloat](val, defVal T) T {
return ZeroOr(val, defVal)
}
// Max compare two value and return max value
func Max[T comdef.XintOrFloat](x, y T) T {
if x > y {
return x
// ZeroOr return default value on val is zero, else return val
func ZeroOr[T comdef.XintOrFloat](val, defVal T) T {
if val != 0 {
return val
}
return y
return defVal
}
// SwapMin compare and always return [min, max] value
func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) {
if x < y {
return x, y
// 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.XintOrFloat](val, max, devVal T) T {
if val < max {
return val
}
return y, x
return devVal
}
// SwapMax compare and always return [max, min] value
func SwapMax[T comdef.XintOrFloat](x, y T) (T, T) {
if x > y {
return x, y
// 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.XintOrFloat](val, max, devVal T) T {
if val <= max {
return val
}
return y, x
return devVal
}
// MaxInt compare and return max value
func MaxInt(x, y int) int {
if x > y {
return x
// 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.XintOrFloat](val, min, defVal T) T {
if val > min {
return val
}
return y
return defVal
}
// SwapMaxInt compare and return max, min value
func SwapMaxInt(x, y int) (int, int) {
if x > y {
return x, y
// 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.XintOrFloat](val, min, defVal T) T {
if val >= min {
return val
}
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 s OR nv(new-value) on s is empty
func OrElse[T comdef.XintOrFloat](in, nv T) T {
if in != 0 {
return in
}
return nv
return defVal
}

View File

@@ -1,37 +0,0 @@
package mathutil
import (
"fmt"
"time"
"github.com/gookit/goutil/basefn"
)
// IsNumeric returns true if the given character is a numeric, otherwise false.
func IsNumeric(c byte) bool {
return c >= '0' && c <= '9'
}
// Percent returns a values percent of the total
func Percent(val, total int) float64 {
if total == 0 {
return float64(0)
}
return (float64(val) / float64(total)) * 100
}
// ElapsedTime calc elapsed time 计算运行时间消耗 单位 ms(毫秒)
func ElapsedTime(startTime time.Time) string {
return fmt.Sprintf("%.3f", time.Since(startTime).Seconds()*1000)
}
// DataSize format value to data size string. eg: 1024 => 1KB, 1024*1024 => 1MB
// alias format.DataSize()
func DataSize(size uint64) string {
return basefn.DataSize(size)
}
// HowLongAgo calc time. alias format.HowLongAgo()
func HowLongAgo(sec int64) string {
return basefn.HowLongAgo(sec)
}

View File

@@ -13,8 +13,8 @@ import (
// RandomInt(100, 999)
// RandomInt(1000, 9999)
func RandomInt(min, max int) int {
rand.Seed(time.Now().UnixNano())
return min + rand.Intn(max-min)
rr := rand.New(rand.NewSource(time.Now().UnixNano()))
return min + rr.Intn(max-min)
}
// RandInt alias of RandomInt()
@@ -32,6 +32,6 @@ func RandIntWithSeed(min, max int, seed int64) int {
// seed := time.Now().UnixNano()
// RandomIntWithSeed(1000, 9999, seed)
func RandomIntWithSeed(min, max int, seed int64) int {
rand.Seed(seed)
return min + rand.Intn(max-min)
rr := rand.New(rand.NewSource(seed))
return min + rr.Intn(max-min)
}

View File

@@ -27,11 +27,16 @@ func IsSimpleKind(k reflect.Kind) bool {
return k > reflect.Invalid && k <= reflect.Float64
}
// IsAnyInt check is intX or uintX type
// 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
@@ -52,6 +57,17 @@ func IsNil(v reflect.Value) bool {
}
}
// 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
}
return false
}
// IsFunc value
func IsFunc(val any) bool {
if val == nil {
@@ -84,6 +100,9 @@ func IsEqual(src, dst any) bool {
return bytes.Equal(bs1, bs2)
}
// IsZero reflect value check, alias of the IsEmpty()
var IsZero = IsEmpty
// IsEmpty reflect value check
func IsEmpty(v reflect.Value) bool {
switch v.Kind() {
@@ -108,11 +127,17 @@ func IsEmpty(v reflect.Value) bool {
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}
// IsEmptyValue reflect value check.
// Difference the IsEmpty(), if value is ptr, will check real elem.
// 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 IsEmptyValue(v reflect.Value) bool {
func IsEmptyReal(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
@@ -128,11 +153,12 @@ func IsEmptyValue(v reflect.Value) bool {
if v.IsNil() {
return true
}
return IsEmptyValue(v.Elem())
return IsEmptyReal(v.Elem())
case reflect.Func:
return v.IsNil()
case reflect.Invalid:
return true
}
return false
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}

View File

@@ -2,6 +2,7 @@ package reflects
import (
"fmt"
"math"
"reflect"
"strconv"
@@ -12,13 +13,19 @@ import (
)
// BaseTypeVal convert custom type or intX,uintX,floatX to generic base type.
func BaseTypeVal(v reflect.Value) (value any, err error) {
return ToBaseVal(v)
}
// ToBaseVal convert custom type or intX,uintX,floatX to generic base type.
//
// intX/unitX => int64
// intX => int64
// unitX => uint64
// floatX => float64
// string => string
//
// returns int64,string,float or error
func BaseTypeVal(v reflect.Value) (value any, err error) {
func ToBaseVal(v reflect.Value) (value any, err error) {
v = reflect.Indirect(v)
switch v.Kind() {
@@ -27,7 +34,7 @@ func BaseTypeVal(v reflect.Value) (value any, err error) {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value = v.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
value = int64(v.Uint()) // always return int64
value = v.Uint() // always return int64
case reflect.Float32, reflect.Float64:
value = v.Float()
default:
@@ -36,14 +43,23 @@ func BaseTypeVal(v reflect.Value) (value any, err error) {
return
}
// ConvToType convert and create reflect.Value by give reflect.Type
func ConvToType(val any, typ reflect.Type) (rv reflect.Value, err error) {
return ValueByType(val, typ)
}
// ValueByType create reflect.Value by give reflect.Type
func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) {
// handle kind: string, bool, intX, uintX, floatX
if typ.Kind() == reflect.String || typ.Kind() <= reflect.Float64 {
return ValueByKind(val, typ.Kind())
return ConvToKind(val, typ.Kind())
}
newRv := reflect.ValueOf(val)
var ok bool
var newRv reflect.Value
if newRv, ok = val.(reflect.Value); !ok {
newRv = reflect.ValueOf(val)
}
// try auto convert slice type
if IsArrayOrSlice(newRv.Kind()) && IsArrayOrSlice(typ.Kind()) {
@@ -59,72 +75,116 @@ func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) {
return
}
// ValueByKind create reflect.Value by give reflect.Kind
// ValueByKind convert and create reflect.Value by give reflect.Kind
func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
return ConvToKind(val, kind)
}
// ConvToKind convert and create reflect.Value by give reflect.Kind
//
// TIPs:
//
// Only support kind: string, bool, intX, uintX, floatX
func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
func ConvToKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
if rv, ok := val.(reflect.Value); ok {
val = rv.Interface()
}
switch kind {
case reflect.Int:
if dstV, err1 := mathutil.ToInt(val); err1 == nil {
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
rv = reflect.ValueOf(dstV)
}
case reflect.Int8:
if dstV, err1 := mathutil.ToInt(val); err1 == nil {
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt8 {
return rv, fmt.Errorf("value overflow int8. val: %v", val)
}
rv = reflect.ValueOf(int8(dstV))
}
case reflect.Int16:
if dstV, err1 := mathutil.ToInt(val); err1 == nil {
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt16 {
return rv, fmt.Errorf("value overflow int16. val: %v", val)
}
rv = reflect.ValueOf(int16(dstV))
}
case reflect.Int32:
if dstV, err1 := mathutil.ToInt(val); err1 == nil {
var dstV int
if dstV, err = mathutil.ToInt(val); err == nil {
if dstV > math.MaxInt32 {
return rv, fmt.Errorf("value overflow int32. val: %v", val)
}
rv = reflect.ValueOf(int32(dstV))
}
case reflect.Int64:
if dstV, err1 := mathutil.ToInt64(val); err1 == nil {
var dstV int64
if dstV, err = mathutil.ToInt64(val); err == nil {
rv = reflect.ValueOf(dstV)
}
case reflect.Uint:
if dstV, err1 := mathutil.ToUint(val); err1 == nil {
var dstV uint64
if dstV, err = mathutil.ToUint(val); err == nil {
rv = reflect.ValueOf(uint(dstV))
}
case reflect.Uint8:
if dstV, err1 := mathutil.ToUint(val); err1 == nil {
var dstV uint64
if dstV, err = mathutil.ToUint(val); err == nil {
if dstV > math.MaxUint8 {
return rv, fmt.Errorf("value overflow uint8. val: %v", val)
}
rv = reflect.ValueOf(uint8(dstV))
}
case reflect.Uint16:
if dstV, err1 := mathutil.ToUint(val); err1 == nil {
var dstV uint64
if dstV, err = mathutil.ToUint(val); err == nil {
if dstV > math.MaxUint16 {
return rv, fmt.Errorf("value overflow uint16. val: %v", val)
}
rv = reflect.ValueOf(uint16(dstV))
}
case reflect.Uint32:
if dstV, err1 := mathutil.ToUint(val); err1 == nil {
var dstV uint64
if dstV, err = mathutil.ToUint(val); err == nil {
if dstV > math.MaxUint32 {
return rv, fmt.Errorf("value overflow uint32. val: %v", val)
}
rv = reflect.ValueOf(uint32(dstV))
}
case reflect.Uint64:
if dstV, err1 := mathutil.ToUint(val); err1 == nil {
var dstV uint64
if dstV, err = mathutil.ToUint(val); err == nil {
rv = reflect.ValueOf(dstV)
}
case reflect.Float32:
if dstV, err1 := mathutil.ToFloat(val); err1 == nil {
var dstV float64
if dstV, err = mathutil.ToFloat(val); err == nil {
if dstV > math.MaxFloat32 {
return rv, fmt.Errorf("value overflow float32. val: %v", val)
}
rv = reflect.ValueOf(float32(dstV))
}
case reflect.Float64:
if dstV, err1 := mathutil.ToFloat(val); err1 == nil {
var dstV float64
if dstV, err = mathutil.ToFloat(val); err == nil {
rv = reflect.ValueOf(dstV)
}
case reflect.String:
if dstV, err1 := strutil.ToString(val); err1 == nil {
rv = reflect.ValueOf(dstV)
} else {
err = err1
}
case reflect.Bool:
if bl, err := comfunc.ToBool(val); err == nil {
if bl, err1 := comfunc.ToBool(val); err1 == nil {
rv = reflect.ValueOf(bl)
} else {
err = err1
}
}
if !rv.IsValid() {
default:
err = comdef.ErrConvType
}
return

317
vendor/github.com/gookit/goutil/reflects/func.go generated vendored Normal file
View File

@@ -0,0 +1,317 @@
package reflects
import (
"errors"
"fmt"
"reflect"
"github.com/gookit/goutil/basefn"
)
// FuncX wrap a go func. represent a function
type FuncX struct {
CallOpt
// Name of func. eg: "MyFunc"
Name string
// rv is the `reflect.Value` of func
rv reflect.Value
rt reflect.Type
}
// NewFunc instance. param fn support func and reflect.Value
func NewFunc(fn any) *FuncX {
var ok bool
var rv reflect.Value
if rv, ok = fn.(reflect.Value); !ok {
rv = reflect.ValueOf(fn)
}
rv = indirectInterface(rv)
if !rv.IsValid() {
panic("input func is nil")
}
typ := rv.Type()
if typ.Kind() != reflect.Func {
basefn.Panicf("non-function of type: %s", typ)
}
return &FuncX{rv: rv, rt: typ}
}
// NumIn get the number of func input args
func (f *FuncX) NumIn() int {
return f.rt.NumIn()
}
// NumOut get the number of func output args
func (f *FuncX) NumOut() int {
return f.rt.NumOut()
}
// Call the function with given arguments.
//
// Usage:
//
// func main() {
// fn := func(a, b int) int {
// return a + b
// }
//
// fx := NewFunc(fn)
// ret, err := fx.Call(1, 2)
// fmt.Println(ret[0], err) // Output: 3 <nil>
// }
func (f *FuncX) Call(args ...any) ([]any, error) {
// convert args to []reflect.Value
argRvs := make([]reflect.Value, len(args))
for i, arg := range args {
argRvs[i] = reflect.ValueOf(arg)
}
ret, err := f.CallRV(argRvs)
if err != nil {
return nil, err
}
// convert ret to []any
rets := make([]any, len(ret))
for i, r := range ret {
rets[i] = r.Interface()
}
return rets, nil
}
// Call2 returns the result of evaluating the first argument as a function.
// The function must return 1 result, or 2 results, the second of which is an error.
//
// - Only support func with 1 or 2 return values: (val) OR (val, err)
// - Will check args and try convert input args to func args type.
func (f *FuncX) Call2(args ...any) (any, error) {
// convert args to []reflect.Value
argRvs := make([]reflect.Value, len(args))
for i, arg := range args {
argRvs[i] = reflect.ValueOf(arg)
}
if f.TypeChecker == nil {
f.TypeChecker = OneOrTwoOutChecker
}
// do call func
ret, err := Call(f.rv, argRvs, &f.CallOpt)
if err != nil {
return emptyValue, err
}
// func return like: (val, err)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0].Interface(), ret[1].Interface().(error)
}
return ret[0].Interface(), nil
}
// CallRV call the function with given reflect.Value arguments.
func (f *FuncX) CallRV(args []reflect.Value) ([]reflect.Value, error) {
return Call(f.rv, args, &f.CallOpt)
}
// WithTypeChecker set type checker
func (f *FuncX) WithTypeChecker(checker TypeCheckerFn) *FuncX {
f.TypeChecker = checker
return f
}
// WithEnhanceConv set enhance convert
func (f *FuncX) WithEnhanceConv() *FuncX {
f.EnhanceConv = true
return f
}
// String of func
func (f *FuncX) String() string {
return f.rt.String()
}
// TypeCheckerFn type checker func
type TypeCheckerFn func(typ reflect.Type) error
// CallOpt call options
type CallOpt struct {
// TypeChecker check func type before call func. eg: check return values
TypeChecker TypeCheckerFn
// EnhanceConv try to enhance auto convert args to func args type
// - support more type: string, int, uint, float, bool
EnhanceConv bool
}
// OneOrTwoOutChecker check func type. only allow 1 or 2 return values
//
// Allow func returns:
// - 1 return: (value)
// - 2 return: (value, error)
var OneOrTwoOutChecker = func(typ reflect.Type) error {
if !good1or2outFunc(typ) {
return errors.New("func allow with 1 result or 2 results where the second is an error")
}
return nil
}
//
// TIP:
// flow func refer from text/template package.
//
//
// reports whether the function or method has the right result signature.
func good1or2outFunc(typ reflect.Type) bool {
// We allow functions with 1 result or 2 results where the second is an error.
switch {
case typ.NumOut() == 1:
return true
case typ.NumOut() == 2 && typ.Out(1) == errorType:
return true
}
return false
}
// Call2 returns the result of evaluating the first argument as a function.
// The function must return 1 result, or 2 results, the second of which is an error.
//
// - Only support func with 1 or 2 return values: (val) OR (val, err)
// - Will check args and try convert input args to func args type.
func Call2(fn reflect.Value, args []reflect.Value) (reflect.Value, error) {
ret, err := Call(fn, args, &CallOpt{
TypeChecker: OneOrTwoOutChecker,
})
if err != nil {
return emptyValue, err
}
// func return like: (val, err)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0], ret[1].Interface().(error)
}
return ret[0], nil
}
// Call returns the result of evaluating the first argument as a function.
//
// - Will check args and try convert input args to func args type.
//
// from text/template/funcs.go#call
func Call(fn reflect.Value, args []reflect.Value, opt *CallOpt) ([]reflect.Value, error) {
fn = indirectInterface(fn)
if !fn.IsValid() {
return nil, fmt.Errorf("call of nil")
}
typ := fn.Type()
if typ.Kind() != reflect.Func {
return nil, fmt.Errorf("non-function of type %s", typ)
}
if opt == nil {
opt = &CallOpt{}
}
if opt.TypeChecker != nil {
if err := opt.TypeChecker(typ); err != nil {
return nil, err
}
}
numIn := typ.NumIn()
var dddType reflect.Type
if typ.IsVariadic() {
if len(args) < numIn-1 {
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
}
dddType = typ.In(numIn - 1).Elem()
} else {
if len(args) != numIn {
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
}
}
// Convert each arg to the type of the function's arg.
argv := make([]reflect.Value, len(args))
for i, arg := range args {
arg = indirectInterface(arg)
// Compute the expected type. Clumsy because of variadic.
argType := dddType
if !typ.IsVariadic() || i < numIn-1 {
argType = typ.In(i)
}
var err error
if argv[i], err = prepareArg(arg, argType, opt.EnhanceConv); err != nil {
return nil, fmt.Errorf("arg %d: %w", i, err)
}
}
return SafeCall(fn, argv)
}
// SafeCall2 runs fun.Call(args), and returns the resulting value and error, if
// any. If the call panics, the panic value is returned as an error.
//
// NOTE: Only support func with 1 or 2 return values: (val) OR (val, err)
//
// from text/template/funcs.go#safeCall
func SafeCall2(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) {
ret, err := SafeCall(fun, args)
if err != nil {
return reflect.Value{}, err
}
// func return like: (val, err)
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0], ret[1].Interface().(error)
}
return ret[0], nil
}
// SafeCall runs fun.Call(args), and returns the resulting values, or an error.
// If the call panics, the panic value is returned as an error.
func SafeCall(fun reflect.Value, args []reflect.Value) (ret []reflect.Value, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
ret = fun.Call(args)
return
}
// prepareArg checks if value can be used as an argument of type argType, and
// converts an invalid value to appropriate zero if possible.
func prepareArg(value reflect.Value, argType reflect.Type, enhanced bool) (reflect.Value, error) {
if !value.IsValid() {
if !CanBeNil(argType) {
return emptyValue, fmt.Errorf("value is nil; should be of type %s", argType)
}
value = reflect.Zero(argType)
}
if value.Type().AssignableTo(argType) {
return value, nil
}
// If the argument is an int-like type, and the value is an int-like type, auto-convert.
if IsIntLike(value.Kind()) && IsIntLike(argType.Kind()) && value.Type().ConvertibleTo(argType) {
value = value.Convert(argType)
return value, nil
}
// enhance convert value to argType, support more type: string, int, uint, float, bool
if enhanced {
return ValueByType(value.Interface(), argType)
}
return emptyValue, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
}

83
vendor/github.com/gookit/goutil/reflects/map.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package reflects
import (
"reflect"
"strconv"
)
// EachMap process any map data
func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) {
if fn == nil {
return
}
if mp.Kind() != reflect.Map {
panic("only allow map value data")
}
for _, key := range mp.MapKeys() {
fn(key, mp.MapIndex(key))
}
}
// EachStrAnyMap process any map data as string key and any value
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) {
EachMap(mp, func(key, val reflect.Value) {
fn(String(key), val.Interface())
})
}
// FlatFunc custom collect handle func
type FlatFunc func(path string, val reflect.Value)
// FlatMap process tree map to flat key-value map.
//
// Examples:
//
// {"top": {"sub": "value", "sub2": "value2"} }
// ->
// {"top.sub": "value", "top.sub2": "value2" }
func FlatMap(rv reflect.Value, fn FlatFunc) {
if fn == nil {
return
}
if rv.Kind() != reflect.Map {
panic("only allow flat map data")
}
flatMap(rv, fn, "")
}
func flatMap(rv reflect.Value, fn FlatFunc, parent string) {
for _, key := range rv.MapKeys() {
path := String(key)
if parent != "" {
path = parent + "." + path
}
fv := Indirect(rv.MapIndex(key))
switch fv.Kind() {
case reflect.Map:
flatMap(fv, fn, path)
case reflect.Array, reflect.Slice:
flatSlice(fv, fn, path)
default:
fn(path, fv)
}
}
}
func flatSlice(rv reflect.Value, fn FlatFunc, parent string) {
for i := 0; i < rv.Len(); i++ {
path := parent + "[" + strconv.Itoa(i) + "]"
fv := Indirect(rv.Index(i))
switch fv.Kind() {
case reflect.Map:
flatMap(fv, fn, path)
case reflect.Array, reflect.Slice:
flatSlice(fv, fn, path)
default:
fn(path, fv)
}
}
}

View File

@@ -1,2 +1,17 @@
// Package reflects Provide extends reflect util functions.
package reflects
import (
"fmt"
"reflect"
)
var emptyValue = reflect.Value{}
var (
anyType = reflect.TypeOf((*any)(nil)).Elem()
errorType = reflect.TypeOf((*error)(nil)).Elem()
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
)

61
vendor/github.com/gookit/goutil/reflects/slice.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
package reflects
import (
"fmt"
"reflect"
)
// MakeSliceByElem create a new slice by the element type.
//
// - elType: the type of the element.
// - returns: the new slice.
//
// Usage:
//
// sl := MakeSliceByElem(reflect.TypeOf(1), 10, 20)
// sl.Index(0).SetInt(10)
//
// // Or use reflect.AppendSlice() merge two slice
// // Or use `for` with `reflect.Append()` add elements
func MakeSliceByElem(elTyp reflect.Type, len, cap int) reflect.Value {
return reflect.MakeSlice(reflect.SliceOf(elTyp), len, cap)
}
// FlatSlice flatten multi-level slice to given depth-level slice.
//
// Example:
//
// FlatSlice([]any{ []any{3, 4}, []any{5, 6} }, 1) // Output: []any{3, 4, 5, 6}
//
// always return reflect.Value of []any. note: maybe flatSl.Cap != flatSl.Len
func FlatSlice(sl reflect.Value, depth int) reflect.Value {
items := make([]reflect.Value, 0, sl.Cap())
slCap := addSliceItem(sl, depth, func(item reflect.Value) {
items = append(items, item)
})
flatSl := reflect.MakeSlice(reflect.SliceOf(anyType), 0, slCap)
flatSl = reflect.Append(flatSl, items...)
return flatSl
}
func addSliceItem(sl reflect.Value, depth int, collector func(item reflect.Value)) (c int) {
for i := 0; i < sl.Len(); i++ {
v := Elem(sl.Index(i))
if depth > 0 {
if v.Kind() != reflect.Slice {
panic(fmt.Sprintf("depth: %d, the value of index %d is not slice", depth, i))
}
c += addSliceItem(v, depth-1, collector)
} else {
collector(v)
}
}
if depth == 0 {
c = sl.Cap()
}
return c
}

View File

@@ -2,21 +2,28 @@ package reflects
import "reflect"
// BKind base data kind type
type BKind uint
// BKind base data kind type, alias of reflect.Kind
//
// Diff with reflect.Kind:
// - Int contains all intX types
// - Uint contains all uintX types
// - Float contains all floatX types
// - Array for array and slice types
// - Complex contains all complexX types
type BKind = reflect.Kind
// base kinds
const (
// Int for all intX types
Int = BKind(reflect.Int)
Int = reflect.Int
// Uint for all uintX types
Uint = BKind(reflect.Uint)
Uint = reflect.Uint
// Float for all floatX types
Float = BKind(reflect.Float32)
Float = reflect.Float32
// Array for array,slice types
Array = BKind(reflect.Array)
Array = reflect.Array
// Complex for all complexX types
Complex = BKind(reflect.Complex64)
Complex = reflect.Complex64
)
// ToBaseKind convert reflect.Kind to base kind
@@ -39,7 +46,7 @@ func ToBKind(kind reflect.Kind) BKind {
return Array
default:
// like: string, map, struct, ptr, func, interface ...
return BKind(kind)
return kind
}
}
@@ -48,6 +55,10 @@ type Type interface {
reflect.Type
// BaseKind value
BaseKind() BKind
// RealType returns a ptr type's real type. otherwise, will return self.
RealType() reflect.Type
// SafeElem returns a type's element type. otherwise, will return self.
SafeElem() reflect.Type
}
type xType struct {
@@ -69,3 +80,13 @@ func TypeOf(v any) Type {
func (t *xType) BaseKind() BKind {
return t.baseKind
}
// RealType returns a ptr type's real type. otherwise, will return self.
func (t *xType) RealType() reflect.Type {
return TypeReal(t.Type)
}
// SafeElem returns the array, slice, chan, map type's element type. otherwise, will return self.
func (t *xType) SafeElem() reflect.Type {
return TypeElem(t.Type)
}

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