Skip to content

Commit

Permalink
feat: support load given format files from a dir path.
Browse files Browse the repository at this point in the history
- add new method SubDataMap() read data as maputil.Data
  • Loading branch information
inhere committed Oct 20, 2022
1 parent 0eaa807 commit 28a568b
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 23 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ dump.Println(user)
- `LoadFlags(keys []string) (err error)` Load from CLI flags
- `LoadExists(sourceFiles ...string) (err error)`
- `LoadFiles(sourceFiles ...string) (err error)`
- `LoadFromDir(dirPath, format string) (err error)` Load custom format files from the given directory, the file name will be used as the key
- `LoadRemote(format, url string) (err error)`
- `LoadSources(format string, src []byte, more ...[]byte) (err error)`
- `LoadStrings(format string, str string, more ...string) (err error)`
Expand All @@ -442,13 +443,15 @@ dump.Println(user)
- `Float(key string, defVal ...float64) float64`
- `String(key string, defVal ...string) string`
- `Strings(key string) (arr []string)`
- `SubDataMap(key string) maputi.Data`
- `StringMap(key string) (mp map[string]string)`
- `Get(key string, findByPath ...bool) (value interface{})`

**Mapping data to struct:**

- `BindStruct(key string, dst interface{}) error`
- `MapOnExists(key string, dst interface{}) error`
- `Decode(dst any) error`
- `BindStruct(key string, dst any) error`
- `MapOnExists(key string, dst any) error`

### Setting Values

Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值
- `LoadOSEnvs(nameToKeyMap map[string]string)` 从ENV载入数据
- `LoadExists(sourceFiles ...string) (err error)` 从存在的配置文件里加载数据,会忽略不存在的文件
- `LoadFiles(sourceFiles ...string) (err error)` 从给定的配置文件里加载数据,有文件不存在则会panic
- `LoadFromDir(dirPath, format string) (err error)` 从给定目录里加载自定格式的文件,文件名会作为 key
- `LoadRemote(format, url string) (err error)` 从远程 URL 加载配置数据
- `LoadSources(format string, src []byte, more ...[]byte) (err error)` 从给定格式的字节数据加载配置
- `LoadStrings(format string, str string, more ...string) (err error)` 从给定格式的字符串配置里加载配置数据
Expand All @@ -425,6 +426,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值
- `String(key string, defVal ...string) string`
- `Strings(key string) (arr []string)`
- `StringMap(key string) (mp map[string]string)`
- `SubDataMap(key string) maputi.Data`
- `Get(key string, findByPath ...bool) (value interface{})`

**将数据映射到结构体:**
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ require (
github.com/json-iterator/go v1.1.12
github.com/mitchellh/mapstructure v1.5.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/stretchr/testify v1.8.0
github.com/yosuke-furukawa/json5 v0.1.1
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
100 changes: 81 additions & 19 deletions load.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/gookit/goutil/fsutil"
"github.com/imdario/mergo"
)

Expand Down Expand Up @@ -184,13 +185,13 @@ func (c *Config) LoadFlags(keys []string) (err error) {
}

// LoadData load one or multi data
func LoadData(dataSource ...interface{}) error { return dc.LoadData(dataSource...) }
func LoadData(dataSource ...any) error { return dc.LoadData(dataSource...) }

