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:
Ralf Haferkamp
2025-04-29 17:48:10 +02:00
committed by GitHub
61 changed files with 1037 additions and 365 deletions
+3 -3
View File
@@ -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
+6 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -19,5 +19,6 @@
*.cov
.DS_Store
*~
testdata/
vendor/
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,6 @@
//go:build !windows
package comdef
// Newline string for non-windows
const Newline = "\n"
+4
View File
@@ -0,0 +1,4 @@
package comdef
// Newline string for windows
const Newline = "\r\n"
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,20 @@
package comfunc
import "path/filepath"
// JoinPaths2 elements, like the filepath.Join()
func JoinPaths2(basePath string, elems []string) string {
paths := make([]string, len(elems)+1)
paths[0] = basePath
copy(paths[1:], elems)
return filepath.Join(paths...)
}
// JoinPaths3 elements, like the filepath.Join()
func JoinPaths3(basePath, secPath string, elems []string) string {
paths := make([]string, len(elems)+2)
paths[0] = basePath
paths[1] = secPath
copy(paths[2:], elems)
return filepath.Join(paths...)
}
+24
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,11 @@
package mathutil
import "github.com/gookit/goutil/comdef"
// Abs get absolute value of given value
func Abs[T comdef.Int](val T) T {
if val >= 0 {
return val
}
return -val
}
+20 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+3 -3
View File
@@ -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