diff --git a/ini_python_multiline_test.go b/ini_python_multiline_test.go index da925c4..52a1ed0 100644 --- a/ini_python_multiline_test.go +++ b/ini_python_multiline_test.go @@ -4,8 +4,8 @@ import ( "path/filepath" "testing" - "gopkg.in/ini.v1" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) type testData struct { @@ -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) @@ -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.`) }) -} \ No newline at end of file +} diff --git a/parser.go b/parser.go index 9995d31..53ab45c 100644 --- a/parser.go +++ b/parser.go @@ -26,6 +26,7 @@ import ( ) const minReaderBufferSize = 4096 + var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`) type parserOptions struct { @@ -36,8 +37,8 @@ type parserOptions struct { UnescapeValueDoubleQuotes bool UnescapeValueCommentSymbols bool PreserveSurroundedQuote bool - DebugFunc DebugFunc - ReaderBufferSize int + DebugFunc DebugFunc + ReaderBufferSize int } type parser struct { @@ -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) diff --git a/struct.go b/struct.go index 2764534..6bc70e4 100644 --- a/struct.go +++ b/struct.go @@ -155,23 +155,41 @@ 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 } @@ -179,13 +197,23 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri 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 } @@ -193,33 +221,38 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri 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) } @@ -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 { diff --git a/struct_test.go b/struct_test.go index d91d6ea..1f032de 100644 --- a/struct_test.go +++ b/struct_test.go @@ -43,29 +43,41 @@ 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 @@ -73,6 +85,13 @@ 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 @@ -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) @@ -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() {