Skip to content

Commit

Permalink
add new methods for mapping data to struct
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 21, 2020
1 parent 785a9fb commit 2ff9a25
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 86 deletions.
3 changes: 1 addition & 2 deletions dotnev/dotenv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,11 @@ func TestLoadFromMap(t *testing.T) {
assert.Equal(t, "val1", Get("DONT_ENV_TEST1"))
assert.Equal(t, 23, Int("DONT_ENV_TEST2"))

assert.Equal(t, "val1", Get("dont_env_test1"))
// on windows, os.Getenv() not case sensitive
if runtime.GOOS == "windows" {
assert.Equal(t, "val1", Get("dont_env_test1"))
assert.Equal(t, 23, Int("dont_env_test2"))
} else {
assert.Equal(t, "", Get("dont_env_test1"))
assert.Equal(t, 0, Int("dont_env_test2"))
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.11

require (
github.com/gookit/goutil v0.3.5
github.com/mitchellh/mapstructure v1.3.3
github.com/stretchr/testify v1.6.1
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 // indirect
)
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gookit/color v1.2.6/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/color v1.2.8/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/color v1.2.9 h1:1gwRqF/u6wZrxzn+thlROF7D4Toi9eVGUidZNAxAZHE=
github.com/gookit/color v1.2.9/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/goutil v0.2.11 h1:cnQnqNHlyaeb5Ysd5caIls/jUnaKAgXKSoW2bwnqMjY=
github.com/gookit/goutil v0.2.11/go.mod h1:Hk4DFtZTUY6CxsnMU3nysqUXYSm8KRattv4TqHXrUos=
Expand All @@ -17,6 +18,8 @@ github.com/gookit/goutil v0.3.5 h1:95hWrCXcz7wuwlvcHw7YyUbFH0fV15YM0WPioYMcZww=
github.com/gookit/goutil v0.3.5/go.mod h1:OHs5W5Xmfj4pCMXHnMxsDPrCc0SRbHLgJ2qs6wr5fxM=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
16 changes: 11 additions & 5 deletions ini.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@ import (
"regexp"
"strings"
"sync"

"github.com/gookit/ini/v2/parser"
)

// some default constants
const (
SepSection = "."
DefSection = "__default"
DefTagName = "ini"
)

var (
errEmptyKey = errors.New("ini: key name cannot be empty")
errSetInReadonly = errors.New("ini: config manager instance in 'readonly' mode")
errEmptyKey = errors.New("ini: key name cannot be empty")
errNotFound = errors.New("ini: key does not exist in the config")
errReadonly = errors.New("ini: config manager instance in 'readonly' mode")
// default instance
dc = New()
)
Expand All @@ -44,6 +47,8 @@ type Options struct {
ParseEnv bool
// ParseVar parse variable reference "%(varName)s". default False
ParseVar bool
// TagName for binding struct
TagName string

// VarOpen var left open char. default "%("
VarOpen string
Expand Down Expand Up @@ -133,8 +138,9 @@ func newDefaultOptions() *Options {

VarOpen: "%(",
VarClose: ")s",
TagName: DefTagName,

DefSection: DefSection,
DefSection: parser.DefSection,
SectionSep: SepSection,
}
}
Expand Down Expand Up @@ -172,7 +178,7 @@ func GetOptions() Options {
}

// Options get options info.
// Notice: return is value. so, cannot change Ini instance
// Notice: return is value. so, cannot change options
func (c *Ini) Options() Options {
return *c.opts
}
Expand Down
7 changes: 5 additions & 2 deletions ini_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/gookit/ini/v2"
"github.com/gookit/ini/v2/parser"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -58,7 +59,7 @@ some = change val
// panic(err)
// }

// Output:
// Out:
// get int
// - val: 100
// get bool
Expand Down Expand Up @@ -87,9 +88,11 @@ noEnv = ${NotExist|defValue}
; comments
[sec1]
age = 23
key = val0
some = value
stuff = things
user_name = inhere
`

func TestLoad(t *testing.T) {
Expand Down Expand Up @@ -155,7 +158,7 @@ func TestBasic(t *testing.T) {
st := assert.New(t)

conf := ini.Default()
st.Equal(ini.DefSection, conf.DefSection())
st.Equal(parser.DefSection, conf.DefSection())

conf.WithOptions(func(opts *ini.Options) {
opts.DefSection = "myDef"
Expand Down
61 changes: 58 additions & 3 deletions manage.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/gookit/goutil/envutil"
"github.com/mitchellh/mapstructure"
)

/*************************************************************
Expand Down Expand Up @@ -225,6 +226,60 @@ func (c *Ini) StringMap(name string) (mp map[string]string) {
return
}

// MapStruct get config data and binding to the structure.
func MapStruct(key string, ptr interface{}) error { return dc.MapStruct(key, ptr) }

// MapStruct get config data and binding to the structure.
// Usage:
// user := &Db{}
// ini.MapStruct("user", &user)
func (c *Ini) MapStruct(key string, ptr interface{}) error {
// binding all data
if key == "" {
defSec := c.opts.DefSection
if defMap, ok := c.data[defSec]; ok {
data := make(map[string]interface{}, len(defMap)+len(c.data)-1)
for key, val := range defMap {
data[key] = val
}
for secKey, secVals := range c.data {
if secKey != defSec {
data[secKey] = secVals
}
}
return mapStruct(c.opts.TagName, data, ptr)
}

// no data of the default section
return mapStruct(c.opts.TagName, c.data, ptr)
}

// parts data of the config
data := c.StringMap(key)
if len(data) == 0 {
return errNotFound
}

return mapStruct(c.opts.TagName, data, ptr)
}

func mapStruct(tagName string, data interface{}, ptr interface{}) error {
mapConf := &mapstructure.DecoderConfig{
Metadata: nil,
Result: ptr,
TagName: tagName,
// will auto convert string to int/uint
WeaklyTypedInput: true,
}

decoder, err := mapstructure.NewDecoder(mapConf)
if err != nil {
return err
}

return decoder.Decode(data)
}

/*************************************************************
* config set
*************************************************************/
Expand All @@ -240,7 +295,7 @@ func Set(key string, val interface{}, section ...string) error {
func (c *Ini) Set(key string, val interface{}, section ...string) (err error) {
// if is readonly
if c.opts.Readonly {
return errSetInReadonly
return errReadonly
}

c.ensureInit()
Expand Down Expand Up @@ -366,7 +421,7 @@ func (c *Ini) Section(name string) Section {
func (c *Ini) SetSection(name string, values map[string]string) (err error) {
// if is readonly
if c.opts.Readonly {
return errSetInReadonly
return errReadonly
}

name = c.formatKey(name)
Expand All @@ -386,7 +441,7 @@ func (c *Ini) SetSection(name string, values map[string]string) (err error) {
func (c *Ini) NewSection(name string, values map[string]string) (err error) {
// if is readonly
if c.opts.Readonly {
return errSetInReadonly
return errReadonly
}

if c.opts.IgnoreCase {
Expand Down
43 changes: 43 additions & 0 deletions manage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,46 @@ func TestIni_Delete(t *testing.T) {

ini.Reset()
}

func TestIni_MapStruct(t *testing.T) {
is := assert.New(t)

err := ini.LoadStrings(iniStr)
is.Nil(err)

type User struct {
Age int
Some string
UserName string `ini:"user_name"`
Subs struct {
Id string
Tag string
}
}

u1 := &User{}
is.NoError(ini.MapStruct("sec1", u1))
is.Equal(23, u1.Age)
is.Equal("inhere", u1.UserName)
ini.Reset()

conf := ini.NewWithOptions(func(opt *ini.Options) {
opt.DefSection = ""
})
err = conf.LoadStrings(`
age = 23
some = value
user_name = inhere
[subs]
id = 22
tag = golang
`)

is.NoError(err)

u2 := &User{}
is.NoError(conf.MapStruct("", u2))
is.Equal(23, u2.Age)
is.Equal("inhere", u2.UserName)
is.Equal("golang", u2.Subs.Tag)
}
8 changes: 5 additions & 3 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@ func (c *Ini) parse(data string) (err error) {

p := parser.NewSimpled()
p.Collector = c.valueCollector
p.DefSection = c.opts.DefSection
p.IgnoreCase = c.opts.IgnoreCase
p.DefSection = c.opts.DefSection

return p.ParseString(data)
}

// collect value form parser
func (c *Ini) valueCollector(section, key, val string, isSlice bool) {
// defSec := c.opts.DefSection
if c.opts.IgnoreCase {
section = strings.ToLower(section)
key = strings.ToLower(key)
// defSec = strings.ToLower(defSec)
section = strings.ToLower(section)
}

// if opts.ParseEnv is true. will parse like: "${SHELL}"
// if opts.ParseEnv is true. will parse like: "${SHELL}". CHANGE: parse ENV on get value
// parse on there, will export data error.
// if c.opts.ParseEnv {
// val = c.parseEnvValue(val)
Expand Down
28 changes: 19 additions & 9 deletions parser/decode_encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ package parser

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"

"github.com/mitchellh/mapstructure"
)

// TagName of mapping data to struct
var TagName = "ini"

// Decode INI content to golang data
func Decode(blob []byte, v interface{}) error {
rv := reflect.ValueOf(v)
func Decode(blob []byte, ptr interface{}) error {
rv := reflect.ValueOf(ptr)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("ini: Decode of non-pointer %s", reflect.TypeOf(v))
return fmt.Errorf("ini: Decode of non-pointer %s", reflect.TypeOf(ptr))
}

// if rv.IsNil() {
Expand All @@ -25,12 +29,20 @@ func Decode(blob []byte, v interface{}) error {
return err
}

bs, err := json.Marshal(p.fullData)
mapConf := &mapstructure.DecoderConfig{
Metadata: nil,
Result: ptr,
TagName: TagName,
// will auto convert string to int/uint
WeaklyTypedInput: true,
}

decoder, err := mapstructure.NewDecoder(mapConf)
if err != nil {
return err
}

return json.Unmarshal(bs, v)
return decoder.Decode(p.fullData)
}

// Encode golang data to INI
Expand All @@ -53,7 +65,6 @@ func EncodeFull(data map[string]interface{}, defSection ...string) (out []byte,
}

defSecName := ""

if len(defSection) > 0 {
defSecName = defSection[0]
}
Expand Down Expand Up @@ -138,10 +149,10 @@ func EncodeSimple(data map[string]map[string]string, defSection ...string) (out
}

var n int64
defSecName := ""
buf := &bytes.Buffer{}
counter := 0
thisWrite := 0
defSecName := ""
orderedSections := make([]string, len(data))

if len(defSection) > 0 {
Expand All @@ -154,7 +165,6 @@ func EncodeSimple(data map[string]map[string]string, defSection ...string) (out
}

sort.Strings(orderedSections)

for _, section := range orderedSections {
// don't add section title for DefSection
if section != defSecName {
Expand Down
Loading

0 comments on commit 2ff9a25

Please sign in to comment.