Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parser: support unmashalling to all basic type pointers #214

Merged
merged 1 commit into from Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions ini_python_multiline_test.go
Expand Up @@ -4,8 +4,8 @@ import (
"path/filepath"
"testing"

"gopkg.in/ini.v1"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/ini.v1"
)

type testData struct {
Expand All @@ -19,12 +19,12 @@ func TestMultiline(t *testing.T) {
path := filepath.Join("testdata", "multiline.ini")
f, err := ini.LoadSources(ini.LoadOptions{
AllowPythonMultilineValues: true,
ReaderBufferSize: 64*1024,
ReaderBufferSize: 64 * 1024,
/*
Debug: func(m string) {
fmt.Println(m)
},
*/
Debug: func(m string) {
fmt.Println(m)
},
*/
}, path)
So(err, ShouldBeNil)
So(f, ShouldNotBeNil)
Expand Down Expand Up @@ -62,4 +62,4 @@ Id ornare arcu odio ut sem. Arcu dictum varius duis at consectetur lorem donec m

Eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus. Habitant morbi tristique senectus et netus et malesuada fames ac. Urna condimentum mattis pellentesque id. Lorem sed risus ultricies tristique nulla aliquet enim tortor at. Ipsum dolor sit amet consectetur adipiscing elit. Convallis a cras semper auctor neque vitae tempus quam. A diam sollicitudin tempor id eu nisl nunc mi ipsum. Maecenas sed enim ut sem viverra aliquet eget. Massa enim nec dui nunc mattis enim. Nam aliquam sem et tortor consequat. Adipiscing commodo elit at imperdiet dui accumsan sit amet nulla. Nullam eget felis eget nunc lobortis. Mauris a diam maecenas sed enim ut sem viverra. Ornare massa eget egestas purus. In hac habitasse platea dictumst. Ut tortor pretium viverra suspendisse potenti nullam ac tortor. Nisl nunc mi ipsum faucibus. At varius vel pharetra vel. Mauris ultrices eros in cursus turpis massa tincidunt.`)
})
}
}
9 changes: 5 additions & 4 deletions parser.go
Expand Up @@ -26,6 +26,7 @@ import (
)

const minReaderBufferSize = 4096

var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)

type parserOptions struct {
Expand All @@ -36,8 +37,8 @@ type parserOptions struct {
UnescapeValueDoubleQuotes bool
UnescapeValueCommentSymbols bool
PreserveSurroundedQuote bool
DebugFunc DebugFunc
ReaderBufferSize int
DebugFunc DebugFunc
ReaderBufferSize int
}

type parser struct {
Expand Down Expand Up @@ -361,8 +362,8 @@ func (f *File) parse(reader io.Reader) (err error) {
UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes,
UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
PreserveSurroundedQuote: f.options.PreserveSurroundedQuote,
DebugFunc: f.options.DebugFunc,
ReaderBufferSize: f.options.ReaderBufferSize,
DebugFunc: f.options.DebugFunc,
ReaderBufferSize: f.options.ReaderBufferSize,
})
if err = p.BOM(); err != nil {
return fmt.Errorf("BOM: %v", err)
Expand Down
84 changes: 58 additions & 26 deletions struct.go
Expand Up @@ -155,71 +155,104 @@ func wrapStrictError(err error, isStrict bool) error {
// but it does not return error for failing parsing,
// because we want to use default value that is already assigned to struct.
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
switch t.Kind() {
vt := t
isPtr := t.Kind() == reflect.Ptr
if isPtr {
vt = t.Elem()
}
switch vt.Kind() {
case reflect.String:
if len(key.String()) == 0 {
return nil
stringVal := key.String()
if isPtr {
field.Set(reflect.ValueOf(&stringVal))
} else if len(stringVal) > 0 {
field.SetString(key.String())
}
field.SetString(key.String())
case reflect.Bool:
boolVal, err := key.Bool()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.SetBool(boolVal)
if isPtr {
field.Set(reflect.ValueOf(&boolVal))
} else {
field.SetBool(boolVal)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
durationVal, err := key.Duration()
// Skip zero value
if err == nil && int64(durationVal) > 0 {
field.Set(reflect.ValueOf(durationVal))
// ParseDuration will not return err for `0`, so check the type name
if vt.Name() == "Duration" {
durationVal, err := key.Duration()
if err != nil {
return wrapStrictError(err, isStrict)
}
if isPtr {
field.Set(reflect.ValueOf(&durationVal))
} else if int64(durationVal) > 0 {
field.Set(reflect.ValueOf(durationVal))
}
return nil
}

intVal, err := key.Int64()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.SetInt(intVal)
if isPtr {
pv := reflect.New(t.Elem())
pv.Elem().SetInt(intVal)
field.Set(pv)
} else {
field.SetInt(intVal)
}
// byte is an alias for uint8, so supporting uint8 breaks support for byte
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
durationVal, err := key.Duration()
// Skip zero value
if err == nil && uint64(durationVal) > 0 {
field.Set(reflect.ValueOf(durationVal))
if isPtr {
field.Set(reflect.ValueOf(&durationVal))
} else {
field.Set(reflect.ValueOf(durationVal))
}
return nil
}

uintVal, err := key.Uint64()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.SetUint(uintVal)
if isPtr {
pv := reflect.New(t.Elem())
pv.Elem().SetUint(uintVal)
field.Set(pv)
} else {
field.SetUint(uintVal)
}

case reflect.Float32, reflect.Float64:
floatVal, err := key.Float64()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.SetFloat(floatVal)
if isPtr {
pv := reflect.New(t.Elem())
pv.Elem().SetFloat(floatVal)
field.Set(pv)
} else {
field.SetFloat(floatVal)
}
case reflectTime:
timeVal, err := key.Time()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.Set(reflect.ValueOf(timeVal))
if isPtr {
field.Set(reflect.ValueOf(&timeVal))
} else {
field.Set(reflect.ValueOf(timeVal))
}
case reflect.Slice:
return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
case reflect.Ptr:
switch t.Elem().Kind() {
case reflect.Bool:
boolVal, err := key.Bool()
if err != nil {
return wrapStrictError(err, isStrict)
}
field.Set(reflect.ValueOf(&boolVal))
default:
return fmt.Errorf("unsupported type '%s'", t)
}
default:
return fmt.Errorf("unsupported type '%s'", t)
}
Expand Down Expand Up @@ -280,7 +313,6 @@ func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
continue
}
}

if key, err := s.GetKey(fieldName); err == nil {
delim := parseDelim(tpField.Tag.Get("delim"))
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
Expand Down
70 changes: 52 additions & 18 deletions struct_test.go
Expand Up @@ -43,36 +43,55 @@ type TestEmbeded struct {
}

type testStruct struct {
Name string `ini:"NAME"`
Age int
Male bool
Optional *bool
Money float64
Born time.Time
Time time.Duration `ini:"Duration"`
Others testNested
OthersPtr *testNested
NilPtr *testNested
*TestEmbeded `ini:"grade"`
Unused int `ini:"-"`
Unsigned uint
Omitted bool `ini:"omitthis,omitempty"`
Shadows []string `ini:",,allowshadow"`
ShadowInts []int `ini:"Shadows,,allowshadow"`
Name string `ini:"NAME"`
Age int
Male bool
Money float64
Born time.Time
Time time.Duration `ini:"Duration"`
Others testNested
OthersPtr *testNested
NilPtr *testNested
*TestEmbeded `ini:"grade"`
Unused int `ini:"-"`
Unsigned uint
Omitted bool `ini:"omitthis,omitempty"`
Shadows []string `ini:",,allowshadow"`
ShadowInts []int `ini:"Shadows,,allowshadow"`
BoolPtr *bool
BoolPtrNil *bool
FloatPtr *float64
FloatPtrNil *float64
IntPtr *int
IntPtrNil *int
UintPtr *uint
UintPtrNil *uint
StringPtr *string
StringPtrNil *string
TimePtr *time.Time
TimePtrNil *time.Time
DurationPtr *time.Duration
DurationPtrNil *time.Duration
}

const _CONF_DATA_STRUCT = `
NAME = Unknwon
Age = 21
Male = true
Optional = true
Money = 1.25
Born = 1993-10-07T20:17:05Z
Duration = 2h45m
Unsigned = 3
omitthis = true
Shadows = 1, 2
Shadows = 3, 4
BoolPtr = false
FloatPtr = 0
IntPtr = 0
UintPtr = 0
StringPtr = ""
TimePtr = 0001-01-01T00:00:00Z
DurationPtr = 0s

[Others]
Cities = HangZhou|Boston
Expand Down Expand Up @@ -154,7 +173,6 @@ func Test_MapToStruct(t *testing.T) {
So(ts.Name, ShouldEqual, "Unknwon")
So(ts.Age, ShouldEqual, 21)
So(ts.Male, ShouldBeTrue)
So(*ts.Optional, ShouldBeTrue)
So(ts.Money, ShouldEqual, 1.25)
So(ts.Unsigned, ShouldEqual, 3)

Expand Down Expand Up @@ -188,6 +206,22 @@ func Test_MapToStruct(t *testing.T) {
So(ts.OthersPtr.Note, ShouldEqual, "Hello world!")

So(ts.NilPtr, ShouldBeNil)

So(*ts.BoolPtr, ShouldEqual, false)
So(ts.BoolPtrNil, ShouldEqual, nil)
So(*ts.FloatPtr, ShouldEqual, 0)
So(ts.FloatPtrNil, ShouldEqual, nil)
So(*ts.IntPtr, ShouldEqual, 0)
So(ts.IntPtrNil, ShouldEqual, nil)
So(*ts.UintPtr, ShouldEqual, 0)
So(ts.UintPtrNil, ShouldEqual, nil)
So(*ts.StringPtr, ShouldEqual, "")
So(ts.StringPtrNil, ShouldEqual, nil)
So(*ts.TimePtr, ShouldNotEqual, nil)
So(ts.TimePtrNil, ShouldEqual, nil)
So(*ts.DurationPtr, ShouldEqual, 0)
So(ts.DurationPtrNil, ShouldEqual, nil)

})

Convey("Map section to struct", func() {
Expand Down