Skip to content

Commit

Permalink
parser: support unmashalling to all basic type pointers (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
jstewmon authored and unknwon committed Nov 9, 2019
1 parent 8ee0b78 commit d544aa9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 55 deletions.
14 changes: 7 additions & 7 deletions ini_python_multiline_test.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit d544aa9

Please sign in to comment.