Skip to content

Commit

Permalink
up: update some parse logic, add more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 16, 2022
1 parent 6a9d2cc commit 3cc63bb
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 53 deletions.
27 changes: 13 additions & 14 deletions manage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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)
}

Expand All @@ -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,
Expand All @@ -282,7 +282,6 @@ func mapStruct(tagName string, data interface{}, ptr interface{}) error {
if err != nil {
return err
}

return decoder.Decode(data)
}

Expand All @@ -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
Expand Down
11 changes: 8 additions & 3 deletions manage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -281,14 +280,20 @@ user_name = inhere
id = 22
tag = golang
`)

is.NoErr(err)

u2 := &User{}
is.NoErr(conf.Decode(u2))
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)
}
83 changes: 83 additions & 0 deletions parser/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)
}
102 changes: 67 additions & 35 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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}
}
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -406,7 +439,6 @@ func mapStruct(tagName string, data interface{}, ptr interface{}) error {
if err != nil {
return err
}

return decoder.Decode(data)
}

Expand Down
2 changes: 1 addition & 1 deletion parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3cc63bb

Please sign in to comment.