// LoadData load data from map OR struct
//
// The dataSources can be:
// - map[string]interface{}
func (c *Config) LoadData(dataSources ...interface{}) (err error) {
// - map[string]any
func (c *Config) LoadData(dataSources ...any) (err error) {
if c.opts.Delimiter == 0 {
c.opts.Delimiter = defaultDelimiter
}
Expand Down Expand Up @@ -287,6 +288,55 @@ func (c *Config) LoadExistsByFormat(format string, configFiles ...string) (err e
return
}

// LoadFromDir Load custom format files from the given directory, the file name will be used as the key.
//
// Example:
//
// // file: /somedir/task.json
// LoadFromDir("/somedir", "json")
//
// // after load
// Config.data = map[string]any{"task": file data}
func LoadFromDir(dirPath, format string) error {
return dc.LoadFromDir(dirPath, format)
}

// LoadFromDir Load custom format files from the given directory, the file name will be used as the key.
//
// Example:
//
// // file: /somedir/task.json
// Config.LoadFromDir("/somedir", "json")
//
// // after load
// Config.data = map[string]any{"task": file data}
func (c *Config) LoadFromDir(dirPath, format string) (err error) {
extName := "." + format
extLen := len(extName)

return fsutil.FindInDir(dirPath, func(fPath string, fi os.FileInfo) error {
baseName := fi.Name()

if strings.HasSuffix(baseName, extName) {
data, err := c.parseSourceToMap(format, fsutil.MustReadFile(fPath))
if err != nil {
return err
}

onlyName := baseName[:len(baseName)-extLen]
err = c.loadDataMap(map[string]any{onlyName: data})

// use file name as key, it cannot be reloaded. SO, cannot append to loadedFiles
// if err == nil {
// c.loadedFiles = append(c.loadedFiles, fPath)
// }

return err
}
return nil
})
}

// ReloadFiles reload config data use loaded files
func ReloadFiles() error { return dc.ReloadFiles() }

Expand Down Expand Up @@ -357,23 +407,16 @@ func (c *Config) loadFile(file string, loadExist bool, format string) (err error

// parse config source code to Config.
func (c *Config) parseSourceCode(format string, blob []byte) (err error) {
format = fixFormat(format)
decode := c.decoders[format]
if decode == nil {
return errors.New("not register decoder for the format: " + format)
}

if c.opts.Delimiter == 0 {
c.opts.Delimiter = defaultDelimiter
data, err := c.parseSourceToMap(format, blob)
if err != nil {
return err
}

// decode content to data
data := make(map[string]interface{})
if err = decode(blob, &data); err != nil {
return
}
return c.loadDataMap(data)
}

// init config data
func (c *Config) loadDataMap(data map[string]any) (err error) {
// first: init config data
if len(c.data) == 0 {
c.data = data
} else {
Expand All @@ -384,7 +427,26 @@ func (c *Config) parseSourceCode(format string, blob []byte) (err error) {
if !c.reloading && err == nil {
c.fireHook(OnLoadData)
}
return err
}

data = nil
return
// parse config source code to Config.
func (c *Config) parseSourceToMap(format string, blob []byte) (map[string]interface{}, error) {
format = fixFormat(format)
decode := c.decoders[format]
if decode == nil {
return nil, errors.New("not register decoder for the format: " + format)
}

if c.opts.Delimiter == 0 {
c.opts.Delimiter = defaultDelimiter
}

// decode content to data
data := make(map[string]any)

if err := decode(blob, &data); err != nil {
return nil, err
}
return data, nil
}
14 changes: 14 additions & 0 deletions load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,20 @@ func TestLoadOSEnvs(t *testing.T) {
ClearAll()
}

func TestLoadFromDir(t *testing.T) {
ClearAll()
assert.NoErr(t, LoadFiles("testdata/json_base.json"))

err := LoadFromDir("testdata/subdir", JSON)
assert.NoErr(t, err)
// dump.P(Data())

assert.Eq(t, "value in sub data", Get("subdata.key01"))
assert.Eq(t, "value in task.json", Get("task.key01"))

ClearAll()
}

func TestReloadFiles(t *testing.T) {
ClearAll()
c := Default()
Expand Down
20 changes: 19 additions & 1 deletion read.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

"github.com/gookit/goutil/envutil"
"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/strutil"
)
Expand Down Expand Up @@ -561,7 +562,7 @@ func (c *Config) StringMap(key string) (mp map[string]string) {
}
}
default:
c.addErrorf("value cannot be convert to map[string]string, key is '%s'", key)
c.addErrorf("value cannot be convert to map[string]string, key is %q", key)
return
}

Expand All @@ -574,3 +575,20 @@ func (c *Config) StringMap(key string) (mp map[string]string) {
}
return
}

// SubDataMap get sub config data as maputil.Map
func SubDataMap(key string) maputil.Map { return dc.SubDataMap(key) }

// SubDataMap get sub config data as maputil.Map
//
// TIP: will not enable parse Env and more
func (c *Config) SubDataMap(key string) maputil.Map {
if mp, ok := c.GetValue(key); ok {
if mmp, ok := mp.(map[string]any); ok {
return mmp
}
}

// keep is not nil
return maputil.Map{}
}
4 changes: 4 additions & 0 deletions testdata/subdir/subdata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"key01": "value in sub data",
"key02": 230
}
4 changes: 4 additions & 0 deletions testdata/subdir/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"key01": "value in task.json",
"key02": 250
}

0 comments on commit 28a568b

Please sign in to comment.