Merge pull request #753 from opencloud-eu/dependabot/go_modules/github.com/gookit/config/v2-2.2.6
build(deps): bump github.com/gookit/config/v2 from 2.2.5 to 2.2.6
This commit is contained in:
@@ -40,7 +40,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-tika v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/config/v2 v2.2.5
|
||||
github.com/gookit/config/v2 v2.2.6
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
|
||||
github.com/invopop/validation v0.8.0
|
||||
@@ -206,7 +206,7 @@ require (
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.11.2 // indirect
|
||||
github.com/goccy/go-yaml v1.12.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
@@ -219,7 +219,7 @@ require (
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/renameio/v2 v2.0.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gookit/goutil v0.6.15 // indirect
|
||||
github.com/gookit/goutil v0.6.18 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/schema v1.4.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
|
||||
@@ -438,8 +438,8 @@ github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
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/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
|
||||
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
@@ -552,10 +552,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gookit/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.5 h1:RECbYYbtherywmzn3LNeu9NA5ZqhD7MSKEMsJ7l+MpU=
|
||||
github.com/gookit/config/v2 v2.2.5/go.mod h1:NeX+yiNYn6Ei10eJvCQFXuHEPIE/IPS8bqaFIsszzaM=
|
||||
github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo=
|
||||
github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY=
|
||||
github.com/gookit/config/v2 v2.2.6 h1:8ZbkSr3gnFg1En8za9X3vldnZca3y3C7kaBLGsdLghE=
|
||||
github.com/gookit/config/v2 v2.2.6/go.mod h1:++APDf3Ebj6mjzW1ALkegvg1evQKyx4FpuQqQZ2s2WM=
|
||||
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
|
||||
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
||||
github.com/gookit/ini/v2 v2.2.3 h1:nSbN+x9OfQPcMObTFP+XuHt8ev6ndv/fWWqxFhPMu2E=
|
||||
github.com/gookit/ini/v2 v2.2.3/go.mod h1:Vu6p7P7xcfmb8KYu3L0ek8bqu/Im63N81q208SCCZY4=
|
||||
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
|
||||
|
||||
+49
-6
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -16,11 +15,12 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"github.com/goccy/go-yaml/internal/errors"
|
||||
"github.com/goccy/go-yaml/parser"
|
||||
"github.com/goccy/go-yaml/token"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Decoder reads and decodes YAML values from an input stream.
|
||||
@@ -488,6 +488,21 @@ func (d *Decoder) fileToNode(f *ast.File) ast.Node {
|
||||
func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node) (reflect.Value, error) {
|
||||
if typ.Kind() != reflect.String {
|
||||
if !v.Type().ConvertibleTo(typ) {
|
||||
|
||||
// Special case for "strings -> floats" aka scientific notation
|
||||
// If the destination type is a float and the source type is a string, check if we can
|
||||
// use strconv.ParseFloat to convert the string to a float.
|
||||
if (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) &&
|
||||
v.Type().Kind() == reflect.String {
|
||||
if f, err := strconv.ParseFloat(v.String(), 64); err == nil {
|
||||
if typ.Kind() == reflect.Float32 {
|
||||
return reflect.ValueOf(float32(f)), nil
|
||||
} else if typ.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(f), nil
|
||||
}
|
||||
// else, fall through to the error below
|
||||
}
|
||||
}
|
||||
return reflect.Zero(typ), errTypeMismatch(typ, v.Type(), src.GetToken())
|
||||
}
|
||||
return v.Convert(typ), nil
|
||||
@@ -877,6 +892,15 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
|
||||
dst.SetInt(int64(vv))
|
||||
return nil
|
||||
}
|
||||
case string: // handle scientific notation
|
||||
if i, err := strconv.ParseFloat(vv, 64); err == nil {
|
||||
if 0 <= i && i <= math.MaxUint64 && !dst.OverflowInt(int64(i)) {
|
||||
dst.SetInt(int64(i))
|
||||
return nil
|
||||
}
|
||||
} else { // couldn't be parsed as float
|
||||
return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
|
||||
}
|
||||
default:
|
||||
return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
|
||||
}
|
||||
@@ -899,6 +923,16 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
|
||||
dst.SetUint(uint64(vv))
|
||||
return nil
|
||||
}
|
||||
case string: // handle scientific notation
|
||||
if i, err := strconv.ParseFloat(vv, 64); err == nil {
|
||||
if 0 <= i && i <= math.MaxUint64 && !dst.OverflowUint(uint64(i)) {
|
||||
dst.SetUint(uint64(i))
|
||||
return nil
|
||||
}
|
||||
} else { // couldn't be parsed as float
|
||||
return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
|
||||
}
|
||||
|
||||
default:
|
||||
return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
|
||||
}
|
||||
@@ -1501,10 +1535,19 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node
|
||||
}
|
||||
continue
|
||||
}
|
||||
k := reflect.ValueOf(d.nodeToValue(key))
|
||||
if k.IsValid() && k.Type().ConvertibleTo(keyType) {
|
||||
k = k.Convert(keyType)
|
||||
|
||||
k := d.createDecodableValue(keyType)
|
||||
if d.canDecodeByUnmarshaler(k) {
|
||||
if err := d.decodeByUnmarshaler(ctx, k, key); err != nil {
|
||||
return errors.Wrapf(err, "failed to decode by unmarshaler")
|
||||
}
|
||||
} else {
|
||||
k = reflect.ValueOf(d.nodeToValue(key))
|
||||
if k.IsValid() && k.Type().ConvertibleTo(keyType) {
|
||||
k = k.Convert(keyType)
|
||||
}
|
||||
}
|
||||
|
||||
if k.IsValid() {
|
||||
if err := d.validateDuplicateKey(keyMap, k.Interface(), key); err != nil {
|
||||
return errors.Wrapf(err, "invalid map key")
|
||||
@@ -1621,7 +1664,7 @@ func (d *Decoder) resolveReference() error {
|
||||
}
|
||||
}
|
||||
for _, reader := range d.referenceReaders {
|
||||
bytes, err := ioutil.ReadAll(reader)
|
||||
bytes, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read buffer")
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
@@ -730,7 +730,7 @@ func Parse(tokens token.Tokens, mode Mode) (*ast.File, error) {
|
||||
|
||||
// Parse parse from filename, and returns ast.File
|
||||
func ParseFile(filename string, mode Mode) (*ast.File, error) {
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
file, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read file: %s", filename)
|
||||
}
|
||||
|
||||
+1
-1
@@ -468,7 +468,7 @@ func (n *rootNode) String() string {
|
||||
|
||||
func (n *rootNode) filter(node ast.Node) (ast.Node, error) {
|
||||
if n.child == nil {
|
||||
return nil, nil
|
||||
return node, nil
|
||||
}
|
||||
filtered, err := n.child.filter(node)
|
||||
if err != nil {
|
||||
|
||||
+10
-3
@@ -196,9 +196,16 @@ func (c *Context) existsBuffer() bool {
|
||||
|
||||
func (c *Context) bufferedSrc() []rune {
|
||||
src := c.buf[:c.notSpaceCharPos]
|
||||
if len(src) > 0 && src[len(src)-1] == '\n' && c.isDocument() && c.literalOpt == "-" {
|
||||
// remove end '\n' character
|
||||
src = src[:len(src)-1]
|
||||
if c.isDocument() && c.literalOpt == "-" {
|
||||
// remove end '\n' character and trailing empty lines
|
||||
// https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator
|
||||
for {
|
||||
if len(src) > 0 && src[len(src)-1] == '\n' {
|
||||
src = src[:len(src)-1]
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
+98
-103
@@ -4,8 +4,9 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml/token"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/goccy/go-yaml/token"
|
||||
)
|
||||
|
||||
// IndentState state for indent
|
||||
@@ -316,100 +317,93 @@ func (s *Scanner) scanDoubleQuote(ctx *Context) (tk *token.Token, pos int) {
|
||||
continue
|
||||
} else if c == '\\' {
|
||||
isFirstLineChar = false
|
||||
if idx+1 < size {
|
||||
nextChar := src[idx+1]
|
||||
switch nextChar {
|
||||
case 'b':
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\b')
|
||||
idx++
|
||||
continue
|
||||
case 'e':
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\x1B')
|
||||
idx++
|
||||
continue
|
||||
case 'f':
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\f')
|
||||
idx++
|
||||
continue
|
||||
case 'n':
|
||||
ctx.addOriginBuf(nextChar)
|
||||
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')
|
||||
idx++
|
||||
continue
|
||||
case 'L': // LS (#x2028)
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xE2', '\x80', '\xA8'}...)
|
||||
idx++
|
||||
continue
|
||||
case 'N': // NEL (#x85)
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xC2', '\x85'}...)
|
||||
idx++
|
||||
continue
|
||||
case 'P': // PS (#x2029)
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xE2', '\x80', '\xA9'}...)
|
||||
idx++
|
||||
continue
|
||||
case '_': // #xA0
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xC2', '\xA0'}...)
|
||||
idx++
|
||||
continue
|
||||
case '"':
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, nextChar)
|
||||
idx++
|
||||
continue
|
||||
case 'x':
|
||||
if idx+3 >= size {
|
||||
// TODO: need to return error
|
||||
//err = xerrors.New("invalid escape character \\x")
|
||||
return
|
||||
}
|
||||
codeNum := hexRunesToInt(src[idx+2 : idx+4])
|
||||
value = append(value, rune(codeNum))
|
||||
idx += 3
|
||||
continue
|
||||
case 'u':
|
||||
if idx+5 >= size {
|
||||
// TODO: need to return error
|
||||
//err = xerrors.New("invalid escape character \\u")
|
||||
return
|
||||
}
|
||||
codeNum := hexRunesToInt(src[idx+2 : idx+6])
|
||||
value = append(value, rune(codeNum))
|
||||
idx += 5
|
||||
continue
|
||||
case 'U':
|
||||
if idx+9 >= size {
|
||||
// TODO: need to return error
|
||||
//err = xerrors.New("invalid escape character \\U")
|
||||
return
|
||||
}
|
||||
codeNum := hexRunesToInt(src[idx+2 : idx+10])
|
||||
value = append(value, rune(codeNum))
|
||||
idx += 9
|
||||
continue
|
||||
case '\\':
|
||||
ctx.addOriginBuf(nextChar)
|
||||
idx++
|
||||
}
|
||||
if idx+1 >= size {
|
||||
value = append(value, c)
|
||||
continue
|
||||
}
|
||||
value = append(value, c)
|
||||
nextChar := src[idx+1]
|
||||
progress := 0
|
||||
switch nextChar {
|
||||
case 'b':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\b')
|
||||
case 'e':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\x1B')
|
||||
case 'f':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\f')
|
||||
case 'n':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\n')
|
||||
case 'r':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\r')
|
||||
case 'v':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, '\v')
|
||||
case 'L': // LS (#x2028)
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xE2', '\x80', '\xA8'}...)
|
||||
case 'N': // NEL (#x85)
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xC2', '\x85'}...)
|
||||
case 'P': // PS (#x2029)
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xE2', '\x80', '\xA9'}...)
|
||||
case '_': // #xA0
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, []rune{'\xC2', '\xA0'}...)
|
||||
case '"':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, nextChar)
|
||||
case 'x':
|
||||
progress = 3
|
||||
if idx+progress >= size {
|
||||
// TODO: need to return error
|
||||
//err = xerrors.New("invalid escape character \\x")
|
||||
return
|
||||
}
|
||||
codeNum := hexRunesToInt(src[idx+2 : idx+progress+1])
|
||||
value = append(value, rune(codeNum))
|
||||
case 'u':
|
||||
progress = 5
|
||||
if idx+progress >= size {
|
||||
// TODO: need to return error
|
||||
//err = xerrors.New("invalid escape character \\u")
|
||||
return
|
||||
}
|
||||
codeNum := hexRunesToInt(src[idx+2 : idx+progress+1])
|
||||
value = append(value, rune(codeNum))
|
||||
case 'U':
|
||||
progress = 9
|
||||
if idx+progress >= size {
|
||||
// TODO: need to return error
|
||||
//err = xerrors.New("invalid escape character \\U")
|
||||
return
|
||||
}
|
||||
codeNum := hexRunesToInt(src[idx+2 : idx+progress+1])
|
||||
value = append(value, rune(codeNum))
|
||||
case '\\':
|
||||
progress = 1
|
||||
ctx.addOriginBuf(nextChar)
|
||||
value = append(value, c)
|
||||
default:
|
||||
value = append(value, c)
|
||||
}
|
||||
idx += progress
|
||||
s.progressColumn(ctx, progress)
|
||||
continue
|
||||
} else if c != '"' {
|
||||
value = append(value, c)
|
||||
@@ -621,6 +615,16 @@ func (s *Scanner) scanNewLine(ctx *Context, c rune) {
|
||||
}
|
||||
}
|
||||
|
||||
// There is no problem that we ignore CR which followed by LF and normalize it to LF, because of following YAML1.2 spec.
|
||||
// > Line breaks inside scalar content must be normalized by the YAML processor. Each such line break must be parsed into a single line feed character.
|
||||
// > Outside scalar content, YAML allows any line break to be used to terminate lines.
|
||||
// > -- https://yaml.org/spec/1.2/spec.html
|
||||
if c == '\r' && ctx.nextChar() == '\n' {
|
||||
ctx.addOriginBuf('\r')
|
||||
ctx.progress(1)
|
||||
c = '\n'
|
||||
}
|
||||
|
||||
if ctx.isEOS() {
|
||||
s.addBufferedTokenIfExists(ctx)
|
||||
} else if s.isAnchor {
|
||||
@@ -840,15 +844,6 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
|
||||
return
|
||||
}
|
||||
case '\r', '\n':
|
||||
// There is no problem that we ignore CR which followed by LF and normalize it to LF, because of following YAML1.2 spec.
|
||||
// > Line breaks inside scalar content must be normalized by the YAML processor. Each such line break must be parsed into a single line feed character.
|
||||
// > Outside scalar content, YAML allows any line break to be used to terminate lines.
|
||||
// > -- https://yaml.org/spec/1.2/spec.html
|
||||
if c == '\r' && ctx.nextChar() == '\n' {
|
||||
ctx.addOriginBuf('\r')
|
||||
ctx.progress(1)
|
||||
c = '\n'
|
||||
}
|
||||
s.scanNewLine(ctx, c)
|
||||
continue
|
||||
case ' ':
|
||||
|
||||
+1
-1
@@ -623,7 +623,7 @@ func IsNeedQuoted(value string) bool {
|
||||
}
|
||||
first := value[0]
|
||||
switch first {
|
||||
case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"', '@', ' ':
|
||||
case '*', '&', '[', '{', '}', ']', ',', '!', '|', '>', '%', '\'', '"', '@', ' ', '`':
|
||||
return true
|
||||
}
|
||||
last := value[len(value)-1]
|
||||
|
||||
+49
-16
@@ -231,15 +231,57 @@ name = config.String("name")
|
||||
fmt.Print(name) // "new name"
|
||||
```
|
||||
|
||||
## Load from flags
|
||||
## Load from ENV
|
||||
|
||||
> Support simple flags parameter parsing, loading
|
||||
Support load ENV vars to config data.
|
||||
|
||||
- Support set value to sub key in map.
|
||||
- eg: `{"DB_USERNAME": "db.username"}` value will set to `username` in `db`
|
||||
|
||||
```go
|
||||
// flags like: --name inhere --env dev --age 99 --debug
|
||||
// os env: APP_NAME=config APP_DEBUG=true DB_USERNAME=someone
|
||||
|
||||
// load ENV info
|
||||
config.LoadOSEnvs(map[string]string{"APP_NAME": "app_name", "APP_DEBUG": "app_debug", "DB_USERNAME": "db.username"})
|
||||
|
||||
// read
|
||||
config.Bool("app_debug") // true
|
||||
config.String("app_name") // "config"
|
||||
```
|
||||
|
||||
## Load from flags
|
||||
|
||||
Support simple CLI flags parameter parsing, load to config data.
|
||||
|
||||
- define format: `name:type:desc` OR `name:type` OR `name:desc` (type, desc is optional)
|
||||
- `type` can set `flag` type. allow: `bool`, `int`, `string`(default)
|
||||
- `desc` can set `flag` description
|
||||
- `name` can be in key path format.
|
||||
- eg: `db.username`, input: `--db.username=someone` values will be mapped to `username` of the `db` configuration
|
||||
|
||||
```go
|
||||
// 'debug' flag is bool type
|
||||
config.LoadFlags([]string{"env", "debug:bool"})
|
||||
// can with flag desc message
|
||||
config.LoadFlags([]string{"env:set the run env"})
|
||||
config.LoadFlags([]string{"debug:bool:set debug mode"})
|
||||
// can set value to map key. eg: myapp --map1.sub-key=val
|
||||
config.LoadFlags([]string{"map1.sub-key"})
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
// flags like: --name inhere --env dev --age 99 --debug --map1.sub-key=val
|
||||
|
||||
// load flag info
|
||||
keys := []string{"name", "env", "age:int" "debug:bool"}
|
||||
keys := []string{
|
||||
"name",
|
||||
"env:set the run env",
|
||||
"age:int",
|
||||
"debug:bool:set debug mode",
|
||||
"map1.sub-key",
|
||||
}
|
||||
err := config.LoadFlags(keys)
|
||||
|
||||
// read
|
||||
@@ -247,18 +289,7 @@ config.String("name") // "inhere"
|
||||
config.String("env") // "dev"
|
||||
config.Int("age") // 99
|
||||
config.Bool("debug") // true
|
||||
```
|
||||
|
||||
## Load from ENV
|
||||
|
||||
```go
|
||||
// os env: APP_NAME=config APP_DEBUG=true
|
||||
// load ENV info
|
||||
config.LoadOSEnvs(map[string]string{"APP_NAME": "app_name", "APP_DEBUG": "app_debug"})
|
||||
|
||||
// read
|
||||
config.Bool("app_debug") // true
|
||||
config.String("app_name") // "config"
|
||||
config.Get("map1") // map[string]any{"sub-key":"val"}
|
||||
```
|
||||
|
||||
## New config instance
|
||||
@@ -377,6 +408,8 @@ type Options struct {
|
||||
}
|
||||
```
|
||||
|
||||
> **TIP**: please visit https://pkg.go.dev/github.com/gookit/config/v2#Options to see the latest options information
|
||||
|
||||
Examples for set options:
|
||||
|
||||
```go
|
||||
|
||||
+41
-4
@@ -220,8 +220,19 @@ name = config.String("name")
|
||||
fmt.Print(name) // new name
|
||||
```
|
||||
|
||||
## 加载配置文件
|
||||
|
||||
- `LoadExists(sourceFiles ...string) (err error)` 从存在的配置文件里加载数据,会忽略不存在的文件
|
||||
- `LoadFiles(sourceFiles ...string) (err error)` 从给定的配置文件里加载数据,有文件不存在则会panic
|
||||
|
||||
> **TIP**: 更多加载方式请查看 `config.Load*` 相关方法
|
||||
|
||||
## 从ENV载入数据
|
||||
|
||||
`LoadOSEnvs` 支持从环境变量中读取数据,并解析为配置数据。格式为 `ENV_NAME: config_key`
|
||||
|
||||
- `config_key` 可以是 key path 格式。 eg: `{"DB_USERNAME": "db.username"}` 值将会映射到 `db` 配置的 `username`
|
||||
|
||||
```go
|
||||
// os env: APP_NAME=config APP_DEBUG=true
|
||||
// load ENV info
|
||||
@@ -234,13 +245,36 @@ config.String("app_name") // "config"
|
||||
|
||||
## 从命令行参数载入数据
|
||||
|
||||
支持简单的从命令行 `flag` 参数解析,加载数据
|
||||
支持简单的从命令行 `flag` 参数解析,加载数据。
|
||||
|
||||
- 配置参数格式为 `name:type:desc` OR `name:type` OR `name:desc` (type, desc 是可选的)
|
||||
- `type` 可以设置 `flag` 的类型,支持 `bool`, `int`, `string`(默认)
|
||||
- `desc` 可以设置 `flag` 的描述信息
|
||||
- `name` 可以是 key path 格式。 eg: `db.username`, input: `--db.username=someone` 值将会映射到 `db` 配置的 `username`
|
||||
|
||||
```go
|
||||
// flags like: --name inhere --env dev --age 99 --debug
|
||||
// 'debug' flag is bool type
|
||||
config.LoadFlags([]string{"env", "debug:bool"})
|
||||
// can with flag desc message
|
||||
config.LoadFlags([]string{"env:set the run env"})
|
||||
config.LoadFlags([]string{"debug:bool:set debug mode"})
|
||||
// can set value to map key. eg: myapp --map1.sub-key=val
|
||||
config.LoadFlags([]string{"map1.sub-key"})
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
// flags like: --name inhere --env dev --age 99 --debug --map1.sub-key=val
|
||||
|
||||
// load flag info
|
||||
keys := []string{"name", "env", "age:int" "debug:bool"}
|
||||
keys := []string{
|
||||
"name",
|
||||
"env:set the run env",
|
||||
"age:int",
|
||||
"debug:bool:set debug mode",
|
||||
"map1.sub-key",
|
||||
}
|
||||
err := config.LoadFlags(keys)
|
||||
|
||||
// read
|
||||
@@ -248,6 +282,7 @@ config.String("name") // "inhere"
|
||||
config.String("env") // "dev"
|
||||
config.Int("age") // 99
|
||||
config.Bool("debug") // true
|
||||
config.Get("map1") // map[string]any{"sub-key":"val"}
|
||||
```
|
||||
|
||||
## 创建自定义实例
|
||||
@@ -366,6 +401,8 @@ type Options struct {
|
||||
}
|
||||
```
|
||||
|
||||
> **提示**: 访问 https://pkg.go.dev/github.com/gookit/config/v2#Options 查看最新的选项信息
|
||||
|
||||
Examples for set options:
|
||||
|
||||
```go
|
||||
@@ -417,7 +454,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值
|
||||
|
||||
- `LoadData(dataSource ...any) (err error)` 从struct或map加载数据
|
||||
- `LoadFlags(keys []string) (err error)` 从命令行参数载入数据
|
||||
- `LoadOSEnvs(nameToKeyMap map[string]string)` 从ENV载入数据
|
||||
- `LoadOSEnvs(nameToKeyMap map[string]string)` 从ENV载入配置数据
|
||||
- `LoadExists(sourceFiles ...string) (err error)` 从存在的配置文件里加载数据,会忽略不存在的文件
|
||||
- `LoadFiles(sourceFiles ...string) (err error)` 从给定的配置文件里加载数据,有文件不存在则会panic
|
||||
- `LoadFromDir(dirPath, format string) (err error)` 从给定目录里加载自定格式的文件,文件名会作为 key
|
||||
|
||||
+7
@@ -116,6 +116,13 @@ func New(name string, opts ...OptionFn) *Config {
|
||||
return NewEmpty(name, opts...).WithDriver(JSONDriver)
|
||||
}
|
||||
|
||||
// NewGeneric create generic config instance with custom options.
|
||||
//
|
||||
// - default add options: ParseEnv, ParseDefault, ParseTime
|
||||
func NewGeneric(name string, opts ...OptionFn) *Config {
|
||||
return NewEmpty(name, ParseEnv, ParseDefault, ParseTime).WithOptions(opts...).WithDriver(JSONDriver)
|
||||
}
|
||||
|
||||
// NewEmpty create config instance with custom options
|
||||
func NewEmpty(name string, opts ...OptionFn) *Config {
|
||||
c := &Config{
|
||||
|
||||
+27
-14
@@ -108,18 +108,19 @@ func (c *Config) LoadOSEnv(keys []string, keyToLower bool) {
|
||||
c.fireHook(OnLoadData)
|
||||
}
|
||||
|
||||
// LoadOSEnvs load data from OS ENVs. format: {ENV_NAME: config_key}
|
||||
// LoadOSEnvs load data from OS ENVs. see Config.LoadOSEnvs
|
||||
func LoadOSEnvs(nameToKeyMap map[string]string) { dc.LoadOSEnvs(nameToKeyMap) }
|
||||
|
||||
// LoadOSEnvs load data from os ENVs. format: {ENV_NAME: config_key}
|
||||
// LoadOSEnvs load data from os ENVs. format: `{ENV_NAME: config_key}`
|
||||
//
|
||||
// - `config_key` allow use key path. eg: `{"DB_USERNAME": "db.username"}`
|
||||
func (c *Config) LoadOSEnvs(nameToKeyMap map[string]string) {
|
||||
for name, key := range nameToKeyMap {
|
||||
for name, cfgKey := range nameToKeyMap {
|
||||
if val := os.Getenv(name); val != "" {
|
||||
if key == "" {
|
||||
key = strings.ToLower(name)
|
||||
if cfgKey == "" {
|
||||
cfgKey = strings.ToLower(name)
|
||||
}
|
||||
|
||||
_ = c.Set(key, val)
|
||||
_ = c.Set(cfgKey, val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +136,8 @@ var validTypes = map[string]int{
|
||||
"string": 1,
|
||||
}
|
||||
|
||||
// LoadFlags load data from cli flags
|
||||
func LoadFlags(keys []string) error { return dc.LoadFlags(keys) }
|
||||
// LoadFlags load data from cli flags. see Config.LoadFlags
|
||||
func LoadFlags(defines []string) error { return dc.LoadFlags(defines) }
|
||||
|
||||
// LoadFlags parse command line arguments, based on provide keys.
|
||||
//
|
||||
@@ -144,13 +145,20 @@ func LoadFlags(keys []string) error { return dc.LoadFlags(keys) }
|
||||
//
|
||||
// // 'debug' flag is bool type
|
||||
// c.LoadFlags([]string{"env", "debug:bool"})
|
||||
func (c *Config) LoadFlags(keys []string) (err error) {
|
||||
// // can with flag desc message
|
||||
// c.LoadFlags([]string{"env:set the run env"})
|
||||
// c.LoadFlags([]string{"debug:bool:set debug mode"})
|
||||
// // can set value to map key. eg: myapp --map1.sub-key=val
|
||||
// c.LoadFlags([]string{"--map1.sub-key"})
|
||||
func (c *Config) LoadFlags(defines []string) (err error) {
|
||||
hash := map[string]int8{}
|
||||
|
||||
// bind vars
|
||||
for _, key := range keys {
|
||||
key, typ := parseVarNameAndType(key)
|
||||
desc := "config flag " + key
|
||||
for _, str := range defines {
|
||||
key, typ, desc := parseVarNameAndType(str)
|
||||
if desc == "" {
|
||||
desc = "config flag " + key
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case "int":
|
||||
@@ -181,7 +189,12 @@ func (c *Config) LoadFlags(keys []string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
_ = c.Set(name, f.Value.String()) // ignore error
|
||||
// if f.Value implement the flag.Getter, read typed value
|
||||
if gtr, ok := f.Value.(flag.Getter); ok {
|
||||
_ = c.Set(name, gtr.Get())
|
||||
// } else { // TIP: basic type flag always implements Getter interface
|
||||
// _ = c.Set(name, f.Value.String()) // ignore error
|
||||
}
|
||||
})
|
||||
|
||||
c.fireHook(OnLoadData)
|
||||
|
||||
+21
-12
@@ -22,35 +22,44 @@ type HookFunc func(event string, c *Config)
|
||||
|
||||
// Options config options
|
||||
type Options struct {
|
||||
// ParseEnv parse env in string value and default value. like: "${EnvName}" "${EnvName|default}"
|
||||
// ParseEnv parse env in string value and default value. default: false
|
||||
//
|
||||
// - like: "${EnvName}" "${EnvName|default}"
|
||||
ParseEnv bool
|
||||
// ParseTime parses a duration string to time.Duration
|
||||
// ParseTime parses a duration string to `time.Duration`. default: false
|
||||
//
|
||||
// eg: 10s, 2m
|
||||
ParseTime bool
|
||||
// Readonly config is readonly
|
||||
Readonly bool
|
||||
// ParseDefault tag on binding data to struct. tag: default
|
||||
// ParseDefault tag on binding data to struct. default: false
|
||||
//
|
||||
// - tag: default
|
||||
ParseDefault bool
|
||||
// EnableCache enable config data cache
|
||||
// Readonly config is readonly. default: false
|
||||
Readonly bool
|
||||
// EnableCache enable config data cache. default: false
|
||||
EnableCache bool
|
||||
// ParseKey parse key path, allow find value by key path. eg: 'key.sub' will find `map[key]sub`
|
||||
// ParseKey support key path, allow find value by key path. default: true
|
||||
//
|
||||
// - eg: 'key.sub' will find `map[key]sub`
|
||||
ParseKey bool
|
||||
// TagName tag name for binding data to struct
|
||||
//
|
||||
// Deprecated: please set tag name by DecoderConfig, or use SetTagName()
|
||||
TagName string
|
||||
// Delimiter the delimiter char for split key path, if `FindByPath=true`. default is '.'
|
||||
// Delimiter the delimiter char for split key path, on `ParseKey=true`.
|
||||
//
|
||||
// - default is '.'
|
||||
Delimiter byte
|
||||
// DumpFormat default write format
|
||||
// DumpFormat default write format. default is 'json'
|
||||
DumpFormat string
|
||||
// ReadFormat default input format
|
||||
// ReadFormat default input format. default is 'json'
|
||||
ReadFormat string
|
||||
// DecoderConfig setting for binding data to struct. such as: TagName
|
||||
DecoderConfig *mapstructure.DecoderConfig
|
||||
// HookFunc on data changed. you can do something...
|
||||
HookFunc HookFunc
|
||||
// MergeOptions settings for merge two data
|
||||
MergeOptions []func(*mergo.Config)
|
||||
// HookFunc on data changed. you can do something...
|
||||
HookFunc HookFunc
|
||||
// WatchChange bool
|
||||
}
|
||||
|
||||
|
||||
+9
-8
@@ -99,10 +99,10 @@ func (c *Config) Data() map[string]any {
|
||||
return c.data
|
||||
}
|
||||
|
||||
// Sub return sub config data by key
|
||||
// Sub return a map config data by key
|
||||
func Sub(key string) map[string]any { return dc.Sub(key) }
|
||||
|
||||
// Sub get sub config data by key
|
||||
// Sub get a map config data by key
|
||||
//
|
||||
// Note: will don't apply any options, like ParseEnv
|
||||
func (c *Config) Sub(key string) map[string]any {
|
||||
@@ -127,23 +127,24 @@ func (c *Config) Keys() []string {
|
||||
}
|
||||
|
||||
// Get config value by key string, support get sub-value by key path(eg. 'map.key'),
|
||||
//
|
||||
// - ok is true, find value from config
|
||||
// - ok is false, not found or error
|
||||
func Get(key string, findByPath ...bool) any { return dc.Get(key, findByPath...) }
|
||||
|
||||
// Get config value by key
|
||||
// Get config value by key, findByPath default is true.
|
||||
func (c *Config) Get(key string, findByPath ...bool) any {
|
||||
val, _ := c.GetValue(key, findByPath...)
|
||||
return val
|
||||
}
|
||||
|
||||
// GetValue get value by given key string.
|
||||
// GetValue get value by given key string. findByPath default is true.
|
||||
func GetValue(key string, findByPath ...bool) (any, bool) {
|
||||
return dc.GetValue(key, findByPath...)
|
||||
}
|
||||
|
||||
// GetValue get value by given key string.
|
||||
// GetValue get value by given key string. findByPath default is true.
|
||||
//
|
||||
// Return:
|
||||
// - ok is true, find value from config
|
||||
// - ok is false, not found or error
|
||||
func (c *Config) GetValue(key string, findByPath ...bool) (value any, ok bool) {
|
||||
sep := c.opts.Delimiter
|
||||
if key = formatKey(key, string(sep)); key == "" {
|
||||
|
||||
+11
-3
@@ -134,20 +134,28 @@ func Getenv(name string, defVal ...string) (val string) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseVarNameAndType(key string) (string, string) {
|
||||
func parseVarNameAndType(key string) (string, string, string) {
|
||||
var desc string
|
||||
typ := "string"
|
||||
key = strings.Trim(key, "-")
|
||||
|
||||
// can set var type: int, uint, bool
|
||||
if strings.IndexByte(key, ':') > 0 {
|
||||
list := strings.SplitN(key, ":", 2)
|
||||
list := strings.SplitN(key, ":", 3)
|
||||
key, typ = list[0], list[1]
|
||||
if len(list) == 3 {
|
||||
desc = list[2]
|
||||
}
|
||||
|
||||
// if type is not valid and has multi words, as desc message.
|
||||
if _, ok := validTypes[typ]; !ok {
|
||||
if desc == "" && strings.ContainsRune(typ, ' ') {
|
||||
desc = typ
|
||||
}
|
||||
typ = "string"
|
||||
}
|
||||
}
|
||||
return key, typ
|
||||
return key, typ, desc
|
||||
}
|
||||
|
||||
// format key
|
||||
|
||||
+2
-2
@@ -28,12 +28,12 @@ func (c *Config) SetData(data map[string]any) {
|
||||
c.fireHook(OnSetData)
|
||||
}
|
||||
|
||||
// Set val by key
|
||||
// Set value by key. setByPath default is true
|
||||
func Set(key string, val any, setByPath ...bool) error {
|
||||
return dc.Set(key, val, setByPath...)
|
||||
}
|
||||
|
||||
// Set a value by key string.
|
||||
// Set a value by key string. setByPath default is true
|
||||
func (c *Config) Set(key string, val any, setByPath ...bool) (err error) {
|
||||
if c.opts.Readonly {
|
||||
return ErrReadonly
|
||||
|
||||
+1
@@ -19,5 +19,6 @@
|
||||
*.cov
|
||||
.DS_Store
|
||||
|
||||
*~
|
||||
testdata/
|
||||
vendor/
|
||||
+1
@@ -1417,6 +1417,7 @@ func BlankOr(val, defVal string) string
|
||||
func ZeroOr[T ~string](val, defVal T) T
|
||||
func ErrorOr(s string, err error, defVal string) string
|
||||
func OrElse(s, orVal string) string
|
||||
func OrElseNilSafe(s *string, orVal string) string
|
||||
func OrHandle(s string, fn comdef.StringHandleFunc) string
|
||||
func Valid(ss ...string) string
|
||||
func Replaces(str string, pairs map[string]string) string
|
||||
|
||||
+1
@@ -1418,6 +1418,7 @@ func BlankOr(val, defVal string) string
|
||||
func ZeroOr[T ~string](val, defVal T) T
|
||||
func ErrorOr(s string, err error, defVal string) string
|
||||
func OrElse(s, orVal string) string
|
||||
func OrElseNilSafe(s *string, orVal string) string
|
||||
func OrHandle(s string, fn comdef.StringHandleFunc) string
|
||||
func Valid(ss ...string) string
|
||||
func Replaces(str string, pairs map[string]string) string
|
||||
|
||||
+3
-3
@@ -135,13 +135,13 @@ func Differences[T any](first, second []T, fn Comparer[T]) []T {
|
||||
return CloneSlice(first)
|
||||
}
|
||||
|
||||
max := firstLen
|
||||
maxLn := firstLen
|
||||
if secondLen > firstLen {
|
||||
max = secondLen
|
||||
maxLn = secondLen
|
||||
}
|
||||
|
||||
result := make([]T, 0)
|
||||
for i := 0; i < max; i++ {
|
||||
for i := 0; i < maxLn; i++ {
|
||||
if i < firstLen {
|
||||
s := first[i]
|
||||
if i, _ := TwowaySearch(second, s, fn); i < 0 {
|
||||
|
||||
+28
-1
@@ -54,7 +54,18 @@ func MustOK(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Must if error is not empty, will panic
|
||||
// Must return like (v, error). will panic on error, otherwise return v.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // old
|
||||
// v, err := fn()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // new
|
||||
// v := goutil.Must(fn())
|
||||
func Must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -62,6 +73,22 @@ func Must[T any](v T, err error) T {
|
||||
return v
|
||||
}
|
||||
|
||||
// MustIgnore for return like (v, error). Ignore return v and will panic on error.
|
||||
//
|
||||
// Useful for io, file operation func: (n int, err error)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // old
|
||||
// _, err := fn()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // new
|
||||
// basefn.MustIgnore(fn())
|
||||
func MustIgnore(_ any, err error) { PanicErr(err) }
|
||||
|
||||
// ErrOnFail return input error on cond is false, otherwise return nil
|
||||
func ErrOnFail(cond bool, err error) error {
|
||||
return OrError(cond, err)
|
||||
|
||||
+5
-4
@@ -93,10 +93,11 @@ func (b *Buffer) writeAnysWithNl(vs []any, nl bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Printf quiet write message to buffer, ignore error.
|
||||
func (b *Buffer) Printf(tpl string, vs ...any) {
|
||||
_, _ = b.WriteString(fmt.Sprintf(tpl, vs...))
|
||||
}
|
||||
// Printf quick write message to buffer, ignore error.
|
||||
func (b *Buffer) Printf(tpl string, vs ...any) { _, _ = fmt.Fprintf(b, tpl, vs...) }
|
||||
|
||||
// Println quick write message with newline to buffer, will ignore error.
|
||||
func (b *Buffer) Println(vs ...any) { _, _ = fmt.Fprintln(b, vs...) }
|
||||
|
||||
// ResetGet buffer string. alias of ResetAndGet()
|
||||
func (b *Buffer) ResetGet() string {
|
||||
|
||||
+11
@@ -26,6 +26,17 @@ func IsEmpty(v any) bool {
|
||||
return reflects.IsEmpty(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// Alias of the IsEmptyReal()
|
||||
var IsZeroReal = IsEmptyReal
|
||||
|
||||
// IsEmptyReal checks for empty given value and also real empty value if the passed value is a pointer
|
||||
func IsEmptyReal(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
return reflects.IsEmptyReal(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// IsFunc value
|
||||
func IsFunc(val any) bool {
|
||||
if val == nil {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package comdef
|
||||
|
||||
// Newline string for non-windows
|
||||
const Newline = "\n"
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package comdef
|
||||
|
||||
// Newline string for windows
|
||||
const Newline = "\r\n"
|
||||
+1
-1
@@ -59,7 +59,7 @@ type SimpleType interface {
|
||||
Int | Uint | Float | ~string | ~bool
|
||||
}
|
||||
|
||||
// ScalarType interface type.
|
||||
// ScalarType basic interface type.
|
||||
//
|
||||
// TIP: has bool type, it cannot be ordered
|
||||
//
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
@@ -112,7 +112,7 @@ func (f *Func) FileLine() (file string, line int) {
|
||||
func (f *Func) Location() string {
|
||||
file, line := f.FileLine()
|
||||
|
||||
return f.Name() + "(), " + path.Base(file) + ":" + strconv.Itoa(line)
|
||||
return f.Name() + "(), " + filepath.Base(file) + ":" + strconv.Itoa(line)
|
||||
}
|
||||
|
||||
// String of the func
|
||||
|
||||
+6
-3
@@ -216,14 +216,17 @@ func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
|
||||
return // ignore I/O error
|
||||
}
|
||||
|
||||
// names, _ := d.Readdirnames(-1)
|
||||
// sort.Strings(names)
|
||||
|
||||
des, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// remove the last '/' char
|
||||
dirLn := len(dir)
|
||||
if dirLn > 1 && dir[dirLn-1] == '/' {
|
||||
dir = dir[:dirLn-1]
|
||||
}
|
||||
|
||||
for _, ent := range des {
|
||||
filePath := dir + "/" + ent.Name()
|
||||
|
||||
|
||||
+27
-8
@@ -6,20 +6,26 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/basefn"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// PathSep alias of os.PathSeparator
|
||||
const PathSep = os.PathSeparator
|
||||
|
||||
// JoinPaths elements, alias of filepath.Join()
|
||||
func JoinPaths(elem ...string) string {
|
||||
return filepath.Join(elem...)
|
||||
}
|
||||
|
||||
// JoinPaths3 elements, like the filepath.Join()
|
||||
func JoinPaths3(basePath, secPath string, elems ...string) string {
|
||||
return comfunc.JoinPaths3(basePath, secPath, elems)
|
||||
}
|
||||
|
||||
// JoinSubPaths elements, like the filepath.Join()
|
||||
func JoinSubPaths(basePath string, elem ...string) string {
|
||||
paths := make([]string, len(elem)+1)
|
||||
paths[0] = basePath
|
||||
copy(paths[1:], elem)
|
||||
return filepath.Join(paths...)
|
||||
func JoinSubPaths(basePath string, elems ...string) string {
|
||||
return comfunc.JoinPaths2(basePath, elems)
|
||||
}
|
||||
|
||||
// SlashPath alias of filepath.ToSlash
|
||||
@@ -35,11 +41,21 @@ func UnixPath(path string) string {
|
||||
return strings.ReplaceAll(path, "\\", "/")
|
||||
}
|
||||
|
||||
// ToAbsPath convert process. will expand home dir
|
||||
// ToAbsPath convert path to absolute path.
|
||||
// Will expand home dir, if empty will return current work dir
|
||||
//
|
||||
// TIP: will don't check path
|
||||
// TIP: will don't check path is really exists
|
||||
func ToAbsPath(p string) string {
|
||||
if len(p) == 0 || IsAbsPath(p) {
|
||||
// return current work dir
|
||||
if len(p) == 0 {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
return wd
|
||||
}
|
||||
|
||||
if IsAbsPath(p) {
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -54,3 +70,6 @@ func ToAbsPath(p string) string {
|
||||
}
|
||||
return filepath.Join(wd, p)
|
||||
}
|
||||
|
||||
// Must2 ok for (any, error) result. if it has error, will panic
|
||||
func Must2(_ any, err error) { basefn.MustOK(err) }
|
||||
|
||||
+7
-8
@@ -2,7 +2,6 @@ package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
@@ -15,7 +14,7 @@ func DirPath(fpath string) string { return filepath.Dir(fpath) }
|
||||
func Dir(fpath string) string { return filepath.Dir(fpath) }
|
||||
|
||||
// PathName get file/dir name from full path
|
||||
func PathName(fpath string) string { return path.Base(fpath) }
|
||||
func PathName(fpath string) string { return filepath.Base(fpath) }
|
||||
|
||||
// Name get file/dir name from full path.
|
||||
//
|
||||
@@ -27,25 +26,25 @@ func Name(fpath string) string {
|
||||
return filepath.Base(fpath)
|
||||
}
|
||||
|
||||
// FileExt get filename ext. alias of path.Ext()
|
||||
// FileExt get filename ext. alias of filepath.Ext()
|
||||
//
|
||||
// eg: path/to/main.go => ".go"
|
||||
func FileExt(fpath string) string { return path.Ext(fpath) }
|
||||
func FileExt(fpath string) string { return filepath.Ext(fpath) }
|
||||
|
||||
// Extname get filename ext. alias of path.Ext()
|
||||
// Extname get filename ext. alias of filepath.Ext()
|
||||
//
|
||||
// eg: path/to/main.go => "go"
|
||||
func Extname(fpath string) string {
|
||||
if ext := path.Ext(fpath); len(ext) > 0 {
|
||||
if ext := filepath.Ext(fpath); len(ext) > 0 {
|
||||
return ext[1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Suffix get filename ext. alias of path.Ext()
|
||||
// Suffix get filename ext. alias of filepath.Ext()
|
||||
//
|
||||
// eg: path/to/main.go => ".go"
|
||||
func Suffix(fpath string) string { return path.Ext(fpath) }
|
||||
func Suffix(fpath string) string { return filepath.Ext(fpath) }
|
||||
|
||||
// Expand will parse first `~` as user home dir path.
|
||||
func Expand(pathStr string) string {
|
||||
|
||||
+2
-3
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -121,7 +120,7 @@ const (
|
||||
//
|
||||
// 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)
|
||||
fileDir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,7 +176,7 @@ func OpenReadFile(filepath string) (*os.File, error) {
|
||||
//
|
||||
// CreateFile("path/to/file.txt", 0664, 0666)
|
||||
func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error) {
|
||||
dirPath := path.Dir(fpath)
|
||||
dirPath := filepath.Dir(fpath)
|
||||
if !IsDir(dirPath) {
|
||||
err := os.MkdirAll(dirPath, dirPerm)
|
||||
if err != nil {
|
||||
|
||||
+3
-9
@@ -27,9 +27,7 @@ func DiscardReader(src io.Reader) {
|
||||
}
|
||||
|
||||
// ReadFile read file contents, will panic on error
|
||||
func ReadFile(filePath string) []byte {
|
||||
return MustReadFile(filePath)
|
||||
}
|
||||
func ReadFile(filePath string) []byte { return MustReadFile(filePath) }
|
||||
|
||||
// MustReadFile read file contents, will panic on error
|
||||
func MustReadFile(filePath string) []byte {
|
||||
@@ -53,9 +51,7 @@ func MustReadReader(r io.Reader) []byte {
|
||||
}
|
||||
|
||||
// ReadString read contents from path or io.Reader, will panic on in type error
|
||||
func ReadString(in any) string {
|
||||
return string(GetContents(in))
|
||||
}
|
||||
func ReadString(in any) string { return string(GetContents(in)) }
|
||||
|
||||
// ReadStringOrErr read contents from path or io.Reader, will panic on in type error
|
||||
func ReadStringOrErr(in any) (string, error) {
|
||||
@@ -78,9 +74,7 @@ func ReadAll(in any) []byte { return MustRead(in) }
|
||||
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))
|
||||
}
|
||||
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) {
|
||||
|
||||
+1
@@ -83,6 +83,7 @@ func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error {
|
||||
// Usage:
|
||||
//
|
||||
// fsutil.PutContents(filePath, contents, fsutil.FsCWAFlags) // append write
|
||||
// fsutil.Must2(fsutil.PutContents(filePath, contents)) // panic on error
|
||||
func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
|
||||
f, err := QuickOpenFile(filePath, basefn.FirstOr(fileFlag, FsCWTFlags))
|
||||
if err != nil {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package goinfo
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -91,7 +91,7 @@ func GetCallersInfo(skip, max int) []string {
|
||||
|
||||
if strings.ContainsRune(file, '/') {
|
||||
name = fc.Name()
|
||||
file = path.Base(file)
|
||||
file = filepath.Base(file)
|
||||
// eg: github.com/gookit/goutil/goinfo_test.someFunc2(),stack_test.go:26
|
||||
callers = append(callers, name+"(),"+file+":"+strconv.Itoa(line))
|
||||
}
|
||||
|
||||
+37
-15
@@ -23,28 +23,50 @@ 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 {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// PanicErr if error is not empty, will panic
|
||||
// PanicErr if error is not empty, will panic.
|
||||
// Alias of basefn.PanicErr()
|
||||
func PanicErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// MustOK if error is not empty, will panic
|
||||
func MustOK(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// PanicIfErr if error is not empty, will panic.
|
||||
// Alias of basefn.PanicErr()
|
||||
func PanicIfErr(err error) { PanicErr(err) }
|
||||
|
||||
// Must if error is not empty, will panic
|
||||
// MustOK if error is not empty, will panic.
|
||||
// Alias of basefn.MustOK()
|
||||
func MustOK(err error) { PanicErr(err) }
|
||||
|
||||
// MustIgnore for return like (v, error). Ignore return v and will panic on error.
|
||||
//
|
||||
// Useful for io, file operation func: (n int, err error)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // old
|
||||
// _, err := fn()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // new
|
||||
// goutil.MustIgnore(fn())
|
||||
func MustIgnore(_ any, err error) { PanicErr(err) }
|
||||
|
||||
// Must return like (v, error). will panic on error, otherwise return v.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // old
|
||||
// v, err := fn()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // new
|
||||
// v := goutil.Must(fn())
|
||||
func Must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
+17
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -106,3 +107,19 @@ func Contains(data, elem any) (valid, found bool) {
|
||||
}
|
||||
return true, false
|
||||
}
|
||||
|
||||
// StringsContains check string slice contains string
|
||||
func StringsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check is number: int or float
|
||||
var numReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`)
|
||||
|
||||
// IsNumeric returns true if the given string is a numeric, otherwise false.
|
||||
func IsNumeric(s string) bool { return numReg.MatchString(s) }
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package comfunc
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// JoinPaths2 elements, like the filepath.Join()
|
||||
func JoinPaths2(basePath string, elems []string) string {
|
||||
paths := make([]string, len(elems)+1)
|
||||
paths[0] = basePath
|
||||
copy(paths[1:], elems)
|
||||
return filepath.Join(paths...)
|
||||
}
|
||||
|
||||
// JoinPaths3 elements, like the filepath.Join()
|
||||
func JoinPaths3(basePath, secPath string, elems []string) string {
|
||||
paths := make([]string, len(elems)+2)
|
||||
paths[0] = basePath
|
||||
paths[1] = secPath
|
||||
copy(paths[2:], elems)
|
||||
return filepath.Join(paths...)
|
||||
}
|
||||
+24
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
)
|
||||
|
||||
@@ -193,6 +194,14 @@ func Keys(mp any) (keys []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// TypedKeys get all keys of the given typed map.
|
||||
func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K) {
|
||||
for key := range mp {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Values get all values from the given map.
|
||||
func Values(mp any) (values []any) {
|
||||
rv := reflect.Indirect(reflect.ValueOf(mp))
|
||||
@@ -207,6 +216,14 @@ func Values(mp any) (values []any) {
|
||||
return
|
||||
}
|
||||
|
||||
// TypedValues get all values from the given typed map.
|
||||
func TypedValues[K comdef.SimpleType, V any](mp map[K]V) (values []V) {
|
||||
for _, val := range mp {
|
||||
values = append(values, val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EachAnyMap iterates the given map and calls the given function for each item.
|
||||
func EachAnyMap(mp any, fn func(key string, val any)) {
|
||||
rv := reflect.Indirect(reflect.ValueOf(mp))
|
||||
@@ -218,3 +235,10 @@ func EachAnyMap(mp any, fn func(key string, val any)) {
|
||||
fn(key.String(), rv.MapIndex(key).Interface())
|
||||
}
|
||||
}
|
||||
|
||||
// EachTypedMap iterates the given map and calls the given function for each item.
|
||||
func EachTypedMap[K comdef.SimpleType, V any](mp map[K]V, fn func(key K, val V)) {
|
||||
for key, val := range mp {
|
||||
fn(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -39,6 +39,17 @@ func SimpleMerge(src, dst map[string]any) map[string]any {
|
||||
return dst
|
||||
}
|
||||
|
||||
// Merge1level merge multi any map[string]any data. only merge one level data.
|
||||
func Merge1level(mps ...map[string]any) map[string]any {
|
||||
newMp := make(map[string]any)
|
||||
for _, mp := range mps {
|
||||
for k, v := range mp {
|
||||
newMp[k] = v
|
||||
}
|
||||
}
|
||||
return newMp
|
||||
}
|
||||
|
||||
// func DeepMerge(src, dst map[string]any, deep int) map[string]any { TODO
|
||||
// }
|
||||
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package mathutil
|
||||
|
||||
import "github.com/gookit/goutil/comdef"
|
||||
|
||||
// Abs get absolute value of given value
|
||||
func Abs[T comdef.Int](val T) T {
|
||||
if val >= 0 {
|
||||
return val
|
||||
}
|
||||
return -val
|
||||
}
|
||||
+20
-2
@@ -192,7 +192,16 @@ func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error) {
|
||||
iVal = int(tVal)
|
||||
}
|
||||
case string:
|
||||
iVal, err = strconv.Atoi(strings.TrimSpace(tVal))
|
||||
sVal := strings.TrimSpace(tVal)
|
||||
iVal, err = strconv.Atoi(sVal)
|
||||
// handle the case where the string might be a float
|
||||
if err != nil && checkfn.IsNumeric(sVal) {
|
||||
var floatVal float64
|
||||
if floatVal, err = strconv.ParseFloat(sVal, 64); err == nil {
|
||||
iVal = int(math.Round(floatVal))
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
case comdef.Int64able: // eg: json.Number
|
||||
var i64 int64
|
||||
if i64, err = tVal.Int64(); err == nil {
|
||||
@@ -287,7 +296,16 @@ func ToInt64With(in any, optFns ...ConvOptionFn[int64]) (i64 int64, err error) {
|
||||
|
||||
switch tVal := in.(type) {
|
||||
case string:
|
||||
i64, err = strconv.ParseInt(strings.TrimSpace(tVal), 10, 0)
|
||||
sVal := strings.TrimSpace(tVal)
|
||||
i64, err = strconv.ParseInt(sVal, 10, 0)
|
||||
// handle the case where the string might be a float
|
||||
if err != nil && checkfn.IsNumeric(sVal) {
|
||||
var floatVal float64
|
||||
if floatVal, err = strconv.ParseFloat(sVal, 64); err == nil {
|
||||
i64 = int64(math.Round(floatVal))
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
case int:
|
||||
i64 = int64(tVal)
|
||||
case int8:
|
||||
|
||||
+5
@@ -58,6 +58,11 @@ func IsNil(v reflect.Value) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidPtr check variable is a valid pointer.
|
||||
func IsValidPtr(v reflect.Value) bool {
|
||||
return v.IsValid() && (v.Kind() == reflect.Ptr) && !v.IsNil()
|
||||
}
|
||||
|
||||
// CanBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
func CanBeNil(typ reflect.Type) bool {
|
||||
switch typ.Kind() {
|
||||
|
||||
+4
@@ -146,6 +146,10 @@ func structToMap(obj reflect.Value, opt *MapOptions, mp map[string]any) (map[str
|
||||
}
|
||||
|
||||
field := reflect.Indirect(obj.Field(i))
|
||||
if !field.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Struct {
|
||||
// collect anonymous struct values to parent.
|
||||
if ft.Anonymous && opt.MergeAnonymous {
|
||||
|
||||
+44
-13
@@ -2,7 +2,9 @@ package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/internal/varexpr"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
@@ -10,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const defaultInitTag = "default"
|
||||
const defaultEnvPrefixTag = "defaultenvprefix"
|
||||
|
||||
// InitOptFunc define
|
||||
type InitOptFunc func(opt *InitOptions)
|
||||
@@ -17,7 +20,8 @@ type InitOptFunc func(opt *InitOptions)
|
||||
// InitOptions struct
|
||||
type InitOptions struct {
|
||||
// TagName default value tag name. tag: default
|
||||
TagName string
|
||||
TagName string
|
||||
EnvPrefixTagName string
|
||||
// ParseEnv var name on default value. eg: `default:"${APP_ENV}"`
|
||||
//
|
||||
// default: false
|
||||
@@ -49,7 +53,7 @@ func Init(ptr any, optFns ...InitOptFunc) error {
|
||||
// fmt.Printf("%+v\n", u1) // Output: {Name:inhere Age:30}
|
||||
func InitDefaults(ptr any, optFns ...InitOptFunc) error {
|
||||
rv := reflect.ValueOf(ptr)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
if !reflects.IsValidPtr(rv) {
|
||||
return errors.New("must be provider an pointer value")
|
||||
}
|
||||
|
||||
@@ -58,15 +62,15 @@ func InitDefaults(ptr any, optFns ...InitOptFunc) error {
|
||||
return errors.New("must be provider an struct value")
|
||||
}
|
||||
|
||||
opt := &InitOptions{TagName: defaultInitTag}
|
||||
opt := &InitOptions{TagName: defaultInitTag, EnvPrefixTagName: defaultEnvPrefixTag}
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
return initDefaults(rv, opt)
|
||||
return initDefaults(rv, opt, "")
|
||||
}
|
||||
|
||||
func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
func initDefaults(rv reflect.Value, opt *InitOptions, envPrefix string) error {
|
||||
rt := rv.Type()
|
||||
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
@@ -80,10 +84,12 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
if !hasTag || val == "-" {
|
||||
continue
|
||||
}
|
||||
prefixVar, _ := sf.Tag.Lookup(opt.EnvPrefixTagName)
|
||||
childPrefixVar := fmt.Sprintf("%s%s", envPrefix, prefixVar)
|
||||
|
||||
fv := rv.Field(i)
|
||||
if fv.Kind() == reflect.Struct {
|
||||
if err := initDefaults(fv, opt); err != nil {
|
||||
if err := initDefaults(fv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
@@ -95,7 +101,7 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
if fv.Kind() == reflect.Struct {
|
||||
if err := initDefaults(fv, opt); err != nil {
|
||||
if err := initDefaults(fv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -109,7 +115,7 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
if el.Kind() == reflect.Struct && fv.Len() > 0 {
|
||||
for i := 0; i < fv.Len(); i++ {
|
||||
subFv := reflect.Indirect(fv.Index(i))
|
||||
if err := initDefaults(subFv, opt); err != nil {
|
||||
if err := initDefaults(subFv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -126,7 +132,7 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
|
||||
fv = fv.Elem()
|
||||
if fv.Kind() == reflect.Struct {
|
||||
if err := initDefaults(fv, opt); err != nil {
|
||||
if err := initDefaults(fv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
@@ -148,7 +154,7 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
// make sub-struct and init. like: `SubStruct`
|
||||
subFv := reflect.New(el)
|
||||
subFvE := subFv.Elem()
|
||||
if err := initDefaults(subFvE, opt); err != nil {
|
||||
if err := initDefaults(subFvE, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -164,7 +170,7 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := initDefaultValue(fv, val, opt.ParseEnv); err != nil {
|
||||
if err := initDefaultValue(fv, val, opt.ParseEnv, envPrefix); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -172,13 +178,32 @@ func initDefaults(rv reflect.Value, opt *InitOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDefaultValue(fv reflect.Value, val string, parseEnv bool) error {
|
||||
func enhanceDefaultVar(val string, envPrefix string) string {
|
||||
cleaned_var := strings.TrimSpace(val)
|
||||
if strings.HasPrefix(cleaned_var, "${") && strings.HasSuffix(cleaned_var, "}") {
|
||||
parts := strings.SplitN(cleaned_var[2:len(cleaned_var)-1], "|", 2)
|
||||
if len(parts) > 0 {
|
||||
env := strings.TrimSpace(parts[0])
|
||||
if env != "" {
|
||||
if len(parts) == 1 {
|
||||
return fmt.Sprintf("${%s%s}", envPrefix, env)
|
||||
} else {
|
||||
return fmt.Sprintf("${%s%s|%s}", envPrefix, env, parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func initDefaultValue(fv reflect.Value, val string, parseEnv bool, envPrefix string) error {
|
||||
if val == "" || !fv.CanSet() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse env var
|
||||
if parseEnv {
|
||||
val = enhanceDefaultVar(val, envPrefix)
|
||||
val = varexpr.SafeParse(val)
|
||||
}
|
||||
|
||||
@@ -189,7 +214,13 @@ func initDefaultValue(fv reflect.Value, val string, parseEnv bool) error {
|
||||
ss := strutil.SplitTrimmed(val, ",")
|
||||
valRv, err := reflects.ConvSlice(reflect.ValueOf(ss), fv.Type().Elem())
|
||||
if err == nil {
|
||||
reflects.SetRValue(fv, valRv)
|
||||
if fv.Kind() == reflect.Array {
|
||||
for i := 0; i < valRv.Len(); i++ {
|
||||
fv.Index(i).Set(valRv.Index(i))
|
||||
}
|
||||
} else {
|
||||
reflects.SetRValue(fv, valRv)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
+13
-6
@@ -50,6 +50,9 @@ type SetOptions struct {
|
||||
// default: false
|
||||
ParseDefaultEnv bool
|
||||
|
||||
// DefaultEnvPrefixTag name. tag: defaultenvprefix
|
||||
DefaultEnvPrefixTag string
|
||||
|
||||
// StopOnError if true, will stop set value on error happened. default: false
|
||||
// StopOnError bool
|
||||
}
|
||||
@@ -76,17 +79,18 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error {
|
||||
}
|
||||
|
||||
opt := &SetOptions{
|
||||
FieldTagName: defaultFieldTag,
|
||||
DefaultValTag: defaultInitTag,
|
||||
FieldTagName: defaultFieldTag,
|
||||
DefaultValTag: defaultInitTag,
|
||||
DefaultEnvPrefixTag: defaultEnvPrefixTag,
|
||||
}
|
||||
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
return setValues(rv, data, opt)
|
||||
return setValues(rv, data, opt, "")
|
||||
}
|
||||
|
||||
func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error {
|
||||
func setValues(rv reflect.Value, data map[string]any, opt *SetOptions, envPrefix string) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -119,7 +123,7 @@ func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error {
|
||||
// set field value by default tag.
|
||||
if !ok && opt.ParseDefault && fv.IsZero() {
|
||||
defVal := ft.Tag.Get(opt.DefaultValTag)
|
||||
if err := initDefaultValue(fv, defVal, opt.ParseDefaultEnv); err != nil {
|
||||
if err := initDefaultValue(fv, defVal, opt.ParseDefaultEnv, envPrefix); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
continue
|
||||
@@ -155,8 +159,11 @@ func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error {
|
||||
continue
|
||||
}
|
||||
|
||||
defEnvPrefixVal := ft.Tag.Get(opt.DefaultEnvPrefixTag)
|
||||
childEnvPrefix := fmt.Sprintf("%s%s", envPrefix, defEnvPrefixVal)
|
||||
|
||||
// recursive processing sub-struct
|
||||
if err = setValues(fv, asMp, opt); err != nil {
|
||||
if err = setValues(fv, asMp, opt, childEnvPrefix); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
continue
|
||||
|
||||
+1
-2
@@ -290,9 +290,8 @@ func LikeMatch(pattern, s string) bool {
|
||||
if pattern[0] == '%' {
|
||||
if ln > 2 && pattern[ln-1] == '%' {
|
||||
return strings.Contains(s, pattern[1:ln-1])
|
||||
} else {
|
||||
return strings.HasSuffix(s, pattern[1:])
|
||||
}
|
||||
return strings.HasSuffix(s, pattern[1:])
|
||||
}
|
||||
|
||||
// eg `abc%`
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ func BaseConvByTpl(src string, fromBase, toBase string) string {
|
||||
}
|
||||
|
||||
// convert to base 10
|
||||
var dec uint64 = 0
|
||||
var dec uint64
|
||||
if fromBase == Base10Chars {
|
||||
var err error
|
||||
dec, err = strconv.ParseUint(src, 10, 0)
|
||||
|
||||
+2
@@ -45,6 +45,8 @@ var layoutMap = map[int][]string{
|
||||
}
|
||||
|
||||
// ToTime convert date string to time.Time
|
||||
//
|
||||
// NOTE: always use local timezone.
|
||||
func ToTime(s string, layouts ...string) (t time.Time, err error) {
|
||||
// custom layout
|
||||
if len(layouts) > 0 {
|
||||
|
||||
+8
-46
@@ -1,14 +1,11 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
mRand "math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/goutil/byteutil"
|
||||
"github.com/gookit/goutil/encodes"
|
||||
)
|
||||
|
||||
// some consts string chars
|
||||
// some constants string chars
|
||||
const (
|
||||
Numbers = "0123456789"
|
||||
HexChars = "0123456789abcdef" // base16
|
||||
@@ -16,52 +13,26 @@ const (
|
||||
AlphaBet = "abcdefghijklmnopqrstuvwxyz"
|
||||
AlphaBet1 = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
||||
|
||||
AlphaNum = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
// AlphaNum chars, can use for base36 encode
|
||||
AlphaNum = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
// AlphaNum2 chars, can use for base62 encode
|
||||
AlphaNum2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
AlphaNum3 = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
||||
)
|
||||
|
||||
func newRand() *mRand.Rand {
|
||||
return mRand.New(mRand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
// RandomChars generate give length random chars at `a-z`
|
||||
func RandomChars(ln int) string {
|
||||
cs := make([]byte, ln)
|
||||
// UnixNano: 1607400451937462000
|
||||
rn := newRand()
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
// rand in 0 - 25
|
||||
cs[i] = AlphaBet[rn.Intn(25)]
|
||||
}
|
||||
return string(cs)
|
||||
return buildRandomString(AlphaBet, ln)
|
||||
}
|
||||
|
||||
// RandomCharsV2 generate give length random chars in `0-9a-z`
|
||||
func RandomCharsV2(ln int) string {
|
||||
cs := make([]byte, ln)
|
||||
// UnixNano: 1607400451937462000
|
||||
rn := newRand()
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
// rand in 0 - 35
|
||||
cs[i] = AlphaNum[rn.Intn(35)]
|
||||
}
|
||||
return string(cs)
|
||||
return buildRandomString(AlphaNum, ln)
|
||||
}
|
||||
|
||||
// RandomCharsV3 generate give length random chars in `0-9a-zA-Z`
|
||||
func RandomCharsV3(ln int) string {
|
||||
cs := make([]byte, ln)
|
||||
// UnixNano: 1607400451937462000
|
||||
rn := newRand()
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
// rand in 0 - 61
|
||||
cs[i] = AlphaNum2[rn.Intn(61)]
|
||||
}
|
||||
return string(cs)
|
||||
return buildRandomString(AlphaNum2, ln)
|
||||
}
|
||||
|
||||
// RandWithTpl generate random string with give template
|
||||
@@ -69,16 +40,7 @@ func RandWithTpl(n int, letters string) string {
|
||||
if len(letters) == 0 {
|
||||
letters = AlphaNum2
|
||||
}
|
||||
|
||||
ln := len(letters)
|
||||
cs := make([]byte, n)
|
||||
rn := newRand()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
// rand in 0 - ln
|
||||
cs[i] = letters[rn.Intn(ln)]
|
||||
}
|
||||
return byteutil.String(cs)
|
||||
return buildRandomString(letters, n)
|
||||
}
|
||||
|
||||
// RandomString generate.
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build !windows
|
||||
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var rn = newRand()
|
||||
|
||||
func newRand() *rand.Rand {
|
||||
return rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
// buildRandomString 生成随机字符串
|
||||
func buildRandomString(letters string, length int) string {
|
||||
// rn := newRand()
|
||||
cs := make([]byte, length)
|
||||
|
||||
lettersN := len(letters)
|
||||
for i := 0; i < length; i++ {
|
||||
cs[i] = letters[rn.Intn(lettersN)]
|
||||
}
|
||||
|
||||
return Byte2str(cs)
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//go:build windows
|
||||
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const MaximumCapacity = math.MaxInt>>1 + 1
|
||||
|
||||
var rn = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
// nearestPowerOfTwo 返回一个大于等于cap的最近的2的整数次幂,参考java8的hashmap的tableSizeFor函数
|
||||
// - cap 输入参数
|
||||
//
|
||||
// 返回一个大于等于cap的最近的2的整数次幂
|
||||
func nearestPowerOfTwo(cap int) int {
|
||||
n := cap - 1
|
||||
n |= n >> 1
|
||||
n |= n >> 2
|
||||
n |= n >> 4
|
||||
n |= n >> 8
|
||||
n |= n >> 16
|
||||
if n < 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if n >= MaximumCapacity {
|
||||
return MaximumCapacity
|
||||
}
|
||||
return n + 1
|
||||
}
|
||||
|
||||
// buildRandomString 生成随机字符串
|
||||
// - letters 字符串模板
|
||||
// - length 生成长度
|
||||
//
|
||||
// 返回一个指定长度的随机字符串
|
||||
func buildRandomString(letters string, length int) string {
|
||||
// 仿照strings.Builder
|
||||
// 创建一个长度为 length 的字节切片
|
||||
bytes := make([]byte, length)
|
||||
strLength := len(letters)
|
||||
if strLength <= 0 {
|
||||
return ""
|
||||
}
|
||||
if strLength == 1 {
|
||||
for i := 0; i < length; i++ {
|
||||
bytes[i] = letters[0]
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&bytes))
|
||||
}
|
||||
|
||||
// letters的字符需要使用多少个比特位数才能表示完
|
||||
// letterIdBits := int(math.Ceil(math.Log2(strLength))),下面比上面的代码快
|
||||
letterIdBits := int(math.Log2(float64(nearestPowerOfTwo(strLength))))
|
||||
// 最大的字母id掩码
|
||||
var letterIdMask int64 = 1<<letterIdBits - 1
|
||||
// 可用次数的最大值
|
||||
letterIdMax := 63 / letterIdBits
|
||||
|
||||
// UnixNano: 1607400451937462000
|
||||
// 循环生成随机字符串
|
||||
for i, cache, remain := length-1, rn.Int63(), letterIdMax; i >= 0; {
|
||||
// 检查随机数生成器是否用尽所有随机数
|
||||
if remain == 0 {
|
||||
cache, remain = rn.Int63(), letterIdMax
|
||||
}
|
||||
// 从可用字符的字符串中随机选择一个字符
|
||||
if idx := int(cache & letterIdMask); idx < strLength {
|
||||
bytes[i] = letters[idx]
|
||||
i--
|
||||
}
|
||||
// 右移比特位数,为下次选择字符做准备
|
||||
cache >>= letterIdBits
|
||||
remain--
|
||||
}
|
||||
|
||||
// 仿照strings.Builder用unsafe包返回一个字符串,避免拷贝
|
||||
// 将字节切片转换为字符串并返回
|
||||
return *(*string)(unsafe.Pointer(&bytes))
|
||||
}
|
||||
+12
-1
@@ -1,6 +1,9 @@
|
||||
package strutil
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BeforeFirst get substring before first sep.
|
||||
func BeforeFirst(s, sep string) string {
|
||||
@@ -137,6 +140,14 @@ func SplitNTrimmed(s, sep string, n int) (ss []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 根据空白字符(空格,TAB,换行等)分隔字符串
|
||||
var whitespaceRegexp = regexp.MustCompile("\\s+")
|
||||
|
||||
// SplitByWhitespace Separate strings by whitespace characters (space, TAB, newline, etc.)
|
||||
func SplitByWhitespace(s string) []string {
|
||||
return whitespaceRegexp.Split(s, -1)
|
||||
}
|
||||
|
||||
// Substr for a string.
|
||||
// if length <= 0, return pos to end.
|
||||
func Substr(s string, pos, length int) string {
|
||||
|
||||
+8
@@ -51,6 +51,14 @@ func OrElse(s, orVal string) string {
|
||||
return orVal
|
||||
}
|
||||
|
||||
// OrElseNilSafe return default value on s is nil, otherwise return s
|
||||
func OrElseNilSafe(s *string, orVal string) string {
|
||||
if s == nil || *s == "" {
|
||||
return orVal
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
// OrHandle return fn(s) on s is not empty.
|
||||
func OrHandle(s string, fn comdef.StringHandleFunc) string {
|
||||
if s != "" {
|
||||
|
||||
+9
-6
@@ -394,12 +394,17 @@ func (c *Cmd) Output() (string, error) {
|
||||
return "DRY-RUN: ok", nil
|
||||
}
|
||||
|
||||
output, err := c.Cmd.Output()
|
||||
bs, err := c.Cmd.Output()
|
||||
|
||||
if c.AfterRun != nil {
|
||||
c.AfterRun(c, err)
|
||||
}
|
||||
return string(output), err
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// AllOutput run and return output, will combine stderr and stdout output
|
||||
func (c *Cmd) AllOutput() (string, error) {
|
||||
return c.CombinedOutput()
|
||||
}
|
||||
|
||||
// CombinedOutput run and return output, will combine stderr and stdout output
|
||||
@@ -412,12 +417,11 @@ func (c *Cmd) CombinedOutput() (string, error) {
|
||||
return "DRY-RUN: ok", nil
|
||||
}
|
||||
|
||||
output, err := c.Cmd.CombinedOutput()
|
||||
|
||||
bs, err := c.Cmd.CombinedOutput()
|
||||
if c.AfterRun != nil {
|
||||
c.AfterRun(c, err)
|
||||
}
|
||||
return string(output), err
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// MustRun a command. will panic on error
|
||||
@@ -444,7 +448,6 @@ func (c *Cmd) Run() error {
|
||||
|
||||
// do running
|
||||
err := c.Cmd.Run()
|
||||
|
||||
if c.AfterRun != nil {
|
||||
c.AfterRun(c, err)
|
||||
}
|
||||
|
||||
+29
-14
@@ -1,10 +1,10 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
|
||||
"github.com/gookit/goutil/cliutil/cmdline"
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
"github.com/gookit/goutil/sysutil/cmdr"
|
||||
)
|
||||
|
||||
@@ -13,17 +13,19 @@ func NewCmd(bin string, args ...string) *cmdr.Cmd {
|
||||
return cmdr.NewCmd(bin, args...)
|
||||
}
|
||||
|
||||
// FlushExec instance
|
||||
// FlushExec command, will flush output to stdout,stderr
|
||||
func FlushExec(bin string, args ...string) error {
|
||||
return cmdr.NewCmd(bin, args...).FlushRun()
|
||||
}
|
||||
|
||||
// QuickExec quick exec an simple command line
|
||||
// QuickExec quick exec a simple command line, return combined output.
|
||||
func QuickExec(cmdLine string, workDir ...string) (string, error) {
|
||||
return ExecLine(cmdLine, workDir...)
|
||||
}
|
||||
|
||||
// ExecLine quick exec an command line string
|
||||
// ExecLine quick exec a command line string, return combined output.
|
||||
//
|
||||
// NOTE: not support | or ; in cmdLine
|
||||
func ExecLine(cmdLine string, workDir ...string) (string, error) {
|
||||
p := cmdline.NewParser(cmdLine)
|
||||
|
||||
@@ -33,11 +35,11 @@ func ExecLine(cmdLine string, workDir ...string) (string, error) {
|
||||
cmd.Dir = workDir[0]
|
||||
}
|
||||
|
||||
bs, err := cmd.Output()
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// ExecCmd a command and return output.
|
||||
// ExecCmd a command and return combined output.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
@@ -49,11 +51,15 @@ func ExecCmd(binName string, args []string, workDir ...string) (string, error) {
|
||||
cmd.Dir = workDir[0]
|
||||
}
|
||||
|
||||
bs, err := cmd.Output()
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// ShellExec exec command by shell cmdLine. eg: "ls -al"
|
||||
// ShellExec exec command by shell cmdLine, return combined output.
|
||||
//
|
||||
// shells e.g. "/bin/sh", "bash", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh", "pwsh.exe"
|
||||
//
|
||||
// eg: ShellExec("ls -al")
|
||||
func ShellExec(cmdLine string, shells ...string) (string, error) {
|
||||
// shell := "/bin/sh"
|
||||
shell := "sh"
|
||||
@@ -61,12 +67,21 @@ func ShellExec(cmdLine string, shells ...string) (string, error) {
|
||||
shell = shells[0]
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command(shell, "-c", cmdLine)
|
||||
cmd.Stdout = &out
|
||||
// "-c" for bash,sh,zsh shell
|
||||
mark := "-c"
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
// special for Windows shell
|
||||
if IsWindows() {
|
||||
// use cmd.exe, mark is "/c"
|
||||
if checkfn.StringsContains([]string{"cmd", "cmd.exe"}, shell) {
|
||||
mark = "/c"
|
||||
} else if checkfn.StringsContains([]string{"powershell", "powershell.exe", "pwsh", "pwsh.exe"}, shell) {
|
||||
// "-Command" for powershell
|
||||
mark = "-Command"
|
||||
}
|
||||
}
|
||||
return out.String(), nil
|
||||
|
||||
cmd := exec.Command(shell, mark, cmdLine)
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/errorx"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// OSVersionInfo 结构体用于存储操作系统版本信息
|
||||
//
|
||||
// NOTE: Windows 10 和 Windows 11 在主版本号和次版本号上是相同的,因此需要通过构建号(win11: DwBuildNumber>=22000)来进一步区分
|
||||
type OSVersionInfo struct {
|
||||
// 主版本号
|
||||
MajorVersion uint16
|
||||
// 次版本号
|
||||
MinorVersion uint16
|
||||
// 构建号
|
||||
BuildNumber uint32
|
||||
// 修订号
|
||||
RevisionNumber uint32
|
||||
}
|
||||
|
||||
// FetchOsVersion Get Windows system version information
|
||||
//
|
||||
// 还可用使用dll获取:
|
||||
//
|
||||
// 通过 GetVersion, GetVersionEx 函数获取的信息不准确. win11获取到 6.2.9200, 实际是 10.0.22631
|
||||
func FetchOsVersion() (*OSVersionInfo, error) {
|
||||
// Windows cmd 执行 ver 命令
|
||||
out, err := ShellExec("ver", "cmd")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parseOsVersionString(out)
|
||||
}
|
||||
|
||||
// IsLtWindows7 判断是否小于 Windows 7
|
||||
func (ov *OSVersionInfo) IsLtWindows7() bool {
|
||||
return ov.MajorVersion < 6 || (ov.MajorVersion == 6 && ov.MinorVersion < 1)
|
||||
}
|
||||
|
||||
// IsWindows7 判断是否为 Windows 7
|
||||
func (ov *OSVersionInfo) IsWindows7() bool {
|
||||
return ov.MajorVersion == 6 && ov.MinorVersion == 1
|
||||
}
|
||||
|
||||
// IsWindows8 判断是否为 Windows 8
|
||||
func (ov *OSVersionInfo) IsWindows8() bool {
|
||||
return ov.MajorVersion == 6 && ov.MinorVersion == 2
|
||||
}
|
||||
|
||||
// IsWindows10 判断是否为 Windows 10
|
||||
func (ov *OSVersionInfo) IsWindows10() bool {
|
||||
return ov.MajorVersion == 10 && ov.MinorVersion == 0 && ov.BuildNumber < 22000
|
||||
}
|
||||
|
||||
// IsWindows11 判断是否为 Windows 11
|
||||
func (ov *OSVersionInfo) IsWindows11() bool {
|
||||
return ov.MajorVersion == 10 && ov.MinorVersion == 0 && ov.BuildNumber >= 22000
|
||||
}
|
||||
|
||||
// Name 获取 Windows 通用的版本名称. eg: xp, win7, win8, win10, win11, unknown
|
||||
func (ov *OSVersionInfo) Name() string {
|
||||
switch ov.MajorVersion {
|
||||
case 10:
|
||||
if ov.BuildNumber < 22000 {
|
||||
return "win10"
|
||||
} else {
|
||||
return "win11"
|
||||
}
|
||||
case 6:
|
||||
switch ov.MinorVersion {
|
||||
case 0:
|
||||
return "vista"
|
||||
case 1:
|
||||
return "win7"
|
||||
case 2:
|
||||
return "win8"
|
||||
case 3:
|
||||
return "win8.1"
|
||||
}
|
||||
case 5:
|
||||
switch ov.MinorVersion {
|
||||
case 1:
|
||||
return "xp"
|
||||
case 2:
|
||||
return "ws2003" // win server 2003
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// String format
|
||||
func (ov *OSVersionInfo) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", ov.MajorVersion, ov.MinorVersion, ov.BuildNumber)
|
||||
}
|
||||
|
||||
func parseOsVersionString(out string) (*OSVersionInfo, error) {
|
||||
// out eg: Microsoft Windows [Version 10.0.22631.4391] => 10.0.22631.4391
|
||||
// 部分系统会输出中文 eg: Microsoft Windows [版本 10.0.22631.4391]
|
||||
out = strings.TrimSpace(out)
|
||||
ns := strings.SplitN(out, "[", 2)
|
||||
if len(ns) < 2 {
|
||||
return nil, errorx.Rawf("cannot parse version info: %s", out)
|
||||
}
|
||||
|
||||
ns = strutil.SplitByWhitespace(strings.Trim(ns[1], "]"))
|
||||
if len(ns) < 2 {
|
||||
return nil, errorx.Rawf("cannot parse version info2: %s", out)
|
||||
}
|
||||
|
||||
var err error
|
||||
var ovi OSVersionInfo
|
||||
// get like: 10.0.22631.4391 or 10.0.22631
|
||||
verStr := ns[1]
|
||||
|
||||
if strings.Count(verStr, ".") >= 3 {
|
||||
_, err = fmt.Sscanf(verStr, "%d.%d.%d.%d", &ovi.MajorVersion, &ovi.MinorVersion, &ovi.BuildNumber, &ovi.RevisionNumber)
|
||||
} else {
|
||||
_, err = fmt.Sscanf(verStr, "%d.%d.%d", &ovi.MajorVersion, &ovi.MinorVersion, &ovi.BuildNumber)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errorx.Rawf("parse version info %q error: %v", verStr, err)
|
||||
}
|
||||
return &ovi, nil
|
||||
}
|
||||
|
||||
// 全局变量
|
||||
var stdOv, stdErr = FetchOsVersion()
|
||||
|
||||
// OsVersion Get operating system version information
|
||||
func OsVersion() *OSVersionInfo {
|
||||
return stdOv
|
||||
}
|
||||
|
||||
// OvParseError error on parse os version info
|
||||
func OvParseError() error {
|
||||
return stdErr
|
||||
}
|
||||
+23
-2
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
@@ -144,6 +145,12 @@ func EnvPaths() []string {
|
||||
return filepath.SplitList(os.Getenv("PATH"))
|
||||
}
|
||||
|
||||
// SearchPathOption settings for SearchPath
|
||||
type SearchPathOption struct {
|
||||
// 限制的扩展名
|
||||
LimitExt []string
|
||||
}
|
||||
|
||||
// SearchPath search executable files in the system $PATH
|
||||
//
|
||||
// Usage:
|
||||
@@ -154,6 +161,9 @@ func SearchPath(keywords string, limit int) []string {
|
||||
ptn := "*" + keywords + "*"
|
||||
list := make([]string, 0)
|
||||
|
||||
// if windows, will limit with .exe, .bat, .cmd
|
||||
isWindows := IsWindows()
|
||||
winExts := []string{".exe", ".bat", ".cmd"}
|
||||
checked := make(map[string]bool)
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
@@ -169,10 +179,21 @@ func SearchPath(keywords string, limit int) []string {
|
||||
checked[dir] = true
|
||||
matches, err := filepath.Glob(filepath.Join(dir, ptn))
|
||||
if err == nil && len(matches) > 0 {
|
||||
list = append(list, matches...)
|
||||
size := len(list)
|
||||
if isWindows {
|
||||
// if windows, will limit with .exe, .bat, .cmd
|
||||
for _, fPath := range matches {
|
||||
fExt := filepath.Ext(fPath)
|
||||
if checkfn.StringsContains(winExts, fExt) {
|
||||
continue
|
||||
}
|
||||
list = append(list, fPath)
|
||||
}
|
||||
} else {
|
||||
list = append(list, matches...)
|
||||
}
|
||||
|
||||
// limit result size
|
||||
size := len(list)
|
||||
if limit > 0 && size >= limit {
|
||||
list = list[:limit]
|
||||
break
|
||||
|
||||
+19
-26
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// MustFindUser must find an system user by name
|
||||
// MustFindUser must find a system user by name
|
||||
func MustFindUser(uname string) *user.User {
|
||||
u, err := user.Lookup(uname)
|
||||
if err != nil {
|
||||
@@ -16,14 +16,13 @@ func MustFindUser(uname string) *user.User {
|
||||
return u
|
||||
}
|
||||
|
||||
// LoginUser must get current user
|
||||
// LoginUser must get current user, will panic if error
|
||||
func LoginUser() *user.User {
|
||||
return CurrentUser()
|
||||
}
|
||||
|
||||
// CurrentUser must get current user
|
||||
// CurrentUser must get current user, will panic if error
|
||||
func CurrentUser() *user.User {
|
||||
// check $HOME/.terminfo
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -31,9 +30,8 @@ func CurrentUser() *user.User {
|
||||
return u
|
||||
}
|
||||
|
||||
// UHomeDir get user home dir path.
|
||||
// UHomeDir get user home dir path, ignore error. (by user.Current)
|
||||
func UHomeDir() string {
|
||||
// check $HOME/.terminfo
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -42,37 +40,32 @@ func UHomeDir() string {
|
||||
}
|
||||
|
||||
// homeDir cache
|
||||
var homeDir string
|
||||
var _homeDir string
|
||||
|
||||
// UserHomeDir is alias of os.UserHomeDir, but ignore error
|
||||
// UserHomeDir is alias of os.UserHomeDir, but ignore error.(by os.UserHomeDir)
|
||||
func UserHomeDir() string {
|
||||
if homeDir == "" {
|
||||
homeDir, _ = os.UserHomeDir()
|
||||
if _homeDir == "" {
|
||||
_homeDir, _ = os.UserHomeDir()
|
||||
}
|
||||
return homeDir
|
||||
return _homeDir
|
||||
}
|
||||
|
||||
// HomeDir get user home dir path.
|
||||
func HomeDir() string {
|
||||
return UserHomeDir()
|
||||
func HomeDir() string { return UserHomeDir() }
|
||||
|
||||
// UserDir will prepend user home dir to subPaths
|
||||
func UserDir(subPaths ...string) string {
|
||||
return comfunc.JoinPaths2(UserHomeDir(), subPaths)
|
||||
}
|
||||
|
||||
// UserDir will prepend user home dir to subPath
|
||||
func UserDir(subPath string) string {
|
||||
dir := UserHomeDir()
|
||||
return dir + "/" + subPath
|
||||
}
|
||||
|
||||
// UserCacheDir will prepend user `$HOME/.cache` to subPath
|
||||
func UserCacheDir(subPath string) string {
|
||||
dir := UserHomeDir()
|
||||
return dir + "/.cache/" + subPath
|
||||
// UserCacheDir will prepend user `$HOME/.cache` to subPaths
|
||||
func UserCacheDir(subPaths ...string) string {
|
||||
return comfunc.JoinPaths3(UserHomeDir(), ".cache", subPaths)
|
||||
}
|
||||
|
||||
// UserConfigDir will prepend user `$HOME/.config` to subPath
|
||||
func UserConfigDir(subPath string) string {
|
||||
dir := UserHomeDir()
|
||||
return dir + "/.config/" + subPath
|
||||
func UserConfigDir(subPaths ...string) string {
|
||||
return comfunc.JoinPaths3(UserHomeDir(), ".config", subPaths)
|
||||
}
|
||||
|
||||
// ExpandPath will parse `~` as user home dir path.
|
||||
|
||||
+6
@@ -3,11 +3,17 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// IsAdmin Determine whether the current user is an administrator(root)
|
||||
func IsAdmin() bool {
|
||||
return os.Getuid() == 0
|
||||
}
|
||||
|
||||
// ChangeUserByName change work user by new username.
|
||||
func ChangeUserByName(newUname string) error {
|
||||
u := MustFindUser(newUname)
|
||||
|
||||
+7
@@ -19,3 +19,10 @@ func ChangeUserUidGid(newUid int, newGid int) (err error) {
|
||||
func ChangeUserUIDGid(newUid int, newGid int) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAdmin Determine whether the current user is an administrator
|
||||
func IsAdmin() bool {
|
||||
// 执行 net session 判断
|
||||
_, err := ExecCmd("net", []string{"session"})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -642,7 +642,7 @@ github.com/goccy/go-json/internal/encoder/vm_color_indent
|
||||
github.com/goccy/go-json/internal/encoder/vm_indent
|
||||
github.com/goccy/go-json/internal/errors
|
||||
github.com/goccy/go-json/internal/runtime
|
||||
# github.com/goccy/go-yaml v1.11.2
|
||||
# github.com/goccy/go-yaml v1.12.0
|
||||
## explicit; go 1.19
|
||||
github.com/goccy/go-yaml
|
||||
github.com/goccy/go-yaml/ast
|
||||
@@ -720,11 +720,11 @@ github.com/google/uuid
|
||||
# github.com/gookit/color v1.5.4
|
||||
## explicit; go 1.18
|
||||
github.com/gookit/color
|
||||
# github.com/gookit/config/v2 v2.2.5
|
||||
# github.com/gookit/config/v2 v2.2.6
|
||||
## explicit; go 1.19
|
||||
github.com/gookit/config/v2
|
||||
github.com/gookit/config/v2/yaml
|
||||
# github.com/gookit/goutil v0.6.15
|
||||
# github.com/gookit/goutil v0.6.18
|
||||
## explicit; go 1.19
|
||||
github.com/gookit/goutil
|
||||
github.com/gookit/goutil/arrutil
|
||||
|
||||
Reference in New Issue
Block a user