Initial QSfera import
This commit is contained in:
+221
@@ -0,0 +1,221 @@
|
||||
# Structs
|
||||
|
||||
`structs` Provide some extends util functions for struct. eg: convert, tag parse, struct data init
|
||||
|
||||
- `structs.Aliases` - implemented a simple string alias map.
|
||||
- Convert a struct to `map[string]any` data
|
||||
- Quickly init struct default values by field "default" tag.
|
||||
- Quickly set struct field values by map data
|
||||
- Parse a struct and collect tags, and parse tag value
|
||||
- And more util functions ...
|
||||
|
||||
## Install
|
||||
|
||||
```shell
|
||||
go get github.com/gookit/goutil/structs
|
||||
```
|
||||
|
||||
## Go docs
|
||||
|
||||
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/structs)
|
||||
|
||||
## Usage
|
||||
|
||||
### Convert to map
|
||||
|
||||
`structs.ToMap()` can be quickly convert a `struct` value to `map[string]any`
|
||||
|
||||
**Examples**:
|
||||
|
||||
```go
|
||||
type User1 struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
city string
|
||||
}
|
||||
|
||||
u1 := &User1{
|
||||
Name: "inhere",
|
||||
Age: 34,
|
||||
city: "somewhere",
|
||||
}
|
||||
|
||||
mp := structs.ToMap(u1)
|
||||
dump.P(mp)
|
||||
```
|
||||
|
||||
**Output**:
|
||||
|
||||
```shell
|
||||
map[string]interface {} { #len=2
|
||||
"age": int(34),
|
||||
"name": string("inhere"), #len=6
|
||||
},
|
||||
```
|
||||
|
||||
### Init default values
|
||||
|
||||
`structs.InitDefaults` Quickly init struct default value by field "default" tag.
|
||||
|
||||
**Examples**:
|
||||
|
||||
```go
|
||||
type Extra struct {
|
||||
City string `default:"chengdu"`
|
||||
Github string `default:"https://github.com/inhere"`
|
||||
}
|
||||
type User struct {
|
||||
Name string `default:"inhere"`
|
||||
Age int `default:"30"`
|
||||
Extra Extra `default:""` // NEED mark tag
|
||||
}
|
||||
|
||||
u := &User{}
|
||||
_ = structs.InitDefaults(u, &structs.InitOptions{})
|
||||
dump.P(u)
|
||||
```
|
||||
|
||||
**Output**:
|
||||
|
||||
```shell
|
||||
&structs_test.User {
|
||||
Name: string("inhere"), #len=6
|
||||
Age: int(30),
|
||||
Extra: structs_test.Extra {
|
||||
City: string("chengdu"), #len=7
|
||||
Github: string("https://github.com/inhere"), #len=25
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Set values from map
|
||||
|
||||
```go
|
||||
data := map[string]interface{}{
|
||||
"name": "inhere",
|
||||
"age": 234,
|
||||
"tags": []string{"php", "go"},
|
||||
"city": "chengdu",
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Tags []string `json:"tags"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
|
||||
u := &User{}
|
||||
err := structs.SetValues(u, data)
|
||||
dump.P(u)
|
||||
```
|
||||
|
||||
**Output**:
|
||||
|
||||
```shell
|
||||
&structs_test.User {
|
||||
Name: string("inhere"), #len=6
|
||||
Age: int(234),
|
||||
Tags: []string [ #len=2
|
||||
string("php"), #len=3
|
||||
string("go"), #len=2
|
||||
],
|
||||
City: string("chengdu"), #len=7
|
||||
},
|
||||
```
|
||||
|
||||
### Tags collect and parse
|
||||
|
||||
Parse a struct for collect tags, and parse tag value
|
||||
|
||||
**Examples**:
|
||||
|
||||
```go
|
||||
// eg: "desc;required;default;shorts"
|
||||
type MyCmd struct {
|
||||
Name string `flag:"set your name;false;INHERE;n"`
|
||||
}
|
||||
|
||||
c := &MyCmd{}
|
||||
p := structs.NewTagParser("flag")
|
||||
|
||||
sepStr := ";"
|
||||
defines := []string{"desc", "required", "default", "shorts"}
|
||||
p.ValueFunc = structs.ParseTagValueDefine(sepStr, defines)
|
||||
|
||||
goutil.MustOK(p.Parse(c))
|
||||
dump.P(p.Tags())
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```shell
|
||||
map[string]maputil.SMap { #len=1
|
||||
"Name": maputil.SMap { #len=1
|
||||
"flag": string("set your name;false;INHERE;n"), #len=28
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
**Parse tag value**
|
||||
|
||||
```go
|
||||
info, _ := p.Info("Name", "flag")
|
||||
dump.P(info)
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```shell
|
||||
maputil.SMap { #len=4
|
||||
"desc": string("set your name"), #len=13
|
||||
"required": string("false"), #len=5
|
||||
"default": string("INHERE"), #len=6
|
||||
"shorts": string("n"), #len=1
|
||||
},
|
||||
```
|
||||
|
||||
## Functions API
|
||||
|
||||
```go
|
||||
func InitDefaults(ptr any, optFns ...InitOptFunc) error
|
||||
func MustToMap(st any, optFns ...MapOptFunc) map[string]interface{}
|
||||
func ParseReflectTags(rt reflect.Type, tagNames []string) (map[string]maputil.SMap, error)
|
||||
func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error)
|
||||
func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error)
|
||||
func ParseTags(st any, tagNames []string) (map[string]maputil.SMap, error)
|
||||
func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
|
||||
func StructToMap(st any, optFns ...MapOptFunc) (map[string]interface{}, error)
|
||||
func ToMap(st any, optFns ...MapOptFunc) map[string]interface{}
|
||||
func TryToMap(st any, optFns ...MapOptFunc) (map[string]interface{}, error)
|
||||
type Aliases struct{ ... }
|
||||
func NewAliases(checker func(alias string)) *Aliases
|
||||
type Data struct{ ... }
|
||||
func NewData() *Data
|
||||
type InitOptFunc func(opt *InitOptions)
|
||||
type InitOptions struct{ ... }
|
||||
type LiteData struct{ ... }
|
||||
type MapOptFunc func(opt *MapOptions)
|
||||
type MapOptions struct{ ... }
|
||||
type SMap struct{ ... }
|
||||
type SetOptFunc func(opt *SetOptions)
|
||||
type SetOptions struct{ ... }
|
||||
type TagParser struct{ ... }
|
||||
func NewTagParser(tagNames ...string) *TagParser
|
||||
type TagValFunc func(field, tagVal string) (maputil.SMap, error)
|
||||
func ParseTagValueDefine(sep string, defines []string) TagValFunc
|
||||
type Value struct{ ... }
|
||||
func NewValue(val any) *Value
|
||||
```
|
||||
|
||||
## Testings
|
||||
|
||||
```shell
|
||||
go test -v ./structs/...
|
||||
```
|
||||
|
||||
Test limit by regexp:
|
||||
|
||||
```shell
|
||||
go test -v -run ^TestSetByKeys ./structs/...
|
||||
```
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Structs
|
||||
|
||||
`structs` 包为 go struct 提供一些有用的工具函数库。例如:convert、tag parse、struct data init
|
||||
|
||||
- 快速将结构体转换为 `map[string]any` 数据
|
||||
- `structs.Aliases` - 实现了一个简单的字符串别名映射。
|
||||
- 通过字段 `"default"` 标签快速初始化结构体默认值
|
||||
- 通过map数据快速设置 struct 字段值
|
||||
- 解析 struct 并收集 tag,解析 tag 值
|
||||
- 以及更多 util 函数...
|
||||
|
||||
## Install
|
||||
|
||||
```shell
|
||||
go get github.com/gookit/goutil/structs
|
||||
```
|
||||
|
||||
## Go docs
|
||||
|
||||
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/structs)
|
||||
|
||||
## 初始化结构体
|
||||
|
||||
- 支持初始化使用环境变量
|
||||
- 支持初始化 slice 字段,嵌套结构体
|
||||
|
||||
```go
|
||||
type ExtraDefault struct {
|
||||
City string `default:"some where"`
|
||||
Github string `default:"${ GITHUB_ADDR }"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `default:"inhere"`
|
||||
Age int `default:"300"`
|
||||
Extra *ExtraDefault `default:""` // 标记需要初始化
|
||||
}
|
||||
|
||||
optFn := func(opt *structs.InitOptions) {
|
||||
opt.ParseEnv = true
|
||||
}
|
||||
|
||||
obj := &User{}
|
||||
err := structs.InitDefaults(obj, optFn)
|
||||
goutil.PanicErr(err)
|
||||
|
||||
dump.P(obj)
|
||||
```
|
||||
|
||||
**初始化结果**:
|
||||
|
||||
```go
|
||||
&structs_test.User {
|
||||
Name: string("inhere"), #len=6
|
||||
Age: int(300),
|
||||
Extra: &structs_test.ExtraDefault {
|
||||
City: string("some where"), #len=10
|
||||
Github: string("https://some .... url"), #len=21
|
||||
},
|
||||
},
|
||||
```
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package structs
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Aliases implemented a simple string alias map.
|
||||
type Aliases struct {
|
||||
mapping map[string]string
|
||||
// Checker custom add alias name checker func
|
||||
Checker func(alias string) // should return bool OR error ??
|
||||
}
|
||||
|
||||
// NewAliases create
|
||||
func NewAliases(checker func(alias string)) *Aliases {
|
||||
return &Aliases{Checker: checker}
|
||||
}
|
||||
|
||||
// AddAlias to the Aliases
|
||||
func (as *Aliases) AddAlias(real, alias string) {
|
||||
if as.mapping == nil {
|
||||
as.mapping = make(map[string]string)
|
||||
}
|
||||
|
||||
if as.Checker != nil {
|
||||
as.Checker(alias)
|
||||
}
|
||||
|
||||
if rn, ok := as.mapping[alias]; ok {
|
||||
panic(fmt.Sprintf("The alias '%s' is already used by '%s'", alias, rn))
|
||||
}
|
||||
as.mapping[alias] = real
|
||||
}
|
||||
|
||||
// AddAliases to the Aliases
|
||||
func (as *Aliases) AddAliases(real string, aliases []string) {
|
||||
for _, a := range aliases {
|
||||
as.AddAlias(real, a)
|
||||
}
|
||||
}
|
||||
|
||||
// AddAliasMap to the Aliases
|
||||
func (as *Aliases) AddAliasMap(alias2real map[string]string) {
|
||||
for a, r := range alias2real {
|
||||
as.AddAlias(r, a)
|
||||
}
|
||||
}
|
||||
|
||||
// HasAlias in the Aliases
|
||||
func (as *Aliases) HasAlias(alias string) bool {
|
||||
if _, ok := as.mapping[alias]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ResolveAlias by given name. if not exists, return the alias self
|
||||
func (as *Aliases) ResolveAlias(alias string) string {
|
||||
if name, ok := as.mapping[alias]; ok {
|
||||
return name
|
||||
}
|
||||
return alias
|
||||
}
|
||||
|
||||
// Mapping get all aliases mapping
|
||||
func (as *Aliases) Mapping() map[string]string {
|
||||
return as.mapping
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
)
|
||||
|
||||
// ToMap quickly convert structs to map by reflection
|
||||
func ToMap(st any, optFns ...MapOptFunc) map[string]any {
|
||||
mp, _ := StructToMap(st, optFns...)
|
||||
return mp
|
||||
}
|
||||
|
||||
// MustToMap alis of TryToMap, but will panic on error
|
||||
func MustToMap(st any, optFns ...MapOptFunc) map[string]any {
|
||||
mp, err := StructToMap(st, optFns...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return mp
|
||||
}
|
||||
|
||||
// TryToMap simple convert structs to map by reflect
|
||||
func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
|
||||
return StructToMap(st, optFns...)
|
||||
}
|
||||
|
||||
// ToSMap quickly and safe convert structs to map[string]string by reflection
|
||||
func ToSMap(st any, optFns ...MapOptFunc) map[string]string {
|
||||
mp, _ := StructToMap(st, optFns...)
|
||||
return maputil.ToStringMap(mp)
|
||||
}
|
||||
|
||||
// TryToSMap quickly convert structs to map[string]string by reflection
|
||||
func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error) {
|
||||
mp, err := StructToMap(st, optFns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return maputil.ToStringMap(mp), nil
|
||||
}
|
||||
|
||||
// MustToSMap alias of ToStringMap(), but will panic on error
|
||||
func MustToSMap(st any, optFns ...MapOptFunc) map[string]string {
|
||||
mp, err := StructToMap(st, optFns...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return maputil.ToStringMap(mp)
|
||||
}
|
||||
|
||||
// ToString quickly format struct to string
|
||||
func ToString(st any, optFns ...MapOptFunc) string {
|
||||
mp, err := StructToMap(st, optFns...)
|
||||
if err == nil {
|
||||
return maputil.ToString(mp)
|
||||
}
|
||||
return fmt.Sprint(st)
|
||||
}
|
||||
|
||||
const defaultFieldTag = "json"
|
||||
|
||||
// CustomUserFunc for map convert
|
||||
// - fName: raw field name in struct
|
||||
//
|
||||
// Returns:
|
||||
// - ok: return true to collect field, otherwise excluded.
|
||||
// - newVal: `newVal != nil` return new value to collect, otherwise collect original value.
|
||||
type CustomUserFunc func(fName string, fv reflect.Value) (ok bool, newVal any)
|
||||
|
||||
// MapOptions for convert struct to map
|
||||
type MapOptions struct {
|
||||
// TagName for map filed. default is "json"
|
||||
TagName string
|
||||
// ParseDepth for parse and collect. TODO support depth
|
||||
ParseDepth int
|
||||
// MergeAnonymous struct fields to parent map. default is true
|
||||
MergeAnonymous bool
|
||||
// ExportPrivate export private fields. default is false
|
||||
ExportPrivate bool
|
||||
// IgnoreEmpty ignore empty value item. default: false
|
||||
IgnoreEmpty bool
|
||||
// UserFunc custom interceptor for filter or handle field value.
|
||||
UserFunc CustomUserFunc
|
||||
}
|
||||
|
||||
// MapOptFunc define
|
||||
type MapOptFunc func(opt *MapOptions)
|
||||
|
||||
// WithMapTagName set tag name for map field
|
||||
func WithMapTagName(tagName string) MapOptFunc {
|
||||
return func(opt *MapOptions) { opt.TagName = tagName }
|
||||
}
|
||||
|
||||
// WithUserFunc custom user func
|
||||
func WithUserFunc(fn CustomUserFunc) MapOptFunc {
|
||||
return func(opt *MapOptions) { opt.UserFunc = fn }
|
||||
}
|
||||
|
||||
// MergeAnonymous merge anonymous struct fields to parent map
|
||||
func MergeAnonymous(opt *MapOptions) { opt.MergeAnonymous = true }
|
||||
|
||||
// ExportPrivate merge anonymous struct fields to parent map
|
||||
func ExportPrivate(opt *MapOptions) { opt.ExportPrivate = true }
|
||||
|
||||
// WithIgnoreEmpty ignore on field value is empty
|
||||
func WithIgnoreEmpty(opt *MapOptions) { opt.IgnoreEmpty = true }
|
||||
|
||||
// StructToMap quickly convert structs to map[string]any by reflection.
|
||||
//
|
||||
// Can custom export field name by tag `json` or custom tag. see MapOptions
|
||||
func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
|
||||
mp := make(map[string]any)
|
||||
if st == nil {
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
obj := reflect.Indirect(reflect.ValueOf(st))
|
||||
if obj.Kind() != reflect.Struct {
|
||||
return mp, errors.New("StructToMap: must be an struct value")
|
||||
}
|
||||
|
||||
opt := &MapOptions{TagName: defaultFieldTag}
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
_, err := structToMap(obj, opt, mp)
|
||||
return mp, err
|
||||
}
|
||||
|
||||
func structToMap(obj reflect.Value, opt *MapOptions, mp map[string]any) (map[string]any, error) {
|
||||
if mp == nil {
|
||||
mp = make(map[string]any)
|
||||
}
|
||||
|
||||
refType := obj.Type()
|
||||
for i := 0; i < obj.NumField(); i++ {
|
||||
sf := refType.Field(i)
|
||||
name := sf.Name
|
||||
// skip un-exported field
|
||||
if !opt.ExportPrivate && IsUnexported(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
tagVal, ok := sf.Tag.Lookup(opt.TagName)
|
||||
if ok && tagVal != "" {
|
||||
sMap, err := ParseTagValueDefault(name, tagVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name = sMap.Default("name", name)
|
||||
if name == "" || name == "-" { // un-exported field
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fv := reflect.Indirect(obj.Field(i))
|
||||
if !fv.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
// opt: ignore empty field
|
||||
if opt.IgnoreEmpty && reflects.IsEmpty(fv) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fv.Kind() == reflect.Struct {
|
||||
// up: special handle time.Time field value
|
||||
if reflects.IsTimeType(fv.Type()) {
|
||||
mp[name] = fv.Interface().(time.Time).Format(time.RFC3339)
|
||||
continue
|
||||
}
|
||||
|
||||
// collect anonymous struct values to parent.
|
||||
if sf.Anonymous && opt.MergeAnonymous {
|
||||
_, err := structToMap(fv, opt, mp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else { // collect struct values to submap
|
||||
sub, err := structToMap(fv, opt, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = sub
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO support struct slice field.
|
||||
|
||||
// up: support custom user func
|
||||
if opt.UserFunc != nil {
|
||||
ok1, newVal := opt.UserFunc(sf.Name, fv)
|
||||
if !ok1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// ok1=true, newVal != nil
|
||||
if newVal != nil {
|
||||
mp[name] = newVal
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if fv.CanInterface() {
|
||||
mp[name] = fv.Interface()
|
||||
} else if fv.CanAddr() { // for unexported field
|
||||
mp[name] = reflects.UnexportedValue(fv)
|
||||
}
|
||||
}
|
||||
|
||||
return mp, nil
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package structs
|
||||
|
||||
// MapStruct simple copy src struct value to dst struct
|
||||
// func MapStruct(srcSt, dstSt any) {
|
||||
// // TODO
|
||||
// }
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// LiteData simple map[string]any struct. no lock
|
||||
type LiteData = Data
|
||||
|
||||
// NewLiteData create, not locked
|
||||
func NewLiteData(data map[string]any) *Data {
|
||||
if data == nil {
|
||||
data = make(map[string]any)
|
||||
}
|
||||
return &LiteData{data: data}
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* data struct and allow enable lock
|
||||
*************************************************************/
|
||||
|
||||
// Data struct, allow enable lock
|
||||
type Data struct {
|
||||
sync.RWMutex
|
||||
lock bool
|
||||
data map[string]any
|
||||
}
|
||||
|
||||
// NewData create new data instance
|
||||
func NewData() *Data {
|
||||
return &Data{
|
||||
lock: true,
|
||||
data: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
// WithLock for operate data
|
||||
func (d *Data) WithLock() *Data {
|
||||
d.lock = true
|
||||
return d
|
||||
}
|
||||
|
||||
// EnableLock for operate data
|
||||
func (d *Data) EnableLock() *Data {
|
||||
return d.WithLock()
|
||||
}
|
||||
|
||||
// Data get all
|
||||
func (d *Data) Data() map[string]any {
|
||||
return d.data
|
||||
}
|
||||
|
||||
// SetData set all data
|
||||
func (d *Data) SetData(data map[string]any) {
|
||||
if !d.lock {
|
||||
d.data = data
|
||||
return
|
||||
}
|
||||
|
||||
d.RLock()
|
||||
d.data = data
|
||||
d.RUnlock()
|
||||
}
|
||||
|
||||
// DataLen of data
|
||||
func (d *Data) DataLen() int {
|
||||
return len(d.data)
|
||||
}
|
||||
|
||||
// ResetData all data
|
||||
func (d *Data) ResetData() {
|
||||
d.data = make(map[string]any)
|
||||
}
|
||||
|
||||
// Merge load new data
|
||||
func (d *Data) Merge(mp map[string]any) {
|
||||
d.data = maputil.SimpleMerge(mp, d.data)
|
||||
}
|
||||
|
||||
// Set value to data
|
||||
func (d *Data) Set(key string, val any) {
|
||||
d.SetValue(key, val)
|
||||
}
|
||||
|
||||
// SetValue to data
|
||||
func (d *Data) SetValue(key string, val any) {
|
||||
if d.lock {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
}
|
||||
|
||||
d.data[key] = val
|
||||
}
|
||||
|
||||
// Value get from data
|
||||
func (d *Data) Value(key string) (val any, ok bool) {
|
||||
if d.lock {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
}
|
||||
|
||||
val, ok = maputil.GetByPath(key, d.data)
|
||||
return
|
||||
}
|
||||
|
||||
// Get val from data
|
||||
func (d *Data) Get(key string) any {
|
||||
return d.GetVal(key)
|
||||
}
|
||||
|
||||
// GetVal get from data
|
||||
func (d *Data) GetVal(key string) any {
|
||||
if d.lock {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
}
|
||||
|
||||
val, _ := maputil.GetByPath(key, d.data)
|
||||
return val
|
||||
}
|
||||
|
||||
// StrVal get from data
|
||||
func (d *Data) StrVal(key string) string {
|
||||
return strutil.QuietString(d.GetVal(key))
|
||||
}
|
||||
|
||||
// IntVal get from data
|
||||
func (d *Data) IntVal(key string) int {
|
||||
return mathutil.QuietInt(d.GetVal(key))
|
||||
}
|
||||
|
||||
// BoolVal get from data
|
||||
func (d *Data) BoolVal(key string) bool {
|
||||
val, ok := d.Value(key)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return comfunc.Bool(val)
|
||||
}
|
||||
|
||||
// String format data
|
||||
func (d *Data) String() string {
|
||||
return maputil.ToString(d.data)
|
||||
}
|
||||
|
||||
// OrderedData data TODO
|
||||
type OrderedData struct {
|
||||
maputil.Data
|
||||
cap int
|
||||
keys []string
|
||||
// vals []any
|
||||
}
|
||||
|
||||
// NewOrderedData instance.
|
||||
func NewOrderedData(cap int) *OrderedData {
|
||||
return &OrderedData{cap: cap, Data: make(maputil.Data, cap)}
|
||||
}
|
||||
|
||||
// Load data
|
||||
func (om *OrderedData) Load(data map[string]any) {
|
||||
om.Data.Load(data)
|
||||
om.keys = om.Data.Keys()
|
||||
}
|
||||
|
||||
// Set key and value to map
|
||||
func (om *OrderedData) Set(key string, val any) {
|
||||
om.keys = append(om.keys, key)
|
||||
om.Data.Set(key, val)
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/internal/varexpr"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
const defaultInitTag = "default"
|
||||
const defaultEnvPrefixTag = "defaultenvprefix"
|
||||
|
||||
// InitOptFunc define
|
||||
type InitOptFunc func(opt *InitOptions)
|
||||
|
||||
// InitOptions struct
|
||||
type InitOptions struct {
|
||||
// TagName default value tag name. tag: default
|
||||
TagName string
|
||||
// EnvPrefix default ENV prefix name.
|
||||
EnvPrefix string
|
||||
// EnvPrefixTagName default value tag name. tag: defaultenvprefix
|
||||
EnvPrefixTagName string
|
||||
// ParseEnv var name on default value. eg: `default:"${APP_ENV}"`
|
||||
//
|
||||
// default: false
|
||||
ParseEnv bool
|
||||
// ParseTime parse string to `time.Duration`, `time.Time`. default: false
|
||||
//
|
||||
// eg: default:"10s", default:"2025-04-23 15:04:05"
|
||||
ParseTime bool
|
||||
// ValueHook before set value hook TODO
|
||||
ValueHook func(val string) any
|
||||
}
|
||||
|
||||
// WithParseTime set parse time string on default value.
|
||||
func (opt *InitOptions) WithParseTime(val bool) *InitOptions {
|
||||
opt.ParseTime = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithParseEnv set parse env var on default value.
|
||||
func (opt *InitOptions) WithParseEnv(val bool) *InitOptions {
|
||||
opt.ParseEnv = val
|
||||
return opt
|
||||
}
|
||||
|
||||
// Init struct default value by field "default" tag.
|
||||
func Init(ptr any, optFns ...InitOptFunc) error {
|
||||
return InitDefaults(ptr, optFns...)
|
||||
}
|
||||
|
||||
// InitDefaults init struct default value by field "default" tag.
|
||||
//
|
||||
// TIPS:
|
||||
//
|
||||
// Support init field types: string, bool, intX, uintX, floatX, array, slice
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type User1 struct {
|
||||
// Name string `default:"inhere"`
|
||||
// Age int32 `default:"30"`
|
||||
// }
|
||||
//
|
||||
// u1 := &User1{}
|
||||
// err = structs.InitDefaults(u1)
|
||||
// fmt.Printf("%+v\n", u1) // Output: {Name:inhere Age:30}
|
||||
func InitDefaults(ptr any, optFns ...InitOptFunc) error {
|
||||
rv := reflect.ValueOf(ptr)
|
||||
if !reflects.IsValidPtr(rv) {
|
||||
return errors.New("must be provider an pointer value")
|
||||
}
|
||||
|
||||
rv = rv.Elem()
|
||||
if rv.Kind() != reflect.Struct {
|
||||
return errors.New("must be provider an struct value")
|
||||
}
|
||||
|
||||
opt := &InitOptions{TagName: defaultInitTag, EnvPrefixTagName: defaultEnvPrefixTag}
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
return initDefaults(rv, opt, "")
|
||||
}
|
||||
|
||||
// type InitBuilder struct {
|
||||
// opt InitOptions
|
||||
// }
|
||||
|
||||
func initDefaults(rv reflect.Value, opt *InitOptions, envPrefix string) error {
|
||||
rt := rv.Type()
|
||||
opt.EnvPrefix = envPrefix
|
||||
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
sf := rt.Field(i)
|
||||
// skip doesn't exported field
|
||||
if IsUnexported(sf.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
val, hasTag := sf.Tag.Lookup(opt.TagName)
|
||||
if !hasTag || val == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
var childPrefixVar string
|
||||
prefixVar, _ := sf.Tag.Lookup(opt.EnvPrefixTagName)
|
||||
if prefixVar != "" {
|
||||
childPrefixVar = fmt.Sprintf("%s%s", envPrefix, prefixVar)
|
||||
// opt.EnvPrefix = childPrefixVar
|
||||
}
|
||||
|
||||
fv := rv.Field(i)
|
||||
if fv.Kind() == reflect.Struct {
|
||||
// special: struct is time.Time type
|
||||
if reflects.IsTimeType(fv.Type()) {
|
||||
if err := initDefaultValue(fv, val, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := initDefaults(fv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip init on field has value. but will check slice and pointer field
|
||||
if !fv.IsZero() {
|
||||
// special: handle for pointer struct field
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
if fv.Kind() == reflect.Struct {
|
||||
if err := initDefaults(fv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if fv.Kind() == reflect.Slice {
|
||||
el := sf.Type.Elem()
|
||||
if el.Kind() == reflect.Pointer {
|
||||
el = el.Elem()
|
||||
}
|
||||
|
||||
// init sub struct in slice. like `[]SubStruct` or `[]*SubStruct`
|
||||
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, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// handle for pointer field
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
if fv.IsNil() {
|
||||
fv.Set(reflect.New(fv.Type().Elem()))
|
||||
}
|
||||
|
||||
fv = fv.Elem()
|
||||
if fv.Kind() == reflect.Struct {
|
||||
if err := initDefaults(fv, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else if fv.Kind() == reflect.Slice {
|
||||
el := sf.Type.Elem()
|
||||
isPtr := el.Kind() == reflect.Pointer
|
||||
if isPtr {
|
||||
el = el.Elem()
|
||||
}
|
||||
|
||||
// init sub struct in slice. like `[]SubStruct` or `[]*SubStruct`
|
||||
if el.Kind() == reflect.Struct {
|
||||
// up: if slice elem is struct and slice len=0, will be skip init default value
|
||||
if fv.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// make sub-struct and init. like: `SubStruct`
|
||||
subFv := reflect.New(el)
|
||||
subFvE := subFv.Elem()
|
||||
if err := initDefaults(subFvE, opt, childPrefixVar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make new slice and set value.
|
||||
newFv := reflect.MakeSlice(reflect.SliceOf(sf.Type.Elem()), 0, 1)
|
||||
if isPtr {
|
||||
newFv = reflect.Append(newFv, subFv)
|
||||
} else {
|
||||
newFv = reflect.Append(newFv, subFvE)
|
||||
}
|
||||
fv.Set(newFv)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := initDefaultValue(fv, val, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enhanceDefaultVar(val string, envPrefix string) string {
|
||||
if len(envPrefix) == 0 {
|
||||
return val
|
||||
}
|
||||
cleanedVar := strings.TrimSpace(val)
|
||||
|
||||
if strings.HasPrefix(cleanedVar, "${") && strings.HasSuffix(cleanedVar, "}") {
|
||||
parts := strings.SplitN(cleanedVar[2:len(cleanedVar)-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, opt *InitOptions) error {
|
||||
if val == "" || !fv.CanSet() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse env var
|
||||
if opt.ParseEnv {
|
||||
val = varexpr.SafeParse(enhanceDefaultVar(val, opt.EnvPrefix))
|
||||
}
|
||||
|
||||
var anyVal any = val
|
||||
|
||||
// enhance: parse special value type. eg: time.Duration, time.Time
|
||||
if opt.ParseTime {
|
||||
newVal, err := reflects.ToTimeOrDuration(val, fv.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
anyVal = newVal
|
||||
}
|
||||
|
||||
// simple slice: convert simple kind(string,intX,uintX,...) to slice. eg: "1,2,3" => []int{1,2,3}
|
||||
if reflects.IsArrayOrSlice(fv.Kind()) && reflects.IsSimpleKind(reflects.SliceElemKind(fv.Type())) {
|
||||
ss := strutil.SplitTrimmed(val, ",")
|
||||
valRv, err := reflects.ConvSlice(reflect.ValueOf(ss), fv.Type().Elem())
|
||||
if err == nil {
|
||||
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
|
||||
}
|
||||
|
||||
// set value
|
||||
return reflects.SetValue(fv, anyVal)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package structs
|
||||
|
||||
// SMap simple map[string]string struct. TODO
|
||||
type SMap struct {
|
||||
data map[string]string //lint:ignore U1000 for unused
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Package structs Provide some extends util functions for struct. eg: tag parse, struct init, value set
|
||||
package structs
|
||||
|
||||
// IsExported field name on struct
|
||||
func IsExported(name string) bool {
|
||||
return name[0] >= 'A' && name[0] <= 'Z'
|
||||
}
|
||||
|
||||
// IsUnexported field name on struct
|
||||
func IsUnexported(name string) bool {
|
||||
if name[0] == '_' {
|
||||
return true
|
||||
}
|
||||
return name[0] >= 'a' && name[0] <= 'z'
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// ErrNotAnStruct error
|
||||
// var emptyStringMap = make(maputil.SMap)
|
||||
var ErrNotAnStruct = errors.New("must input an struct value")
|
||||
|
||||
// ParseTags for parse struct tags.
|
||||
func ParseTags(st any, tagNames []string) (map[string]maputil.SMap, error) {
|
||||
p := NewTagParser(tagNames...)
|
||||
|
||||
if err := p.Parse(st); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Tags(), nil
|
||||
}
|
||||
|
||||
// ParseReflectTags parse struct tags info.
|
||||
func ParseReflectTags(rt reflect.Type, tagNames []string) (map[string]maputil.SMap, error) {
|
||||
p := NewTagParser(tagNames...)
|
||||
|
||||
if err := p.ParseType(rt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Tags(), nil
|
||||
}
|
||||
|
||||
// TagValFunc handle func
|
||||
type TagValFunc func(field, tagVal string) (maputil.SMap, error)
|
||||
|
||||
// TagParser struct
|
||||
type TagParser struct {
|
||||
// TagNames want parsed tag names.
|
||||
TagNames []string
|
||||
// ValueFunc tag value parse func.
|
||||
ValueFunc TagValFunc
|
||||
|
||||
// key: field name
|
||||
// value: tag map {tag-name: value string.}
|
||||
tags map[string]maputil.SMap
|
||||
}
|
||||
|
||||
// Tags map data for struct fields
|
||||
func (p *TagParser) Tags() map[string]maputil.SMap {
|
||||
return p.tags
|
||||
}
|
||||
|
||||
// NewTagParser instance
|
||||
func NewTagParser(tagNames ...string) *TagParser {
|
||||
return &TagParser{
|
||||
TagNames: tagNames,
|
||||
ValueFunc: ParseTagValueDefault,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse an struct value
|
||||
func (p *TagParser) Parse(st any) error {
|
||||
rv := reflect.ValueOf(st)
|
||||
if rv.Kind() == reflect.Ptr && !rv.IsNil() {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
return p.ParseType(rv.Type())
|
||||
}
|
||||
|
||||
// ParseType parse a struct type value
|
||||
func (p *TagParser) ParseType(rt reflect.Type) error {
|
||||
if rt.Kind() != reflect.Struct {
|
||||
return ErrNotAnStruct
|
||||
}
|
||||
|
||||
// key is field name.
|
||||
p.tags = make(map[string]maputil.SMap)
|
||||
return p.parseType(rt, "")
|
||||
}
|
||||
|
||||
func (p *TagParser) parseType(rt reflect.Type, parent string) error {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
sf := rt.Field(i)
|
||||
|
||||
// skip don't exported field
|
||||
name := sf.Name
|
||||
if name[0] >= 'a' && name[0] <= 'z' {
|
||||
continue
|
||||
}
|
||||
|
||||
smp := make(maputil.SMap)
|
||||
for _, tagName := range p.TagNames {
|
||||
// eg: `json:"age"`
|
||||
// eg: "name=int0;shorts=i;required=true;desc=int option message"
|
||||
tagVal := sf.Tag.Get(tagName)
|
||||
if tagVal == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
smp[tagName] = tagVal
|
||||
}
|
||||
|
||||
pathKey := name
|
||||
if parent != "" {
|
||||
pathKey = parent + "." + name
|
||||
}
|
||||
|
||||
p.tags[pathKey] = smp
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Kind() == reflect.Ptr {
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// field is struct.
|
||||
if ft.Kind() == reflect.Struct {
|
||||
err := p.parseType(ft, pathKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info parse the give field, returns tag value info.
|
||||
//
|
||||
// info, err := p.Info("Name", "json")
|
||||
// exportField := info.Get("name")
|
||||
func (p *TagParser) Info(field, tag string) (maputil.SMap, error) {
|
||||
field = strutil.UpperFirst(field)
|
||||
fTags, ok := p.tags[field]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("field %q not found", field)
|
||||
}
|
||||
|
||||
val, ok := fTags.Value(tag)
|
||||
if !ok {
|
||||
return make(maputil.SMap), nil
|
||||
}
|
||||
|
||||
// parse tag value
|
||||
return p.ValueFunc(field, val)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* some built in tag value parse func
|
||||
*************************************************************/
|
||||
|
||||
// ParseTagValueDefault parse like json tag value.
|
||||
//
|
||||
// see json.Marshal():
|
||||
//
|
||||
// // JSON as key "myName", skipped if empty.
|
||||
// Field int `json:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in JSON as key "Field" (the default), but skipped if empty.
|
||||
// Field int `json:",omitempty"`
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `json:"-"`
|
||||
//
|
||||
// // Field appears in JSON as key "-".
|
||||
// Field int `json:"-,"`
|
||||
//
|
||||
// Int64String int64 `json:",string"`
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// {
|
||||
// "name": "myName", // maybe is empty, on tag value is "-"
|
||||
// "omitempty": "true",
|
||||
// "string": "true",
|
||||
// // ... more custom bool settings.
|
||||
// }
|
||||
func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) {
|
||||
ss := strutil.SplitTrimmed(tagVal, ",")
|
||||
ln := len(ss)
|
||||
if ln == 0 || tagVal == "," {
|
||||
return maputil.SMap{"name": field}, nil
|
||||
}
|
||||
|
||||
mp = make(maputil.SMap, ln)
|
||||
if ln == 1 {
|
||||
// valid field name
|
||||
if ss[0] != "-" {
|
||||
mp["name"] = ss[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ln > 1
|
||||
mp["name"] = ss[0]
|
||||
// other settings: omitempty, string
|
||||
for _, key := range ss[1:] {
|
||||
mp[key] = "true"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTagValueQuick quick parse tag value string by sep(;)
|
||||
func ParseTagValueQuick(tagVal string, defines []string) maputil.SMap {
|
||||
parseFn := ParseTagValueDefine(";", defines)
|
||||
mp, _ := parseFn("", tagVal)
|
||||
return mp
|
||||
}
|
||||
|
||||
// ParseTagValueDefine parse tag value string by given defines.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// eg: "desc;required;default;shorts"
|
||||
// type MyStruct {
|
||||
// Age int `flag:"int option message;;a,b"`
|
||||
// }
|
||||
// sepStr := ";"
|
||||
// defines := []string{"desc", "required", "default", "shorts"}
|
||||
func ParseTagValueDefine(sep string, defines []string) TagValFunc {
|
||||
defNum := len(defines)
|
||||
|
||||
return func(field, tagVal string) (maputil.SMap, error) {
|
||||
ss := strutil.SplitNTrimmed(tagVal, sep, defNum)
|
||||
ln := len(ss)
|
||||
mp := make(maputil.SMap, ln)
|
||||
if ln == 0 {
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
for i, val := range ss {
|
||||
key := defines[i]
|
||||
mp[key] = val
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseTagValueNamed parse k-v tag value string. it's like INI format contents.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// eg: "name=val0;shorts=i;required=true;desc=a message"
|
||||
// =>
|
||||
// {name: val0, shorts: i, required: true, desc: a message}
|
||||
func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) {
|
||||
ss := strutil.Split(tagVal, ";")
|
||||
ln := len(ss)
|
||||
if ln == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
mp = make(maputil.SMap, ln)
|
||||
for _, s := range ss {
|
||||
if !strings.ContainsRune(s, '=') {
|
||||
err = fmt.Errorf("parse tag error on field '%s': must match `KEY=VAL`", field)
|
||||
return
|
||||
}
|
||||
|
||||
key, val := strutil.TrimCut(s, "=")
|
||||
if len(keys) > 0 && !arrutil.StringsHas(keys, key) {
|
||||
err = fmt.Errorf("parse tag error on field '%s': invalid key name '%s'", field, key)
|
||||
return
|
||||
}
|
||||
|
||||
mp[key] = val
|
||||
}
|
||||
return
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// Value data store
|
||||
type Value struct {
|
||||
// V value
|
||||
V any
|
||||
}
|
||||
|
||||
// NewValue instance.
|
||||
func NewValue(val any) *Value {
|
||||
return &Value{
|
||||
V: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Set value
|
||||
func (v *Value) Set(val any) {
|
||||
v.V = val
|
||||
}
|
||||
|
||||
// Reset value
|
||||
func (v *Value) Reset() {
|
||||
v.V = nil
|
||||
}
|
||||
|
||||
// Val get
|
||||
func (v *Value) Val() any {
|
||||
return v.V
|
||||
}
|
||||
|
||||
// Val get
|
||||
// func (v *Value) ValOr[T any](defVal T) T {
|
||||
// return v.V
|
||||
// }
|
||||
|
||||
// Int value get
|
||||
func (v *Value) Int() int {
|
||||
if v.V == nil {
|
||||
return 0
|
||||
}
|
||||
return mathutil.QuietInt(v.V)
|
||||
}
|
||||
|
||||
// Int64 value
|
||||
func (v *Value) Int64() int64 {
|
||||
if v.V == nil {
|
||||
return 0
|
||||
}
|
||||
return mathutil.QuietInt64(v.V)
|
||||
}
|
||||
|
||||
// Bool value
|
||||
func (v *Value) Bool() bool {
|
||||
if v.V == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if bl, ok := v.V.(bool); ok {
|
||||
return bl
|
||||
}
|
||||
|
||||
if str, ok := v.V.(string); ok {
|
||||
return strutil.QuietBool(str)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Float64 value
|
||||
func (v *Value) Float64() float64 {
|
||||
if v.V == nil {
|
||||
return 0
|
||||
}
|
||||
return mathutil.QuietFloat(v.V)
|
||||
}
|
||||
|
||||
// String value
|
||||
func (v *Value) String() string {
|
||||
if v.V == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if str, ok := v.V.(string); ok {
|
||||
return str
|
||||
}
|
||||
return strutil.QuietString(v.V)
|
||||
}
|
||||
|
||||
// Strings value
|
||||
func (v *Value) Strings() (ss []string) {
|
||||
if v.V == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ss, ok := v.V.([]string); ok {
|
||||
return ss
|
||||
}
|
||||
if str, ok := v.V.(string); ok {
|
||||
return strutil.Split(str, comdef.DefaultSep)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SplitToStrings split string value to strings. sep default is comma(,)
|
||||
func (v *Value) SplitToStrings(sep ...string) (ss []string) {
|
||||
if v.V == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if str, ok := v.V.(string); ok {
|
||||
return strutil.Split(str, sepStr(sep))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SplitToInts split string value to []int. sep default is comma(,)
|
||||
func (v *Value) SplitToInts(sep ...string) (ss []int) {
|
||||
if v.V == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if str, ok := v.V.(string); ok {
|
||||
ints, err := arrutil.StringsToInts(strutil.Split(str, sepStr(sep)))
|
||||
if err == nil {
|
||||
return ints
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsEmpty value
|
||||
func (v *Value) IsEmpty() bool {
|
||||
return v.V == nil
|
||||
}
|
||||
|
||||
func sepStr(seps []string) string {
|
||||
if len(seps) > 0 {
|
||||
return seps[0]
|
||||
}
|
||||
return comdef.DefaultSep
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Wrapper struct for read or set field value
|
||||
type Wrapper struct {
|
||||
// src any // source struct
|
||||
|
||||
// raw reflect.Value of source struct
|
||||
rv reflect.Value
|
||||
|
||||
// FieldTagName field name for read/write value. default tag: json
|
||||
FieldTagName string
|
||||
|
||||
// caches for field rv and name and tag name TODO
|
||||
fieldNames []string //lint:ignore U1000 for unused
|
||||
fvCacheMap map[string]reflect.Value //lint:ignore U1000 for unused
|
||||
}
|
||||
|
||||
// Wrap quick create a struct wrapper
|
||||
func Wrap(src any) *Wrapper { return NewWrapper(src) }
|
||||
|
||||
// NewWrapper create a struct wrapper
|
||||
func NewWrapper(src any) *Wrapper {
|
||||
return WrapValue(reflect.ValueOf(src))
|
||||
}
|
||||
|
||||
// WrapValue create a struct wrapper
|
||||
func WrapValue(rv reflect.Value) *Wrapper {
|
||||
rv = reflect.Indirect(rv)
|
||||
if rv.Kind() != reflect.Struct {
|
||||
panic("must be provider an struct value")
|
||||
}
|
||||
return &Wrapper{rv: rv}
|
||||
}
|
||||
|
||||
// Get field value by name, name allows to use dot syntax.
|
||||
func (r *Wrapper) Get(name string) any {
|
||||
val, ok := r.Lookup(name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Lookup field value by name, name allows to use dot syntax.
|
||||
func (r *Wrapper) Lookup(name string) (val any, ok bool) {
|
||||
fv := r.rv.FieldByName(name)
|
||||
if !fv.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
if fv.CanInterface() {
|
||||
return fv.Interface(), true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set field value by name. name allows using dot syntax.
|
||||
func (r *Wrapper) Set(name string, val any) error {
|
||||
fv := r.rv.FieldByName(name)
|
||||
if !fv.IsValid() {
|
||||
return errors.New("field " + name + " not found")
|
||||
}
|
||||
|
||||
if !fv.CanSet() {
|
||||
return errors.New("can not set value for field: " + name)
|
||||
}
|
||||
|
||||
fv.Set(reflect.ValueOf(val))
|
||||
return nil
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gookit/goutil/comdef"
|
||||
"github.com/gookit/goutil/reflects"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// NewWriter create a struct writer
|
||||
//
|
||||
// TIP: must be pointer for set field value
|
||||
func NewWriter(ptr any) *Wrapper {
|
||||
rv := reflect.ValueOf(ptr)
|
||||
|
||||
if rv.Kind() != reflect.Pointer {
|
||||
panic("must be provider an pointer value")
|
||||
}
|
||||
return WrapValue(rv)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* set values to a struct
|
||||
*************************************************************/
|
||||
|
||||
// SetOptFunc define
|
||||
type SetOptFunc func(opt *SetOptions)
|
||||
|
||||
// BeforeSetFunc hook type.
|
||||
type BeforeSetFunc func(fieldName string, value any, fv reflect.Value) any
|
||||
|
||||
// SetOptions for set values to struct
|
||||
type SetOptions struct {
|
||||
// FieldTagName get field name for read value. default tag: json
|
||||
FieldTagName string
|
||||
// BeforeSetFn hook func. will fire on before set value.
|
||||
// - you can modify value here.
|
||||
// - returns nil will skip set value.
|
||||
// - returns value will be set to field value.
|
||||
BeforeSetFn BeforeSetFunc
|
||||
|
||||
// ParseTime parse string to `time.Duration`, `time.Time`. default: false
|
||||
//
|
||||
// eg: default:"10s", default:"2025-04-23 15:04:05"
|
||||
ParseTime bool
|
||||
// ParseDefault init default value by DefaultValTag tag value. default: false
|
||||
//
|
||||
// see InitDefaults()
|
||||
ParseDefault bool
|
||||
// DefaultValTag name. tag: default
|
||||
DefaultValTag string
|
||||
|
||||
// ParseDefaultEnv parse env var on default tag. eg: `default:"${APP_ENV}"` default: false
|
||||
ParseDefaultEnv bool
|
||||
// DefaultEnvPrefixTag name. tag: defaultenvprefix
|
||||
DefaultEnvPrefixTag string
|
||||
|
||||
// StopOnError if true, will stop set value on error happened. default: false
|
||||
// StopOnError bool
|
||||
}
|
||||
|
||||
// WithParseDefault value by tag "default"
|
||||
func WithParseDefault(opt *SetOptions) { opt.ParseDefault = true }
|
||||
|
||||
// WithBeforeSetFn value by tag "default"
|
||||
func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc {
|
||||
return func(opt *SetOptions) { opt.BeforeSetFn = fn }
|
||||
}
|
||||
|
||||
// TODO refactoring SetValues to the struct
|
||||
type ValuesSetter struct {
|
||||
src any // source struct
|
||||
// raw reflect.Value of source struct
|
||||
rv reflect.Value
|
||||
|
||||
option *SetOptions
|
||||
initOpt *InitOptions
|
||||
}
|
||||
|
||||
// BindData set values to struct ptr from map data.
|
||||
func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error {
|
||||
return SetValues(ptr, data, optFns...)
|
||||
}
|
||||
|
||||
// SetValues set values to struct ptr from map data.
|
||||
//
|
||||
// TIPS:
|
||||
//
|
||||
// Only support set: string, bool, intX, uintX, floatX
|
||||
func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error {
|
||||
rv := reflect.ValueOf(ptr)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return errors.New("must be provider an pointer value")
|
||||
}
|
||||
|
||||
rv = rv.Elem()
|
||||
if rv.Kind() != reflect.Struct {
|
||||
return errors.New("must be provider an struct value")
|
||||
}
|
||||
|
||||
opt := &SetOptions{
|
||||
FieldTagName: defaultFieldTag,
|
||||
DefaultValTag: defaultInitTag,
|
||||
DefaultEnvPrefixTag: defaultEnvPrefixTag,
|
||||
}
|
||||
|
||||
for _, fn := range optFns {
|
||||
fn(opt)
|
||||
}
|
||||
return setValues(rv, data, opt, "")
|
||||
}
|
||||
|
||||
func setValues(rv reflect.Value, data map[string]any, opt *SetOptions, envPrefix string) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var es comdef.Errors
|
||||
initOpt := &InitOptions{
|
||||
EnvPrefix: envPrefix,
|
||||
ParseEnv: opt.ParseDefaultEnv,
|
||||
ParseTime: opt.ParseTime,
|
||||
}
|
||||
rt := rv.Type()
|
||||
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
ft := rt.Field(i)
|
||||
name := ft.Name
|
||||
// skip don't exported field
|
||||
if name[0] >= 'a' && name[0] <= 'z' {
|
||||
continue
|
||||
}
|
||||
|
||||
// get field name
|
||||
tagVal, ok0 := ft.Tag.Lookup(opt.FieldTagName)
|
||||
if ok0 {
|
||||
info, err := ParseTagValueDefault(name, tagVal)
|
||||
if err != nil {
|
||||
es = append(es, err)
|
||||
continue
|
||||
}
|
||||
name = info.Get("name")
|
||||
}
|
||||
|
||||
fv := rv.Field(i)
|
||||
val, ok := data[name]
|
||||
|
||||
// set field value by default tag.
|
||||
if !ok && opt.ParseDefault && fv.IsZero() {
|
||||
defVal := ft.Tag.Get(opt.DefaultValTag)
|
||||
if err := initDefaultValue(fv, defVal, initOpt); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// handle for pointer field
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
if fv.IsNil() {
|
||||
fv.Set(reflect.New(fv.Type().Elem()))
|
||||
}
|
||||
fv = fv.Elem()
|
||||
}
|
||||
|
||||
// hook: call BeforeSet func
|
||||
if opt.BeforeSetFn != nil {
|
||||
val = opt.BeforeSetFn(ft.Name, val, fv)
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
ok = true // on value is not nil
|
||||
}
|
||||
|
||||
// field is struct
|
||||
if fv.Kind() == reflect.Struct {
|
||||
valRf := reflects.Indirect(reflect.ValueOf(val))
|
||||
|
||||
// up: special handle time.Time field
|
||||
if reflects.IsTimeType(fv.Type()) {
|
||||
// maybe val is time.Time
|
||||
if reflects.IsTimeType(valRf.Type()) {
|
||||
if err := reflects.SetValue(fv, valRf); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// val is datetime string
|
||||
tm, err := strutil.ToTime(strutil.StringOr(val, ""))
|
||||
if err != nil {
|
||||
es = append(es, err)
|
||||
} else if err = reflects.SetValue(fv, tm); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// val as map
|
||||
asMp, err := reflects.TryAnyMap(valRf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("must provide map for set struct field %q, err=%v", ft.Name, err)
|
||||
es = append(es, err)
|
||||
continue
|
||||
}
|
||||
|
||||
defEnvPrefixVal := ft.Tag.Get(opt.DefaultEnvPrefixTag)
|
||||
childEnvPrefix := fmt.Sprintf("%s%s", envPrefix, defEnvPrefixVal)
|
||||
|
||||
// recursive processing sub-struct
|
||||
if err = setValues(fv, asMp, opt, childEnvPrefix); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// set field value
|
||||
if ok && val != nil {
|
||||
if err := reflects.SetValue(fv, val); err != nil {
|
||||
es = append(es, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return es.ErrOrNil()
|
||||
}
|
||||
Reference in New Issue
Block a user