diff --git a/manage.go b/manage.go index e425842..6bd6a85 100644 --- a/manage.go +++ b/manage.go @@ -224,7 +224,13 @@ func (c *Ini) StringMap(name string) (mp map[string]string) { } // MapStruct get config data and binding to the structure. -func MapStruct(key string, ptr interface{}) error { return dc.MapStruct(key, ptr) } +func MapStruct(key string, ptr any) error { return dc.MapStruct(key, ptr) } + +// Decode all data to struct pointer +func (c *Ini) Decode(ptr any) error { return c.MapStruct("", ptr) } + +// MapTo mapping all data to struct pointer +func (c *Ini) MapTo(ptr any) error { return c.MapStruct("", ptr) } // MapStruct get config data and binding to the structure. // If the key is empty, will bind all data to the struct ptr. @@ -233,15 +239,16 @@ func MapStruct(key string, ptr interface{}) error { return dc.MapStruct(key, ptr // // user := &Db{} // ini.MapStruct("user", &user) -func (c *Ini) MapStruct(key string, ptr interface{}) error { +func (c *Ini) MapStruct(key string, ptr any) 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) + data := make(map[string]any, 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 @@ -250,7 +257,7 @@ func (c *Ini) MapStruct(key string, ptr interface{}) error { return mapStruct(c.opts.TagName, data, ptr) } - // no data of the default section + // no default section return mapStruct(c.opts.TagName, c.data, ptr) } @@ -259,17 +266,10 @@ func (c *Ini) MapStruct(key string, ptr interface{}) error { if len(data) == 0 { return errNotFound } - return mapStruct(c.opts.TagName, data, ptr) } -// Decode all data to struct pointer -func (c *Ini) Decode(ptr interface{}) error { return c.MapStruct("", ptr) } - -// MapTo mapping all data to struct pointer -func (c *Ini) MapTo(ptr interface{}) error { return c.MapStruct("", ptr) } - -func mapStruct(tagName string, data interface{}, ptr interface{}) error { +func mapStruct(tagName string, data any, ptr any) error { mapConf := &mapstructure.DecoderConfig{ Metadata: nil, Result: ptr, @@ -282,7 +282,6 @@ func mapStruct(tagName string, data interface{}, ptr interface{}) error { if err != nil { return err } - return decoder.Decode(data) } @@ -300,7 +299,7 @@ func Set(key string, val interface{}, section ...string) error { // Set a value to the section by key. // // if section is empty, will set to default section -func (c *Ini) Set(key string, val interface{}, section ...string) (err error) { +func (c *Ini) Set(key string, val any, section ...string) (err error) { // if is readonly if c.opts.Readonly { return errReadonly diff --git a/manage_test.go b/manage_test.go index 1f56267..9e6f53e 100644 --- a/manage_test.go +++ b/manage_test.go @@ -250,7 +250,6 @@ func TestIni_Delete(t *testing.T) { func TestIni_MapStruct(t *testing.T) { is := assert.New(t) - err := ini.LoadStrings(iniStr) is.Nil(err) @@ -281,7 +280,6 @@ user_name = inhere id = 22 tag = golang `) - is.NoErr(err) u2 := &User{} @@ -289,6 +287,13 @@ tag = golang is.Eq(23, u2.Age) is.Eq("inhere", u2.UserName) is.Eq("golang", u2.Subs.Tag) - is.Err(conf.MapStruct("not-exist", u2)) + + // UserErr struct + type UserErr struct { + Age map[int]string `json:"age"` + } + + ue := &UserErr{} + err = conf.Decode(ue) } diff --git a/parser/options_test.go b/parser/options_test.go index e7cf2f1..6a2b9af 100644 --- a/parser/options_test.go +++ b/parser/options_test.go @@ -3,6 +3,7 @@ package parser_test import ( "testing" + "github.com/gookit/goutil/dump" "github.com/gookit/goutil/testutil/assert" "github.com/gookit/ini/v2/parser" ) @@ -25,3 +26,85 @@ desc = i'm a developer, use\n go,php,java assert.NotEmpty(t, p.LiteData()) assert.Eq(t, "i'm a developer, use\n go,php,java", p.LiteSection(p.DefSection)["desc"]) } + +func TestWithParseMode_full(t *testing.T) { + text := ` +age = 345 +name = inhere +tags[] = go +tags[] = php +tags[] = java +[site] +github = github.com/inhere +` + + // User struct + type User struct { + Age int `ini:"age"` + Name string `ini:"name"` + Tags []string `ini:"tags"` + } + + // lite mode + p := parser.New() + err := p.ParseBytes([]byte(text)) + assert.NoErr(t, err) + assert.NotEmpty(t, p.LiteData()) + dump.P(p.ParsedData()) + u := &User{} + err = p.Decode(u) + assert.NoErr(t, err) + assert.Eq(t, 345, u.Age) + assert.Eq(t, "inhere", u.Name) + assert.Empty(t, u.Tags) + + // full mode + p = parser.New(parser.WithParseMode(parser.ModeFull)) + err = p.ParseString(text) + assert.NoErr(t, err) + assert.NotEmpty(t, p.FullData()) + dump.P(p.ParsedData()) + u1 := &User{} + err = p.Decode(u1) + assert.NoErr(t, err) + assert.Eq(t, 345, u1.Age) + assert.Eq(t, "inhere", u1.Name) + assert.NotEmpty(t, u1.Tags) +} + +func TestWithTagName(t *testing.T) { + text := ` +age = 345 +name = inhere +desc = i'm a developer, use\n go,php,java +[site] +github = github.com/inhere +` + + p := parser.NewLite(parser.WithTagName("json")) + err := p.ParseString(text) + assert.NoErr(t, err) + assert.NotEmpty(t, p.LiteData()) + + // User struct + type User struct { + Age int `json:"age"` + Name string `json:"name"` + } + + u := &User{} + err = p.Decode(u) + assert.NoErr(t, err) + assert.Eq(t, 345, u.Age) + assert.Eq(t, "inhere", u.Name) + + // UserErr struct + type UserErr struct { + Age map[int]string `json:"age"` + } + + ue := &UserErr{} + err = p.Decode(ue) + // dump.P(ue) + assert.Err(t, err) +} diff --git a/parser/parser.go b/parser/parser.go index 1110304..cedd824 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -83,7 +83,7 @@ type Parser struct { // parsed bool // for full parse(allow array, map section) - fullData map[string]interface{} + fullData map[string]any // for simple parse(section only allow map[string]string) liteData map[string]map[string]string } @@ -95,30 +95,28 @@ func New(fns ...OptFunc) *Parser { } } -// NewFulled create a full mode Parser with some options -func NewFulled(fns ...func(*Parser)) *Parser { +// NewLite create a lite mode Parser. alias of New() +func NewLite(fns ...OptFunc) *Parser { return New(fns...) } + +// NewSimpled create a lite mode Parser +func NewSimpled(fns ...func(*Parser)) *Parser { p := &Parser{ - Options: NewOptions(WithParseMode(ModeFull)), + Options: NewOptions(), } return p.WithOptions(fns...) } -// NewSimpled create a simple mode Parser -func NewSimpled(opts ...func(*Parser)) *Parser { +// NewFulled create a full mode Parser with some options +func NewFulled(fns ...func(*Parser)) *Parser { p := &Parser{ - Options: NewOptions(WithParseMode(ModeLite)), + Options: NewOptions(WithParseMode(ModeFull)), } - return p.WithOptions(opts...) + return p.WithOptions(fns...) } // Parse a INI data string to golang func Parse(data string, mode parseMode, opts ...func(*Parser)) (p *Parser, err error) { - if mode == ModeFull { - p = NewFulled(opts...) - } else { - p = NewSimpled(opts...) - } - + p = New(WithParseMode(mode)) err = p.ParseString(data) return } @@ -167,22 +165,17 @@ func (p *Parser) ParseReader(r io.Reader) (err error) { return } -// ParseFrom a data scanner -func (p *Parser) ParseFrom(in *bufio.Scanner) (int64, error) { - return p.parse(in) -} - // init parser func (p *Parser) init() { if p.ParseMode == ModeFull { - p.fullData = make(map[string]interface{}) + p.fullData = make(map[string]any) } else { p.liteData = make(map[string]map[string]string) } } -// fullParse will parse array item -func (p *Parser) parse(in *bufio.Scanner) (bytes int64, err error) { +// ParseFrom a data scanner +func (p *Parser) ParseFrom(in *bufio.Scanner) (bytes int64, err error) { p.init() bytes = -1 @@ -282,15 +275,15 @@ func (p *Parser) collectFullValue(section, key, val string, isSlice bool) { // first create if !exists { if isSlice { - p.fullData[section] = map[string]interface{}{key: []string{val}} + p.fullData[section] = map[string]any{key: []string{val}} } else { - p.fullData[section] = map[string]interface{}{key: val} + p.fullData[section] = map[string]any{key: val} } return } switch sd := secData.(type) { - case map[string]interface{}: // existed section + case map[string]any: // existed section curVal, ok := sd[key] if ok { switch cv := curVal.(type) { @@ -315,9 +308,9 @@ func (p *Parser) collectFullValue(section, key, val string, isSlice bool) { p.fullData[section] = sd case string: // found default section value if isSlice { - p.fullData[section] = map[string]interface{}{key: []string{val}} + p.fullData[section] = map[string]any{key: []string{val}} } else { - p.fullData[section] = map[string]interface{}{key: val} + p.fullData[section] = map[string]any{key: val} } } } @@ -377,23 +370,63 @@ func (p *Parser) LiteSection(name string) map[string]string { func (p *Parser) Reset() { // p.parsed = false if p.ParseMode == ModeFull { - p.fullData = make(map[string]interface{}) + p.fullData = make(map[string]any) } else { p.liteData = make(map[string]map[string]string) } } +// Decode mapping the parsed data to struct ptr +func (p *Parser) Decode(ptr any) error { + return p.MapStruct(ptr) +} + // MapStruct mapping the parsed data to struct ptr -func (p *Parser) MapStruct(ptr interface{}) (err error) { +func (p *Parser) MapStruct(ptr any) (err error) { if p.ParseMode == ModeFull { - err = mapStruct(p.TagName, p.fullData, ptr) - } else { - err = mapStruct(p.TagName, p.liteData, ptr) + if p.NoDefSection { + return mapStruct(p.TagName, p.fullData, ptr) + } + + // collect all default section data to top + anyMap := make(map[string]any, len(p.fullData)+4) + if defData, ok := p.fullData[p.DefSection]; ok { + for key, val := range defData.(map[string]any) { + anyMap[key] = val + } + } + + for group, mp := range p.fullData { + if group == p.DefSection { + continue + } + anyMap[group] = mp + } + return mapStruct(p.TagName, anyMap, ptr) } - return + + defData := p.liteData[p.DefSection] + defLen := len(defData) + anyMap := make(map[string]any, len(p.liteData)+defLen) + + // collect all default section data to top + if defLen > 0 { + for key, val := range defData { + anyMap[key] = val + } + } + + for group, smp := range p.liteData { + if group == p.DefSection { + continue + } + anyMap[group] = smp + } + + return mapStruct(p.TagName, anyMap, ptr) } -func mapStruct(tagName string, data interface{}, ptr interface{}) error { +func mapStruct(tagName string, data any, ptr any) error { mapConf := &mapstructure.DecoderConfig{ Metadata: nil, Result: ptr, @@ -406,7 +439,6 @@ func mapStruct(tagName string, data interface{}, ptr interface{}) error { if err != nil { return err } - return decoder.Decode(data) } diff --git a/parser/parser_test.go b/parser/parser_test.go index 2bb1135..fe4b9d5 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -186,7 +186,7 @@ arr[] = val2 } func TestParser_ParseString(t *testing.T) { - p := New() + p := New(WithParseMode(ModeFull)) err := p.ParseString(` key1 = val1 arr = val2